在实体框架中“包含”链接

时间:2014-12-29 16:33:46

标签: c# linq entity-framework

不确定这是否是标题中的正确用语,但对下面描述的某种行为有疑问。

假设:

public class FooBar
{
    // etc
    public virtual ICollection<Foo> Foos { get; set; }
    public virtual ICollection<Bar> Bars { get; set; }
}

public class Foo
{
}

public class Bar
{
}

public class FooBarRepo
{
    private readonly EntitiesContext _context;

    public FooBarRepo()
    {
        this._context = new EntitiesContext();
    }

    public IQueryable<FooBar> GetIncludeFoo()
    {
        return this
            ._context
            .FooBars
            .Include(i => i.Foos);
    }

    public IQueryable<FooBar> GetIncludeBar()
    {
        return this
            ._context
            .FooBars
            .Include(i => i.Bars);
    }
}

我没有测试床来确认这种行为,所以我想确保我正确地解释/记住 - 但是如果我要按照这样的定义抛出一个额外的功能:

public IQueryable<FooBar> GetIncludeBarChainedWithGetIncludeFoo()
{
    return this
        .GetIncludeFoo()
        .Include(i => i.Bars);
}

我似乎记得在致电GetIncludeBarChainedWithGetIncludeFoo()时,我只收到我的Bars,而不是我希望调用Foos时的额外GetIncludeFoo()通话的一部分。

可以确认/解释这种行为吗?

2 个答案:

答案 0 :(得分:0)

免责声明:自从我使用EF以来,EF可能已发生变化。我是在EF 4.x的水平上写作的。以后的版本可能添加了一些更好的支持传播包括向上查询,我实际上从来没有检查过。但我有点怀疑。所以,请不要把所有这些都用于绝对的事实,请自己尝试。例如,我不记得GroupBy是否真的打破了Inclusions,但我确定Select会导致对匿名类型的投射 - 确实如此。


不,在你的例子中,它会起作用。

在您的示例中,当调用GetIncludeBarChainedWithGetIncludeFoo时,您的代码实际调用/构建以下序列/查询:

    // from GetIncludeFoo
    return this
        ._context
        .FooBars
        .Include(i => i.Foos)
    // from GetIncludeBarChainedWithGetIncludeFoo
        .Include(i => i.Bars);

这将100% - 按照您的想法行事:它将返回FooBars,同时预装了Foos和Bars。简单的链接绝对可以,就像你也可以做的那样:

    // from GetIncludeFoo
    return this
        ._context
        .FooBars
        .Include("Foos")
        .Include("Bars");

然而,当你将这两个包括你的例子中的几个方法分开时,就会出现一种微妙的陷阱。我们来看看:

    return this
        .GetIncludeFoo()       // let's say they all are extensions just for clarity
        .DoSomeMyOtherThings()
        .GetIncludeBar()

如果&#34; DoSomeMyOtherThings&#34;使任何会导致查询改变其形状并松散其与特定表格的紧密绑定(如投影),然后即使看起来没问题,IncludeBar也会失败。所有包含(定义要拉动的新源)都应该在任何其他操作之前。

    return this
        .GetIncludeFoo()       // let's say they all are extensions just for clarity
        .GetIncludeBar()
        .DoSomeMyOtherThings()

这看起来很明显,但是当你开始将包含/ wheres / choices / groupbys包装成漂亮的方法时,然后当你开始混合方法时,它很容易忘记:

    return this
        .GetFooThatAre("Green")
        .GetBestBarForEachFoo()
        .GetBuzzFromBar()

第一种方法将能够包括&#34; Foos&#34;。第二种方法可能会在Includeing&#34; Bars&#34;中成功。但是最后一个可能无法包含&#34; Buzz&#34;因为在第二种方法中隐藏了groupby和/或投影。

即使使用漂亮的包装器,查询仍然必须是:

    return this
        .GetIncludeFoo()
        .GetIncludeBar()
        .GetIncludeBuzz()
        .GetFooThatAre("Green")
        .GetBestBarForEachFoo()
        .GetBuzzFromBar()

或者像那样。

答案 1 :(得分:0)

我必须有一个稍微不同的场景,在那里的某个地方有一个ToList()或类似的东西,因为我无法重现我遇到问题的场景:

public class FooBarRepo : IFooBarRepo
{

    private readonly Entities _context;

    public FooBarRepo()
    {
        this._context = new Entities();
    }

    public IQueryable<FooBar> Get()
    {
        return this._context.FooBar;
    }

    public IQueryable<FooBar> GetIncludeFoo()
    {
        return this.Get().Include(i => i.Foo);
    }

    public IQueryable<FooBar> GetIncludeBar()
    {
        return this.Get().Include(i => i.Bar);
    }

    public IQueryable<FooBar> GetIncludeFooAndBar()
    {
        return this
            .Get()
            .Include(i => i.Foo)
            .Include(i => i.Bar);
    }

    public IQueryable<FooBar> GetIncludeFooAndChainBar()
    {
        return this.GetIncludeBar().Include(i => i.Foo);
    }

    public void Dispose()
    {
        this._context.Dispose();
    }
}

class Program
{
    static void Main(string[] args)
    {

        IEnumerable<FooBar> get;
        IEnumerable<FooBar> getIncludeFoo;
        IEnumerable<FooBar> getIncludeBar;
        IEnumerable<FooBar> getIncludeFooAndBar;
        IEnumerable<FooBar> getIncludeFooAndChainBar;

        using (var context = new EntityFrameworkTesting.TestIncludeChaining.Repository.FooBarRepo())
        {
            get = context.Get().ToList();

            getIncludeFoo = context.GetIncludeFoo().ToList();

            getIncludeBar = context.GetIncludeBar().ToList();

            getIncludeFooAndBar = context.GetIncludeFooAndBar().ToList();

            getIncludeFooAndChainBar = context.GetIncludeFooAndChainBar().ToList();
        }
    }
}

生成SQL:

-- get
SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name]
FROM [dbo].[_FooBar] AS [Extent1]

-- getIncludeFoo
SELECT 
[Project1].[Id] AS [Id], 
[Project1].[Name] AS [Name], 
[Project1].[C1] AS [C1], 
[Project1].[Id1] AS [Id1], 
[Project1].[FooBarId] AS [FooBarId]
FROM ( SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[FooBarId] AS [FooBarId], 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM  [dbo].[_FooBar] AS [Extent1]
    LEFT OUTER JOIN [dbo].[_Foo] AS [Extent2] ON [Extent1].[Id] = [Extent2].[FooBarId]
)  AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC

-- getIncludeBar
SELECT 
[Project1].[Id] AS [Id], 
[Project1].[Name] AS [Name], 
[Project1].[C1] AS [C1], 
[Project1].[Id1] AS [Id1], 
[Project1].[FooBarId] AS [FooBarId]
FROM ( SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[FooBarId] AS [FooBarId], 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM  [dbo].[_FooBar] AS [Extent1]
    LEFT OUTER JOIN [dbo].[_Bar] AS [Extent2] ON [Extent1].[Id] = [Extent2].[FooBarId]
)  AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC

-- getIncludeFooAndBar
SELECT 
[UnionAll1].[Id] AS [C1], 
[UnionAll1].[Id1] AS [C2], 
[UnionAll1].[Name] AS [C3], 
[UnionAll1].[C1] AS [C4], 
[UnionAll1].[Id2] AS [C5], 
[UnionAll1].[FooBarId] AS [C6], 
[UnionAll1].[C2] AS [C7], 
[UnionAll1].[C3] AS [C8]
FROM  (SELECT 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Id] AS [Id1], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Id] AS [Id2], 
    [Extent2].[FooBarId] AS [FooBarId], 
    CAST(NULL AS int) AS [C2], 
    CAST(NULL AS int) AS [C3]
    FROM  [dbo].[_FooBar] AS [Extent1]
    LEFT OUTER JOIN [dbo].[_Foo] AS [Extent2] ON [Extent1].[Id] = [Extent2].[FooBarId]
UNION ALL
    SELECT 
    2 AS [C1], 
    [Extent3].[Id] AS [Id], 
    [Extent3].[Id] AS [Id1], 
    [Extent3].[Name] AS [Name], 
    CAST(NULL AS int) AS [C2], 
    CAST(NULL AS int) AS [C3], 
    [Extent4].[Id] AS [Id2], 
    [Extent4].[FooBarId] AS [FooBarId]
    FROM  [dbo].[_FooBar] AS [Extent3]
    INNER JOIN [dbo].[_Bar] AS [Extent4] ON [Extent3].[Id] = [Extent4].[FooBarId]) AS [UnionAll1]
ORDER BY [UnionAll1].[Id1] ASC, [UnionAll1].[C1] ASC

-- getIncludeFooAndChainBar
SELECT 
[UnionAll1].[Id] AS [C1], 
[UnionAll1].[Id1] AS [C2], 
[UnionAll1].[Name] AS [C3], 
[UnionAll1].[C1] AS [C4], 
[UnionAll1].[Id2] AS [C5], 
[UnionAll1].[FooBarId] AS [C6], 
[UnionAll1].[C2] AS [C7], 
[UnionAll1].[C3] AS [C8]
FROM  (SELECT 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Id] AS [Id1], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Id] AS [Id2], 
    [Extent2].[FooBarId] AS [FooBarId], 
    CAST(NULL AS int) AS [C2], 
    CAST(NULL AS int) AS [C3]
    FROM  [dbo].[_FooBar] AS [Extent1]
    LEFT OUTER JOIN [dbo].[_Bar] AS [Extent2] ON [Extent1].[Id] = [Extent2].[FooBarId]
UNION ALL
    SELECT 
    2 AS [C1], 
    [Extent3].[Id] AS [Id], 
    [Extent3].[Id] AS [Id1], 
    [Extent3].[Name] AS [Name], 
    CAST(NULL AS int) AS [C2], 
    CAST(NULL AS int) AS [C3], 
    [Extent4].[Id] AS [Id2], 
    [Extent4].[FooBarId] AS [FooBarId]
    FROM  [dbo].[_FooBar] AS [Extent3]
    INNER JOIN [dbo].[_Foo] AS [Extent4] ON [Extent3].[Id] = [Extent4].[FooBarId]) AS [UnionAll1]
ORDER BY [UnionAll1].[Id1] ASC, [UnionAll1].[C1] ASC

如果我再次遇到我无法找到的实际情况,我会更新/提出一个新问题。但在此期间,感谢理智检查。