EF Core AsSplitQuery not respecting OrderBy - postgresql

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.

Related

Entity Framework translate method call

I'm using Entity Framework core 6 with SQL Server. I have the following query:
dbContext.Table1
.Where(t1 => some_condition(t1.some_property))
.Select(t1 => new
{
Field1 = t1.some_property,
Field2 = dbContext.Table2.Where(t2 => some_condition(t1.some_property, t2.some_property)).First(), // <-- this is my question
});
The thing is that this subquery dbContext.Table2.Where(t2 => some_condition(t1, t2)).First(), is used in many other places. I need to extract it somehow and call it instead of copy-pasting it everywhere.
I tried to extract it in a function like this:
public string Get(string some_property) => context.Table2
.Where(t => some_condition(some_property, t.some_property))
.First();
dbContext.Table1
.Where(t1 => some_condition(t1.some_property))
.Select(t1 => new
{
Field1 = t1.some_property,
Field2 = Get(t1.some_property)
});
But with that I get the subquery to execute separately and for every element in the collection (N+1 problem. I was expecting it though).
Is there a way to achieve that ? maybe using expression trees (which I'm not yet very familiar with).

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))

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.

How to do a search with EF CodeFirst

Currently, to do a search using EF CodeFirst and a repository pattern, based on user input to multiple text boxes on an mvc search view/page, I do something like the following:
public PagedList<Entity1> PlayerUserSearch(Entity1SearchParameters searchParameters, int? pageSize, int? startEntity, Func<Entity1, object> sortOrder, bool sortDesc)
{
IQueryable<Entity1> query = from entities in this.DataContext.Entity1s.Include("Entity2List")
where entities.Entity2List.Any()
select entities;
if (searchParameters.Entity2PrimaryKeyId.HasValue)
query = query.Where(e => e.Id == searchParameters.Entity2PrimaryKeyId.Value);
if (searchParameters.HasStats.HasValue)
{
if (searchParameters.HasStats.Value)
query = query.Where(u => u.Entity2List.Any(e => e.Stat != null));
else
query = query.Where(u => u.Entity2List.Any(e => e.Stat == null));
}
if (searchParameters.Entity2OtherField.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField == searchParameters.Entity2OtherField));
if (searchParameters.Entity2OtherField2.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2));
if (searchParameters.Active.HasValue)
query = query.Where(e => e.Active == searchParameters.Active.Value);
return this.GetPageByStartEntity(pageSize.Value, startEntity.Value, query, sortOrder, sortDesc);
}
The problem with this is that for every time I add on another where that checks the child of Entity1 (Entity2) for a certain field, it takes on a new " AND EXISTS" clause to the sql statement generated, so that it is doing an exists and checking table Entity2 all over again for every different field checked, rather than doing a single EXISTS on Entity in the query, and checking all fields I tacked on to the query (i.e. EntityOtherField1 and EntityOtherField2). I haven't been able to find a better way to do a search based on user inputs than constantly checking for the input being there (add to the search parameters)) and then tacking on a new where to the current query. Can anyone tell me if there is a better way to do this? Thanks!
I think what you're looking for is using Specification Pattern.
var spec = new specification<Entity2>(s => true);
if (searchParameters.HasStats.Value)
{
spec = spec.And(e => e.Stat != null);
}
if (searchParameters.Entity2OtherField2.HasValue)
{
spec = spec.And(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2);
}
query = query.Where(u => u.Entity2List.Any(spec));
Or I believe you can make it more standard by separating the filter logic and using spec.IsStatisfiedBy method.
A good framework for repository/specification implemetation on Entity Framework can be found here.

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