EF Core: Strategies for segmenting IQueryable for re-use & loading selected properties only - entity-framework-core

I'm looking for a way efficiently generate and re-use part of a (fairly large) query in Entity Framework Core.
My goals are as follows:
Generate efficient SQL; including returning only the columns
needed from the relevant child tables, and avoiding N+1 scenarios
Abstract and separate the part of the queryable that runs from the grand-child level and below (first level of ThenInclude()), because this portion of the query will be used on other parent-level entities which have common grand-child entities, and I want to keep the code DRY.
My current query looks something like this (simplified) example:
var itemDetailsQuery = getQueryable(x => x.Id == itemId);
var item = await itemDetailsQuery.FirstOrDefaultAsync();
private IQueryable<Item> getQueryable(Expression<Func<Item,bool>> predicate)
{
return _itemRepository
.GetAll()
.Where(predicate)
.Include(x => x.ItemChildren)
.ThenInclude(x => x.ItemGrandChildren)
.ThenInclude(x => x.ItemGreatGrandChildren1)
.Include(x => x.ItemChildren)
.ThenInclude(x => x.ItemGrandChildren)
.ThenInclude(x => x.ItemGreatGrandChildren2)
.Include(x => x.ItemChildren)
.ThenInclude(x => x.ItemGrandChildren)
.ThenInclude(x => x.ItemGreatGrandChildren3)
.ThenInclude(x => x.ItemGreatGreatGrandChildren)
.ThenInclude(x => x.ItemGreatGreatGreatGrandChildren);
}
The above works, but:
loads every column from the relevant rows / entities and is therefore inefficient
I have to duplicate the query code if I want to load entities of type ItemGrandChildren and below from a parent entity type other than Item.
I found the post below which was very intriguing, suggesting usage of expressions and projections to achieve the kind of result I wanted, which was very neat because I could leverage an expression as a projection in each class:
https://benjii.me/2018/01/expression-projection-magic-entity-framework-core/
My implementation of this was as follows, with each child class having its own projection, so multiple levels are chained together using this strategy (note the ToList() is necesary because my collection properties are ICollections as opposed to IEnumerables; although I am worried this may cause premature execution:
private IQueryable<Item> getQueryable(Expression<Func<Item,bool>> predicate)
{
return _itemRepository
.GetAll()
.AsExpandable()
.Where(predicate)
.Select(x => new Item
{
Id = x.Id,
Name = x.Name,
ItemChildren = x.ItemChildren.AsQueryable().Select(ItemChild.Projection).ToList(),
});
}
However, when I try this, past the second layer of children, nothing is loaded; it may be that this was not designed to be used in a chained manner with multiple layers of nesting.
I've investigated alternative strategies, including extension methods, but although I can get these to work replacing first-tier Includes, I cannot find a way to have these replace a ThenInclude so I could do something like:
private IQueryable<Item> getQueryable(Expression<Func<Item,bool>> predicate)
{
return _itemRepository
.GetAll()
.Where(predicate)
.Include(x => x.ItemChildren)
.IncludeGrandChildren();
}
private IIncludableQueryable<ItemGrandChildren, ItemGreatGrandChildren3> IncludeGrandChildren(this IQueryable<ItemGrandChildren> values)
{
return values
.Include(x => x.ItemGreatGrandChildren1)
.Include(x => x.ItemGreatGrandChildren2)
.Include(x => x.ItemGreatGrandChildren3)
.ThenInclude(x => x.ItemGreatGreatGrandChildren)
.ThenInclude(x => x.ItemGreatGreatGreatGrandChildren);
}
I get the feeling that I'm missing or misunderstanding something fundamental, but any advice would be appreciated.

Related

Linq Lambda expression OrderBY on a ThenInclude clause

I have a query on an object that has a collection. I need to sort on a value in the collection. How can I use an OrderBY clause on a property of the collection?
var v = await _context.CollectionA
.Include(x => x.CollectionB).ThenInclude(x => x.CollectionC)
I am hoping to sort on a property that exists on CollectionC
accomplish something like...
.OrderBy(x => x.ObjectA.CollectionB.CollectionC.Ordinal)
Try the following query:
var query = _context.CollectionA
.Include(x => x.CollectionB)
.ThenInclude(x => x.CollectionC.OrderBy(c => c.Ordinal));
Eager Loading has no direct SQL translation, but I think it is what you need.

How to include two collections of a collection using Linq

I have a DBSet, it has a collection that I want to include. that collection has two objects that I want to include. If I use .ThenInclude on the first then I can't seem to get a reference to be able to include the second.
var a = _context.ModelA.Include(x => x.CollectionB).ThenInclude(x => x.ObjectC)
Where can I put a .Include(x => x.ObjectD) // which also belongs to CollectionB
Multiple inclusion does not exist in EFCore, You have to do it like this
var a = _context.ModelA
.Include(x => x.CollectionB).ThenInclude(x => x.ObjectC)
.Include(x => x.CollectionB).ThenInclude(x => x.ObjectD)

Include Collection of Collection in 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))

Include inner nested objects when retrieving parent from database

There are lets say ParentObject which contains ChildObjects which contain GrandChildObjects (All of these are being stored in database), basically multiple nested classes, and the thing is when i retrieve ParentObject from dbContext I want it to be fully populated/refreshed, however ChildObjects are usually null, that is fixed by including .Include(x => x.ChildObject) , but how can i do this to deeper nested objects? So far this is what I'm using to retrieve all ParentObjects:
// db -> DbContext
return db.Parents
.Include(x => x.Child1)
.Include(x => x.Child2)
.Include(x => x.Child3);
// Now ChildObjects are refreshed, however objects inside them are not (GrandChildObjects)
By simply selecting the appropriate property. Either one:
.Include(x => x.Child1.GrandChild)
Or many:
.Include(x => x.Child1.Select(c => c.GrandChild))

Where Clause With Null Object Using Entity Framework v5.0RC

I was trying to do the following
public IList<Category> GetMainCategories()
{
return _context.Category
.Where(x => x.ParentCategory == null)
.OrderBy(x => x.SortOrder)
.ToList();
}
However, no matter what I try this returns no results even though I can see in the collection that ALL ParentCategory's are null? I have read EF has problems with nulls like this and have also tried
.Where(x => x.ParentCategory.Equals(null))
And
.Where(x => Equals(x.ParentCategory.Id, null))
.Where(x => Equals(x.ParentCategory, null))
But still same result? I'm lost? How the heck do I check if an object it null? When I inspect it in VS2010 is clearly states its null?
Update
I can get it working doing this, BUT its insanely inefficient!!! MUST be able to do this in the query or I'm rather shocked by EF! Any help greatly appreciated?
public IList<Category> GetMainCategories()
{
var cats = _context.Category
.OrderBy(x => x.SortOrder)
.ToList()
.Where(cat => cat.ParentCategory == null)
.ToList();
return cats;
}
If the query ...
_context.Category
.Where(x => x.ParentCategory == null)
.OrderBy(x => x.SortOrder)
.ToList()
... returns an empty collection it means that all categories in the database have a ParentCategory or that the categories table is empty. That's all.
The fact that in the result collection of this query ...
_context.Category
.OrderBy(x => x.SortOrder)
.ToList()
... every category has no ParentCategory doesn't prove that every category has no ParentCategory in the database.
Do you know the basics of loading or not-loading related entities with EF? I suggest to read this introduction for EF >= 4.1/DbContext: http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
In your results it is not guaranteed that the references are actually null in the database even if you see null in your returned results.
Use this query to force EF to load the reference data:
public IList<Category> GetMainCategories()
{
return _context.Category
.Include(x => x.ParentCategory) // for the new EF versions
.Include("ParentCategory") // for older EF versions
// .Where(x => x.ParentCategory == null)
.OrderBy(x => x.SortOrder)
.ToList();
}
Note that the Include() method does not impact how the Where works, it just makes sure that when you view the ParentCategory property in debugger window (or access from code) you will have the data there.
It might be outdated but I guess this is the answer you were looking for:
public abstract class YourContext : DbContext
{
public YourContext()
{
(this as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
}
}
This should solve your problems as Entity Framerwork will use 'C# like' null comparison.