实体框架使用投影来急切加载一些带有null检查的项目

本文关键字:null 检查 项目 加载 框架 投影 实体 | 更新日期: 2023-09-27 18:14:47

我收到了一个内部异常的EntityCommandCompilationException "嵌套查询不受支持。Operation1='Case' Operation2='Collect'"用于以下非常简单的实体框架查询:

    var myQuery = from bp in dbContext.MyBackpacks
                  select new
                  {
                      myContainer = bp.ContainerInBackpack,
                      myContainerContents = (bp.ContainerInBackpack == null) ? null : bp.ContainerInBackpack.Contents.Where(x => x.ContentsString == "Beverage"),
                  };
    var myList = myQuery.ToList();

我的实体和数据上下文的相关部分定义如下:

    public class MyBackpack
    {
        public Guid MyBackpackID { get; set; }
        [ForeignKey("ContainerInBackpackID")]
        public virtual MyContainer ContainerInBackpack { get; set; }
        public Guid? ContainerInBackpackID { get; set; }
    }
    public class MyContainer
    {
        public Guid MyContainerID { get; set; }
        public virtual ICollection<MyContainerContents> Contents { get; set; }
    }
    public class MyContainerContents
    {
        public Guid MyContainerContentsID { get; set; }
        public string ContentsString { get; set; }
        public Guid? MyContainerID { get; set; }
        [ForeignKey("MyContainerID")]
        public virtual MyContainer MyContainer { get; set; }
    }
    // ...
    public virtual DbSet<MyContainerContents> MyContents { get; set; }
    public virtual DbSet<MyContainer> MyContainers { get; set; }
    public virtual DbSet<MyBackpack> MyBackpacks { get; set; }

我正在使用实体框架6.1.1。UseNullDatabaseSemantics的两个设置都存在这个问题。我从其他帖子中了解到,实体框架的设计师决定不支持这种情况,但想知道是否有人有一个建议的解决方案,将返回所有的数据在一个单一的行程数据库。我意识到一个解决方法是简单地使用Include来返回MyContainerContents类型的所有项目,但我希望投影仅返回匹配的容器内容。这个答案似乎表明,使用投影是返回一些相关项目的推荐方法,但是正如这个例子所表明的,我还没有能够使这种方法始终如一地工作。(事实上,在其他一些例子中,我在实体框架代码中遇到了StackOverflowException,但我们将把它留到另一个场合。)

更新:为了回应@usr的建议,我去掉了空参考检查。这只产生一个查询,没有错误。因此,有时可以使用投影提前加载子集合的某些成员。但如果有一些指导,当这样做,当使用子查询和空引用检查会使事情太复杂的实体框架将是有帮助的。下面是没有null引用检查的SQL(当然,如上所述,没有使用null引用检查生成SQL):

SELECT 
    [Project2].[MyBackpackID] AS [MyBackpackID], 
    [Project2].[C1] AS [C1], 
    [Project2].[ContainerInBackpack_MyContainerID] AS [ContainerInBackpack_MyContainerID], 
    [Project2].[C2] AS [C2], 
    [Project2].[MyContainerContentsID] AS [MyContainerContentsID], 
    [Project2].[ContentsString] AS [ContentsString], 
    [Project2].[MyContainer_MyContainerID] AS [MyContainer_MyContainerID]
    FROM ( SELECT 
        [Project1].[MyBackpackID] AS [MyBackpackID], 
        [Project1].[ContainerInBackpack_MyContainerID] AS [ContainerInBackpack_MyContainerID], 
        [Project1].[C1] AS [C1], 
        [Project1].[MyContainerContentsID] AS [MyContainerContentsID], 
        [Project1].[ContentsString] AS [ContentsString], 
        [Project1].[MyContainer_MyContainerID] AS [MyContainer_MyContainerID], 
        CASE WHEN ([Project1].[MyContainerContentsID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
        FROM ( SELECT 
            [Extent1].[MyBackpackID] AS [MyBackpackID], 
            [Extent1].[ContainerInBackpack_MyContainerID] AS [ContainerInBackpack_MyContainerID], 
            1 AS [C1], 
            [Extent2].[MyContainerContentsID] AS [MyContainerContentsID], 
            [Extent2].[ContentsString] AS [ContentsString], 
            [Extent2].[MyContainer_MyContainerID] AS [MyContainer_MyContainerID]
            FROM  [dbo].[MyBackpacks] AS [Extent1]
            LEFT OUTER JOIN [dbo].[MyContainerContents] AS [Extent2] ON ([Extent2].[MyContainer_MyContainerID] IS NOT NULL) AND ([Extent1].[ContainerInBackpack_MyContainerID] = [Extent2].[MyContainer_MyContainerID]) AND (N'Beverage' = [Extent2].[ContentsString])
        )  AS [Project1]
    )  AS [Project2]
    ORDER BY [Project2].[MyBackpackID] ASC, [Project2].[ContainerInBackpack_MyContainerID] ASC, [Project2].[C2] ASC

实体框架使用投影来急切加载一些带有null检查的项目

这个投影无论如何都不会给您带来任何效率提升,因为您不能在同一个查询中急切地加载子集合。这所做的就是为每个外部项发出一个子查询。选择n+1问题

链接的答案有这个问题。

因此,答案是:你想要的是可能的,但不是有效的。下面是一个低效的解决方法:
myContainerContents = bp.ContainerInBackpack.Contents
   .Where(x => bp.ContainerInBackpack != null && x.ContentsString == "Beverage"),

如果条件为false,则返回一个空序列。