从EF Core 2迁移到EF Core 3

12

将我的项目从(dotnet core 2/ef core 2)升级到(dotnet core 3/ef core 3)后,几乎所有的实体框架 LINQ 查询都无法正常工作。尽管我已经阅读了这篇文章,但仍不清楚该怎么做。

以下是一些我遇到问题的示例:

var league = await dbContext.League.LastAsync();

虽然这段代码在 ef core 2 中可以正常工作,但在 ef core 3 中会抛出异常。 我找到的唯一解决方法是以下代码,但仍不符合我的要求,因为它不像以前那样是异步的。

var league = dbContext.League.AsEnumerable().Last();

另一个抛出相同异常的示例是以下代码:

var user = await dbContext.User.FirstOrDefaultAsync(u =>
                u.UserId == userId && string.Equals(u.Token, token, StringComparison.InvariantCulture));

我仍然可以使用AsEnumerable(),但异步版本的FirstOrDefault在那里不可用,所以这不是一个选择。有人能为我提供指导吗?

编辑
这是异常:

System.InvalidOperationException: The LINQ expression 'Last<League>(DbSet<League>)' 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(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

1
我不知道为什么我不能在问题中放置链接,但是这是链接 https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client - Shahriyar
这些示例会抛出什么样的异常?请注意,使用“AsEnumerable”是错误的,因为它会从数据库中获取所有数据,然后返回最后一个。这绝对是应该避免的。 - Darjan Bogdan
@DarjanBogdan 嗯,我现在不在家,但是有关无法翻译查询的问题,我从所有工作正常的 EF Core 2 查询中都遇到了这个问题。 - Shahriyar
@Shahriyar,“但是在那里不可用的是FirstOrDefault的异步版本”,你能告诉我为什么它在那里不可用吗? - TanvirArjel
1
查询 Last 而没有指定顺序有点毫无意义... - grek40
当然,FirstOrDefaultAsync() 是可用的。你只是缺少一个 using <nmspc>; - H H
2个回答

13
回答你的问题会非常冗长,因为它涉及到EF Core 3.0中发生的一些不同变化;因此,让我们只考虑其中的一小部分。
正如你在问题中提到的,微软在这篇文章中对3.0版本的更改有一些令人困惑的描述。
上述文章的第一部分是:“LINQ查询不再在客户端上评估”。它说,在此之前,开发人员通常会编写包含两个部分的查询;一个部分是针对数据库的查询,另一个部分是仅为客户端代码所知的表达式。在这种情况下,潜在昂贵表达式的客户端评估只触发警告。但在新版本中,EF Core只允许最后一个Select()调用在客户端上进行评估,并在存在无法转换为SQL或参数的表达式时引发异常。
为了澄清这一部分,让我们看一下迭戈·维加在他的EF Core 3.0发布博客文章中描述的示例。 显式切换到客户端评估: 如果您的查询基于无法转换为SQL的表达式过滤数据,则可能需要在查询中间插入对AsEnumerable()、AsAsyncEnumerable()、ToList()或ToListAsync()之一的调用,以显式切换到客户端评估。例如,以下查询将不再在EF Core 3.0中工作,因为where子句中的一个谓词需要客户端评估:
var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n) && IsSpecialCustomer(c));

但是如果您知道在客户端处理部分过滤是合理的,您可以将查询重写为:
var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n)) 
    .AsEnumerable() // Start using LINQ to Objects (switch to client evaluation)
    .Where(c => IsSpecialCustomer(c));

在上面的示例中,IsSpecialCustomer(c)是一种方法,无法转换为SQL,因为它是一种仅在客户端代码中可用的C#方法。因此,开发人员应该重新编写查询以便可以进行翻译,或者查询数据库,然后使用.AsEnumerable()评估数据库结果并将结果过滤基于IsSpecialCustomer(c)返回值。这就是为什么你仍然可以在你的代码中访问AsEnumerable()的原因。
现在,让我们看一下为什么FirstOrDefaultAsync()方法不可用?
嗯,有两个原因导致了这种情况。
我之前已经回答了第一个原因:在3.0版本中删除了检测非可组合SQL的代码。
第二个原因是:查询管道不理解表达式树中的异步可查询运算符(例如:当你试图在EF.CompileQuery()上访问它时)。
总之,有几篇有趣的文章可以阅读: ef core 3中的40个重大变化 宣布entity framework core 3.0预览9和entity framework 6.3预览9

在 Github 上的 EF Core 问题


1
感谢您的出色解释。FirstOrDefaultAsync问题是由于在String.Equals上使用StringComparison.InvariantCulture,正如其他答案所提到的那样。 - Shahriyar
没错。String.Equals也是不可组合的另一个例子。 - user6311045

4
  1. 使用 OrderByDesc() 对某个属性进行排序,然后使用 FirstAsync()。(https://github.com/aspnet/EntityFrameworkCore/issues/18211

  2. 无法翻译不变比较,可能之前已经在客户端评估过了。根据你的数据库排序设置,你可能只需要普通的等于操作。

在没有任何筛选条件的情况下在 DbSet 上调用 AsEnumerable() 会将所有数据都从本地拉取,这不是你想在生产环境中做的事情。尝试按照上面的方法进行重写,并监视生成的 SQL,以确保获得高性能的查询。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接