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

时间:2011-05-26 16:34:31

标签: entity-framework entity-framework-4.1

我一直在关注Applying filters when explicitly loading related entities,无法让它与多对多关系合作。

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

简要说明:
Student可能会花费很多CoursesCourse会有很多StudentsStudent可以生成多个Presentation,但Presentation只能由一个Student生成。 我们所拥有的是StudentsCourses之间的多对多关系,以及StudentPresentations之间的一对多关系。

我还添加了一个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: After loading presentations然而,在对Courses执行相同操作后,没有任何更改: After attempting to load courses

因此,我尝试在不调用Query的情况下加载课程,并且按预期工作: Courses loaded

(我删除了Where子句以进一步突出显示这一点 - 最后两次加载尝试仅因“Query()”调用而不同

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

顺便说一下,我检查了最后两次Course - 加载尝试的SQL调用,它们是100%相同的,所以看起来它的EF无法填充集合。

1 个答案:

答案 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();