【问题标题】:EF 4.1 loading filtered child collections not working for many-to-manyEF 4.1 加载过滤的子集合不适用于多对多
【发布时间】:2011-05-26 16:34:31
【问题描述】:

我一直在查看Applying filters when explicitly loading related entities,但无法使其适用于多对多关系。

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

简要说明:
一个Student 可以包含多个Courses,一个Course 可以包含多个Students
一个Student 可以生成多个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:但是,在对 Courses 执行相同操作后,没有任何变化:

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

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

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

顺便说一句,我检查了最后两次 Course-loading 尝试的 SQL 调用,它们 100% 相同,所以似乎是 EF 未能填充集合。

【问题讨论】:

    标签: entity-framework entity-framework-4.1


    【解决方案1】:

    我可以完全重现您描述的行为。我的工作是这样的:

    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()
    

    智能感知说:

    从 数据库。请注意,实体 已经存在的上下文不是 用来自的值覆盖 数据库。

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

    DbExtensions.Load(this IQueryable source);
    

    ... Intellisense 说:

    枚举查询,使得对于 服务器查询,例如 System.Data.Entity.DbSet, System.Data.Objects.ObjectSet, System.Data.Objects.ObjectQuery, 和其他人查询的结果 将被加载到关联的 System.Data.Entity.DbContext, System.Data.Objects.ObjectContext 或 客户端上的其他缓存。这是 相当于调用 ToList 然后 没有 实际创建的开销 列表。

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

    问题仍然存在:为什么填充 Presentations 集合而不填充 Courses 集合。我认为答案是:因为Relationship Span

    Relationship Span 是 EF 中的一项功能,它自动修复上下文中或刚刚加载到上下文中的对象之间的关系。但这并不适用于所有类型的关系。仅当多重性在一端为 0 或 1 时才会发生。

    在我们的示例中,这意味着:当我们将Presentations 加载到上下文中时(通过我们过滤的显式查询),EF 还将Presentation 实体的外键加载到Student 实体 - “透明地”,这意味着,无论 FK 是否在 not 模型中作为属性公开。这个加载的 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();
    

    【讨论】:

    • 是的,有趣的发现。不过,这会产生更多的 SQL 代码 - 基本上是为具有所有连接的学生进行新的选择(而没有 Include 的版本只使用了 Courses 和映射表来仅获取必要的数据)。所以它并不完全等同,但似乎是一种功能性的解决方法。
    • 响应您的编辑:因此,如果我们比较 context.Entry(...).Collections(...).Load()context.Entry(...).Collections(...).Query().Load() - SQL 是相同的,结果将加载到上下文中,但使用 Query() 会以某种方式使 EF “忘记”来填充 Student 对象的集合...对我来说绝对是一个错误...
    • @Yakimych:也许我找到了解释,请参阅我的 Edit2。
    • @Slauma - 这是一些令人印象深刻的挖掘,我必须承认 ;) 对于正在发生的事情,这似乎是最合理的解释。但即使你是对的,而且这不是一个错误——这绝对是一个没有经过深思熟虑的设计。我的意思是,Load 不会返回任何东西,很少有人会使用Load 将一些数据加载到上下文中,以便稍后从那里获取它。我宁愿编写一个普通查询并将相同的结果放入列表中以供进一步使用。所以在我看来,调用Load 的唯一目的是填充已获取对象的集合。
    • 所以作为使用 EF 的开发人员,我肯定希望在调用 Load 时填充集合,无论是一对多还是多对多关系,并且修复行为不应该让我真正关心。但是您挖掘的信息确实令人印象深刻,我今天肯定获得了新的 EF 内部工作;)除非有人很快提出更好的解释(我真的怀疑),否则您的答案会被接受。当然还有我的+1! ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 2020-08-27
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 2011-01-20
    相关资源
    最近更新 更多