I have Parent and Child entities related to each other as 1 to M. I need to query childs together with parents within a single SQL query, but the Include method is not properly working for some cases.
This one makes a correct single query for both Parent and Child tables (via JOIN):
var r1 =
ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 1)
.ToList();
Once i create an anonymous object on the fly, children are getting lost and SQL contains only Parent's fields. Retrieval of children remains lazy - they are still not loaded:
var r2 =
ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 2)
.Select(p => new { myParent = p})
.ToList();
Questions:
Why it's so?
How can I construct a new anonymous object in my LINQ so childs are not geting lost?
p.s. i'd like keep Childs property of Parent virtual.
This is a general problem in all versions of EF known to me. EF tries hard to pass the 'includes' as far as possible, but when "the shape of the query changes", the 'includes' are irreversibly lost.
The "shape" of the query changes for example, when:
a projection is used (select not whole object but just some fields, or different object)
a group-by or other aggregation is used
.. and probably in some more cases, which currently I dont remember.
Sadly, I also dont remember where on MSDN I stumbled upon the "shape of the query" explanation. If I find it, I'll drop here a link.
The solution is actually quite simple: simply specify the 'include' part not early, but at the final result. So, instead of set.include(x) at beginning, do .Select( .. => new { .., x }) to include the 'x' manually. It also works during grouping, as you can do a projection there too.
However, this is not a solution. This is a manual patch/hotfix, which does not solve anything. Considering that you might want to expose a "IQueryable<>" through some interface, you may like to expose a "base query" with some things already .Included. And of course, this is simply not possible to do in a general way, as if the client of the interface does a projection or grouping, he will lose the includes and he will not even know which ones should be. For me, this is a major deficiency in EF.
EDIT: just found one: .Include in following query does not include really Not MSDN, but just as good.
As you're creating an anonymous object the Parent DbSet Set<Parent>() of the context is not being populated with any data and therefore neither are the Children being stored in the context. One solution could be to add the children to the anonymous object but I'm not sure this would cause them to be added to the Set<Child> DbSet.
var r2 = ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 2)
.Select(p => new { myParent = p, children = p.Childs })
.ToList();
Related
This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 2 years ago.
I have two models that are related. Cars and announcements. Announcement and car ha one-to-one relationship.
How can I include a where in the relation?
updated: the name of the property is announcement not announcements and it is not a collection.
this.context.Cars.Include(a => a.announcement); // stuck here, I want to find the
// announcements that are active.
For a Where() on the Car:
this.context.Cars.Include(c => c.announcements).Where(c => c.Value == value);
Note, I've change your 'a' to a 'c' on the Include() because it represents a car, not an announcement.
For a Where() on the Announcement:
this.context.Cars.Where(c => c.announcements.Value == value);
Note, there is no need to Include() announcements in order to look at it in the Where(). You only need Include() if you're going to read announcements data in the programme once the query is executed.
Note: since you have use the plural form for the announcements property, I deduced it's a collection. My answer is based on this assumption. Btw., the rule is to use PascalCase for properties.
Include always includes all the related records. You have to make the test afterwards:
IEnumerable<Announcement> activeAnnouncements = context.Cars
.Include(c => c.announcements)
.SelectMany(c => c.announcements)
.Where(a => a.IsActive);
Note that SelectMany flattens nested sequences. Here, it produces a sequence of all announcements in all the cars.
If you had a one-to-many relationship and needed the cars together with the active announcements, you could combine the two in a ValueTuple:
IEnumerable<(Car c, Announcement a)> carsAndActiveAnnouncements = context.Cars
.Include(c => c.announcements)
.SelectMany(c => c.announcements, (c, a) => (c, a)) // 1st (c, a) are lambda parameters,
// 2nd creates tuple.
.Where(ca => ca.a.IsActive);
Get all the cars with active announcements, but including all the announcements (in a one-to-one relationship, this is always a single active announcement):
IEnumerable<Car> carsHavingActiveAnnouncements = context.Cars
.Include(c => c.announcements)
.Where(c => c.announcements.Any(a => a.IsActive));
And finally, you can add this property to the Car class (expression bodied syntax):
public IEnumerable<Announcement> ActiveAnnouncements =>
announcements.Where(a => a.IsActive);
Same as (full syntax):
public IEnumerable<Announcement> ActiveAnnouncements
{
get {
return announcements.Where(a => a.IsActive);
}
}
It lets you retrieve the active announcements easily at any time. The result is accurate even after edits.
But since, in this case, you have a one-to-one relationship, this does not help much.
Update:
Since, according to your update, announcement is not a collection and since you want to select the cars having an active announcement, here is my new solution:
IEnumerable<Car> carsHavingAnActiveAnnouncement = context.Cars
.Include(c => c.announcement)
.Where(c => c.announcement.IsActive);
Note that the Include is not required for the Where clause to work, as it will be translated to SQL and does not rely on an object reference. But it is of course legitimate, if you want the announcements to be loaded.
You elaborated on the purpose of the query in a comment:
My original problem is only select the cars that have an active annoucement. In another worlds only car data is needed.
If you don't need to load the announcement data, then you don't need to use Include.
I suspect this may be a confusion between EF/LINQ and SQL. In SQL, you have to join your data (= "include" it in the data set) before you can use it, regardless of whether you intend to use it in a SELECT, WHERE, or other.
But with EF/LINQ, you only need to use Include in a very specific circumstance: when you want to load this data in the result set, and when you don't already use a Select() to return a custom type (because this overrides the include behavior anyway).
What you want is to filter the data, and that doesn't require you to include it. You can simply call Where() to filter the data appropriately:
var carsWithAnActiveAnnouncement = db.Cars
.Where(c => c.announcement.IsActive)
.ToList();
Note: I assumed the property is called IsActive for the sake of example. You can correct this if needed.
This gives you the correct filtered list of cars without actually loading the announcements data, since you said you don't need it.
I am very new with C# and need some help. I am working on someone elses code and they are pulling data from a Model. I am trying to join two tables and need to use Include but the error is '==' cannot be applied to Guid and IQueryable. Could someone help with this please. Thanks in advance!
Yes, I am.
.Where() represents your filter. .Select() represents what you want back. If you just want the entities back you don't need a .Select().
If you have an association between menu items and MenuItemProgramData, for example, a MenuItem holds a reference to a MenuItemProgramData then you don't even need the first ID select statement:
return context.DbMenuItems
.Where(x => x.MenItemsProgramData.Plu == plu);
Note: If your context defines DbSet<T> for your various top level entities, you can just use context.Ts rather than .GetItems<T>.
If the relationship exists then this is the preferred approach. Let SQL do the work. The consumer of your method can further .Select() the applicable data, sort it, paginate it, and even append .Include() if you do want to interact with the entire entity graph.
If you don't have a relationship between the menu item and that program data, and know that the # of item IDs from the first query will remain relatively small (say, sub-100) then:
var itemIds = context.DbMenuItemProgramDatas
.Where(x => x.Plu == plu)
.Select(x => x.MenuItemId)
.ToList();
Without the .ToList() you are dealing with an IQueryable which EF would potentially still attempt to translate to SQL statements when later consumed. By using .ToList() it will execute the SQL and populate a List<int>. (Assuming the menu item ID is an int)
To get the IQueryable menu item data rows:
return context.DbMenuItems
.Where(x => itemIds.Contains(x.Id));
And that is it.
Edit: Based on the comment "I want to return a field named ParentId to know if it is empty or not. That's all but I need both tables linked to get that answer."
Additionally, looking back at the original code, the naming of the method is a bit misleading. GetItemProgramDataForSubItems implies returning MenuItemsProgramData rather than MenuItems... However, if ParentId is a property of MenuItem, then the caller of this method can use:
var hasParentId = context.GetItemProgramDataForSubItems(plu)
.Any(x => x.ParentId.HasValue);
If the ParentId is on the MenuItemsProgramData:
var hasParentId = context.GetItemProgramDataForSubItems(plu)
.Any(x => x.MenuItemsProgramData.ParentId.HasValue);
Beyond that, you may want to elaborate on what your entities and relationships look like, and what exactly you aim to accomplish from your method or business logic.
This question already has answers here:
Filtering on Include in EF Core
(9 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I am trying to get something like the following to work:
_dbmsParentSections = FactoryTools.Factory.PdfSections
.Include(x => x.Children.OrderBy(y => y.Order).ToList())
.Include(x => x.Hint).Include(x => x.Fields)
.Where(x => x.FormId == FormId && x.Parent == null)
.OrderBy(o => o.Order)
.ToList();
The part that causes the exception is:
.Include(x => x.Children.OrderBy(y => y.Order).ToList())
EDIT:
Upon further observation,
_dbmsParentSections.ForEach(x => x.Children = x.Children.OrderBy(y => y.Order).ToList());
did the job for me (after the initial Factory call and without the Children.OrderBy).
It seems you cannot sort the children collection in your query.
Either sort after the query or load the children in a second query.
Similar question and answer here
According to this documentation, starting with EF Core 5.0, you can sort by a property of your Included Entity:
await context.Parents
.OrderBy(parent => parent.Order)
.Include(parent => parent.Children.OrderBy(child => child.Order))
.ToListAsync();
The above example sorts Parent entities by their Order, as well as their Children entities by the Children entities' Order property.
The extension method Includeis a mere wrapper around DbQuery.Include. Internally it does not execute the expressions but only parses them, i.e. it takes their member expressions and converts them to a path as string. The path is used as input for DbQuery.Include.
It has been requested before to enhance the functionality of Include, e.g. to allow partly loaded collections by including a Where clause. Ordering could be another change request. But as you see, because of the internal working of Include the whole mechanism will have to be re-engineered to implement such enhancements. I don't see it on the current road map so it may take a while...
Depending on the use case you might not need to load in separate query or sort afterwards.
In my case I needed them ordered for when looping in the view so I just ordered there
#foreach (var subObject in Object.SubObjects.OrderBy(x=>x.Order))
I use this code por order the include, using a select and a function to order the collection.
Is not the best but work fine if subcollection is small
// GET: api/Tareas
[HttpGet]
public IEnumerable<Tarea> GetTareas()
{
var result = _context.Tareas
.Include(p => p.SubTareas)
.Select(p => SortInclude(p));
return result;
}
private Tarea SortInclude(Tarea p)
{
p.SubTareas = (p.SubTareas as HashSet<SubTarea>)?
.OrderBy(s => s.Position)
.ToHashSet<SubTarea>();
return p;
}
This will never gona work. EF include is try to understand and translate everything to SQL, but you want to much from this. Load all entities without sorting and .ToList()-ing, and write an extension method for IEnumerable to get an ordered result.
Generally if you're using a bunch of includes, it's because you need to access child properties in a view. What I do is order the child collection when I need to access it in a view.
For example, I might build some Include statements for a master/detail form. There's no sense ordering this at the initial EF query. Instead, why not order these child records at the view level when you're actually accessing them?
I might have a survey with multiple survey questions. If I want to present the questions in a particular order at do it at the partial view level when I'm passing the model child collection to the partial view.
#Html.Partial("_ResponsesPartial",Model.SurveyResponses.OrderBy(x =>
x.QuestionId))
You should not convert an IQueryable type to IEnumerable and call Include because Include is not supported by IEnumerable type.
In short, never call Include after ToList
IQueryable = server side call (SQL)
IEnumerable = client side (loaded in memory)
Ok I've got to be doing something really stupid, but I just can't see what it is.
I have the following query:
Recipes recipe = null;
var q = session.QueryOver<Recipes>(() => recipe)
.Where(p => p.Metadata.SkillCommon)
.Where(p => !p.Hidden);
The Recipes model has a property called Metadata which is a one-to-one mapping to the RecipeMetadata model. In other words, every recipe has one and only one RecipeMetadata. It's mapped like so:
HasOne(x => x.Metadata);
When I run this query, I get the exception:
An unhandled exception of type 'NHibernate.QueryException' occurred in
NHibernate.dll
Additional information: could not resolve property: Metadata.SkillCommon of: KitchenPC.DB.Models.Recipes
If I remove the reference to Metadata:
var q = session.QueryOver<Recipes>(() => recipe)
//.Where(p => p.Metadata.SkillCommon)
.Where(p => !p.Hidden);
The query works fine. Not only that, I can see the Metadata property under the debugger and I can see the value of Metadata.SkillCommon. So, it is mapped correctly to the database, eagerly loaded (since OneToOne mappings are always eager), and populated with the correct value. Why in the world can't I do a query on it?
It also has nothing to do with SkillCommon (which is a Boolean). I also can't filter on other properties within Metadata, such as numeric values.
Please point out my obviously idiotic error so I can continue on with my evening. Thanks!
Update:
After reading this article, I'm beginning to think I don't really have a One-To-One mapping. It seems like I do, because a single recipe will always have a single RecipeMetadata row, and no other Recipe will point to that same row, but perhaps this is not the case, since technically two recipes could share the same metadata with this schema, I just prevent it from happening through a unique constraint. Thoughts?
Update 2:
I switched to a many-to-one mapping, as suggested by many posts. In my Recipes mapping I now have:
References(x => x.Metadata).Column("RecipeId");
I can now run the query:
var q = session.QueryOver<Recipes>(() => recipe)
.Fetch(prop => prop.Metadata).Eager()
.Where(p => !p.Hidden);
And the RecipeMetadata row is joined in and everything works. However, still when I add:
.Where(p => p.Metadata.SkillCommon)
I get the same exception as above. So, this problem had nothing to do with OneToOne mappings.
Figured this out, though I'm not sure if this is the best and/or only way to do this. It seems like it should just work but sadly, no.
You need to call .JoinAlias and explicitly create a JOIN against this entity. You can then refer to that join later on:
Models.RecipeMetadata metadata = null;
var q = session.QueryOver<Recipes>(() => recipe)
.JoinAlias(r => r.Metadata, () => metadata)
.Where(() => metadata.SkillCommon)
.Where(p => !p.Hidden);
This appears to work great and generate the desired SQL.
I have a many to many relationship in EF Code First between Contacts and Lists. ProxyCreation and LazyLoading are disabled to allow serialization of the entities.
I have a query that is meant to return contacts that are in a given list.
// GET api/Contacts
[Queryable]
public IQueryable<Contact> GetContacts(int bulkListId)
{
var bulkList = db.BulkLists.Include(c => c.Contacts).Where(c => c.ID == bulkListId).SingleOrDefault();
if (bulkList == null)
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
return bulkList.Contacts.AsQueryable().OrderBy(c => c.ID).Include(c => c.AddressBookType).Include(c => c.BulkLists);
}
Although this works, it doesn't work as intended. It results in the correct set of contacts who are in a given list but these contacts only have that list populated in their Lists property of the relationship. So when this is serialized and gets back to the client it hides the other lists that the contacts are members of.
I can't see how the query is filtering it in this way and how I might change it to include the full set of lists. Any advice would be very much appreciated.
You're cheating! :)
By adding AsQueryable() to bulkList.Contacts you make it possible to continue with Include without making the compiler complain. BUT...
As per MSDN on DbExtensions.Include:
This extension method calls the Include(String) method of the IQueryable source object, if such a method exists. If the source IQueryable does not have a matching method, then this method does nothing.
(emphasis mine)
And EntityCollection does not have an Include method, so nothing happens.
You'll have to expand the include list in your first statement, probably like so:
db.BulkLists
.Include(c => c.Contacts.Select(c => c.AddressBookType))
.Include(c => c.Contacts.Select(c => c.BulkLists))