从dotnet Core 2.2.6更改为3.0.0后的EF Linq错误

mon*_*see 6 c# .net-core entity-framework-core-3.0

我正在尝试将解决方案升级到新的Core Framework 3.0.0。现在我有一个小问题,我不明白。

看,此方法在2.2.6中没有问题:

public async Task<IEnumerable<ApplicationUser>> GetBirthdayUsersCurrentMonth()
    {
        return await ApplicationDbContext.Users
            .Where(x => x.Gender != ApplicationUser.GenderTypes.generic)
            .Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
            .Where(x => x.RetireDate == null)
            .OrderBy(x => x.BirthDate.GetValueOrDefault())
            .ToListAsync();
    }
Run Code Online (Sandbox Code Playgroud)

现在在3.0.0中,我收到一个Linq错误,它是这样说的:

InvalidOperationException:LINQ表达式'Where(source:where(source:DbSet,predicate:(a)=>(int)a.Gender!= 0),谓词:(a)=> a。 == DateTime.Now.Month)'无法翻译。以可以翻译的形式重写查询,或者通过插入对AsEnumerable(),AsAsyncEnumerable(),ToList()或ToListAsync()的调用来显式切换到客户端评估

当我禁用此行时:

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
Run Code Online (Sandbox Code Playgroud)

错误消失了,但是我当然会吸引所有用户。而且我在此查询中看不到错误。这可能是EF Core 3.0.0中的错误吗?

Dan*_*rth 9

原因是在EF Core 3中禁用了隐式客户端评估。

这意味着以前,您的代码未在服务器上执行WHERE子句。取而代之的是,EF将所有行加载到内存中,并在内存中评估了表达式。

要在升级后解决此问题,首先,您需要确定EF不能完全转换为SQL的内容。我的猜测是对的调用GetValueOrDefault(),因此请尝试像这样重写它:

.Where(x => x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month)
Run Code Online (Sandbox Code Playgroud)


小智 6

当您尝试将解决方案的 .netCore 版本升级到 3.0 时,我将在执行升级的人员的范围内回答您的问题:

通过引用EF Core 3.0 破坏性更改官方文档,您会发现该行

不再在客户端评估 LINQ 查询

您下面的查询将不再在客户端进行评估,因为 EF 无法解释 GetValueOrDefault():

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
Run Code Online (Sandbox Code Playgroud)

这在 3.0 之前工作的原因是因为它评估无法转换为原始 SQL 的段之前的所有内容,然后在客户端 (c#) 端评估其余的段。这意味着您的代码被粗略评估为:

return (await ApplicationDbContext.Users
            .Where(x => x.Gender != ApplicationUser.GenderTypes.generic).ToListAsync()) //sql evaluated till here
            .Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
            .Where(x => x.RetireDate == null)
            .OrderBy(x => x.BirthDate.GetValueOrDefault())
            .ToList();
Run Code Online (Sandbox Code Playgroud)

这在 EF Core 3.0 中不再允许,因为隐藏客户端评估在具有较大数据集的生产中是不利的,而在开发中,可能会忽略性能影响。

您有 2 个解决方案。

首选是将受影响的行重写为类似这样的内容, defaultMonthValue 是一个 const int ,其中包含一些在 GetValueOrDefault() 扩展中使用的默认月份整数。

.Where(x => (x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month) || (x.BirthDate == null && defaultMonthValue == DateTime.Now.Month))
Run Code Online (Sandbox Code Playgroud)

第二个但不推荐的解决方案是在问题段之前显式添加 .AsEnumerable() 以强制 EF 评估先前的语句。

.AsEnumerable() // switches to LINQ to Objects
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
Run Code Online (Sandbox Code Playgroud)

对于打算从 2.2 迁移到 3.0 并希望在实际迁移之前测试 2.2 代码库中的客户端评估重大更改的人的一些提示:

Microsoft docs 开始,将以下内容添加到您的 startup.cs 以模拟 3.0 客户端查询抛出。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
        .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
Run Code Online (Sandbox Code Playgroud)