在 EFCore 急切加载 LINQ 查询中,如何在 ThenInclude() 表达式中引用***实体?

Posted

技术标签:

【中文标题】在 EFCore 急切加载 LINQ 查询中,如何在 ThenInclude() 表达式中引用***实体?【英文标题】:In an EFCore eager loading LINQ query, how to reference the top-level entity within the ThenInclude() expression? 【发布时间】:2021-09-17 09:33:07 【问题描述】:

我有一个使用 EFCore 5 的 LINQ 查询,它急切地加载了多个级别的相关实体。在其中一个中,我需要根据***实体上的字段过滤引用的实体,有没有办法做到这一点?

我想要的查询,除了在Where 内,我如何引用product

context.Products
  .Include(product => product.PrimaryComponent)
  .ThenInclude(component => component.ComponentRules
                              .Where(cRule => cRule.FactoryId == product.FactoryId))
 
  .Where( /* other filters */ )

Where() 表达式中,我可以引用cRulecomponent,但我不能引用product

表格:

dbo.Product ( Id int, FactoryId int, PrimaryComponentId int )
dbo.Component ( Id int, Name nvarchar(100) ) 
dbo.ComponentRule ( ComponentId int, RuleId int, FactoryId int, Notes nvarchar(max) ) 

-- These tables aren't used in these queries but here they are fyi: 
dbo.Rule ( Id int, Name nvarchar(100), ... ) 
dbo.Factory ( Id int, Name nvarchar(100), ... ) 

在这个数据库中,产品使用组件,每个组件都有许多关联的规则,这取决于我们谈论的是哪个工厂。每个产品仅在一个工厂中构建,因此当我获得 ComponentRule 对象时,我只想加载与产品的 FactoryId 相关的对象,而不是所有工厂的所有 ComponentRule。每个 Component 仍然会有几个 ComponentRules,只是没有那么多。

如果我要编写一个 SQL 查询,这就是我的想法:

select * 
from dbo.Product
inner join dbo.Component 
                on Product.PrimaryComponentId = Component.Id
inner join dbo.ComponentRule
                on Component.Id = ComponentRule.ComponentId

-- The line below is the tricky one: 
               and ComponentRule.FactoryId = Product.FactoryId 

-- ... plus other filters
where 

我不能轻易地只为它编写 SQL,因为我确实引入了其他几个实体并使用 .AsSplitQuery() 来提高效率。所以我真的很想能够从.ThenInclude(...) 中引用***Product.FactoryId。有什么办法吗?

【问题讨论】:

查询结果会有什么用?如果您打算将其用于只读目的,则投影可能是一种替代方法。 BR 是的,只读使用。所以你的意思是像@GraphWalk 的答案,在Select() 中使用过滤? 我想到了一些更简单的东西,比如:***.com/a/52025598/14072498 谢谢,我看看能不能那样做。我实际上包含了 17 个不同的实体,所以像这样交换并不是一件容易的事。但我会尝试一下。 我只是希望有一种简单的方法可以做到这一点,但我错过了。例如在 SQL 中很容易。也许我们需要像 .ThenInclude( parent, child => ... ) 这样的重载,而不仅仅是 .ThenInclude( child => ... ) 【参考方案1】:

UPD:假设您的模型是:

// in your OnModelCreating()
modelBuilder.Entity<ComponentRule>()
            .HasOne(p => p.Component)
            .WithMany(b => b.ComponentRules);

public class Product

    public int Id  get; set; 
    public int FactoryId  get; set; 

    public int PrimaryComponentId  get; set; 
    public Component PrimaryComponent  get; set; 

public class Component

    public int Id  get; set; 
    public string Name  get; set; 

    public List<ComponentRule> ComponentRules  get; set; 

public class ComponentRule

    public int ComponentId  get; set; 
    public Component Component  get; set; 

    public int FactoryId  get; set; 

也许你可以这样做:

context.Products
  // 'Include's are not needed for LINQ query with custom projection (thanks Svyatoslav Danyliv)
  // .Include(product => product.PrimaryComponent)
  // .ThenInclude(component => component.ComponentRules)
  .Where( /* other filters */ )
  .Select(product => new Product 
     Id = product.Id,
     FactoryId = product.FactoryId,
     PrimaryComponent = new Component 
       Id = product.PrimaryComponent.Id,
       Name = product.PrimaryComponent.Name,
       ComponentRules = product.PrimaryComponent.ComponentRules
                         .Where(r => r.FactoryId == product.FactoryId).ToList()
     ,
  )

【讨论】:

一条注释,使用自定义投影的 LINQ 查询不需要包含。 谢谢,我看看能不能做到这样。实际上,我包含了 17 个不同的实体,因此交换它并非易事:) 但我会尝试尝试一下。

以上是关于在 EFCore 急切加载 LINQ 查询中,如何在 ThenInclude() 表达式中引用***实体?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 LINQ 中急切加载整个 SQL 表?

在 NHibernate 3.0 Linq 中急切加载多个兄弟姐妹和孙辈(堂兄弟?)的良好行为

EF Core - 为啥“无限”嵌套急切加载?

EF Core,投影子集合急切加载

Linq to Entities - 使用 Include() 急切加载

.NET Core/EF Core 2.0 升级后急切加载“类型之间未定义强制运算符”