Entity Framework Core LINQ 麻烦创建(选择案例存在)查询

我试图用一个额外的列列出所有项目,描述它是否由当前用户拥有。


所以我正在寻找一个生成类似于以下 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]))

笔记:


上面的代码是手动修改的,所以可能有错别字。请无视他们。


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


慕码人2483693
浏览 136回答 2
2回答

喵喔喔

您可能需要为查询中的“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 Itemsselect 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

万千封印

您需要告诉 EF 加载相关数据,在本例中为OwnedItems表。解决此问题的一种方法是包含相关表。如果有链接表的外键,则可以像这样轻松完成:var dataWithRelatedData = db_context.Items.Include(x => x.OwnedItems).Select ...避免大量往返数据库的另一种方法是将两个数据集加载到单独的查询中,然后将它们合并到内存中。因此,您将首先对Items进行查询,然后将数据返回到OwnedItems的另一个查询,最后将它们合并到一个对象列表中。这只会对数据库进行 2 次调用,从而提高性能。
打开App,查看更多内容
随时随地看视频慕课网APP