I\'m trying to create a simple query using EFCore, returning the list of people i\'m conversing with, and the last message that was sent between the two of us (pretty much like
The query you are looking for standardly is expressed in LINQ to Entities (EF) with something like this (no join
s, no GroupBy
, use navigation properties):
var query = context.Visitors
.Select(v => new
{
Visitor = v,
Message = v.VisitorChatMessages
.OrderByDescending(m => m.CreatedAt)
.FirstOrDefault()
});
But here is the trap. EF6 creates quite inefficient SQL query, and EF Core until now produces (again quite inefficient) N + 1 SQL queries.
But this is changing in EF Core 3.0 in a positive direction! Usually (and still) I don't recommend using the preview (beta) versions of EF Core 3.0, because they are rewriting the whole query translation/processing pipeline, so many things don't work as expected.
But today I've updated my EF Core test environment to EF Core 3.0 Preview 9 and I'm pleased to see that the above query now nicely translates to the following single SQL query:
SELECT [v].[Id], [v].[CreatedAt], [v].[CreatedBy], [v].[Email], [v].[Fingerprint], [v].[IP], [v].[IsDeleted], [v].[LastUpdatedAt], [v].[LastUpdatedBy], [v].[Name], [v].[Phone], [t0].[Id], [t0].[CreatedAt], [t0].[CreatedBy], [t0].[IsDeleted], [t0].[IsFromVisitor], [t0].[LastUpdatedAt], [t0].[LastUpdatedBy], [t0].[Message], [t0].[UserId], [t0].[VisitorId]
FROM [Visitors] AS [v]
LEFT JOIN (
SELECT [t].[Id], [t].[CreatedAt], [t].[CreatedBy], [t].[IsDeleted], [t].[IsFromVisitor], [t].[LastUpdatedAt], [t].[LastUpdatedBy], [t].[Message], [t].[UserId], [t].[VisitorId]
FROM (
SELECT [c].[Id], [c].[CreatedAt], [c].[CreatedBy], [c].[IsDeleted], [c].[IsFromVisitor], [c].[LastUpdatedAt], [c].[LastUpdatedBy], [c].[Message], [c].[UserId], [c].[VisitorId], ROW_NUMBER() OVER(PARTITION BY [c].[VisitorId] ORDER BY [c].[CreatedAt] DESC) AS [row]
FROM [ChatMessages] AS [c]
) AS [t]
WHERE [t].[row] <= 1
) AS [t0] ON [v].[Id] = [t0].[VisitorId]
Note the beautiful utilization of the ROW_NUMBER() OVER (PARTITION BY ORDER BY)
construct. This is the first time EF query translation does that ever. I'm excited. Good job, EF Core team!
Update: The exact equivalent of your first query (which btw fails with runtime exception in Preview 9)
from c in context.ChatMessages
orderby c.CreatedAt descending
group c by c.VisitorId into x
select x.First()
but with additional information is
from v in context.Visitors
from c in v.VisitorChatMessages
.OrderByDescending(c => c.CreatedAt)
.Take(1)
orderby c.CreatedAt descending
select new
{
Visitor = v,
Message = c
})
The generated SQL is pretty much the same - just the LEFT OUTER JOIN
becomes INNER JOIN
and there is additional ORDER BY
at the end.
Looks like that to make this work, it's essential to avoid GroupBy
and use GroupJoin
(which collection navigation property represents in LINQ to Entities queries) or correlated SelectMany
to achieve the desired grouping.