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

时间:2014-07-24 23:32:45

标签: c# .net entity-framework

我收到了EntityCommandCompilationException内部异常"不支持嵌套查询。 Operation1 ='案例'操作2 ='收集'"对于以下非常简单的实体框架查询:

    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; }

我正在使用Entity Framework 6.1.1。 UseNullDatabaseSemantics的两个设置都存在问题。我从其他帖子中了解到,实体框架的设计者决定不支持这种情况,但是想知道是否有人建议的解决方法会在一次访问数据库时返回所有数据。我意识到一个解决方法是简单地使用Include返回类型MyContainerContents的所有项目,但我希望投影只返回匹配的容器内容。 This answer似乎建议使用投影是返回某些相关项目的推荐方法,但正如此示例所示,我无法使此方法始终如一地工作。 (实际上,在其他一些示例中,我在实体框架代码中遇到了StackOverflowException,但我们会在另一个场合留下它。)

更新:为了回应@ usr的建议,我拿出了空参考检查。这产生了一个查询而没有错误。因此,有时可以使用投影来提前加载子集合的一些成员。但是如果有关于何时执行此操作以及何时使用子查询和空引用检查会使实体框架过于复杂的指导将会有所帮助。这里是没有空引用检查的SQL(当然,如上所述,没有使用空引用检查生成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

1 个答案:

答案 0 :(得分:0)

无论如何,此预测都不会为您带来任何效率提升,因为您无法在同一查询中急切地加载子集合。所有这一切都是每个外部项目发出一个子查询。选择n + 1问题。

链接的答案有这个问题。

因此答案是:你想要的是可能但不高效。这是一种效率低下的解决方法:

myContainerContents = bp.ContainerInBackpack.Contents
   .Where(x => bp.ContainerInBackpack != null && x.ContentsString == "Beverage"),

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