Linq Lambda expression OrderBY on a ThenInclude clause - entity-framework-core

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.

Related

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)

EF.Functions.Like() for an array of string

I want to filter the IQueryable<T> with the help of EF.Functions.Like() method, which accepts a string parameter, to make use of array of strings. Also, I want this filter to be applied on an IQueryable and not on a List.
var configurations = _dbContext.Configurations
.Include(x => x.ChildTable)
.Where(x => x.Id == Id);
if (!string.IsNullOrEmpty(request.Filter))
{
configurations = configurations.Where(x => EF.Functions.Like(string.Join(", ", x.ChildTable.Select(x => x.Name).ToArray()), '%' + request.Filter + '%'));
}
In the above code, Configuration has a one-many relationship with the ChildTable. Meaning, each configuration will have a more than one ChildTable entries related. So, with that given, x.ChildTable.Select(x => x.Name) is of type IEnumerable<string>. And, I want to filter the Configuration records whose ChildTable.Name entries is like the given request.Filter.
Try the following query:
var configurations = _dbContext.Configurations
.Include(x => x.ChildTable)
.Where(x => x.Id == Id);
if (!string.IsNullOrEmpty(request.Filter))
{
configurations = configurations
.Where(x => x.ChildTable.Any(c => EF.Functions.Like(c.Name, '%' + request.Filter + '%')));
}

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.

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

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.

Linq to Entities using Lambda Expressions and multiple where conditions

I am trying to select a list of objects at the end of a fairly long chain of joins/selects using Linq to Entities written as Lambda Expressions... Here is what I have currently the following two statements.
var formDefId = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDefId).First();
var rules = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDef)
.SelectMany(x => x.Events
.Where(y => y.EventTypeId == 7))
.Select(x => x.RuleGroup)
.SelectMany(x => x.Rules)
.SelectMany(x => x.RuleFormXmls
.Where(y => y.FormDefId == formDefId));
What I would like to do, is combine the two queries, and use the FormDefId returned by
.Select(x => x.FormDef)
in the final where clause instead of having to use the formDefId from a separate query.
Is this something that is possible?
Thank you in advance for your help
It is much easier to write this using query syntax.. Each from in query syntax corresponds to a SelectMany in lambda syntax. This allows you to have all the variables in scope.
var rules =
from ft in _unitOfWork.AsQueryableFor<FormTrack>()
from e in ft.FormDef.Events
from r in e.RuleGroup.Rules
from x in r.RuleFormXmls
where ft.FormTrackId == formTrackId
where e.EventTypeId == 7
where x.FormDefId == ft.FormDefId
select x