我正在尝试将解决方案升级到新的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(源:Where(源:DbSet,谓词:(a)=>(int)a.Gender!= 0),谓词:(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中的错误?
答案 0 :(得分:15)
原因是在EF Core 3中禁用了隐式客户端评估。
这意味着以前,您的代码未在服务器上执行WHERE子句。取而代之的是,EF将所有行加载到内存中并评估了内存中的表达式。
要在升级后解决此问题,首先,您需要确定EF不能完全转换为SQL的原因。我的猜测是对GetValueOrDefault()
的调用,因此请尝试像这样重写它:
.Where(x => x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month)
答案 1 :(得分:5)
当您尝试将解决方案的.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));
}
答案 2 :(得分:1)
供将来的读者使用。
我遇到了同样的问题。
通过简单地替换“内联日期计算”,我得以解决了这个问题。
“之前”可查询(如下)(无效):
(只需在“之前”关注此部分
“ perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan)“
)
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”可查询(在下面)(效果很好):
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;
我猜是因为我的问题和原始问题的....... DateTimes有点棘手。
因此,虽然(是)令人讨厌的是以前可以工作的代码..停止工作了……理解“默认值是在客户端上不运行代码”对于性能非常重要。
PS,我在3.1上
我的软件包参考了完整性。
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
答案 3 :(得分:0)
如丹尼尔·希尔加斯(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中看来,如果这些是类本身提供的标准方法,则不建议使用上述评估方法事件。
感谢您的帮助。
答案 4 :(得分:0)
当我升级到efcore 3.0时,我遇到了同样的问题。 efcore 3.0中发生的变化是,当他无法执行查询时,他只会停止并引发错误。
如果以前版本的efcore的行为相同,但是当他不识别参数时,他将只执行不带参数的查询(获取更多数据,然后您想要),但是也许您没有注意到。
您可以在日志中检查。
如果要在efcore 3.0中执行查询,则必须在查询中使用数据库表中的字段。 他将无法识别未映射到de db的对象属性
现在重新编写查询很痛苦,但是一段时间内会获胜
答案 5 :(得分:0)
就我而言,问题是通过使用 DateTime
中的 Ticks x.BirthDate.Ticks
答案 6 :(得分:-1)
如果列表位于IQueryable,则需要使用Tolist()。
list.ToList().Where(p => (DateTime.Now - p.CreateDate).Days <= datetime).AsQueryable();
答案 7 :(得分:-3)
例如,确保您的POCO类字段已显式定义getter和setter
public class Devices
{
public int Id { get; set; }
public string SerialNumber { get; set; }
}