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

时间:2019-09-24 07:09:18

标签: 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();
    }

现在在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中的错误?

8 个答案:

答案 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; }
}