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

Posted

技术标签:

【中文标题】从 dotnet Core 2.2.6 更改为 3.0.0 后出现 EF Linq 错误【英文标题】:EF Linq Error after change from dotnet Core 2.2.6 to 3.0.0 【发布时间】:2020-01-24 06:08:32 【问题描述】:

我正在尝试将解决方案升级到新的 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();
    

现在在 3.0.0 中,我收到一个 Linq 错误:

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

当我禁用此行时:

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

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

【问题讨论】:

而不是添加 3 'Where' 条件为什么不使用 '&&' 操作? @Dr.Roggia:翻译正确地处理了这两种情况。这不是这里的问题。 Peter B,不,我试过了。这会引发编译器错误。 Lambda 表达式可能不是空传播运算符 【参考方案1】:

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

这意味着以前,您的代码没有在服务器上执行 WHERE 子句。相反,EF 将所有行加载到内存中并计算内存中的表达式。

要在升级后解决此问题,首先,您需要弄清楚 EF 究竟是什么无法转换为 SQL。我的猜测是调用GetValueOrDefault(),因此尝试像这样重写它:

.Where(x => x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month)

【讨论】:

假设 EF Core 的翻译与 EF6 相当,第一个子句 (x.BirthDate != null) 可能是多余的,因为第二个子句应该只捕获具有值的行。 @WiktorZychla:很好,这是可能的。但是,EF Core 是一个全新的开发,与 EF6 有很多已知差异,因此这种假设可能不正确 我似乎无法使用可为空的 DateTime 来实现这一点。这将不起作用 ...&amp;&amp; DateTime.Now.Subtract(p.LastTest.Value).TotalDays / 30 &gt; p.PAssetCategory.PATIntervalMonths)) 而这将: ... &amp;&amp; 120 / 30 &gt; p.PAssetCategory.PATIntervalMonths 。我是否遗漏了一些明显的东西,因为@Monsee 似乎可以正常工作? (我假设你 BirthDate 可以为空?) 不知道3.1版本还是一样的吗?【参考方案2】:

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

通过引用EF Core 3.0 breaking changes official docs, 你会找到这条线

不再在客户端评估 LINQ 查询

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

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

这在 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();

这在 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))

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

.AsEnumerable() // switches to LINQ to Objects
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

对于打算从 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));

【讨论】:

【参考方案3】:

正如 Daniel Hilgarth 所写,他的解决方案很好并且有效。 Wiktor Zychla 的加入似乎也有效。 我将方法改写如下:

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.BirthDate.Value.Month == DateTime.Now.Month)
            .Where(x => x.RetireDate == null)
            .OrderBy(x => x.BirthDate)
            .ToListAsync();
    

因此,在 Core 3.0.0 中,如果这些是类本身提供的标准方法,则使用上述评估方法事件并不是一个好主意。

感谢您的帮助。

【讨论】:

不,这是 EF Core 3 的非常好的更改。隐式客户端评估可能是一个巨大的性能问题。 ORM - 与 EF 一样 - 将永远无法将所有 C# 代码转换为 SQL。以防万一您不知道,这就是幕后发生的事情:EF 获取您的 C# 代码并从中构建 SQL 查询。 有时无法将所有句子翻译成 SQL。 F.E:当您想要格式化一个值或制作一个复杂的公式时。然后你就知道性能会受到影响。显示警告或“弃用”而不破坏兼容性不是更好吗?【参考方案4】:

为未来的读者。

我也遇到了同样的问题。

通过对“内联日期计算”进行简单替换,我能够解决这个问题。

“BEFORE”IQueryable(下)(不起作用):

(只关注“之前”中的这部分

"perParentMaxUpdateDate.UpdateDateStamp "

)

    IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
                                                 join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
                                                 on new  CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID  equals new  CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID 
                                                 where magicValues.Contains(chd.MyChildMagicStatus)
                                                 && perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan) /* <- FOCUS HERE */
                                                 select chd;

“AFTER”IQueryable(如下)(效果很好):

    DateTime cutOffDate = DateTime.Now.Add(cutOffTimeSpan); /* do this external of the IQueryable......and use this declaration of the DateTime value ..... instead of the "inline" time-span calcuation as seen above */

    IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
                                                 join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
                                                 on new  CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID  equals new  CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID 
                                                 where magicValues.Contains(chd.MyChildMagicStatus)
                                                 && perParentMaxUpdateDate.UpdateDateStamp < cutOffDate /* <- FOCUS HERE */
                                                 select chd;

我猜是因为我的问题和原始问题的问题.......日期时间有点棘手。

因此,虽然(是的)​​以前工作的代码..停止工作很烦人......了解“默认为不在客户端上运行代码”对于性能非常重要。

PS,我在 3.1

我的包参考完整性。

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />

【讨论】:

【参考方案5】:

我升级到 efcore 3.0 时遇到了同样的问题。 efcore 3.0 的变化是当他无法执行查询时,他只会停下来并抛出错误。

如果之前版本的 efcore 的行为是相同的,但是当他没有识别参数时,他会执行不带参数的查询(获取比你想要的更多的数据)但也许你从来没有注意到。

您可以在日志中查看。

如果您想在 efcore 3.0 中执行查询,您必须在查询中使用数据库表中的字段。 他将无法识别未映射到 de db 的对象属性

现在重写查询很痛苦,但过一段时间就会成功

【讨论】:

【参考方案6】:

在我的情况下,问题是使用 DateTime

中的 Ticks
 x.BirthDate.Ticks

【讨论】:

【参考方案7】:

如果你的列表是IQueryable在哪里,你需要使用Tolist()。

list.ToList().Where(p => (DateTime.Now - p.CreateDate).Days <= datetime).AsQueryable();

【讨论】:

以上是关于从 dotnet Core 2.2.6 更改为 3.0.0 后出现 EF Linq 错误的主要内容,如果未能解决你的问题,请参考以下文章

无法从部署中心为 Dotnet core 3.1 配置 CI 构建。有啥方法可以设置 CI Azure DevOps

四附加到进程调试(.NET Core)

在 Dotnet Core 3.1 中将数据从一个控制器传递到另一个控制器

Docker构建抱怨它无法找到nuget fallback包文件夹

Core Data 将属性从 Integer 16 更改为 Integer 32

Dotnet Core 3.1:如何将 AddJsonFile 与文件的绝对路径一起使用?