EF岩芯管柱大小写灵敏度不起作用



我有一段在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.

文档、文档、排序规则和区分大小写中解释了区分大小写和排序规则


这不是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 && 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 COLLATION case_insensitive (
provider = icu,
locale = 'und-u-ks-level2',
deterministic = false
);
CREATE INDEX IX_Table1_ItemNumberCI ON Table1 (title COLLATE "case_insensitive");`

LINQ查询不必更改。

因为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参考:

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

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

为什么

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

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

TLDR:重要的部分是将两个字符串与EF进行比较。功能。排序规则(字符串1,"SQL_Latin1_General_CP1_CS_AS")==字符串2


如@Panagiotis Kanavos上文所述,有关区分大小写和校勘的所有相关信息可在校勘和区分大小写-EF Core | Microsoft Docs 中找到

对我来说,一个使用最少代码重构的快速解决方案是使用显式排序规则查询并执行以下操作:

public async Task<bool> DoesItemNumberExists(Guid revisionId, string itemNumber) {
var doesExist = await _repository.AnyAsync(a => EF.Functions.Collate(a.ItemNo, "SQL_Latin1_General_CP1_CS_AS") == itemNumber && a.SoqHeading_NP.SoqRevisionId == revisionId);
return doesExist; 
}

最新更新