EF 4.1加载过滤的子集合不适用于多对多

Yak*_*ych 13 entity-framework entity-framework-4.1

我一直在寻找在显式加载相关实体时应用过滤器,并且无法使其适用于多对多关系.

我创建了一个简单的模型:模型

简要说明:
A Student可以占用很多Courses,Course也可以有很多Students.
A Student可以制作很多Presentation,但Presentation只能由一个制作Student.
所以我们所拥有的是StudentsCourses之间的多对多关系,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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

加载演示文稿后,我看到计数Presentations从0更改为1: 加载演示文稿后.但是,在做同样的Courses事情后没有任何改变: 试图加载课程后

所以我尝试在没有调用的情况下加载课程Query,它按预期工作: 课程已加载

(我删除了该Where子句以进一步强调这一点 - 最后两次加载尝试仅因"Query()"调用而不同)

现在,我看到的唯一区别是一个关系是一对多,而另一个关系是多对多.这是EF的错误,还是我错过了什么?

顺便说一句,我检查了最后两次Course加载尝试的SQL调用,它们是100%完全相同的,所以它似乎是EF无法填充集合.

Sla*_*uma 18

我可以准确再现您描述的行为.我的工作是这样的:

context.Entry(student)
       .Collection(s => s.Courses)
       .Query()
       .Include(c => c.Students)
       .Where(c => c.Id == 1)
       .Load();
Run Code Online (Sandbox Code Playgroud)

我不知道为什么Include(...)当我们只想加载一个集合时,我们也应该被迫加载多对多关系()的另一面.对我来说,它确实像一个错误,除非我错过了这个要求的隐藏原因,这个原因记录在某处或没有.

编辑

另一个结果:您的原始查询(不包括)...

context.Entry(student)
       .Collection(s => s.Courses)
       .Query()
       .Where(c => c.Id == 1)
       .Load();
Run Code Online (Sandbox Code Playgroud)

......实际上将课程加载到DbContext......

var localCollection = context.Courses.Local;
Run Code Online (Sandbox Code Playgroud)

......表明.Id 1的课程确实在这个集合中,这意味着:加载到上下文中.但它不在学生对象的子集合中.

编辑2

也许这不是一个错误.

首先:我们在这里使用两个不同的版本Load:

DbCollectionEntry<TEntity, TElement>.Load()
Run Code Online (Sandbox Code Playgroud)

Intellisense说:

从数据库加载实体集合.请注意,上下文中已存在的实体不会被数据库中的值覆盖.

对于其他版本(扩展方法IQueryable)...

DbExtensions.Load(this IQueryable source);
Run Code Online (Sandbox Code Playgroud)

... Intellisense说:

枚举查询,以便对于服务器查询(如System.Data.Entity.DbSet,System.Data.Objects.ObjectSet,System.Data.Objects.ObjectQuery等)的查询,查询结果将加载到关联的系统中.Data.Entity.DbContext,System.Data.Objects.ObjectContext或客户端上的其他缓存.这相当于调用ToList然后丢弃列表而没有实际创建列表的开销.

因此,在此版本中,不保证填充子集合,只保证将对象加载到上下文中.

问题仍然存在:为什么要Presentations填充集合而不是Courses集合.我认为答案是:因为关系跨度.

关系Span是EF中的一项功能,它自动修复上下文中或刚刚加载到上下文中的对象之间的关系.但是对于所有类型的关系都不会发生这种情况.只有当一端的多重性为0或1时才会发生.

在我们的示例中,它表示:当我们加载Presentations到上下文中时(通过我们过滤的显式查询),EF还会将Presentationentites 的外键加载到Student实体 - "透明地",这意味着,无论FK是否作为属性公开在模型中没有.这个加载的FK允许EF识别加载的Presentations属于Student已经在上下文中的实体.

但这不是Courses收集的情况.课程没有Student实体的外键.中间有多对多的连接表.因此,当我们加载CoursesEF时,不会识别这些课程属于Student上下文中的那些课程,因此不会修复Student实体中的导航集合.

由于性能原因,EF仅为引用(而不是集合)执行此自动修复:

为了修复关系,EF透明地重写查询,为所有在另一端具有0..1或1的多重性的关系带来关系信息; 换言之,作为实体引用的导航属性.如果一个实体与多重性大于1的关系,EF将不会带回关系信息,因为它可能会受到性能影响,并且与将一个外国人与其余记录一起带来相比.带来关系信息意味着检索记录具有的所有外键.

Zeeshan Hirani的第128页引用EF的深度指南.

它基于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();
Run Code Online (Sandbox Code Playgroud)