EF Core 5 - 查询中 Include()/Where() 方法的顺序重要吗?

mve*_*imi 7 c# linq entity-framework entity-framework-core .net-5

我正在从 .NET Core 2.2 升级到 .NET 5.0,一次升级一个主要版本(2.2 -> 3.0 -> 3.1 -> 5.0),并且我对 MySQL 数据库进行了 LINQ 查询,该查询在升级到 5.0 后工作方式有所不同。该查询在 2.2、3.0 和 3.1 中工作正常,但升级到 5.0 后,其中一个调用Include()似乎没有任何效果。查询是:

var adminClient = (this._context.AdminUserClients
    .Include(f => f.Client)
    .Where(f => f.User.Id == user.Id && f.Client.Id != 1)
    .Include(f => f.Client.ClientUserRoles)  // This Include does not seem to have an effect
    .ThenInclude(f => f.User)
    .FirstOrDefault())?.Client;
Run Code Online (Sandbox Code Playgroud)

(型号见下文。)

当它在 EF Core 3.1 中运行时(2.2 和 3.0 类似),它会生成一个带有两个子查询的 SQL 语句,一个用于连接AdminUserClients, AspNetUsers, and Clients,一个用于连接ClientUserRolesand AspNetUsers。然后,它连接两个子查询以生成结果。在 EF Core 5.0 中,生成的 SQL 语句不引用ClientUserRoles-- 它本质上只是 3.1 SQL 语句中的第一个子查询。

如果我修改查询以在调用Where()之后移动调用Include(),它会起作用:

var adminClient = (this._context.AdminUserClients
    .Include(f => f.Client)
    .Include(f => f.Client.ClientUserRoles)  // This Include runs fine
    .ThenInclude(f => f.User)
    .Where(f => f.User.Id == user.Id && f.Client.Id != 1)
    .FirstOrDefault())?.Client;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,生成的 SQL 语句实际上与 3.1 中生成的 SQL 语句相同。

我不知道为什么这会有所不同。也许在方法User中引用该对象之前需要包含该对象Where()?但这对我来说没有意义,因为 (1) 它适用于 2.2、3.0 和 3.1,并且 (2) 我的理解是方法的顺序相对于 和Include()方法的顺序Where()不应该影响返回集(尽管我知道它会影响性能)。

问题

  1. 原始查询中的Include()和方法的顺序是否有问题?Where()
  2. 从 EF Core 3.1 到 EF Core 5.0 的更改是否会导致此查询的行为发生变化?

关于拆分查询的注意事项

因为我要从 2.2 升级到 5.0,所以我用来UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)模拟 2.2 的行为。不过,我测试过:

  1. 添加AsSplitQuery()到查询中。
  2. 使用UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)
  3. UseQuerySplittingBehavior()完全删除。在所有这些情况下,行为都是相同的。

模型

public class AdminUserClient
{
    public long Id { get; set; }
    [Required]
    public ApplicationUser User { get; set; }
    [Required]
    public Client Client { get; set; }
    [Required]
    public DateTime CreatedOn { get; set; }
}

public class ApplicationUser : IdentityUser
{
    public UserNotificationsSetting NotificationSetting { get; set; }
    [JsonIgnore]
    public ClientUserRole ClientUserRole { get; set; }
    public bool Locked { get; set; }
    public string Name { get; set; }
}

public class Client
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public bool RequireTwoFactor { get; set; }
    public ApplicationUser CreatedBy { get; set; }
    public DateTime CreatedOn { get; set; }
    [JsonIgnore]
    public ICollection<ClientUserRole> ClientUserRoles { get; set; }
    public bool IsDeleted { get; set; }
    public bool Locked { get; set; }
}

public class ClientUserRole
{
    public long Id { get; set; }
    [Required]
    public long ClientId { get; set; }
    [JsonIgnore]
    public Client Client { get; set; }
    [Required]
    public string UserId { get; set; }
    public ApplicationUser User { get; set; }
    [Required]
    public ApplicationUser CreatedBy { get; set; }
    [Required]
    public DateTime CreatedOn { get; set; }
    [Required]
    [Column(TypeName = "nvarchar(15)")]
    public UserRole Role { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

更新

一位贡献者已确认这是 EF Core 5.0 错误:请参阅https://github.com/dotnet/efcore/issues/24953

Iva*_*oev 3

根据定义(设计),Include与其他 LINQ 查询运算符的相对顺序应该不重要 - 唯一的要求是Include针对启动查询的实体。

因此,上述 EF Core 版本之间在这方面没有有意更改。然而,定义/设计是一回事,实现是另一回事。简单地说,您遇到了 EF Core 5.x 错误,因此最好将其报告给他们的 GitHub 问题跟踪器。

该问题并不总是出现,并且似乎与谓词中使用的表达式有关Where(通常Include不应影响过滤器、排序等 LINQ 运算符中导航属性的使用),更具体地说,与Client此处的导航属性有关

f.Client.Id != 1
Run Code Online (Sandbox Code Playgroud)

结合

.Include(f => f.Client)
Run Code Online (Sandbox Code Playgroud)

如果您从 中删除该条件Where,或将 移至具有该条件Include之后Where,则其余部分包括工作。而使用这种组合则不然。

因此,请报告该问题,以便让他们知道并最终修复它。在那之前,由于您无法删除条件(显然),因此请重新排序Includes.

如果您想要安全(并且不想遇到类似的意外错误),即使定义中没有要求,也可以将 all 放在Includes查询的开头,紧接在 之后DbSet,然后是所有其他运算符。