简体   繁体   English

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

[英]“Include” chaining in Entity Framework

Not sure if that's the correct term in the title, but had a question regarding a certain behavior described below. 不确定标题中的用语是否正确,但对以下所述的某些行为有疑问。

Given: 鉴于:

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

I don't have a test bed to confirm this behavior so wanted to make sure I was interpreting / remembering correctly - but if I were to throw an additional function in defined as such: 我没有测试床来确认此行为,因此想确保自己在正确解释/记住-但是如果要抛出一个额外的函数,如下所示:

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

I seem to recall when calling GetIncludeBarChainedWithGetIncludeFoo() , I'm only getting my Bars , not the additional Foos I would expect from the call to GetIncludeFoo() being a part of the call. 我似乎记得当调用GetIncludeBarChainedWithGetIncludeFoo() ,我只是得到了Bars ,而不是我希望从对GetIncludeFoo()的调用中获得的其他Foos成为调用的一部分。

Can this behavior be confirmed/explained? 可以确认/解释这种行为吗?

Disclaimer: EF may have changed since I played with it. 免责声明:自从我玩过EF以来,它可能已经发生了变化。 I'm writing at the level of EF 4.x. 我在EF 4.x级别上写作。 Later versions might have added some better support for propagating Include upwards the query, I actually never have checked. 更高版本可能为传播“包括向上查询”添加了一些更好的支持,而我实际上从未检查过。 But I somewhat doubt in that. 但是我对此有些怀疑。 So, please do not take all of this for absolute truth, do try on your own. 因此,请不要将所有这些都当作绝对真理,请自行尝试。 For example, I don't recall whether GroupBy really breaks the Inclusions, but I'm sure that Select which results in a projection to an anonymous type - does. 例如,我不记得GroupBy是否真的破坏了Inclusions,但是我确定Select会导致对匿名类型的投影-会。


No, in your example, it would work. 不,在您的示例中,它将起作用。

In your example, when calling GetIncludeBarChainedWithGetIncludeFoo , your code actually calls/builds the following sequence/query: 在您的示例中,当调用GetIncludeBarChainedWithGetIncludeFoo ,您的代码实际上会调用/构建以下序列/查询:

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

and this will 100%-ly behave as you thought: it will return FooBars with preloaded both Foos and Bars alike. 并且这将100%-ly发挥您的预期:它将返回同时预加载了Foos和Bars的FooBars。 Simple chaining is absolutely OK, just as you also could do: 简单链接绝对可以,就像您也可以这样做:

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

However, when you are splitting those two Includes over several methods like you did in your example, a way to a subtle pitfall opens. 但是,当像您在示例中那样将这两个“包含”拆分为几种方法时,将打开一种微妙陷阱的方法。 Let's look at: 让我们看一下:

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

If the "DoSomeMyOtherThings" makes anything that will cause the query to change its shape and loose its tight binding to a specific table, like a projection, then the IncludeBar will fail even if it seemed ok. 如果“ DoSomeMyOtherThings”所做的任何事情都将导致查询改变其形状并将其紧密绑定释放到特定表(如投影),则IncludeBar将失败,即使看起来还可以。 All includes (that define new sources to be pulled), should precede any other operations. 所有包含项(定义了要提取的新源)应在任何其他操作之前。

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

It may be seem obvious, but when you start wrapping includes/wheres/selects/groupbys into pretty methods and when you then start mixing methods together, it's really easy to forget about that: 看起来似乎很明显,但是当您开始将include / wheres / selects / groupbys包装到漂亮的方法中,然后开始将方法混合在一起时,真的很容易忘记这一点:

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

The first method will be able to Include "Foos". 第一种方法将能够包含“ Foos”。 The second method probably will succeed in Includeing "Bars". 第二种方法可能会成功包含“ Bars”。 However the last one may be unable to Include "Buzz" because of the groupby and/or projection hidden in the second method. 但是,由于隐藏在第二种方法中的groupby和/或投影,最后一个可能无法包含“嗡嗡声”。

Even when using pretty wrappers, the query still has to be: 即使使用漂亮的包装器,查询仍然必须是:

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

or someting like that. 或类似的东西。

I must have had a slightly different scenario, had a ToList() somewhere in there, or something similar because I am unable to reproduce the scenario I had a question on: 我肯定有一个稍微不同的场景,在其中某处有一个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();
        }
    }
}

generated SQL: 生成的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

If I happen across the actual scenario again which I was unable to find, I'll update/ask a new question. 如果再次遇到无法找到的实际情况,我将更新/询问一个新问题。 But in the interim, thanks for the sanity check. 但是在此期间,感谢您的健全性检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM