【问题标题】:Entity Framework Core LINQ trouble creating (select case exists) queriesEntity Framework Core LINQ 麻烦创建(选择案例存在)查询
【发布时间】:2018-12-12 15:48:12
【问题描述】:

我正在尝试列出所有项目,其中包含一个额外的列来描述它是否由当前用户拥有。

所以我正在寻找生成类似于以下 SQL 的 Linq 查询:

SELECT *,
   CASE WHEN
     EXISTS (
       SELECT NULL FROM OwnedItems
       WHERE OwnedItems.UserId = @UserId AND OwnedItems.ItemId = Items.Id
   )
   THEN 'true'
   ELSE 'false'
   END AS Owned
FROM Items;

根据互联网以及成功的 LinqPad 实验,此代码应该可以工作。

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   Item = item
}

在 LinqPad 中,此代码生成与我想要的完全相同的 SQL。但在我的项目中,它做了一些完全不同的事情。

我的代码是一个使用 Entity Framework Core 2.1 的 .Net Core 2.1 项目。由于它是一个核心项目,因此我无法在 LinqPad 中直接对其进行测试,因为它尚不受支持。

在我的项目中,此代码会生成一个未过滤的 SELECT 语句来查询每个项目,然后为每个项目单独查询以检查它是否存在于 OwnedItems 表中。像这样:

运行此查询的 1 个实例:

Executed DbCommand (68ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
  SELECT *
  FROM [Items] AS [item]

数百个这样的查询需要几秒钟才能运行:

 Executed DbCommand (32ms) [Parameters=[@__userId_0='?' (DbType = Int32), @_outer_Id='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
  SELECT CASE
      WHEN EXISTS (
          SELECT 1
          FROM [OwnedItems] AS [ownedItems]
          WHERE ([ownedItems].[UserId] = @__userId_0) AND ([ownedItems].[ItemId] = @_outer_Id))
      THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
  END

一些进一步的信息,也许它会有所帮助: 如果我使用同一行作为 where 子句的一部分,它会完美运行。

var q = from item in Items
    where OwnedItems.Any(o => o.UserId == userId && o.ItemId == item.Id)
    select item;

上面的 linq 生成了这个漂亮的 sql:

SELECT *
  FROM [Items] AS [item]
  WHERE EXISTS (
      SELECT 1
      FROM [OwnedItems] AS [o]
      WHERE ([o].[UserId] = @__userId_0) AND ([o].[ItemId] = [item].[Id]))

注意事项:

  • 以上代码已被手动修改,因此其中可能存在拼写错误。请忽略它们。

  • 我知道这个特定的查询可以使用左连接和检查空值来完成,但我的实际查询更复杂,我需要(嵌套)exists 子句。

解决方案更新

正如@KorsG 指出的,如果 Item 没有具体化,则会生成正确的查询。 我发现即使我写了以下内容,也无法实现 Item :

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   // Item = item  //THIS LINE GENERATES BAD QUERY
   Item = new Item {
       Id = item.Id,
       Name = item.Name,
       ...
       [Literally every single property listed one by one] = item.CorrespondingProperty
       ...
   }
}

所以我实际上可以实现整个项目,我只需要显式键入每个最后一个属性。好玩!

【问题讨论】:

  • In my project this code results in an unfiltered SELECT statement querying every Item, then for each of them a separate query to check if it exists in the OwnedItems table. -- 这也是您想要的查询所做的。我们可以看到查询 EF Core 2.1 的输出吗?
  • 不完全一样。 LinqPad 在一次访问 sql 服务器的过程中运行它大约需要 0.2 秒。在我的项目中,使用数百个查询运行相同的查询需要几秒钟。我从我的项目 SQL 查询中添加了一些代码。
  • 啊,在你更新了你的问题之后,你的意思就更清楚了。我相信发布的答案会帮助您解决这个问题

标签: c# sql entity-framework linq linqpad


【解决方案1】:

您可能需要为查询中的“OwnedItems”导航属性启用即时加载: https://docs.microsoft.com/en-us/ef/core/querying/related-data#eager-loading

如果我应该举个例子,请发布您的完整 linq 查询。

更新 1

似乎子查询在 EF Core 中有 N+1 个问题,可能会在版本 3 中修复。

参考:https://github.com/aspnet/EntityFrameworkCore/issues/10001

更新 2

如果您不需要完全实现“项目”,您应该能够做这样的事情,您可以创建一个匿名对象,而不是应该将 EF “欺骗”成您想要的:

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   Item = new { Id = item.Id, Name = item.Name }
}

参考:https://github.com/aspnet/EntityFrameworkCore/issues/11186

【讨论】:

  • 我没有在这里使用导航属性,或者至少我不确定这会有什么帮助。但你可能在正确的轨道上,我只是不太明白。我更新了问题以在“where”子句中包含具有相同行的版本。那个有效。您认为延迟加载可能仍然是一个问题吗? (顺便说一下,上面发布的 linq 是整个查询,我只是在它后面放了一个 ToList 来实际运行)
  • 我很惊讶,在阅读了您的链接后,我仍然有点困惑,但没有实现项目实际工作。我实际上完全需要它们,但我可以解决它。我现在将其标记为答案。我仍然希望我只是不急切地加载一些东西。谢谢!
【解决方案2】:

您需要告诉 EF 加载相关数据,在本例中为 OwnedItems 表。

解决此问题的一种方法是包含相关表。如果有链接表的外键,则可以像这样轻松完成:

var dataWithRelatedData = db_context.Items.Include(x => x.OwnedItems).Select ...

另一种避免大量往返数据库的方法是将两个数据集加载到单独的查询中,然后将它们合并到内存中。因此,您将首先对 Items 进行查询,然后将数据返回给 OwnedItems 的另一个查询,最后将它们合并到一个对象列表中。这只会对数据库进行 2 次调用,从而提高性能。

【讨论】:

  • OwnedItems 表有 ItemId 列,它是 Items 表的外键。没有其他方式的链接,因为一个项目可以由多个用户拥有。所以你的确切代码可能不起作用,但我正在尝试以某种方式加载它。
  • 关系是1-1、1-*还是-都没有关系,使用Include 只要在 EF 模型中定义了到相关表的导航
  • 感谢您的帮助,我仍在努力将多方包含在一对多关系中,或者是否完全有意义。
猜你喜欢
  • 2016-06-05
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-21
相关资源
最近更新 更多