【问题标题】:EF core string case sensitivity not workingEF核心字符串区分大小写不起作用
【发布时间】:2021-10-13 11:33:11
【问题描述】:

我有一段代码在 EF Core 2.2 中工作,用于比较字符串大小写,如下所示。

public async Task<bool> DoesItemNumberExists(Guid revisionId, string itemNumber)
{
    var doesExist = await _repository.AnyAsync(a => string.Equals(a.ItemNo, itemNumber, StringComparison.Ordinal) && a.SoqHeading_NP.SoqRevisionId == revisionId);

    return doesExist;
}

我在 EF Core 5 中运行相同的代码,但应用程序崩溃了。有什么帮助吗?

以下是我得到的异常

The LINQ expression 'DbSet<SoqItem>()
    .Where(s => s.IsDeleted == False)
    .Join(
        inner: DbSet<SoqHeading>()
            .Where(s0 => s0.SoqRevisionId == __ef_filter__RevisionId_0 && s0.IsDeleted == False), 
        outerKeySelector: s => EF.Property<Nullable<Guid>>(s, "SoqHeadingId"), 
        innerKeySelector: s0 => EF.Property<Nullable<Guid>>(s0, "Id"), 
        resultSelector: (o, i) => new TransparentIdentifier<SoqItem, SoqHeading>(
            Outer = o, 
            Inner = i
        ))
    .Any(s => string.Equals(
        a: s.Outer.ItemNo, 
        b: __itemNumber_0, 
        comparisonType: Ordinal) && s.Inner.SoqRevisionId == __revisionId_1)' could not be translated. Additional information: Translation of the 'string.Equals' overload with a 'StringComparison' parameter is not supported. See https://go.microsoft.com/fwlink/?linkid=2129535 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

【问题讨论】:

  • 导致应用崩溃的异常是什么?
  • 学习要点:请永远不要再发布关于 SO 的问题,该问题声明、暗示甚至暗示您遇到了异常,但不包括完整、准确的异常类型和消息。我真的不敢相信我每天看到的有多少人说“我出错了”,但没有再提任何关于它的事情(就像这是一些无关紧要、无关紧要的事情/无论如何我们都会立即确切地知道它是什么);这是我们可以拥有的调试中最有用的一条信息
  • 请阅读错误信息?它是更好的之一。. 让我们知道您对此不了解的地方,以便我们指导您的学习 - 如果我们只是说“这样做,它会解决它”,那么您将不会学到太多
  • @thanzeel 这不是 EF Core 5 错误。查询始终无法转换为 SQL,但 EF Core 2 通过将 everything 加载到内存中然后匹配客户端上的记录而不使用索引来解决此问题。 EF Core 3 禁用了这种极其不幸的行为。如果您想避免对性能造成巨大影响不要尝试强制大小写匹配。确保列的排序规则与您想要的匹配并在其上创建索引。在查询中只使用==
  • @thanzeel 如果您希望为不同的查询同时区分大小写和 in 匹配,则必须使用不同的排序规则和索引创建不同的列(可能已计算)那也是。索引受排序规则的影响,因为这显然会影响列值的相等性和顺序。

标签: c# asp.net-core entity-framework-core


【解决方案1】:

大小写敏感性和排序规则在文档中、文档中、Collations and Case Sensitivity 中进行了说明


这不是 EF Core 5 错误。该查询始终无法转换为 SQL,但 EF Core 2 通过将所有内容加载到内存中然后匹配客户端上的记录而不使用索引来解决此问题。 EF Core 的第一个版本中的 LINQ 翻译非常有限,甚至无法翻译 GROUP BY。实体框架会在这种情况下抛出。不过,为了避免破坏在 EF 6 中完美运行的代码,EF Core 1 和 2 使用了客户端评估:他们将尽可能多的内容转换为 SQL,然后在客户端上加载内存中的数据,并使用执行查询的其余部分LINQ to Objects。

这意味着,如果您想计算 100K 行的 SUM,EF Core 1-2 会将所有 100K 行加载到内存中并继续将值一一相加。没关系加入两个表,每个表有 1000 行 - 这是 1M 比较。

即使在 EF Core 2.1 中,客户端评估也会产生运行时警告,并且可以完全禁用。在 EF Core 3.1 中,客户端评估被完全禁用。

为了让您的查询正常工作不要尝试强制大小写或排序规则。只需使用简单的相等:

var itemExists=context.Products.Any(a=>a.ItemNumber == itemNumber && 
                                       a.SoqHeading_NP.SoqRevisionId == revisionId);

这将被翻译成WHERE ItemNumber=@itemNumber &amp;&amp; SoqHeading_NP.SoqRevisionId = @revisionId。该查询将使用覆盖ItemNumberSoqRevisionId 列的任何索引以尽可能快地生成结果。

用于相等匹配的排序规则是列的排序规则。如果这是区分大小写的,您将获得区分大小写的匹配。如果不是,您将获得区分大小写的匹配。索引是使用列的排序规则构建的,因此如果您尝试使用不同的排序规则进行匹配,则会阻止服务器使用任何索引。

如果你想在不同的查询中使用不同的大小写匹配并且仍然使用索引,你需要为每个案例创建不同的索引。你如何做到这一点取决于数据库

  • 在 SQL Server 中,不区分大小写是最常见的选项。要同时使用 区分大小写的搜索,您可以使用二进制(因此区分大小写)排序规则为计算列创建索引,例如:
alter table Table1 add ItemNumberCS as COLLATE ..._BIN;
create index IX_Table1_ItemNumberCS on Table1 (ItemNumberCS);

区分大小写的查询应使用ItemNumberCS 列。

  • 在 PostgreSQL 中,所有排序规则都区分大小写。不过从 v12 开始,您可以 create a custom collation 并在计算索引表达式中使用它。要使用区分大小写的搜索,您可以创建不区分大小写的排序规则和索引,例如:
CREATE COLLATION case_insensitive (
      provider = icu,
      locale = 'und-u-ks-level2',
      deterministic = false
);

CREATE INDEX IX_Table1_ItemNumberCI ON Table1 (title COLLATE "case_insensitive");`

LINQ 查询不必更改。

【讨论】:

  • 所以我可以做到这一点,我猜是 'builder.Property(p => p.ItemNo).UseCollat​​ion().IsRequired();'。但我不知道 UseCollat​​ion() 构造函数会发生什么。请帮忙
  • 我使用了这个并且它有效。 var doesExist = await _repository.AnyAsync(a => EF.Functions.Collat​​e(a.ItemNo, "SQL_Latin1_General_CP1_CS_AS") == itemNumber && a.SoqHeading_NP.SoqRevisionId == revisionId); .这对性能有影响吗?
  • @thanzeel 这正是你不应该做的事情。该查询将无法使用任何索引并最终扫描整个表
  • 我可以通过索引实现这一点吗?
  • 请帮助我们!!
【解决方案2】:

因为StringComparison.Ordinal 语句无法转换为SQL 查询。

您应该在没有StringComparison.Ordinal 的情况下读取数据,并且 当从SQL 读取数据并进入应用程序内存时,您可以使用StringComparison.Ordinal

public async Task<bool> DoesItemNumberExists(Guid revisionId, string itemNumber)
{
    var selectedRows = await _dbContext.YourTable.Where(a => a.ItemNo == itemNumber  && a.SoqHeading_NP.SoqRevisionId == revisionId).ToListAsync();
    return selectedRows.Any(a =>  string.Equals(a.ItemNo, itemNumber, StringComparison.Ordinal));
}

Microsoft reference:

在 3.0 之前,当 EF Core 无法将作为查询的一部分的表达式转换为 SQL 或参数时,它会自动评估客户端上的表达式。默认情况下,客户端对可能昂贵的表达式的评估只会触发警告。

新行为 从 3.0 开始,EF Core 仅允许在客户端评估顶级投影(查询中的最后一个 Select() 调用)中的表达式。 When 查询的任何其他部分的表达式都不能转换为 SQL 或参数,会引发异常。

为什么?

查询的自动客户端评估允许执行许多查询,即使其中的重要部分无法翻译。此行为可能会导致可能仅在生产中变得明显的意外且可能具有破坏性的行为。例如,Where() 调用中无法转换的条件会导致表中的所有行都从数据库服务器传输,并在客户端应用过滤器。如果表在开发中只包含几行,这种情况很容易被发现,但当应用程序转移到生产时,表可能包含数百万行,就会受到严重影响。客户评估警告也被证明在开发过程中很容易被忽视。

除此之外,自动客户端评估可能会导致问题,其中改进特定表达式的查询翻译会导致版本之间出现意外的重大更改。

【讨论】:

  • db server 本身一定有比较的方法
  • 有。阅读错误消息中提供给您的链接
  • @CaiusJard 你是这个意思吗? EF.Functions.Collate
  • @thanzeel 是的 - 从一开始就使用正确的排序规则,索引列并且不要尝试更改排序规则 - 这会阻止使用索引跨度>
猜你喜欢
  • 2021-05-11
  • 2022-11-22
  • 1970-01-01
  • 2017-09-02
  • 2016-01-26
  • 2013-06-20
  • 2014-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多