I have a table, Items, which has a many to one relationship with two distinct parents.
I want to select the counts of ParentA for each ParentB.
In SQL this is simple:
SELECT "ParentBId", count(distinct "ParentAId")
FROM "Items"
GROUP BY "ParentBId"
In Linq I have this statement:
var itemCounts = await _context.Items
.GroupBy(item => item.ParentBId,
(parentBId, items) => new
{
ParentBId = parentBId,
Count = items.Select(item => item.ParentAId).Distinct().Count(),
}).ToDictionaryAsync(group => group.ParentBId, group => group.Count);
When running this query, EF is blowing up with this error:
System.InvalidOperationException: Processing of the LINQ expression 'AsQueryable<string>(Select<Item, string>(
source: NavigationTreeExpression
Value: default(IGrouping<string, Item>)
Expression: (Unhandled parameter: e),
selector: (item) => item.ParentAId))' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
...
The Items table does use Table per hierarchy with a discriminator column to determine what the item type is. I do not know if this is a factor.
I have seen lots of people recommend the items.Select(i => i.Field).Distinct().Count() option, but this doesn't seem to be working here. Any other suggestions?
Thanks!
Currently any kind of distinction inside groups (like Distinct inside ElementSelector of GroupBy or another GroupBy inside ElementSelector of GroupBy) isn't supported by EF Core. If you insist on using EF in this case, you have to fetch some data in memory:
var result = (await _context.Items
.Select(p => new { p.ParentAId, p.ParentBId })
.Distinct()
.ToListAsync()) // When EF supports mentioned cases above, you can remove this line!
.GroupBy(i => i.ParentBId, i => i.ParentAId)
.ToDictionary(g => g.Key, g => g.Distinct().Count());
Related
I'm having trouble with a LINQ query.
var cRecords = context.ClassificationRecords
.GroupBy(p => p.ImageRecordId)
.Select(g => g.OrderByDescending(c => c.Created).FirstOrDefault())
.ToList();
What I'm trying to do is get a list of the last dated classification record for each user. I'll include the user and some other stuff, but that's the stripped down version.
The problem is that the above is giving me an error:
System.InvalidOperationException: 'The LINQ expression '(GroupByShaperExpression:
KeySelector: (c.ImageRecordId),
ElementSelector:(EntityShaperExpression:
EntityType: ClassificationRecord
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
)
)
.OrderByDescending(c => c.Created)' 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(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
I've seen this query used in several places on here and other websites, so it should be working..?
I'm using .Net Core 3.1 with EntityFrameworkCore 3.1.11
The query is being ran inside a hangfire worker program, so not a Blazor server - not sure if it makes a difference.
One possible work around is to modify your query into something EF 3.1 can translate - how efficient this will be depends on your database engine. I think this should work but you need the primary key to match the records:
var cRecords = context.ClassificationRecords
.Where(c => c.PrimaryKey == context.ClassificationRecords.Where(c2 => c2.ImageRecordId == c.ImageRecordId)
.OrderByDescending(c2 => c2.Created)
.First().PrimaryKey)
.ToList();
This query is not possible with EFC. After GroupBy you can select only grouping key or aggregation result. Faster query here is Window Functions usage which are not supported by EF.
So there is workaround:
var grouped =
from c in ontext.ClassificationRecords
group c by c.ImageRecordId into g
select new
{
ImageRecordId = g.Key,
Created = g.Max(x => x.Created)
};
var query =
from g in grouped
join c in context.ClassificationRecords
on new { g.Created, g.ImageRecordId } equals { c.Created, c.ImageRecordId }
select c;
var result = query.ToList();
The goal is to get the first DateTime and Last DateTime from a collection on an Entity (Foreign Key). My Entity is an organization and my collection are Invoices. I'm grouping results since Organizations unfortunately are not Unique. I'm dealing with duplicate data and cannot assume my organizations are unique so I'm grouping by a Number field on my Entity.
I'm using .NET Core 2.1.2 with Entity Framework.
I'm trying to get the following query generated from LINQ:
SELECT MIN([organization].[Id]) AS Id, MIN([organization].[Name]) AS Name,
MIN([organization].[Number]) AS Number, MIN([invoice].[Date])
AS First, MAX([invoice].[Date]) AS Last
FROM [organization]
INNER JOIN [invoice] ON [invoice].[OrganizationId] = [organization].[Id]
GROUP BY [organization].[Number], [organization].[Name]
ORDER BY [organization].[Name]
However I have no idea how to get to write the LINQ query to get it to generate this result.
I got as far as:
await _context
.Organization
.Where(z => z.Invoices.Any())
.GroupBy(organization => new
{
organization.Number,
organization.Name
})
.Select(grouping => new
{
Id = grouping.Min(organization => organization.Id),
Name = grouping.Min(organization => organization.Name),
Number= grouping.Min(organization => organization.Number),
//First = ?,
//Last = ?
})
.OrderBy(z => z.Name)
.ToListAsync();
I have no clue how to write the LINQ query in such a way that it generates the above.
I have a couple questions still:
Are the Min statements for Id, Name and Number correct ways of getting the
first element in the grouping?
Do I need a join statement or is "WHERE EXISTS" better (this got generated before I changed the code)?
Does anyone know how to finish writing the LINQ statement? Because I have to get the first and last Date from the Invoices Collection on my Organization Entity:
organization.Invoices.Min(invoice => invoice.Date)
organization.Invoices.Max(invoice => invoice.Date)
Here is the trick.
To make inner join by using collection navigation property simple use SelectMany and project all primitive properties that you need later (this is important for the current EF Core query translator). Then perform the GroupBy and project the key properties / aggregates. Finally do the ordering.
So
var query = _context
.Organization
.SelectMany(organization => organization.Invoices, (organization, invoice) => new
{
organization.Id,
organization.Number,
organization.Name,
invoice.Date
})
.GroupBy(e => new
{
e.Number,
e.Name
})
.Select(g => new
{
Id = g.Min(e => e.Id),
Name = g.Key.Name,
Number = g.Key.Number,
First = g.Min(e => e.Date),
Last = g.Max(e => e.Date),
})
.OrderBy(e => e.Name);
is translated to
SELECT MIN([organization].[Id]) AS [Id], [organization].[Name], [organization].[Number],
MIN([organization.Invoice].[Date]) AS [First], MAX([organization.Invoice].[Date]) AS [Last]
FROM [Organization] AS [organization]
INNER JOIN [Invoice] AS [organization.Invoice] ON [organization].[Id] = [organization.Invoice].[OrganizationId]
GROUP BY [organization].[Number], [organization].[Name]
ORDER BY [organization].[Name]
I am using entity framework core 2.1, I have a database context with an accessor for a model containing a boolean field represented as a non nullable bit field in an MS SQL database. I want to construct a query that evaluates in SQL efficiently that provides me a count of all rows in the table, and those with the bit column enabled.
var groups = await this.context.Models
.AsNoTracking()
.GroupBy(i => 1)
.Select(g => new ViewModel
{
Count = g.Count(),
Revoked = g.Count(p => p.IsRevoked)
})
.ToArrayAsync();
In order to force the query to consume all rows, I use ToArray, however the group by, count and where clauses log they cannot be evaluated remotely.
Other attempts such as:
var query = await this.context.Models
.AsNoTracking()
.GroupBy(i => i.IsRevoked)
.ToArrayAsync();
Produces two groups which I can later inspect but they fail to evaluate the bit column the same.
How can I generate a single expression that produces a new object with the count of all rows and the count of the subset which have the bit field enabled?
The first technique (group by constant) worked well in EF6. Just instead of predicate based Count which has not direct SQL equivalent, using the conditional Sum produced a nice GROUP BY SQL.
Unfortunately, this doesn't translate to SQL in EF Core, even in 2.1.
Fortunately, combining it with intermediate projection produces the desired SQL translation in EF 2.1:
var counts = await this.context.Models
.Select(e => new { Revoked = e.IsRevoked ? 1 : 0 })
.GroupBy(e => 1)
.Select(g => new ViewModel
{
Count = g.Count(),
Revoked = g.Sum(e => e.Revoked)
})
.ToArrayAsync();
In my recent application, i have a Document entity and this document can refer from a user to another user, also each group of users has a DocumentStation; these refers logged in DocumentStationHistory table:
Now, i want list all last document refers that loges in DocumentStationHistory table to a Dictionary using EF code first(group by documentId).
so i wrote these method:
public Dictionary<int, DocumentStationHistory> GetLastDocumentStationHistoryListOfDocuments(string criteria)
{
Dictionary<int, DocumentStationHistory> result = new Dictionary<int, DocumentStationHistory>();
using (IUnitOfWork uow = new MyContext())
{
DocumentStationHistoryRepository repository = new DocumentStationHistoryRepository(uow);
result = repository.All().
Include(x => x.DocumentStation).
Where(criteria,new object[]{}).
OrderBy(d=>d.DocumentId).
OrderBy(d=>d.DocumentStationHistoryId).
GroupBy(g => (int)g.DocumentId).
ToDictionary(g => (int)g.Key, g => g.LastOrDefault());
return result;
}
}
it return a dictionary, but the result isn't correct, it doesn't return the last refer of each document, also the DocumentStation navigation property, in the result is null.
where is my mistake?
Two problems for the ordering:
You're using OrderBy twice, which almost certainly doesn't do what you think it does. You should usually use OrderBy followed by ThenBy
I don't believe GroupBy is guaranteed to maintain the ordering of the rest of the sequence. You should order within the grouping:
result = service.All()
.Include(x => x.DocumentStation)
.Where(criteria, new object[]{})
.GroupBy(g => (int)g.DocumentId)
.ToDictionary(g => (int)g.Key,
g => g.OrderBy(d => d.DocumentId)
.ThenBy(d => d.DocumentStationHistoryId)
.Last());
(There's no need to use LastOrDefault - there has to be at least one element, otherwise there wouldn't be a group.)
An alternative to using Last would be to use OrderByDescending and ThenByDescending, then First, by the way.
I don't know about the DocumentStation inclusion part, I'm afraid.
Trying to implement following queries in NHibernate QueryOver.
SQL
SELECT
SUM(GrossChargeAmount)
FROM Charges
INNER JOIN Account on Account.Chargekey = Charges.Chargekey
SELECT
SUM(GrossChargeAmount),
Account.AccountHolder
FROM Charges
INNER JOIN Account on Account.Chargekey = Charges.Chargekey
GROUP BY
Account.AccountHolder
NHibernate:
var accountAlias = null;
var baseQuery = session.QueryOver<Charges>()
.JoinAlias(c => c.Account, () => accountAlias)
.SelectList(s => s.SelectSum(c => c.GrossChargeAmount))
I have code that constructs the baseQuery. I then, depending on method parameters will want to append the GROUP BY to the baseQuery like:
baseQuery.SelectList(s => s.GroupBy(() => accountAlias.AccountHolder)
This however ends up resetting projections on my baseQuery and essentially "overwritting" the initial projections ( .SelectList(s => s.SelectSum(c => c.GrossChargeAmount)) ). At this stage an execution of the query will not have any SUM() bu just the GROUP BY.
TLDR; How do I add projections to an Nhibernate QueryOver instance?
Last, but not least I'm running on NHibernate 2.0.50727
try:
baseQuery.SelectList(s =>
s.SelectSum(c => c.GrossChargeAmount)
.GroupBy(() => accountAlias.AccountHolder))
you could also build up the expression passed in to SelectList if you prefer