如何以N:N关系手动加载相关实体?

时间:2014-01-04 09:39:39

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

我正在使用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不会填充相关属性

感谢。

2 个答案:

答案 0 :(得分:9)

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

关系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语句甚至显式加载而不分配到导航集合,即在上面的最后一个代码片段中使用.Load()而不是.Query().ToList() - 不会修复关系并将导航集留空。

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

答案 1 :(得分:1)

添加@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对象时,我尝试将关系插入到链接表中时出现异常,因为已存在具有相同键(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属性,而不是在幕后关心它是多对多的关系。