实体框架中的“包含”链接
本文关键字:包含 链接 框架 实体 | 更新日期: 2023-09-27 18:35:31
不确定这是否是标题中的正确术语,但对下面描述的某种行为有疑问。
鉴于:
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()
成为电话的一部分。
可以确认/解释这种行为吗?
免责声明:自从我使用它以来,EF 可能已经发生了变化。我正在 EF 4.x 级别编写。更高版本可能会为向上传播包含查询添加一些更好的支持,我实际上从未检查过。但我对此有些怀疑。所以,请不要把这一切都当作绝对的真理,一定要自己尝试。例如,我不记得 GroupBy 是否真的破坏了包含,但我确信 Select 这会导致投影到匿名类型 - 确实如此。
不,在您的示例中,它会起作用。
在您的示例中,当调用 GetIncludeBarChainedWithGetIncludeFoo
时,您的代码实际上调用/构建以下序列/查询:
// from GetIncludeFoo
return this
._context
.FooBars
.Include(i => i.Foos)
// from GetIncludeBarChainedWithGetIncludeFoo
.Include(i => i.Bars);
这将 100% 按照您的想法运行:它将返回预加载 Foos 和 Bars 的 FooBars。简单的链接是绝对可以的,就像你也可以做的那样:
// 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()
如果"DoSomeMyOtherThings"做出任何会导致查询改变其形状并松散其与特定表的紧密绑定(如投影)的操作,那么即使看起来没问题,IncludeBar 也会失败。所有包含(定义要拉取的新源)都应在任何其他操作之前。
return this
.GetIncludeFoo() // let's say they all are extensions just for clarity
.GetIncludeBar()
.DoSomeMyOtherThings()
这可能看起来很明显,但是当你开始将 include/wheres/selects/groupbys 包装成漂亮的方法,然后当你开始将方法混合在一起时,很容易忘记这一点:
return this
.GetFooThatAre("Green")
.GetBestBarForEachFoo()
.GetBuzzFromBar()
第一种方法将能够包含"Foos"。第二种方法可能会成功包含"柱线"。但是,最后一个可能无法包含"Buzz",因为第二种方法中隐藏了分组依据和/或投影。
即使使用漂亮的包装器,查询仍然必须是:
return this
.GetIncludeFoo()
.GetIncludeBar()
.GetIncludeBuzz()
.GetFooThatAre("Green")
.GetBestBarForEachFoo()
.GetBuzzFromBar()
或类似的东西。
我一定有一个稍微不同的场景,那里的某个地方有一个 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
如果我再次遇到我无法找到的实际场景,我将更新/提出一个新问题。 但在此期间,感谢您的健全性检查。