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

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

  • 使用T-SQL,我使用T-SQL从数据库加载主要实体,如下所示:

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

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

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

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

我想知道为什么以及它是否存在替代方案。

我使用这种方式是因为我想使用T-SQL来创建动态查询。 如果我使用预先加载,我对动态查询的灵活性与使用TSQL时不同,效率较低。 如果我使用显式加载我做N个额外的查询,主要实体的结果中的每个记录之一用我的方式,我只有一个额外的查询,因为我一次得到所有相关的实体。 如果我使用延迟加载我有同样的问题,N个额外的查询。

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

谢谢。

您正在谈论的function称为关系跨度关系修复 ,实际上 – 正如您所注意到的 – 它不适用于多对多关系。 它只有在关联的至少一端具有多重性1(或0..1)时才有效,即它适用于一对多或一对一的关系。

关系Span依赖于具有外键的实体。 如果它具有显式外键属性( 外键关联 )或仅具有相应数据库表中的外键列而没有模型中的属性( 独立关联 ),则无关紧要。 在这两种情况下,当实体加载时,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.Local集合和辅助集合中的FK值。

要添加@Slauma答案:

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

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

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

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

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

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

在那之后,我能够像任何其他导航属性一样使用Labels属性,而不是在幕后关心它是多对多的关系。