LINQ - using a selected collection between methods/classes - linq

After a lot of searching I wasn't able to find anything that clearly describes why I'm seeing this behavior (and I presume it's something very simple I'm missing - I'm still very much a beginner :)
I have a method (RefreshFilter) that takes an object (rfp) as a parameter. rfp has a property named 'Items' that is of type List.
I have 2 calls to RefreshFilter that look like this:
rfp = RefreshFilter(rfp, FilteredBy.Category)
rfp = RefreshFilter(rfp, FilteredBy.Industry)
Here is the RefreshFilter method:
public FilterParams RefreshFilterList(FilterParams rfp, FilteredBy filteredBy)
{
using (myEntity context as new myEntity())
{
itemsInCategory = (from i in context.items
join ic in context.ItemsCategories on i.Id equals ic.items.id
where ic.Categories.Id == '52'
select i).ToList<items>();
rfp.Items = rfp.Items.Intersect(itemsInCategory).ToList<items>();
}
return rfp;
}
The first call to RefreshFilter(...) works just fine and returns a FilterParams object with a .items property that contains an intersected list.
The second call to RefreshFilter(...) always returns a FilterParams object with a .items property containing an list of 0 elements (which is not expected since I know there are matching elements in the lists).
Through some testing, I believe I have been able to narrow this down to being related to the context that rfp.Items is set in. However I was always under the impression that the proper way to share collections between contexts was to select them into collection objects and pass these objects around, but it seems that these objects are still tied to their initial context in some way.
Thanks,

Try this:
public FilterParams RefreshFilterList(FilterParams rfp, FilteredBy filteredBy)
{
using (myEntity context as new myEntity())
{
itemIdsInCategory = (from i in context.items
join ic in context.ItemsCategories on i.Id equals ic.items.id
where ic.Categories.Id == '52'
select i.Id).ToList<int>();
rfp.Items = rfp.Items.Where(i => itemIdsInCategory.Contains(i.Id)).ToList<Item>();
}
return rfp;
}
Here we are only comparing the unique Ids of the items (int in this case) which could provide a performance benefit as well.

Related

Return value of IEnumerable<T>.Contains changes if called multiple times

I'm having trouble understanding why multiple calls of Contains return different values for the same parameter on the same enumerable.
While I understand that the collection can be modified, thus changing the result in a subsequent call, this can be ruled out here.
Consider the following (stripped-down) code in an MVC view.
The purpose of this will be to display a list of checkboxes (as there's no HTML-helper for that), and determining through the model's properties which ones should be checked when opening the view.
#foreach (var d in Model.AllDomains) {
bool isChecked = Model.Project.Domains.Contains(d.ID);
<input #(isChecked ? "checked=\"checked\" " : "")type="checkbox" value="#d.ID" />
// more stuff here
}
Changing this to use an actual List makes the whole thing work as expected:
var tmp = Model.Project.Domains.ToList();
#foreach (var d in Model.AllDomains) {
bool isChecked = tmp.Contains(d.ID);
<input #(isChecked ? "checked=\"checked\" " : "")type="checkbox" value="#d.ID" />
// more stuff here
}
The following is the model that is bound to my view (again simplified to make it more readable):
public ProjectVM GetByID(int id) {
return new ProjectVM {
Project = new Project {
... // Other properties here
Domains = from d in MyObjectModel.Projects[id].Domains
select d.ID
},
AllDomains = from d in MyObjectModel.Domains
orderby d.Name
select new {
ID = d.ID,
Name = d.Name
}
};
}
Now, while from debugging I know that Model.Project.Domains will contain the correct number of entries, as well as the correct values, calling .Contains() on the method returns an arbitrary result - either true or false.
In fact, if I put the line with the Contains() call into the debugger's "Watch" tab multiple times, even with an hard coded argument (e.g. 4) the result will alternate from true to false with every call.
What is happening here, what am I overlooking?
Because of the way that Model.Project.Domains is instantiated, its actual type is a WhereSelectEnumerableIterator<T>, but this implements IEnumerable<T> so that shouldn't be an issue...
It seems that the root cause of the problem was a sloppy/unusual implementation of the Enumerator in the foundation classes of our object model which made GetEnumerator() return an iterator which was already used in the previous call
Since Contains() stops iterating over the collection after the first match is found, it would return false on such an Enumerator if the seeked value was in the part which had already been searched in the previous iteration.
A negative result of Contains() caused the enumerator to reset internally, which explained the "toggling" result described in my original post.

Any way to add a property using linq?

So I have this list, it returns an ID and a thumbnail. ex. List<PersonPicture>
and I have this list, List<Person> which has a property named "picture" in it.
Is there anyway that I can merge this two properties and add the List<PersonPicture> to the property named "picture" in it and base this via the ID since they have the same?
Any help would be appreciated.
You can use an anonymous object for this, below an example:
List<PersonPicture> pictures = LoadPictures();
List<Person> persons = LoadPersons();
var result = persons.Select(pers => new
{
Id = pers.Id,
Name = pers.Name,
Picture = pictures.Where(pic => pic.PersId == pers.Id)
.FirstOrDefault()
.Thumbnail
};
Another solution is to use a Join:
var result = persons.Join(pictures,
pers => pers.Id,
pic => pic.PersId,
(pers, pic) => {
return new
{
Id = pers.Id,
Name = pers.Name,
Picture = pic.Thumbnail
};
});
LINQ isn't quite designed for modifying existing collections like this, but you can do it:
foreach (tup in people
.Join(
picture,
person => person.ID,
picture => picture.ID,
Tuple.Create
))
{
tup.Item1.Picture = tup.Item2;
}
EDIT: Note that this will produce unpredictable results if a person has more than one picture. Is this a possibility, and how should it be dealt with?
You could either use a Join or the Zip operator in linq. These links will take you to questions about the syntax of using both of them. Basically the Join just adds the two lists together based on a key just like in SQL and the Zip merges the two lists by matching the position of each element in each list..
You want to join the two lists based on a shared key -- the ID.
Basically, you want to use the Join operator in LINQ to find pairs of Person and PersonPicture that match the same ID:
persons.Join(pictures, // join these two lists
person => person.Id, // extract key from person
personPicture => personPicture.PersonId, // extract key from picture
(person, personPicture) => ??? // do something with each matching pair
The question you now face is what to do with each matching pair; Join lets you supply a delegate that takes a matching pair and returns something else, and the result of the Join operation will be a list of those 'something else's produced from each of the matching pairs.
Your problem is that you want to take each pair and do something with it -- specifically, you want to copy the picture from the PersonPicture object to the Person object. Since LINQ is all about finding data but not modifying it, this is not trivial.
You can do this in two ways. One is to create a temporary object from each pair, and then iterate over that and do your thing:
var pairs = persons.Join(pictures,
person => person.Id,
personPicture => personPicture.PersonId,
(person, personPicture) => new { person, personPicture };
foreach (var pair in pairs)
pair.person.Picture = pair.personPicture.Thumbnail;
(You can use a Tuple instead of a temporary object, as was suggested in another answer).
This works, but seems clumsy because of the temporary object (be it an anonymous object or a tuple).
Alternatively, you can do the assignment right inside the delegate, and return the Person object itself, since you're done with the PersonPicture object:
var personsWithPicturesPopulated = persons.Join(pictures,
person => person.Id,
personPicture => personPicture.PersonId,
(person, personPicture) => {
person.Picture = personPicture.Thumbnail;
return person;
});
This has the added bonus of giving you the list of persons for which you found a match in the personPictures list, omitting the ones without a match; this is sometimes exactly what you need (and other times it isn't, in which case you can discard the result of the join).

Returning a list using LINQ

I want to return a list of objects stored in a database using LINQ queries.
I tried the following
public BO.Hotel getHotels()
{
TripBagEntities db = new TripBagEntities();
var hotels = (from m in db.HotelEntities
where m.id < 10
select m).ToList().First();
return Mapper.ToHotelObject(hotels);
}
This returns only the first item in the list. How can I return the entire list?
Thanks in advance
Firstly, as per the comment, you should understand exactly what each line of your existing code does first. (You should also try to follow .NET naming conventions.) If you're guessing around which bit of your code does what, it would be a good idea to read a good tutorial geared towards the LINQ provider you're using (Entity Framework?).
We don't really know what Mapper.ToHotelObject does, or whether there's already a method for converting a whole sequence. This should work though:
public List<BO.Hotel> GetHotels()
{
// Note: you may want a using statement here...
TripBagEntities db = new TripBagEntities();
var hotels = db.HotelEntities
.Where(m => m.id < 10)
.AsEnumerable()
.Select(Mapper.ToHotelObject)
.ToList();
}
Or if the method group conversion doesn't work:
public List<BO.Hotel> GetHotels()
{
// Note: you may want a using statement here...
TripBagEntities db = new TripBagEntities();
var hotels = db.HotelEntities
.Where(m => m.id < 10)
.AsEnumerable()
.Select(m => Mapper.ToHotelObject(m))
.ToList();
}
Note that I've used "dot notation" for the whole query, as it makes life simpler when you're using things like AsEnumerable and ToList, and your query expression wasn't complicated anyway.
The AsEnumerable call "shifts" the query into LINQ to Objects, so that the query-to-SQL translation part doesn't need to try to convert Mapper.ToHotelObject into SQL, which I assume would fail.
You need to make your function return List<BO.Hotel> instead of a Hotel, and adjust your query and Mapper function accordingly.
public List<BO.Hotel> getHotels()
{
TripBagEntities db = new TripBagEntities();
return (from m in db.HotelEntities
where m.id < 10
select Mapper.ToHotelObject(m)).ToList();
}

Access a collection via LINQ and set a single member to a new object

I am trying to access a user object in a collection with the id = to users101 and set this to another users.
Controller.MyObject.SingleOrDefault(x => x.Id == "user101") = OtherUser();
Thanks in advance.
You can't do it with one LINQ expression.
Usually LINQ extensions works on enumerables, if MyObject is a collection you first have to find the required item and then overwrite it with the new object (moreover SingleOrDefault() will simply return null if condition is not satisfied).
You should write something like this (exact code depends on what MyObject is):
var item = Controller.MyObject.SingleOrDefault(x => x.Id == "user101");
if (item != null)
Controller.MyObject[Controller.MyObject.IndexOf(item)] = new OtherUser();
Please note that if you do not really need the check performed by SingleOrDefault() you can simplify the code (and avoid the double search performed in SingleOrDefault() and IndexOf()).
If this is "performance critical" maybe it is better to write an ad-hoc implementation that does this task in one single pass.
Try it in two lines:
var objectWithId = Controller.MyObject.SingleOrDefault(x => x.Id == "user101");
(objectWithId as WhateverTypeOfObjectOtherUserIs) = OtherUser();

Unable to create a constant value of type (type) Only primitive types ('such as Int32, String, and Guid') are supported in this context

I've read ALL of:
Unable to create a constant value of type 'System.Object' in Entity Framework
Entity Framework - "Unable to create a constant value of type 'Closure type'..." error
Entity Framework - Union causes "Unable to create a constant value of type.."
Only primitive types ('such as Int32, String, and Guid') are supported in this context
and searched a bit more, but still no solution. I've seen that this happens on EF 3.5 and in 4.0 the Contains method should be supported, but I'm in EF 4 but I'm getting this error. I have a photo gallery, where albums can have any number of different photos, and each photo can belong to any number of albums. So this is a many-to-many relationship.
I've got a VisibleObjects property which is used by about 100 other methods that work well, but I'm pasting it anyway: (I'm definitely sure the problem is not caused by something here)
static IQueryable<GlobalObject> VisibleObjects
{
get
{
return from obj in db.GlobalObjectSet where obj.IsVisible && !obj.SiteUser.IsDeactivated orderby obj.ID descending select obj;
}
}
I've tried several different queries:
I have a VisiblePhotos property:
This wasn't working:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>() where a.Photos.Contains(p) select p;
}
Changed to this:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>() where a.Photos.Any(other => p.ID == other.ID) select p;
}
Still didn't work.
Here is the calling method:
public static List<Photo> GetLatestPhotosByAlbum(Album alb, int count = 3)
{
lock (sync)
{
return alb.VisiblePhotos().OrderByDescending(p => p.ID).Take(count).ToList();
}
}
Wasn't working, changed to this:
public static List<Photo> GetLatestPhotosByAlbum(Album alb, int count = 3)
{
lock (sync)
{
return (from p in VisibleObjects.OfType<Photo>()
where alb.Photos.Any(ph => ph.ID == ph.ID)
select p).ToList();
}
}
Still isn't working. Complaining about not being able to create a constant of my Photo object type, which is an Entity object with an ID property, if it helps. I am not sure of the real cause of the error and I don't have any other ideas of queries in my mind. I think the method name is pretty self explanatory: I'm trying to get the photos in a given album. Loading album entries into memory is not a solution, the query should run on database, not memory. I need an explanation of this exception, why it is occuring here, and how can I get my query to work.
It will not work because you want to use local Album in linq-to-entities query. You must either use navigation property on p to get its album:
var query = from p in VisibleObjects.OfType<Photo>()
where p.Album.Id == alb.Id
select p;
or you must build complex query with some join between photos and albums. You cannot pass local object and any its relation to the query. Only simple properties can be passed.
I think that EF is trying to convert where a.Photos.Contains(p) into SQL like WHERE p IN (a.Photos), but it doesn't know how to express a.Photos in SQL. The SQL you want probably looks like WHERE p.Id IN (1, 2, 3), so you could try doing that in C#:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
var photoIds = a.Photos.Select(p => p.Id).ToArray();
return from p in VisibleObjects.OfType<Photo>() where photoIds.Contains(p.Id) select p;
}
I ran into a similar problem, and instead of IQueryable, I tried using List, and it worked. May be of some help.
I tried another way around and it worked:
static IQueryable<Photo> VisiblePhotos(this Album a)
{
return from p in VisibleObjects.OfType<Photo>()
where p.Albums.Any(alb => a.ID == alb.ID)
select p;
}
Quite weird to see this works but the other one not. But I'm still wondering why Contains is not working.

Resources