From eb096031f30ddcdb1499a77b9bd03d8222c7c11d Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Tue, 1 Sep 2026 11:28:33 +0200 Subject: [PATCH 1/2] Fix handling decimal for EF_DECIMAL collate. --- .../Internal/SqliteRelationalConnection.cs | 11 ++-- .../BuiltInDataTypesSqliteTest.cs | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 576c9b1bb3b..fa1cd907330 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -183,9 +183,14 @@ private void InitializeDbConnection(DbConnection connection) sqliteConnection.CreateCollation( "EF_DECIMAL", - (x, y) => decimal.Compare( - decimal.Parse(x, NumberStyles.Number, CultureInfo.InvariantCulture), - decimal.Parse(y, NumberStyles.Number, CultureInfo.InvariantCulture))); + (x, y) => (decimal.TryParse(x, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var xValue), + decimal.TryParse(y, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var yValue)) switch + { + (true, true) => decimal.Compare(xValue, yValue), + (true, _) => -1, + (_, true) => 1, + _ => string.CompareOrdinal(x, y) + }); } else { diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 3d9a99a410b..3edd827073a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -1950,6 +1950,59 @@ CROSS JOIN "BuiltInDataTypes" AS "b0" """); } + [Fact] + public virtual void Can_query_OrderBy_decimal_with_invalid_database_values() + { + using var context = CreateContext(); + using var transaction = context.Database.BeginTransaction(); + + context.AddRange( + new BuiltInDataTypes { Id = 230, PartitionId = 210, TestDecimal = 2m }, + new BuiltInDataTypes { Id = 231, PartitionId = 210, TestDecimal = 20m }, + new BuiltInDataTypes { Id = 232, PartitionId = 210, TestDecimal = 0m }, + new BuiltInDataTypes { Id = 233, PartitionId = 210, TestDecimal = 0m }, + new BuiltInDataTypes { Id = 234, PartitionId = 210, TestDecimal = 0m }, + new BuiltInDataTypes { Id = 235, PartitionId = 210, TestDecimal = 0m }); + + context.SaveChanges(); + + context.Database.ExecuteSql( + $"""UPDATE "BuiltInDataTypes" SET "TestDecimal" = {"1e1"} WHERE "Id" = {232}"""); + context.Database.ExecuteSql( + $"""UPDATE "BuiltInDataTypes" SET "TestDecimal" = {"n/a"} WHERE "Id" = {233}"""); + context.Database.ExecuteSql( + $"""UPDATE "BuiltInDataTypes" SET "TestDecimal" = {"zzz"} WHERE "Id" = {234}"""); + context.Database.ExecuteSql( + $"""UPDATE "BuiltInDataTypes" SET "TestDecimal" = {"1e40"} WHERE "Id" = {235}"""); + + Fixture.TestSqlLoggerFactory.Clear(); + + var results = context.Set() + .Where(e => e.PartitionId == 210) + .OrderBy(e => e.TestDecimal) + .Select(e => e.Id) + .ToList(); + + Assert.Equal( + [ + 230, // 2 + 232, // 1e1 == 10 + 231, // 20 + 235, // Invalid values compare ordinally: "1e40" + 233, // "n/a" + 234 // "zzz" + ], + results); + + AssertSql( + """ +SELECT "b"."Id" +FROM "BuiltInDataTypes" AS "b" +WHERE "b"."PartitionId" = 210 +ORDER BY "b"."TestDecimal" COLLATE "EF_DECIMAL" +"""); + } + private void AssertTranslationFailed(Action testCode) => Assert.Contains( CoreStrings.TranslationFailed("")[21..], From 59e38fa655d925f2afc879e6f22e10bb9bf6c796 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Tue, 1 Sep 2026 11:34:17 +0200 Subject: [PATCH 2/2] Add `static` to the lambdas for functions/aggregates/collations (and explicit names). --- .../Internal/SqliteRelationalConnection.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index fa1cd907330..268eba7c978 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -100,8 +100,8 @@ private void InitializeDbConnection(DbConnection connection) } sqliteConnection.CreateFunction( - "regexp", - (pattern, input) + name: "regexp", + static (pattern, input) => input == null || pattern == null ? null @@ -109,52 +109,52 @@ private void InitializeDbConnection(DbConnection connection) isDeterministic: true); sqliteConnection.CreateFunction( - "ef_mod", - (decimal? dividend, decimal? divisor) => divisor == 0m ? null : dividend % divisor, + name: "ef_mod", + static (decimal? dividend, decimal? divisor) => divisor == 0m ? null : dividend % divisor, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_add", - (decimal? left, decimal? right) => left + right, + static (decimal? left, decimal? right) => left + right, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_divide", - (decimal? dividend, decimal? divisor) => divisor == 0m ? null : dividend / divisor, + static (decimal? dividend, decimal? divisor) => divisor == 0m ? null : dividend / divisor, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_compare", - (decimal? left, decimal? right) => left.HasValue && right.HasValue + static (decimal? left, decimal? right) => left.HasValue && right.HasValue ? decimal.Compare(left.Value, right.Value) : default(int?), isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_multiply", - (decimal? left, decimal? right) => left * right, + static (decimal? left, decimal? right) => left * right, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_negate", - (decimal? m) => -m, + static (decimal? m) => -m, isDeterministic: true); sqliteConnection.CreateAggregate( - "ef_avg", + name: "ef_avg", seed: (0m, 0ul), - ((decimal sum, ulong count) acc, decimal? value) => value is null + static ((decimal sum, ulong count) acc, decimal? value) => value is null ? acc : (acc.sum + value.Value, acc.count + 1), - ((decimal sum, ulong count) acc) => acc.count == 0 + static ((decimal sum, ulong count) acc) => acc.count == 0 ? default(decimal?) : acc.sum / acc.count, isDeterministic: true); sqliteConnection.CreateAggregate( - "ef_max", + name: "ef_max", seed: null, - (decimal? max, decimal? value) => max is null + static (decimal? max, decimal? value) => max is null ? value : value is null ? max @@ -162,9 +162,9 @@ private void InitializeDbConnection(DbConnection connection) isDeterministic: true); sqliteConnection.CreateAggregate( - "ef_min", + name: "ef_min", seed: null, - (decimal? min, decimal? value) => min is null + static (decimal? min, decimal? value) => min is null ? value : value is null ? min @@ -172,9 +172,9 @@ private void InitializeDbConnection(DbConnection connection) isDeterministic: true); sqliteConnection.CreateAggregate( - "ef_sum", + name: "ef_sum", seed: null, - (decimal? sum, decimal? value) => value is null + static (decimal? sum, decimal? value) => value is null ? sum : sum is null ? value @@ -182,8 +182,8 @@ private void InitializeDbConnection(DbConnection connection) isDeterministic: true); sqliteConnection.CreateCollation( - "EF_DECIMAL", - (x, y) => (decimal.TryParse(x, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var xValue), + name: "EF_DECIMAL", + static (x, y) => (decimal.TryParse(x, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var xValue), decimal.TryParse(y, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var yValue)) switch { (true, true) => decimal.Compare(xValue, yValue),