I have a piece of code which works in EF Core 2.2 used to compare string casing as shown below.
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;
}
I run the same code in EF Core 5 and the application crashes. Any help?
Below is the exception i get
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.
Case-sensitivity and collations are explained in the docs, in the docs, in Collations and Case Sensitivity
That's not an EF Core 5 bug. The query always failed to get translated to SQL but EF Core 2 covered this up by loading everything in memory then matching the records on the client without the benefit of indexing. LINQ translation in the first versions of EF Core was so limited that even GROUP BY
couldn't be translated. Entity Framework would throw in such cases. To avoid breaking code that worked perfectly in EF 6 though, EF Core 1 and 2 used client-side evaluation: They translated what they could to SQL then loaded the data in-memory, on the client, and execute the rest of the query using LINQ to Objects.
This means that if you wanted to calculate a SUM for 100K rows, EF Core 1-2 would load all 100K rows in memory and proceed to add the values one by one. Never mind joining two tables with 1000 rows each - that's 1M comparisons.
Even in EF Core 2.1 though, client-side evaluation would generate runtime warnings and could be disabled completely. In EF Core 3.1 client-side evaluation was disabled completely.
To get your query to work properly don't try to force the case or the collation. Just use a simple equality :
var itemExists=context.Products.Any(a=>a.ItemNumber == itemNumber &&
a.SoqHeading_NP.SoqRevisionId == revisionId);
This will be translated to WHERE ItemNumber=@itemNumber && SoqHeading_NP.SoqRevisionId = @revisionId
. The query will use any indexes that cover the ItemNumber
and SoqRevisionId
columns to produce a result as fast as possible.
The collation used for the equality match is the column's collation. If that is case sensitive, you get case-sensitive matching. If not, you get case-insensitive matching. Indexes are built using the column's collation, so if you try to use a different collation for matching you'll prevent the server from using any indexes.
If you want to use different case matching in different queries and still use indexes, you need to create different indexes for each case. How you do that depends on the database
alter table Table1 add ItemNumberCS as COLLATE ..._BIN;
create index IX_Table1_ItemNumberCS on Table1 (ItemNumberCS);
Case-sensitive queries should use the ItemNumberCS
column.
CREATE COLLATION case_insensitive (
provider = icu,
locale = 'und-u-ks-level2',
deterministic = false
);
CREATE INDEX IX_Table1_ItemNumberCI ON Table1 (title COLLATE "case_insensitive");`
The LINQ query won't have to change.