EF Core: Getting the `SUM` of nested collection - entity-framework-core

I have this code:
decimal returned = await db.ReturnSalesDeliveries
.Include(m => m.Items).ThenInclude(m => m.ReturnSCItem)
.Include(m => m.ReturnSalesContract)
.Where(m => m.ReturnSalesContract.SalesDeliveryId == sdId)
.Select(m => m.Items.Sum(x => x.InQty * x.ReturnSCItem.Price)).DefaultIfEmpty().SumAsync();
It was fine when I write this using EF, but it was not working on EF Core.
Apparently it cannot be translated to SQL.
This is the error:
Cannot perform an aggregate function on an expression containing an
aggregate or a subquery.
How can I rewrite this?

Rewrite query in the following way and note that Includes are not needed if you do not load whole entity.
decimal returned = await db.ReturnSalesDeliveries
.Where(m => m.ReturnSalesContract.SalesDeliveryId == sdId)
.SelectMany(m => m.Items)
.SumAsync(x => x.InQty * x.ReturnSCItem.Price);

Related

GroupBy + OrderByDescending, returning separate id [duplicate]

I need to get top 10 rows for each group in a table with entity framework.
Based on other solution on SO, I tried 2 things:
var sendDocuments = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.Select(t => new
{
t.Key,
Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
})
.ToArrayAsync();
error:
System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId),
ElementSelector:(EntityShaperExpression:
EntityType: DbDocument
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False ) )
.OrderByDescending(t2 => t2.InsertedDateTime)' could not be translated. Either rewrite the query in a form that can be translated,
> or switch to client evaluation explicitly by inserting a call to
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().
and
var sendDocuments2 = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
.ToArrayAsync();
error:
System.InvalidOperationException: 'Processing of the LINQ expression
't => t
.OrderByDescending(t2 => t2.InsertedDateTime)
.AsQueryable()
.Take(10)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Any other idea?
Update (EF Core 6.0):
EF Core 6.0 added support for translating GroupBy result set projection, so the original code for taking (key, items) now works as it should, i.e.
var query = context.Set<DbDocument>()
.Where(e => partnerIds.Contains(e.SenderId))
.GroupBy(e => e.SenderId)
.Select(g => new
{
g.Key,
Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
});
However flattening (via SelectMany) is still unsupported, so you have to use the below workaround if you need such query shape.
Original (EF Core 3.0/3.1/5.0):
This is a common problem, unfortunately not supported by EF Core 3.0/3.1/5.0 query translator specifically for GroupBy.
The workaround is to do the groping manually by correlating 2 subqueries - one for keys and one for corresponding data.
Applying it to your examples would be something like this.
If you need (key, items) pairs:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.Select(key => new
{
Key = key,
Documents =
context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
.ToList() // <--
});
If you need just flat result set containing top N items per key:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
);

How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1

I need to get top 10 rows for each group in a table with entity framework.
Based on other solution on SO, I tried 2 things:
var sendDocuments = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.Select(t => new
{
t.Key,
Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
})
.ToArrayAsync();
error:
System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId),
ElementSelector:(EntityShaperExpression:
EntityType: DbDocument
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False ) )
.OrderByDescending(t2 => t2.InsertedDateTime)' could not be translated. Either rewrite the query in a form that can be translated,
> or switch to client evaluation explicitly by inserting a call to
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().
and
var sendDocuments2 = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
.ToArrayAsync();
error:
System.InvalidOperationException: 'Processing of the LINQ expression
't => t
.OrderByDescending(t2 => t2.InsertedDateTime)
.AsQueryable()
.Take(10)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Any other idea?
Update (EF Core 6.0):
EF Core 6.0 added support for translating GroupBy result set projection, so the original code for taking (key, items) now works as it should, i.e.
var query = context.Set<DbDocument>()
.Where(e => partnerIds.Contains(e.SenderId))
.GroupBy(e => e.SenderId)
.Select(g => new
{
g.Key,
Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
});
However flattening (via SelectMany) is still unsupported, so you have to use the below workaround if you need such query shape.
Original (EF Core 3.0/3.1/5.0):
This is a common problem, unfortunately not supported by EF Core 3.0/3.1/5.0 query translator specifically for GroupBy.
The workaround is to do the groping manually by correlating 2 subqueries - one for keys and one for corresponding data.
Applying it to your examples would be something like this.
If you need (key, items) pairs:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.Select(key => new
{
Key = key,
Documents =
context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
.ToList() // <--
});
If you need just flat result set containing top N items per key:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
);

Linq include nested collection and orderby

I have nested entities like Property -> Facility -> Farm -> License etc.
I want to get the most recent license and my original query looks like this.
return _db.Properties
.Include(c => c.Contact)
.Include(f => f.Facilities.Select(c => c.Contact))
.Include(f => f.Facilities.Select(ff => ff.Farms.Select(s => s.Species)))
.Include(x => x.Facilities.Select(l => l.Licenses))
.SingleOrDefault(x => x.PropertyID == id);
Since I want to get the most recent license I tried
return _db.Properties
.Include(c => c.Contact)
.Include(f => f.Facilities.Select(c => c.Contact))
.Include(f => f.Facilities.Select(ff => ff.Farms.Select(s => s.Species)))
.Include(x => x.Facilities.Select(l => l.Licenses.OrderByDescending(d => d.IssueDate)))
.SingleOrDefault(x => x.PropertyID == id);
which fails of course because you cant use an orderby clause there.
If I try an orderby clause at the end of the linq it will start at Property and I will need to drill down to the licenses again.
What is the preferred convention for this?
--EDIT--
Based on Robert's suggestion I flipped the query to get the Licenses first but I guess I need to do some projection since this query only gives me the Property entity and no child entities.
return _db.Licenses.OrderByDescending(x => x.IssueDate)
.Include(f => f.Facility)
.Include(f => f.Facility.Property)
.Include(f => f.Facility.Property.Contact)
.Include(c => c.Facility.Contact)
.Include(f => f.Facility.Farms)
.Include(f => f.Facility.Farms.Select(s => s.Species))
.Where(x => x.Facility.Property.PropertyID == id)
.Select(x => x.Facility.Property).SingleOrDefault();
Any ideas to get all children?

Linq To Sql Entity Framework Getting Nested Entities

I'm trying to do an include of a nested entity. I assumed the below code would work but it does not recognize the CapitalMailOrders entitycollection. Can anyone point me in the right direction to include these?
var result = db.Contacts
.Include(a => a.IDXPageLinks)
.Include(b => b.ReboGatewayLoginInfoes)
.Include(c => c.SocialMedias)
.Include(d => d.WebSiteInfoes)
.Include(e => e.ContactImages)
.Include(f => f.RealtorSetUpProcesses)
.Include(h => h.RealtorSetUpProcesses.CapitalMailOrders) // getting compile time error here. Doesn't recognize Entity
.Include(g => g.Contact_CarrierCode_Assignments)
.FirstOrDefault(c => c.ContactID == id);
Thanks
The extra entity level is accessed from a collection and so you need to add a Select in order to bring all the entries into the include.
.Include(h => h.RealtorSetUpProcesses.Select(h2 => h2.CapitalMailOrders)

how to write udate query in yii for mongo db?

User::model()->update($_id, array('$set' => array('d' => 1)[, array('deleted' => 1)[, array('w' => 2)]]);
I want to update user table with specified user id.
it's not working
The brackets are optional parts, this stems from the PHP manual ( i.e.: http://php.net/manual/en/function.pathinfo.php ) so:
User::model()->update($_id, array('$set' => array('d' => 1)
[, array('deleted' => 1)[, array('w' => 2)]]);
Will never work and will actually throw a syntax error in PHP in its current state. Instead you can try:
User::model()->update($_id, array('$set' => array('d' => 1)));