从 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 来实现这一点。这将不起作用 ...&& DateTime.Now.Subtract(p.LastTest.Value).TotalDays / 30 > p.PAssetCategory.PATIntervalMonths))
而这将: ... && 120 / 30 > 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
在 Dotnet Core 3.1 中将数据从一个控制器传递到另一个控制器
Docker构建抱怨它无法找到nuget fallback包文件夹