diff --git a/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs b/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs index 1c488d0ba6b..1cc866f06bf 100644 --- a/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs +++ b/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs @@ -777,9 +777,25 @@ IEnumerable> GetTypeTypeParameters(INamedTypeSymbol t /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override Expression VisitLiteralExpression(LiteralExpressionSyntax literal) - => _semanticModel.GetTypeInfo(literal) is { ConvertedType: { } type } - ? Constant(literal.Token.Value, ResolveType(type)) - : Constant(literal.Token.Value); + { + var value = literal.Token.Value; + if (_semanticModel.GetTypeInfo(literal) is not { ConvertedType: { } type }) + { + return Constant(value); + } + + var clrType = ResolveType(type); + if (value is not null + && value.GetType() != clrType.UnwrapNullableType() + && value.GetType().IsNumeric() + && clrType.IsNumeric()) + { + value = System.Convert.ChangeType( + value is char c ? (int)c : value, clrType.UnwrapNullableType(), CultureInfo.InvariantCulture); + } + + return Constant(value, clrType); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.Design.Tests/Query/CSharpToLinqTranslatorTest.cs b/test/EFCore.Design.Tests/Query/CSharpToLinqTranslatorTest.cs index 1b765ea5bc2..8f520b459ef 100644 --- a/test/EFCore.Design.Tests/Query/CSharpToLinqTranslatorTest.cs +++ b/test/EFCore.Design.Tests/Query/CSharpToLinqTranslatorTest.cs @@ -265,6 +265,39 @@ public void Literal_decimal() () => 1.5m, "1.5m"); + [Theory] + [InlineData("(decimal amount) => amount > 0 ? amount : 0")] + [InlineData("(decimal amount) => amount > 0m ? amount : 0")] + [InlineData("(decimal amount) => amount > 0m ? amount : 0m")] + public void Literal_implicitly_converted_to_decimal(string code) + => AssertExpression( + () => new[] { 1m }.Select(amount => amount > 0m ? amount : 0m), + $"new[] {{ 1m }}.Select({code})"); + + [Fact] + public void Literal_char_implicitly_converted_to_decimal() + => AssertExpression( + () => new decimal[] { 'a' }, + "new decimal[] { 'a' }"); + + [Fact] + public void Literal_implicitly_converted_to_byte() + => AssertExpression( + () => new byte[] { 255 }, + "new byte[] { 255 }"); + + [Fact] + public void Literal_implicitly_converted_to_long() + => AssertExpression( + () => new long[] { 1 }, + "new long[] { 1 }"); + + [Fact] + public void Literal_implicitly_converted_to_double() + => AssertExpression( + () => new double[] { 1 }, + "new double[] { 1 }"); + [Fact] public void Literal_null() => AssertExpression( diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs index c3b38dbb314..905b29c9c92 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs @@ -16,6 +16,40 @@ public AdHocPrecompiledQueryRelationalTestBase(NonSharedFixture fixture, ITestOu : base(fixture) => TestOutputHelper = testOutputHelper; + [Fact] + public virtual async Task Decimal_projection_with_implicitly_converted_zero() + { + var contextFactory = await InitializeNonSharedTest(); + + await Test( + """ +await using var context = new AdHocPrecompiledQueryRelationalTestBase.DecimalContext(dbContextOptions); +var id = 1; +var amount = 2m; + +// Generate and compile the query without executing it. +Func> query = () => context.Groups.Where(group => group.Id == id) + .Select(group => group.Balance + (amount > 0 ? amount : 0)) + .SingleAsync(); +""", + typeof(DecimalContext), + contextFactory.GetOptions(), + interceptorCodeAsserter: code => Assert.Contains("InterceptsLocation", code)); + } + + public class DecimalContext(DbContextOptions options) : DbContext(options) + { + public DbSet Groups { get; set; } = null!; + } + + public class DecimalGroup + { + public int Id { get; set; } + + [Precision(18, 2)] + public decimal Balance { get; set; } + } + [Fact] public virtual async Task Index_no_evaluatability() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs index 96f4ab85563..dac7e118cb2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs @@ -19,6 +19,13 @@ protected override DbContextOptionsBuilder UseWindowFunctionAggregates(DbContext return optionsBuilder; } + public override async Task Decimal_projection_with_implicitly_converted_zero() + { + await base.Decimal_projection_with_implicitly_converted_zero(); + + AssertSql(); + } + public override async Task GroupBy_elements_and_aggregates_as_window_functions() { await base.GroupBy_elements_and_aggregates_as_window_functions();