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

C# EF Core 5-查询中Include()/Where()方法的顺序重要吗?,c#,entity-framework,linq,entity-framework-core,.net-5,C#,Entity Framework,Linq,Entity Framework Core,.net 5,我正在从.NET Core 2.2升级到.NET 5.0,一次只升级一个主要版本(2.2->3.0->3.1->5.0),我有一个LINQ查询到一个MySQL数据库,在升级到5.0之后,它的工作方式有所不同。查询在2.2、3.0和3.1中运行良好,但在升级到5.0之后,其中一个Include()调用似乎没有任何效果。查询是: var adminClient = (this._context.AdminUserClients .Include(f => f.Client) .

我正在从.NET Core 2.2升级到.NET 5.0,一次只升级一个主要版本(2.2->3.0->3.1->5.0),我有一个LINQ查询到一个MySQL数据库,在升级到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;
(有关模型,请参见下文。)

当它在EF Core 3.1中运行时(2.2和3.0类似),它会生成一个包含两个子查询的SQL语句,一个子查询用于连接
AdminUserClient
AspNetUsers
、和
Client
,另一个子查询用于连接
ClientUserRoles
AspNetUsers
。然后,它连接两个子查询以生成结果。在EF Core 5.0中,生成的SQL语句不引用
ClientUserRoles
——它本质上只是3.1 SQL语句中的第一个子查询

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

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;
在本例中,生成的SQL语句实际上与3.1中生成的SQL语句相同

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

问题
  • 原始查询中
    Include()
    Where()
    方法的顺序是否有问题
  • 是否有从EF Core 3.1到EF Core 5.0的更改会导致此查询的行为发生更改
  • 关于拆分查询的说明 因为我正在从2.2升级到5.0,所以我使用
    UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
    来模拟2.2行为。但是,我测试了:

  • AsSplitQuery()
    添加到查询中
  • 使用
    useQuerySplitingBehavior(QuerySplitingBehavior.SingleQuery)
  • 完全删除
    useQuerySplitingBehavior()
    。 在所有这些情况下,行为都是一样的
  • 模型
    公共类AdminUserClient
    {
    公共长Id{get;set;}
    [必需]
    公共应用程序用户{get;set;}
    [必需]
    公共客户端{get;set;}
    [必需]
    public DateTime CreatedOn{get;set;}
    }
    公共类应用程序用户:IdentityUser
    {
    公共用户通知设置通知设置{get;set;}
    [JsonIgnore]
    公共ClientUserRole ClientUserRole{get;set;}
    公共布尔锁定{get;set;}
    公共字符串名称{get;set;}
    }
    公共类客户端
    {
    公共长Id{get;set;}
    公共字符串名称{get;set;}
    公共字符串说明{get;set;}
    公共bool RequireTwoFactor{get;set;}
    公共应用程序用户由{get;set;}创建
    public DateTime CreatedOn{get;set;}
    [JsonIgnore]
    公共ICollection ClientUserRoles{get;set;}
    公共布尔被删除{get;set;}
    公共布尔锁定{get;set;}
    }
    公共类ClientUserRole
    {
    公共长Id{get;set;}
    [必需]
    公共长ClientId{get;set;}
    [JsonIgnore]
    公共客户端{get;set;}
    [必需]
    公共字符串用户标识{get;set;}
    公共应用程序用户{get;set;}
    [必需]
    公共应用程序用户由{get;set;}创建
    [必需]
    public DateTime CreatedOn{get;set;}
    [必需]
    [列(TypeName=“nvarchar(15)”)]
    公共用户角色{get;set;}
    }
    
    更新 这已被确认为来自其中一个贡献者的EF Core 5.0缺陷:请参见。

    根据定义(设计),与其他LINQ查询运算符的
    包含的相对顺序应该无关紧要-唯一的要求是启动查询的实体必须是
    包含

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

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

    f.Client.Id != 1
    
    结合

    .Include(f => f.Client)
    
    如果您从
    Where
    中删除该条件,或将
    包含
    移动到具有该条件的
    Where
    之后,则剩余的包括工作。而有了这个组合,他们就不会了

    所以,报告它,让他们知道它,并最终解决它。在此之前,由于无法删除标准(显然),请对
    包含的内容重新排序


    如果您希望安全(并且不会遇到类似的意外错误),即使定义中没有要求,也可以将所有
    包含的
    放在查询的开头,紧跟在
    DbSet
    之后,然后是所有其他操作符。

    @Magnetron Good point。我应该说得更清楚些——我是专门指
    Include()
    Where()
    方法之间的相对顺序。谢谢Ivan。我打开了这个问题,并将根据该问题的任何反馈更新这个问题。EF核心贡献者之一已经确认这是一个bug。
    .Include(f => f.Client)