我一直在关注Applying filters when explicitly loading related entities,无法让它与多对多关系合作。
我创建了一个简单的模型:
简要说明:
Student
可能会花费很多Courses
而Course
会有很多Students
。
Student
可以生成多个Presentation
,但Presentation
只能由一个Student
生成。
我们所拥有的是Students
和Courses
之间的多对多关系,以及Student
和Presentations
之间的一对多关系。
我还添加了一个Student
,一个Course
和一个Presentation
相互关联。
以下是我正在运行的代码:
class Program
{
static void Main()
{
using (var context = new SportsModelContainer())
{
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
Student student = context.Students.Find(1);
context.
Entry(student).
Collection(s => s.Presentations).
Query().
Where(p => p.Id == 1).
Load();
context.
Entry(student).
Collection(s => s.Courses).
Query().
Where(c => c.Id == 1).
Load();
// Trying to run Load without calling Query() first
context.Entry(student).Collection(s => s.Courses).Load();
}
}
}
加载演示文稿后,我看到Presentations
的计数从0更改为1: 。 然而,在对Courses
执行相同操作后,没有任何更改:
因此,我尝试在不调用Query
的情况下加载课程,并且按预期工作:
(我删除了Where
子句以进一步突出显示这一点 - 最后两次加载尝试仅因“Query()”调用而不同
现在,我看到的唯一区别是一个关系是一对多,而另一个关系是多对多关系。这是一个EF错误,还是我错过了什么?
顺便说一下,我检查了最后两次Course
- 加载尝试的SQL调用,它们是100%相同的,所以看起来它的EF无法填充集合。
答案 0 :(得分:18)
我可以准确再现您描述的行为。我的工作是:
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Include(c => c.Students)
.Where(c => c.Id == 1)
.Load();
我不知道为什么在我们只想加载一个集合时,我们也应该被迫加载多对多关系(Include(...)
)的另一面。对我来说,感觉确实像一个错误,除非我错过了这个要求的某些隐藏原因,这个原因记录在某处或没有。
修改强>
另一个结果:您的原始查询(不包含)...
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.Load();
...实际上将课程加载到DbContext
中......
var localCollection = context.Courses.Local;
...显示。 Id 1的课程确实在这个集合中,这意味着:加载到上下文中。但它不在学生对象的子集合中。
修改2
也许这不是一个错误。
首先:我们在这里使用两个不同版本的Load
:
DbCollectionEntry<TEntity, TElement>.Load()
Intellisense说:
从中加载实体集合 数据库。注意实体那个 已经存在于上下文中的不是 用来自的值覆盖 数据库中。
对于其他版本(IQueryable
的扩展方法)...
DbExtensions.Load(this IQueryable source);
... Intellisense说:
枚举查询以便for 服务器查询,例如 System.Data.Entity.DbSet, System.Data.Objects.ObjectSet, System.Data.Objects.ObjectQuery, 和其他人查询的结果 将被加载到关联的 System.Data.Entity.DbContext, System.Data.Objects.ObjectContext或 客户端上的其他缓存。这是 相当于调用ToList然后 扔掉没有的列表 实际创建的开销 列表。
因此,在此版本中,无法保证子集合已填充,只是将对象加载到上下文中。
问题仍然存在:为什么要填充Presentations
集合,而不是Courses
集合。我认为答案是:因为关系跨度。
关系跨度是EF中的一项功能,可自动修复上下文中或刚刚加载到上下文中的对象之间的关系。但是对于所有类型的关系都不会发生这种情况。只有当一端的多重性为0或1时才会发生。
在我们的示例中,它表示:当我们将Presentations
加载到上下文中时(通过我们过滤的显式查询),EF还会将Presentation
entites的外键加载到Student
实体 - “透明地”,这意味着,无论FK是否在模型中暴露为非属性。加载的FK允许EF识别加载的Presentations
属于已在上下文中的Student
实体。
但Courses
集合并非如此。课程没有Student
实体的外键。中间有多对多的连接表。因此,当我们加载Courses
时,EF无法识别这些课程属于上下文中的Student
,因此无法修复Student
实体中的导航集合。
出于性能原因,EF仅为引用(而不是集合)执行此自动修复:
要透明地修复关系,EF 重写要带来的查询 所有关系的关系信息 它的多重性为0..1或1 另一端;换一种说法 作为实体的导航属性 参考。如果一个实体有 与多样性的关系 大于1,EF不会带回来 关系信息,因为它可以 与...相比,性能受到打击 带来一个外国人 其余的记录。瞻 关系信息意味着检索所有 记录中的外键。
引自Zeeshan Hirani's in depth guide to EF的第128页。
它基于EF 4和ObjectContext,但我认为这在EF 4.1中仍然有效,因为DbContext主要是ObjectContext的包装。
不幸的是,在使用Load
时要记住相当复杂的内容。
另一个编辑
那么,当我们想要明确地加载多对多关系的一个过滤方时,我们能做什么?也许只有这个:
student.Courses = context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.ToList();