简体   繁体   中英

C# Sql Raw Query to Entity Framework query

I have a group data in my database like that:

Code   ModificationDate
--------------------------
 A     2020/01/02
 A     2020/01/01
 B     2020/01/03
 B     2020/01/01
 C     2020/01/04
 C     2020/01/01

And I want to get the value with the most recent ModificationDate from each group of codes.

I tried some linq queries, but I always get the same error

Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable'

To workaround it, I wrote this code:

var query = _context.Customers.FromSqlRaw(@"
            SELECT t.* FROM(
                  SELECT[Code], MAX([ModificationDate]) as [ModificationDate]
                  FROM[Customers]
                  GROUP BY[Code]
            ) c
            INNER JOIN[Customers] t
            ON t.[Code] = c.[Code] AND t.[ModificationDate] = c.[ModificationDate]");

It works fine, but I want to translate it to a linq query that doesn't trigger the mentioned error.

I don't want to use AsEnumerable or ToList because I have a lot of data and load it into memory is going to be slow.

This exception occurs frequently with GroupBy in EF core 3/5. In your case there is way to make EF happy by following this query pattern (based on the Chinook sample database, not knowing your class model):

from c in Customers
from i in c.Invoices.OrderByDescending(i => i.InvoiceDate).Take(1)
select new 
{
    c.LastName,
    i.BillingAddress
}

This is even the preferred query pattern, even failing sufficient GroupBy support, because (at least in Sql Server, maybe other providers too), EF translates Take into the efficient ROW_NUMBER() OVER(PARTITION BY ...) construct in SQL. The LINQ query above is translated to this SQL statement in EF-core 3.1.10:

SELECT [c].[LastName], [t0].[BillingAddress]
FROM [Customer] AS [c]
INNER JOIN (
    SELECT [t].[InvoiceId], [t].[BillingAddress], [t].[BillingCity], [t].[BillingCountry], [t].[BillingPostalCode], [t].[BillingState], [t].[CustomerId], [t].[InvoiceDate], [t].[Total]
    FROM (
        SELECT [i].[InvoiceId], [i].[BillingAddress], [i].[BillingCity], [i].[BillingCountry], [i].[BillingPostalCode], [i].[BillingState], [i].[CustomerId], [i].[InvoiceDate], [i].[Total]
    , ROW_NUMBER() OVER(PARTITION BY [i].[CustomerId] ORDER BY [i].[InvoiceDate] DESC) AS [row]
        FROM [Invoice] AS [i]
    ) AS [t]
    WHERE [t].[row] <= 1
) AS [t0] ON [c].[CustomerId] = [t0].[CustomerId]

It's a bit disappointing, though, that EF doesn't reduce the fields in the subquery according to the properties requested in the LINQ query. That could be achieved by a query like this:

from c in Customers
from i in c.Invoices.OrderByDescending(i => i.InvoiceDate)
    .Select(i => new { i.BillingAddress, i.BillingCity })
    .Take(1)
select new 
{
    c.LastName,
    i.BillingAddress,
    i.BillingCity
}

...unfortunately causing some code repetition.

Finally i'm using this:

IQueryable<Customer> query =
           from c in _context.Customers.GroupBy(a => a.Code).Select(a => a.Max(a => a.ModificationDate))
           from s in _context.Customers
           where c == s.ModificationDate
           select s;

This works fine

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM