EF Core 6, GroupBy with inner collection Sum - linq

I want to create a query which will group a set by some criteria, and it will create a result set witch will contain the sum of some inner list.
This is my query, which fails:
var invoices = await _dbContext.Beneficiaries
.Where(dbEntry => dbEntry.Id == beneficiaryId && dbEntry.ProviderId == providerId)
.SelectMany(dbEntry => dbEntry.Invoices)
.GroupBy(dbEntry => dbEntry.IssueDate.Month)
.Select(dbEntry => new
{
IssueMonth = dbEntry.Key,
VAT = dbEntry.Max(invoice => invoice.VAT),
TotalPay = dbEntry.Select(invoice => invoice.InvoiceEntries.Sum(entry => entry.DelegateHourlyRate)).Max(),
TotalSell = dbEntry.Select(invoice => invoice.InvoiceEntries.Sum(entry => entry.BeneficiaryHourlyRate)).Max(),
})
.Where(group => group.IssueMonth <= _todayDate.UtcNow.Month && group.IssueMonth >= _todayDate.UtcNow.Month - (int)by)
.ToListAsync();
Following is the class hierarchy
public class Beneficiary
{
public ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public ICollection<InvoiceEntry> InvoiceEntries { get; set; }
}
public class InvoiceEntry
{
public decimal DelegateHourlyRate { get; set; }
public decimal BeneficiaryHourlyRate { get; set; }
}
This is the exception I'm getting with EF version 5.0.9.
The LINQ expression 'GroupByShaperExpression: KeySelector:
b.IssueDate, ElementSelector:EntityShaperExpression: EntityType:
Invoice ValueBufferExpression: ProjectionBindingExpression:
EmptyProjectionMember IsNullable: True .Max(invoice =>
invoice.InvoiceEntries.Count)' 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 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
This is the exception I'm getting with EF version 6.0.8.
SqlException: Cannot perform an aggregate function on an expression
containing an aggregate or a subquery. Cannot perform an aggregate
function on an expression containing an aggregate or a subquery.
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
</ItemGroup>
Another variation
var invoices2 = await _dbContext.Beneficiaries
.Where(dbEntry => dbEntry.Id == beneficiaryId && dbEntry.ProviderId == providerId)
.SelectMany(dbEntry => dbEntry.Invoices
.GroupBy(dbEntry => dbEntry.IssueDate.Month)
.Select(dbEntry => new
{
IssueMonth = dbEntry.Key,
VAT = dbEntry.Sum(invoice => invoice.VAT),
TotalPay = dbEntry.Sum(invoice => invoice.InvoiceEntries.Sum(entry => entry.DelegateHourlyRate)),
TotalSell = dbEntry.Sum(invoice => invoice.InvoiceEntries.Sum(entry => entry.BeneficiaryHourlyRate))
}))
.Where(group => group.IssueMonth <= _todayDate.UtcNow.Month && group.IssueMonth >= _todayDate.UtcNow.Month - (int)by)
.ToListAsync();
Which results in
SqlException: Cannot perform an aggregate function on an expression
containing an aggregate or a subquery. Cannot perform an aggregate
function on an expression containing an aggregate or a subquery.
Cannot perform an aggregate function on an expression containing an
aggregate or a subquery. Cannot perform an aggregate function on an
expression containing an aggregate or a subquery.

If I understand your query correctly, it should be another SelectMany. Rewritten query in Query Syntax for readability and change time range condition for using table indexes, if they are exist for sure.
var current = _todayDate.UtcNow;
var prev = current.Date.AddMonths(-1);
var query =
from b in _dbContext.Beneficiaries
from invoice in b.Invoices
where invoice.IssueDate <= current && invoice.IssueDate >= prev
from invoiceEntry in invoice.InvoiceEntries
group new { invoice, invoiceEntry } by new { invoice.IssueDate.Year, invoice.IssueDate.Month } into g
select new
{
IssueMonth = g.Key.Month,
VAT = g.Max(x => x.invoice.VAT),
TotalPay = g.Sum(x => x.invoiceEntry.DelegateHourlyRate),
TotalSell = g.Sum(x => x.invoiceEntry.BeneficiaryHourlyRate)
};
var invoices = query.ToList();

Related

How can I add Sum from another Table in my Model?

So I have my View setup like this in the controller:
public ActionResult View(Guid projectID)
{
OnboardModel model = context.onboard_projectInfos.Where(x => x.projectID == projectID).Select(x =>
new OnboardModel()
{
propertymanagername = x.propertymanagername,
propertymanagercontactemail = x.propertymanagercontactemail,
date_modified = (DateTime)x.date_modified,
projectmanagercontactnumber = x.projectmanagercontactnumber,
Developer = x.onboard_projectCreate.Developer,
status1 = x.onboard_projectCreate.status1,
ProjectName = x.onboard_projectCreate.ProjectName
}).SingleOrDefault();
var pix = projectID.ToString();
context.onboard_BuildingInfos.Where(x => x.buildprojectID == pix).GroupBy(x => x.buildprojectID).Select(g => {
model.totalres = g.Sum(b => b.numberofres);
model.totalcom = g.Sum(b => b.numberofcommer);
});
return View(model);
}
Problem is grabbing the sum of numberofres and numberofcommer from BuildingInfos.
Using .Select gives me the error:
Error CS0411 The type arguments for method 'Queryable.Select(IQueryable, Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
How to I write this LINQ statement correctly?
Thanks.
You cannot modify an object within a select (you can only create a new object). Further, you can't add new properties to an existing object.
We'll assume that OnboardModel defines the totalres and totalcom properties.
var query = context.onboard_BuildingInfos
.Where(x => x.buildprojectID == pix)
.GroupBy(x => x.buildprojectID);
foreach(var g in query)
{
model.totalres = g.Sum(b => b.numberofres);
model.totalcom = g.Sum(b => b.numberofcommer);
}

LINQ include and Projection

I have some classes defining entities with relationships
Account
has many Conversations [IEnumerable<Conversation> Conversations]
Conversation
has many Participants [IEnumerable<Account> Participants]
has many Messages [IEnumerable<Message> Messages]
Message
has one Sender [Account Sender]
has one Conversation [Conversation Conversation]
I'm trying to write a LINQ query that returns a list of Conversation ordered by date and including related participants and messages.
public async Task<List<Conversation>> FindAllByAccountIdAsync(Int32 id)
{
return await _Db.Conversations
.Where(c => c.Participants.Any(p => p.AccountId == id))
.Include(c => c.Participants)
.Include(c => c.Messages)
.ToListAsync();
}
This do the work but includes to much data i do not really need.
public async Task<List<Conversation>> FindAllByAccountIdAsync(Int32 id)
{
return await _Db.Conversations
.Where(c => c.Participants.Any(a => a.AccountId == id))
.Include(c => c.Participants.Select(a=> new
{
AccountId = a.AccountId,
Profile = new { FullName = a.Profile.FullName,
Email = a.Profile.Email
}
}))
// Only return the last message in
// Eventually I would not return an array with a single object but just the single object inside associated with the property LastMessageIn
.Include(c => c.Messages.OrderBy(m => m.Date).Select(m=> new
{
Body = m.Body,
SenderId = m.Sender.AccountId
}).Last())
.ToListAsync();
}
This script returns a mile long exception
{"message":"An error has occurred.","exceptionMessage":"The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties........}
My mind resist understanding and learning LINQ I do not know if its just me but as soon requirements exceeds basic querying and projection it break out of my control
Someone have some hints?
I'm not sure if I understand your question, but I believe you want something like this:
public async Task<List<Conversation>> FindAllByAccountIdAsync(Int32 id)
{
return await _Db.Conversations
.Where(c => c.Participants.Any(p => p.AccountId == id))
.Include(c => c.Participants)
.Include(c => c.Messages)
.Select(c => new
{
Participants = c.Participants.Select(a=> new
{
AccountId = a.AccountId,
Profile = new { FullName = a.Profile.FullName,
Email = a.Profile.Email
}
},
//EDIT: using OrderByDescending and FirstOrDefault
Messages = c.Messages.OrderByDescending(m => m.Date).Select(m=> new
{
Body = m.Body,
SenderId = m.Sender.AccountId
}).FirstOrDefault())
//others properties here
}
.ToListAsync();
}
You cannot project on an Include. An include is simply Eager Loading. The output does not change in the C#. Only the amount of data that is originally loaded (ie performance) changes.
It seems you want a projection and not eager loading, which are completely incompatible concepts.
However I cannot understand what exactly what you are trying to achieve.
public async Task<List<Conversation>> FindAllByAccountIdAsync(Int32 id)
{
return await _Db.Conversations
.Where(c => c.Participants.Any(p => p.AccountId == id))
.Include(c => c.Participants.Select(_=>_))
.Include(c => c.Messages.Select(_=>_))
.ToListAsync();
}
Should be enough.

RavenDb - ArgumentException: The field '__document_id' is not indexed, cannot sort on fields that are not indexed

I have RavenDB set up which is queried through a WebApi layer. The RavenDb layer returns an IQueriable onto which OData filters are applied in the WebApi layer. Every Employee object that is saved in the RavenDB has a Version property associated with it (value of DateTime.UtcNow.Ticks while saving the document). Recently I was working on a requirement where I can save same Employee multiple times over the period of time (as separate entities, differing in their property values but with same Id), but I only want to fetch the latest one based on its Version value.
In order to achieve this I used MapReduce as described below :
public class Employee_Version : AbstractIndexCreationTask<Employee>
{
public Employee_Version()
{
Map = employees => from employee in employees
select new Employee
{
FirstName = employee.FirstName,
LastName = employee.LastName,
Departments = employee.Departments,
Id = employee.Id,
Version = employee.Version,
ManagerId = employee.ManagerId,
EmployeeId=employee.EmployeeId
};
Reduce = results => from result in results
group result by result.ManagerId
into g
select new
{
ManagerId = g.OrderByDescending(d => d.Version).First().ManagerId,
Departments = g.OrderByDescending(d => d.Version).First().Departments,
FirstName = g.OrderByDescending(d => d.Version).First().FirstName,
LastName = g.OrderByDescending(d => d.Version).First().LastName,
Version = g.OrderByDescending(d => d.Version).First().Version,
Id = g.OrderByDescending(d => d.Version).First().Id,
EmployeeId = g.OrderByDescending(d => d.Version).First().EmployeeId
};
}
}
Raven Repository Code :
public IQueryable<Employee> GetEmployees(Expression<Func<Employee, bool>> expression)
{
using (var session = DocumentStore.OpenSession())
{
return session.Query<Employee, Employee_Version>().Statistics(out querysStatistics).Where(expression),
}
}
Web Api Layer Code :
Expression<Func<Employee, bool>> managerIdFilter = e => e.ManagerId == 123;
var employeeQueryable = _employeeRepository.GetEmployees(managerIdFilter);
var queryable = modelOptions.ApplyTo(employeeQueryable.Queryable, new ODataQuerySettings
{
EnableConstantParameterization = false,
HandleNullPropagation = HandleNullPropagationOption.False
});
When I query it like :
http://localhost/employee/list?$top=1
I get following exception :
Inner ExcpetionUrl: \"/databases/documents/indexes/Document/Version?&query=ManagerId%3A123&pageSize=1&sort=__document_id&SortHint-__document_id=String\"\ \ \ \ System.ArgumentException: The field '__document_id' is not indexed, cannot sort on fields that are not indexed
The same query works fine if no OData filter is used.

linq union merging sublists

Given a list of objects as follows:
Instance
- InstanceID
Version
- VersionID
- List<Instance> Instances
Activity
- ActivityID
- List<Version> Versions
I want to produce a list like this:
Activity
- ActivityID
- List<Instance> Instances
currently stuck at:
(from activity in activities
select new {
activity.ActivityID,
VersionGroup = (from version in activity.Versions
group version by version.ActivityID into versionGroup
select versionGroup)
})
Just not sure how to get to the instance level.
(from activity in activities
select new { activity.ActivityID,
Instances = activity.ActivityVersions.SelectMany(v => v.ActivityInstances).AsEnumerable() });
You can use the SelectMany method to flatten a sublist:
var result = activities.Select(a => new
{
a.ActivityId,
Instances = a.Versions.SelectMany(v => v.Instances)
.GroupBy(i => i.InstanceID)
.Select(grp => grp.First())
.ToList()
});
You can relpace the GroupBy logic with a Distinct with a custom IEqualityComparer<Instance>:
var result = activities.Select(a => new
{
a.ActivityId,
Instances = a.Versions.SelectMany(v => v.Instances)
.Distinct(new InstanceComparer())
.ToList()
});
class InstanceComparer : IEqualityComparer<Instance>
{
public bool Equals(Instance x, Instance y)
{
return x.InstanceID == y.InstanceID;
}
public int GetHashCode(Instance obj)
{
return obj.InstanceID.GetHashCode();
}
}
I haven't done the null check but they are trivial. This is, of course, assuming this is LINQ to Object as there is no tags that says otherwise.

ravendb count query not equal tolist count

ravendb query return different result for count method and tolist().count
query 1(return 9):
var count = session.Query<MobileForm,ByFormNameIndex>().Where(x => x.RequestType == RequestType.Db && x.BelongTo == oaname).ToList().Count;
query 2(return 44):
var count = session.Query<MobileForm,ByFormNameIndex>().Where(x => x.RequestType == RequestType.Db && x.BelongTo == oaname).Count();
index define:
public class ByFormNameIndex : AbstractIndexCreationTask<MobileForm>
{
public ByFormNameIndex()
{
Map = mobileForms => from form in mobileForms
select new
{
form.FormName,
form.BelongTo,
form.RequestType,
form.CreateTime,
form.Uuid
};
Analyzers.Add(x => x.FormName, "Lucene.Net.Analysis.PanGu.PanGuAnalyzer,PanGu.Lucene.Analyzer, Version=1.3.1.0, Culture=neutral, PublicKeyToken=null");
Indexes.Add(x => x.FormName, FieldIndexing.Analyzed);
Indexes.Add(x => x.BelongTo, FieldIndexing.NotAnalyzed);
Indexes.Add(x => x.RequestType, FieldIndexing.NotAnalyzed);
Indexes.Add(x => x.Uuid, FieldIndexing.NotAnalyzed);
}
}
query1 return the right count, so what's the differrent for this to method?show i new to rebuild the index to get the right result?
That is by design.
Count() will give you the total count.
ToList() gives you the first page only. And then you get the count on that.

Resources