简体   繁体   English

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

[英]EF Linq Error after change from dotnet Core 2.2.6 to 3.0.0

I'm trying to upgrade a solution to the new Core Framework 3.0.0.我正在尝试将解决方案升级到新的 Core Framework 3.0.0。 Now I'm having a small issue I don't understand.现在我有一个我不明白的小问题。

Look, this method was unproblematic in 2.2.6:看,这种方法在 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();
    }

Now in 3.0.0 I get a Linq Error saying this:现在在 3.0.0 中,我得到一个 Linq 错误说:

InvalidOperationException: The LINQ expression 'Where( source: Where( source: DbSet, predicate: (a) => (int)a.Gender,= 0): predicate. (a) => a.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)' could not be translated, Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() 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()

When I disable this line:当我禁用此行时:

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

The error is gone but off course I get all users.错误消失了,但我当然会得到所有用户。 And I can't see an error in this query.而且我在这个查询中看不到错误。 Could this perhaps be a bug in EF Core 3.0.0?这可能是 EF Core 3.0.0 中的错误吗?

The reason is that implicit client evaluation has been disabled in EF Core 3.原因是 EF Core 3 中禁用了隐式客户端评估。

What that means is that previously, your code didn't execute the WHERE clause on the server.这意味着以前,您的代码没有在服务器上执行 WHERE 子句。 Instead, EF loaded all rows into memory and evaluated the expression in memory.相反,EF 将所有行加载到 memory 并评估 memory 中的表达式。

To fix this issue after the upgrade, first, you need to figure out what exactly EF can't translate to SQL.要在升级后解决此问题,首先,您需要弄清楚 EF 究竟是什么无法转换为 SQL。 My guess would be the call to GetValueOrDefault() , therefore try rewriting it like this:我的猜测是调用GetValueOrDefault() ,因此尝试像这样重写它:

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

As you are trying to upgrade your solution's .netCore version to 3.0, I will answer your question in the scope of a person performing an upgrade:当您尝试将解决方案的 .netCore 版本升级到 3.0 时,我将在执行升级的人的 scope 中回答您的问题:

By referencing the EF Core 3.0 breaking changes official docs , you will find the line通过参考EF Core 3.0 重大更改官方文档,您会发现这一行

LINQ queries are no longer evaluated on the client LINQ 查询不再在客户端评估

Your query below will no longer be evaluated client side because GetValueOrDefault() cannot be interpreted by EF:由于 EF 无法解释 GetValueOrDefault(),因此您的下面的查询将不再在客户端进行评估:

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

The reason this was working prior to 3.0 was because it evaluates everything prior to the segment where it cannot translate to raw SQL, then evaluate on the client (c#) side the rest of the segments.这在 3.0 之前工作的原因是因为它在无法转换为原始 SQL 的段之前评估所有内容,然后在客户端(c#)端评估段的 rest。 This means that your code is roughly evaluated to:这意味着您的代码被粗略评估为:

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

This is no longer allowed in EF Core 3.0 because the rationale was that hiding away client side evaluation is disadvantageous in production with larger datasets whereas in development, performance hits may be overlooked.这在 EF Core 3.0 中不再允许,因为隐藏客户端评估在具有较大数据集的生产中是不利的,而在开发中,可能会忽略性能影响。

You have 2 solutions.您有 2 个解决方案。

The preferred is to rewrite the affected line to be something like this, with the defaultMonthValue being a const int with some default month integer that was used inside your GetValueOrDefault() extension.首选是将受影响的行重写为类似这样的内容,其中 defaultMonthValue 是一个 const int,其中包含一些在 GetValueOrDefault() 扩展中使用的默认月份 integer。

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

The second, but not recommended solution is to explicitly add.AsEnumerable() before the problem segment here to force EF to evaluate the prior statements.第二种但不推荐的解决方案是在此处的问题段之前显式添加.AsEnumerable() 以强制 EF 评估先前的语句。

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

Some tips for people who intend to migrate to 3.0 from 2.2 and want to test the client evaluation breaking change in your 2.2 codebase prior to actual migration:对于打算从 2.2 迁移到 3.0 并希望在实际迁移之前测试 2.2 代码库中的客户端评估重大更改的人的一些提示:

As from Microsoft docs , add the following to your startup.cs to simulate 3.0 client side query throws.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));
}

As Daniel Hilgarth wrote his solution is fine and works.正如 Daniel Hilgarth 所写,他的解决方案很好并且有效。 The Addition of Wiktor Zychla seems to work, too. Wiktor Zychla 的加入似乎也有效。 I rewrote the method as follows:我重写了方法如下:

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

So, as it seems in Core 3.0.0 it's not a good idea to use as mentioned evaluation-methods event if these are standard methods served by the classes itself.因此,就像在 Core 3.0.0 中一样,如果这些是类本身提供的标准方法,那么使用上述评估方法事件并不是一个好主意。

Thanks for your help.谢谢你的帮助。

For future readers.对于未来的读者。

I got this same issue.我遇到了同样的问题。

By making a simple substitution for an "inline date calculation", I was able to get past the issue.通过对“内联日期计算”进行简单替换,我能够解决这个问题。

"BEFORE" IQueryable (below) (does not work): “之前”IQueryable(下)(不起作用):

(just focus on this part in the "before" (只关注“之前”中的这一部分

" perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan) " " 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" IQueryable (below) (that works fine): “之后”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;

I'm guessing that because of my issue and the issue of the original question.......DateTimes are a little trickier.我猜是因为我的问题和原始问题的问题...... DateTimes 有点棘手。

So while (yes) it is annoying that previously working code..stops working......understanding the "defaults to NOT running code on the client" is very important for performance.因此,虽然(是的)以前工作的代码..停止工作很烦人......了解“默认为不在客户端上运行代码”对于性能非常重要。

PS, I'm on 3.1 PS,我是3.1

My package references for completeness.为了完整起见,我的 package 参考资料。

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

I had the same issue when i upgraded to efcore 3.0.当我升级到 efcore 3.0 时,我遇到了同样的问题。 What has changed in efcore 3.0 is that when he can not execute the query, he will just stop and throw an error. efcore 3.0 的变化是,当他无法执行查询时,他只会停下来并抛出错误。

The behaviour was the same if the previous versions of efcore, but when he did not recognized the parameters, he would just execute the query without the parameters (get more data then u wanted) but maybe you never noticed.之前版本的 efcore 的行为是相同的,但是当他没有识别参数时,他只会执行不带参数的查询(获得比你想要的更多的数据)但也许你从来没有注意到。

You can check that in the logs.您可以在日志中检查。

If you want to execute queries in efcore 3.0 you'll have to use fields in your query that are also in your db tables.如果要在 efcore 3.0 中执行查询,则必须在查询中使用也在 db 表中的字段。 He will not recognize object properties that are not mapped to de db他将无法识别未映射到 de db 的 object 属性

it's a pain right now to re-write queries but it will be a win in a while现在重新编写查询很痛苦,但过段时间就会成功

Make sure that your POCO class fields has explicitly defined getter and setter, for example确保您的 POCO class 字段已明确定义 getter 和 setter,例如

public class Devices
{
    public int Id { get; set; }
    public string SerialNumber { get; set; }
}

In my case the issue was by using Ticks from DateTime就我而言,问题是使用来自DateTimeTicks

 x.BirthDate.Ticks

If your list is IQueryable is where, you need to use Tolist ().如果你的列表是IQueryable在哪里,你需要使用Tolist()。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM