【问题标题】:EF Core 2.2 LINQ query not working in EF Core 3.0EF Core 2.2 LINQ 查询在 EF Core 3.0 中不起作用
【发布时间】:2020-01-25 07:40:40
【问题描述】:

以下代码在 EF Core 2.2 上运行良好,但在 EF Core 3.0 上无法运行

 var items = (from asset in Context.Assets
              join assetCategory in Context.AssetCategories on asset.CategoryId equals assetCategory.Id
              group assetCategory by assetCategory.Id into assetCategories
              select new AssetCategorySummary
              {
                  CategoryId = assetCategories.Key,
                  CategoryName = assetCategories.Select(p => p.CategoryName).FirstOrDefault(),
                  TotalAsset = assetCategories.Count()
              }).ToListAsync();

我得到的错误:

处理 LINQ 表达式 'AsQueryable(Select( 来源:NavigationTreeExpression 值:默认值(IGrouping) 表达式:(未处理的参数:e), 选择器:(p) => p.CategoryName))' by 'NavigationExpandingExpressionVisitor' 失败的。这可能表示 EF Core 中的错误或限制。 有关更多详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101433

需要帮助

【问题讨论】:

  • 这不是问题。升级到 EF 核心 3 时预计会发生变化。除非有与之相关的实际问题,否则无需在此处报告。
  • @S.阿齐兹·卡兹达尔 是的!这就是我告诉你的fetch the data into memory and then make your query,虽然它没有效果,因为这会降低性能或在大量数据的情况下失败。
  • @TanvirArjel 需要一种额外的方法来做到这一点。有什么东西可以将原始 sql 写入数据库吗?不是实体。某事Context.Database.RawSql
  • @S.AzizKazdal 您可以编写原始 sql 并使用 FromSql 方法。更多详情:entityframeworkcore.com/querying-data-raw-sql-queries
  • @TanvirArjel no FromSql 这不是我所说的。我想执行此查询并获取要列出的值。 select Kategori.Id,Kategori.CategoryName,count(Varlik.Id) as [Varlık Sayısı] from Assets Varlik inner join AssetCategories Kategori on Kategori.Id = Varlik.CategoryId group by Kategori.Id,Kategori.CategoryName

标签: entity-framework ef-core-3.0


【解决方案1】:

这是由于 EF Core 3.0 中的 breaking changes 之一,即:LINQ queries are no longer evaluated on the client

因此,请以 EF Core 可以将表达式转换为 T-SQL 或将数据提取到内存中然后进行查询的方式编写查询。

【讨论】:

  • 自动客户端评估已完全消失 - 这不是一个选项,也无法启用。
  • 是的,请参阅第二个链接中的 缓解措施 部分 - 如果无法完全翻译查询,请以可翻译的形式重写查询,或使用 AsEnumerable()、ToList() 或类似方法将数据显式带回客户端,然后可以使用 LINQ-to-Objects 进一步处理。 如您所见,没有启用它的选项. QueryClientEvaluationWarning 也被标记为 [Obsolete("Automatic client evaluation is no longer supported. This event is no longer generated.")]
  • 我没有得到这个重大变化。现在即使是一个简单的 group by 也失败了,它曾经在 ef 2.x 中工作
【解决方案2】:

原始查询存在问题,但 EF Core 将其隐藏在地毯下,从而减慢了一切速度。

客户端评估在 LINQ to SQL 中引入并在实体框架中删除时是邪恶的。我想不出为什么人们虽然将它添加回 EF Core 是个好主意,但现在它已经消失了,这是一件好事。原始查询也不会在 EF 6.2 中运行。

原始查询需要进行一些修复,这可能会提高性能。首先,从关系和导航属性生成连接是 ORM 的工作。

其次,即使在 SQL 中,也不可能在 SELECT 子句中添加不属于 GROUP BY 或聚合的字段。除非使用窗口函数,否则没有与 FirstOrDefault() 等效的聚合函数。

要在 SQL 中获取类别名称,我们必须将其包含在 GROUP BY 中或使用 CTE/子查询按 ID 分组,然后查找类别名称,例如:

SELECT CategoryID,CategoryName,Count(*)
FROM Assets inner join AssetCategories on CategoryID=AssetCategories.ID
GROUP BY CategoryID,CategoryName

SELECT CategoryID,CategoryName,Cnt
FROM (select CategoryID, Count(*) as Cnt
      from Assets
      group by CategoryID) a 
INNER JOIN AssetCategories on CategoryID=AssetCategories.ID

相当于 LINQ 中的第一个查询是:

 var items = (from asset in Context.Assets
              join assetCategory in Context.AssetCategories on asset.CategoryId equals assetCategory.Id
              group asset by new {assetCategory.Id,assetCategory.CategoryName} into summary
              select new AssetCategorySummary
              {
                  CategoryId   = summary.Key.Id,
                  CategoryName = summary.Key.Name,
                  TotalAsset   = summary.Count()
              }).ToListAsync();

如果实体被修改,例如 Asset 具有 Category 属性,则查询可以简化为:

 var items = (from asset in Context.Assets
              group asset by new {asset.Category.Id,asset.Category.CategoryName} into summary
              select new AssetCategorySummary
              {
                  CategoryId   = summary.Key.Id,
                  CategoryName = summary.Key.Name,
                  TotalAsset   = summary.Count()
              }).ToListAsync();

这需要一些测试以确保它创建一个健全的查询。过去发生了一些意外,我没有时间检查最终 EF Core 3.0 中生成的 SQL

更新

LINQPad 6 可以使用 EF Core 3,甚至可以使用外键约束从数据库生成 DbContext。

这个查询

 var items = (from asset in Context.Assets
              group asset by new {asset.Category.Id,asset.Category.CategoryName} into summary
              select new AssetCategorySummary
              {
                  CategoryId   = summary.Key.Id,
                  CategoryName = summary.Key.Name,
                  TotalAsset   = summary.Count()
              }).ToListAsync();

生成一个不错的 SQL 查询:

SELECT [a0].[ID] AS [CategoryId], [a0].[CategoryName], COUNT(*) AS [TotalAsset]
FROM [Assets] AS [a]
INNER JOIN [AssetCategories] AS [a0] ON [a].[CategoryID] = [a0].[ID]
GROUP BY [a0].[ID], [a0].[CategoryName]

使用 join 生成相同的 SQL 查询。

【讨论】:

    【解决方案3】:

    您仍然可以通过客户端评估在客户端上执行任何类型的设置操作,只需在执行设置操作之前插入AsEnumerable()。这就是在 3.0 之前的版本中处理所有集合操作的方式,并且根据具体的用例,客户端评估的性能可能与服务器评估一样好。

    【讨论】:

      猜你喜欢
      • 2020-03-29
      • 2021-05-03
      • 2019-03-27
      • 2020-03-13
      • 1970-01-01
      • 2021-02-22
      • 2021-04-27
      • 1970-01-01
      • 2019-12-17
      相关资源
      最近更新 更多