From cdfca222c1d6873c43dfcbccd6067db5a53b12de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lker=20amca?= Date: Sat, 10 Oct 2026 22:22:28 +0300 Subject: [PATCH 1/2] Convert unsigned enums to decimal through their underlying type EnumToNumberConverter converted the enum through long, so values of a ulong-backed enum above long.MaxValue were written as negative decimals (ulong.MaxValue became -1) and reading them back threw an OverflowException, even though the default mapping hints ask for decimal(20, 0) precisely to hold the full ulong range. Convert through the enum's underlying type instead. --- .../ValueConversion/EnumToNumberConverter.cs | 4 +-- .../EnumToNumberConverterTest.cs | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs b/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs index 95d3c906b89..3eb91a09619 100644 --- a/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs +++ b/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs @@ -94,7 +94,7 @@ private static Expression> ToNumber() return Expression.Lambda>( Expression.Convert( typeof(TNumber) == typeof(decimal) - ? Expression.Convert(param, typeof(long)) + ? Expression.Convert(param, typeof(TEnum).GetEnumUnderlyingType()) : param, typeof(TNumber)), param); } @@ -105,7 +105,7 @@ private static Expression> ToEnum() return Expression.Lambda>( Expression.Convert( typeof(TNumber) == typeof(decimal) - ? Expression.Convert(param, typeof(long)) + ? Expression.Convert(param, typeof(TEnum).GetEnumUnderlyingType()) : param, typeof(TEnum)), param); } diff --git a/test/EFCore.Tests/Storage/ValueConversion/EnumToNumberConverterTest.cs b/test/EFCore.Tests/Storage/ValueConversion/EnumToNumberConverterTest.cs index 5f6b52490d8..94ccbed0a01 100644 --- a/test/EFCore.Tests/Storage/ValueConversion/EnumToNumberConverterTest.cs +++ b/test/EFCore.Tests/Storage/ValueConversion/EnumToNumberConverterTest.cs @@ -124,6 +124,31 @@ public void Can_convert_decimals_to_enums() Assert.Equal(default, converter(0)); } + private static readonly ValueConverter _unsignedEnumToDecimal + = new EnumToNumberConverter(); + + [Fact] + public void Can_convert_unsigned_enums_to_decimals() + { + var converter = _unsignedEnumToDecimal.ConvertToProviderExpression.Compile(); + + Assert.Equal(1m, converter(Planets.Mercury)); + Assert.Equal(9223372036854775808m, converter(Planets.Venus)); + Assert.Equal(18446744073709551615m, converter(Planets.Earth)); + Assert.Equal(0m, converter(default)); + } + + [Fact] + public void Can_convert_decimals_to_unsigned_enums() + { + var converter = _unsignedEnumToDecimal.ConvertFromProviderExpression.Compile(); + + Assert.Equal(Planets.Mercury, converter(1m)); + Assert.Equal(Planets.Venus, converter(9223372036854775808m)); + Assert.Equal(Planets.Earth, converter(18446744073709551615m)); + Assert.Equal(default, converter(0m)); + } + private static readonly ValueConverter _enumToFloat = new EnumToNumberConverter(); @@ -272,4 +297,11 @@ private enum Beatles George = 1, Ringo = -1 } + + private enum Planets : ulong + { + Mercury = 1, + Venus = 9223372036854775808, + Earth = ulong.MaxValue + } } From 3bcab9a103c5df0910d027af4e1a7ceef362e621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lker=20amca?= Date: Tue, 13 Oct 2026 20:48:31 +0300 Subject: [PATCH 2/2] Remove JSON test overrides that expected ulong.MaxValue enums to be stored as -1 JsonTypesRelationalTestBase overrode the ulong enum JSON tests to expect -1 for EnumU64.Max ("because ulong is converted to long on SQL Server"), which was the EnumToNumberConverter bug fixed in the previous commit. SQL Server now writes 18446744073709551615 like the other providers, so the overrides are removed, along with the SQLite overrides that only restored the base expectations. --- .../JsonTypesRelationalTestBase.cs | 47 ------------------- .../JsonTypesSqliteTest.cs | 35 -------------- 2 files changed, 82 deletions(-) diff --git a/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs index 4886c4e4d32..5f559f8dc97 100644 --- a/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs @@ -161,53 +161,6 @@ public virtual Task Can_read_write_collection_of_ASCII_string_JSON_values(object { RelationalAnnotationNames.StoreType, storeType }, { CoreAnnotationNames.Unicode, false } }); - public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) - { - if (value == EnumU64.Max) - { - json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server - } - - return base.Can_read_write_ulong_enum_JSON_values(value, json); - } - - public override Task Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) - { - if (Equals(value, ulong.MaxValue)) - { - json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server - } - - return base.Can_read_write_nullable_ulong_enum_JSON_values(value, json); - } - - public override Task Can_read_write_collection_of_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(EnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64)8 - ], - """{"Prop":[0,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server - mappedCollection: true); - - public override Task Can_read_write_collection_of_nullable_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableEnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - null, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64?)8 - ], - """{"Prop":[0,null,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server - mappedCollection: true); - protected override void AssertElementFacets(IElementType element, Dictionary? facets) { base.AssertElementFacets(element, facets); diff --git a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs index f12cb9ffd12..4bbffe75d7b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs @@ -223,41 +223,6 @@ protected class HalfType public Half Half { get; set; } } - public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) - => Can_read_and_write_JSON_value(nameof(EnumU64Type.EnumU64), value, json); - - public override Task Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableEnumU64Type.EnumU64), - value == null ? default(EnumU64?) : (EnumU64)value, json); - - public override Task Can_read_write_collection_of_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(EnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64)8 - ], - """{"Prop":[0,18446744073709551615,0,1,8]}""", - mappedCollection: true); - - public override Task Can_read_write_collection_of_nullable_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableEnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - null, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64?)8 - ], - """{"Prop":[0,null,18446744073709551615,0,1,8]}""", - mappedCollection: true); - protected override ITestStoreFactory NonSharedTestStoreFactory => SqliteTestStoreFactory.Instance;