【问题标题】:How to manually load related entities in a N:N relationship?如何手动加载 N:N 关系中的相关实体?
【发布时间】:2014-01-04 09:39:39
【问题描述】:

我使用的是 EF5,当关系为 1:N 时,如果我想加载相关实体,我会执行以下操作:

  • 使用 T-SQL,我从数据库中加载具有类似 T-SQL 的主要实体:

    select * 
    from MainEntities 
    where ...
    
  • 使用 T-SQL 我加载相关实体

    select * 
    from RelatedEntities 
    where IDMainEntity IN (---)
    

此时 EF 使用相关实体填充主要实体的属性导航。另外,在dbContext中每个实体的类型的本地属性中,我拥有每种类型的所有实体。

但是,如果我对 N:N 关系执行相同操作,则我没有该关系的中间表的实体,并且当我执行查询时,我在 dbContext 的本地实体中拥有每种类型,但未填充属性导航。

我想知道为什么以及是否存在其他选择。

我使用这种方式是因为我想使用 T-SQL 来创建动态查询。如果我使用急切加载,我对动态查询的灵活性不及使用 TSQL 时的灵活性,而且效率较低。如果我使用显式加载我做N个附加查询,主实体结果中的每条记录一个用我的方式,我只附加一个查询,因为我一次得到所有相关实体。如果我使用延迟加载,我也会遇到同样的问题,另外还有 N 个查询。

当关系为 N:N 时,为什么 EF 不填充相关属性?

谢谢。

【问题讨论】:

    标签: c# entity-framework-5 many-to-many


    【解决方案1】:

    您正在谈论的功能称为 Relationship SpanRelationship Fixup 并且确实 - 正如您所注意到的 - 它不适用于多对多关系。它仅在关联的至少一端具有多重性 1(或 0..1)时才有效,即它适用于一对多或一对一的关系。

    Relationship Span 依赖于具有外键的实体。无论是有显式外键属性(外键关联)还是对应数据库表中只有外键列而模型中没有属性(独立关联 em>)。在这两种情况下,FK 值都将在实体加载时加载到上下文中。基于此外键值,EF 能够确定是否将具有与此 FK 值相同的主键值的相关实体附加到上下文,如果是,它可以“修复关系”,即它可以填充导航属性正确。

    现在,在多对多关系中,两个相关实体都没有外键。外键存储在此关系的链接表中,并且 - 如您所知 - 链接表没有相应的模型实体。因此,外键永远不会被加载,因此上下文无法确定哪些附加实体是相关的,也无法修复多对多关系并填充导航集合。

    EF 将支持您使用多对多关系中填充的导航集合构建正确对象图的唯一 LINQ 查询是急切加载...

    var user = context.Users.Include(u => u.Roles).First();
    

    ...或延迟加载...

    var user = context.Users.First();
    var rolesCount = user.Roles.Count();
    // Calling Count() or any other method on the Roles collection will fill
    // user.Roles via lazy loading (if lazy loading is enabled of course)
    

    ...或直接将结果分配给导航集合的显式加载:

    var user = context.Users.First();
    user.Roles = context.Entry(user).Collection(u => u.Roles).Query().ToList();
    

    加载相关实体的所有其他方式——如投影、直接 SQL 语句,甚至不分配给导航集合的显式加载,即在上面的最后一个代码 sn-p 中使用 .Load() 而不是 .Query().ToList() - 赢了t 修复关系并将导航集合留空。

    如果您打算主要执行 SQL 查询而不是 LINQ 查询,我能看到的唯一选择是您编写自己的关系管理。除了两个相关实体的表之外,您还必须查询链接表。您可能需要一个辅助类型(不是实体)和包含链接表的两个 FK 列值的集合,还可能需要一个辅助例程,通过检查您找到的实体的主键值来填充导航集合附加在 DbSet<T>.Local 集合和辅助集合中的 FK 值中。

    【讨论】:

    • 对不起,但在这个link 的情况下,Entry(...).Collection(...).Load() 被呈现为工作变体,它适用于我的情况(导航属性已正确填充)。为了公正起见,我使用 EF 6 和 EF 6 Compact - 而不是 EF5 作为主题启动器使用。可能强制使用 .Query().ToList() 已过时或有点不正确?
    • 如你所说:“链接表没有对应的模型实体”。在 EF Core 3.x 中,您必须在模型中创建一个类/表来表示 N:N 关系。所以它已经可以用它了吗?还需要什么吗?
    【解决方案2】:

    添加@Slauma 答案:

    我最近遇到了同样的问题,在调用 Query().Where().Load() 后没有设置导航属性感到沮丧,尽管我可以看到对象已加载到 DbContext 中。

    我需要集合成为我的主要对象的一部分,并像使用任何其他导航属性一样使用它,而不仅仅是管理单独的集合,所以我这样做了:

    project.Labels = this.Context
                        .Entry (project)
                        .Collection (p => p.Labels)
                        .Query ()
                        .Where (l => l.CreateUserName == this.UserId)
                        .ToList();
    

    这样做的问题是 EF 认为我添加了新的关系,我不能责怪它,但这不是我想要的。结果,在尝试保存 Project 对象时,当 EF 尝试将关系插入链接表时出现异常,因为已存在具有相同键 (projectId + labelId) 的行。

    所以,最后是重置项目和标签之间的关系状态:

    foreach (Label l in project.Labels)
                    {
                        ((System.Data.Entity.Infrastructure.IObjectContextAdapter)this.Context.AsDbContext ()).ObjectContext.ObjectStateManager.ChangeRelationshipState<Project> (project, l, p => p.Labels, EntityState.Unchanged);
                    }
    

    在那之后,我可以像使用任何其他导航属性一样使用 Labels 属性,而不必在意幕后的多对多关系。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-16
      • 1970-01-01
      • 2012-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-04
      • 1970-01-01
      相关资源
      最近更新 更多