【问题标题】:C# Sql Raw Query to Entity Framework queryC# Sql 原始查询到实体框架查询
【发布时间】:2021-03-02 17:30:44
【问题描述】:

我的数据库中有这样的组数据:

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

我想从每组代码中获取最新的ModificationDate 的值。

我尝试了一些 linq 查询,但总是遇到同样的错误

要么以可翻译的形式重写查询,要么通过插入对“AsEnumerable”的调用显式切换到客户端评估

为了解决这个问题,我编写了以下代码:

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]");

它工作正常,但我想将其转换为不会触发上述错误的 linq 查询。

我不想使用AsEnumerableToList,因为我有很多数据并且将其加载到内存中会很慢。

【问题讨论】:

  • 做这样的事情:db.OrderByDescending(x => x.ModificationDate).GroupBy(x => x.ModificationDate).Selectx => x.First()).ToList();
  • 它触发了同样的错误:.First()' 不能被翻译。以可翻译的形式重写查询,或通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用显式切换到客户端评估。 :(
  • 您的模型有问题。模型中的每个表都应该是一个 Enumerable() 对象。右键单击模型变量 db 并选择定义以找到模型。
  • .AsEnumerable() 是否将整个数据加载到内存中!
  • 那么在这个例子中使用 .AsEnumerable() 是一个好习惯吗?

标签: c# sql entity-framework-core


【解决方案1】:

在 EF 核心 3/5 中,GroupBy 经常发生此异常。在您的情况下,可以通过遵循此查询模式(基于 Chinook 示例数据库,不知道您的类模型)使 EF 满意:

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

这甚至是首选的查询模式,即使没有足够的 GroupBy 支持,因为(至少在 Sql Server 中,可能还有其他提供程序),EF 将 Take 转换为 SQL 中的高效 ROW_NUMBER() OVER(PARTITION BY ...) 构造。上面的 LINQ 查询在 EF-core 3.1.10 中被翻译成这条 SQL 语句:

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]

不过,有点令人失望的是,EF 没有根据 LINQ 查询中请求的属性减少子查询中的字段。这可以通过这样的查询来实现:

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
}

...不幸的是导致一些代码重复。

【讨论】:

  • 这适用于集合导航属性甚至手动连接(使用相关子查询语法而不是 GroupJoin 语法),但不适用于具有标准模式 query.GroupBy(e =&gt; KeyExpr(e)).SelectMany(g =&gt; g.OrderBy{Descending}(e =&gt; OrderExpr(e)).Take(N))GroupBy 场景。它确实需要实现Support ability to select top N of each group #13805。有趣的是,这实际上代表了where row_number() over (partition by order by) &lt;= N 模式。
  • @Ivan 是的,我知道,我想知道为什么 EF 让 GroupBy 对自己的支持如此困难。
【解决方案2】:

我终于用这个了:

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;

这很好用

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-20
    • 2015-06-16
    • 1970-01-01
    • 2016-06-08
    • 2013-03-08
    • 1970-01-01
    • 2016-05-28
    相关资源
    最近更新 更多