Include Collection of Collection in Entity Framework - entity-framework

I'm using Entity Framework 4.3
I have 3 tables, Lender, Product and ProductDetail
A Lender has multiple Products and a Product has Multiple ProductDetail rows
Here's a chunk of code I'm trying to use:
Lender SingleOrDefault(Expression<Func<Lender, bool>> predicate)
{
using (var uow = new UnitOfWork(Connections.LoanComparision))
{
var r = new Repository<Lender>(uow.Context);
return r.Find(predicate)
.Where(x =>
x.IsPublished &&
x.Products.Any(y => y.IsPublished))
.Include(x => x.Products.SelectMany(y => y.ProductDetails))
.SingleOrDefault();
}
}
The issue is with the Include - I'm trying to get the Lender => Products => ProductDetails. I can't see the table in intellisense even though I know it is linked correctly. I thought the SelectMany might work but it gives me a runtime error.
Any ideas as to how this can be achieved?

You do selectmany however it is for flatten list of list to list.
To include relevant list you should do select as noted in comments noted by Developer user
Include(x => x.Products.Select(y => y.ProductDetails))

Related

Is there an alternate for an EF Core where clause using List.Contains(object, IEqualityComparer)?

The following syntax will indeed not be translated by EF Core.
var books = await context.Books
.Where(c => detachedBooks.Contains(c, new BookEqualityComparer()))
.AsNoTrackingWithIdentityResolution()
.ToListAsync();
I wanted to use a separate BookEqualityComparer here in order not to inherit from IEquatable in Entity Book so as not to interfere with the regular working of EF Core.
detachedBooks is a list of detached Book entities, i.e. the Id are not available.
I doubt anything that complex would be ever supported in EF. Select your relevant details from your detached entities or DTOs and use those. For instance something like this would generally match rows based on their PKs or elements of a composite key:
var bookIds = detachedBooks.Select(x => x.BookId).ToList();
var books = await context.Books
.Where(c => bookIds.Contains(c.BookId))
.AsNoTrackingWithIdentityResolution()
.ToListAsync();

EF Core AsSplitQuery not respecting OrderBy

I am using EF Core to query my DB. As I have some includes i get this warning
Compiling a query which loads related collections for more than one collection navigation either via 'Include' or through projection but no 'QuerySplittingBehavior' has been configured. By default Entity Framework will use 'QuerySplittingBehavior.SingleQuery' which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'
so when i add AsSplitQuery()
public async Task<Board> GetBoardAsync (Guid id) {
return await _context.Boards.Include (x => x.Lists.OrderBy(x => x.Order))
.ThenInclude (x => x.Items.OrderBy(x => x.Order))
.AsSplitQuery()
.FirstOrDefaultAsync (x => x.Id == id);
}
OrderBy is not respected when returning data.
How to overcome this warning and respect OrderBy
thanks
Try this:-
public async Task<Board> GetBoardAsync (Guid id) {
return await _context.Boards.Include (x => x.Lists.OrderBy(x => x.Order))
.ThenInclude (x => x.Items.OrderBy(x => x.Order))
.Where(x => x.Id == id)
.AsSplitQuery().FirstOrDefaultAsync();
}
Also, you can use AsNoTracking() for queries to better performance.hope it will resolve your issue.
UPDATE
Use ThenBy instead of OrderBy because ThenBy works for several sorting criteria.

Entity Framework - How to get an entity including a filtered collection of child entities

I have categories, and categories have a collection of products (Category.Products). I need to retrieve a category from the db by its id, but instead of including all its products, I want it to only include products with a given condition (example, order=0)
How can I do this with linq?
I tried with:
var e = db.Categories
.Include(a => a.products)
.Where(a => a.products.Any(r => r.order == 0))
.FirstOrDefault(p => p.id == id_category);
I don't think you can do that. In any case, the call to .Include() should be after any where clause, or it won't work.
In order to filter child collection you can try to select that to YouCustomModelor anonymous projection.
Note that it is not currently possible to filter which related entities are loaded. Include will always bring in all related entities Msdn reference.
var e = db.Categories
.Where(c => c.id == id_category)
.Select(p=> new
{
category = p,
products = p.Products.Where(k=>k.order==0)
}.FirstOrDefault();
var e = db.Categories.Where(a => a.order == 0);

Nested Where on 1-to-many in LINQ2Entity

I'm using EF4. Having 2 entities:
Person { Name }
Hobbys { Person.Name, IsCoolHobby }
1 Person can have several hobbys.
I now have
IQueryable<Person> p;
p = container.PersonSet.Include("Hobbys").AsQueryable();
p = p.Where(x => x ?????);
List<Person> tmp = p.ToList();
How can i return only those Persons who have cool hobbys (IsCoolHobby == true)? I tried join but i was not able to load them into the list (select can only return Person, Hobby or new Type - but how to map them to entity objects again?)
Thanks
How can i return only those Persons who have cool hobbys (IsCoolHobby
== true)?
List<Person> tmp = container.PersonSet.Include("Hobbys")
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.ToList();
This will load the people who have at least one cool hobby but the Hobbys collection for those people will always contain all hobbys, also the uncool hobbys.
Edit
Unfortunately filtering and sorting children during eager loading (Include) is currently not supported. There is a request on the EF feature suggestion page for this feature. The request has status "Under review", so there is a little hope that it might get implemented in the future. (Probably far future: At least the first docs about EF 5 (beta) on MSDN say explicitly that eager loading with filtering/sorting is still not implemented.)
For now there are only two workarounds. The first is to use a projection:
var projectedData = container.PersonSet
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.Select(p => new
{
Person = p,
CoolHobbys = p.Hobbys.Where(h => h.IsCoolHobby)
})
.ToList();
The result is a collection of anonymous objects which contain a user who has cool hobbys and a collection of those cool hobbys. If you don't disable change tracking (by using the NoTracking option for the query) the person's hobbys collection should be filled with the result automatically.
The second option is to use "explicit" loading with CreateSourceQuery:
List<Person> tmp = container.PersonSet
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.ToList();
foreach (var person in tmp)
{
person.Hobbys.Attach(person.Hobbys.CreateSourceQuery()
.Where(h => h.IsCoolHobby).ToList());
}
Two things to note here:
CreateSourceQuery is only available on EntityCollections, i.e. if you are using EntityObject derived entities. It's not available for POCO entities in EF 4.0. (EF >= 4.1/DbContext has the option for explicit loading also for POCOs -> Query() method.)
The above code represents 1+N roundtrips to the database: The first for the person collection without the hobbys and then one additional query per person to load the cool hobbys.

Entity Framework Include with condition

I need to filter a dealer based on id and the uncomplete checkins
Initially, it returned the dealer based only on id:
// TODO: limit checkins to those that are not complete
return this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
.FirstOrDefault();
As you can see the requirement is to limit the checkins.
Here's what I did:
var query = from d in this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
select new
{
Dealer = d,
Groups = from g in d.Groups
select new
{
Items = from i in g.Items
select new
{
Group = i.Group,
Observations = i.Observations,
Recommendations = i.Recommendations
}
},
Checkins = from c in d.Checkins
where c.Complete == true
select new
{
Inspections = from i in c.Inspections
select new
{
InspectionItems = i.InspectionItems
}
}
};
var dealer = query.ToArray().Select(o => o.Dealer).First();
return dealer;
It works.
However, I am not convinced I am doing the right thing.
What is the best way to accomplish what I did? A stored procedure maybe?
I am not sure I even have to use Include clause anymore
Thank you.
If you want to load filtered relations with single query you indeed have to execute such projection but you don't need those calls to Include. Once you are building projections includes are not use - you have returned data under your control.
Stored procedure will help you only if you fall back to plain ADO.NET because stored procedures executed through Entity framework are not able to fill related entities (only flattened structures).
Automatic fixupu mentioned by #Andreas requires multiple database queries and as I know it works only if lazy loading is disabled because proxied object somehow doesn't have information about fixup and it still has its internal flags for each relation as not loaded so when you access them for the first time they still execute additional query.
Maybe you can make use of the relation fixup mechanism in the EF ObjectContexts. When you do multiple queries in the same context for entities, that are related by associations, these are resolved.
Assuming your association between Dealers and Checkins is 1:n with navigation properties on each side, you could do like:
var dealer = yourContext.Dealers
.Where(p => p.DealerId == id)
.FirstOrDefault();
if(dealer != null)
{
yourContext.Checkins
.Where(c => c.Complete && c.DealerId == dealer.DealerId)
.ToList();
I have not tested this by now, but since EF recognises that the Checkins, it inserts into the context by the second query belong to the dealer from the first query, corresponding references are created.
#Andreas H:
Awesome, thank you a lot.
I had to adjust your suggestion like this and it worked:
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Include("Inspections")
.Include("Inspections.InspectionItems")
.Where(c => !c.Complete && c.Dealer.DealerId == dealer.DealerId)
.ToList();
}
return dealer;
I still have to use the Include otherwise it won't return the referenced entities.
Note also that Dealer.Groups are unrelated to the Dealer.Checkins.
So if there's no checkins satisfying the condition, Groups still need to be returned.
It's interesting to note that at first, I put the two include for checkins to the dealer
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Where(c => c.Complete && c.DealerId == id)
.ToList();
}
return dealer;
but it returned all the Checkins including those which are not complete.
I don't understand exactly why the latter doesn't work but the former does, how are the entities are resolved. I somehow can intuit that the former returns all data.
Your accepted solution will generate multiple database queries. As Ladislav Mrnka said a projection is the only way to pull your result with one query. The maintance of your code indeed hard. Maybe you could use an IQueryable-Extension that builds the projection dynamically and keep your code clean:
var query = this.ObjectContext.Dealers.SelectIncluding( new List<Expression<Func<T,object>>>>(){
x => x.Groups,
x => x.Groups.Select(y => y.Items),
x => x.Groups.Select(y => y.Items.Select(z => z.Observations)),
x => x.Groups.Select(y => y.Items.Select(z => z.Recommendations)),
x => x.Checkins.Where(y => y.Complete==true),
x => x.Checkins.Select(y => y.Inspections),
x => x.Checkins.Select(y => y.Inspections.Select(z => z.InspectionItems))
});
var dealer = query.First();
return dealer;
You can find the extension at thiscode/DynamicSelectExtensions on github