From 934460964e4435c83b123a4e0cd6cb5ba851274e Mon Sep 17 00:00:00 2001 From: Koen Date: Mon, 1 Jun 2026 03:03:39 +0000 Subject: [PATCH 1/2] fix AVG over nullable int?/long? window --- .../WindowFunctionMethodCallTranslator.cs | 4 ++-- .../Infrastructure/WindowFunctionTestBase.cs | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs b/src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs index e9add81..d300dac 100644 --- a/src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs +++ b/src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs @@ -81,7 +81,7 @@ [new OrderingExpression( // cast the argument so SQL computes a floating-point AVG, not integer division. [NeedsFloatCast(expr, method.ReturnType) ? _sqlExpressionFactory.ApplyDefaultTypeMapping( - _sqlExpressionFactory.Convert(expr, method.ReturnType)) + _sqlExpressionFactory.Convert(expr, typeof(double))) : expr], spec, method.ReturnType), @@ -209,7 +209,7 @@ private WindowFunctionSqlExpression MakeNavigation( // SQL Server performs integer division for AVG(int) — cast the argument when the // CLR return type is floating-point but the expression is an integer type. private static bool NeedsFloatCast(SqlExpression expr, Type returnType) => - returnType == typeof(double) + (returnType == typeof(double) || returnType == typeof(double?)) && expr.Type is var t && (t == typeof(int) || t == typeof(long) || t == typeof(int?) || t == typeof(long?)); diff --git a/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs b/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs index 0b1bcc8..442a89e 100644 --- a/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs +++ b/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs @@ -798,6 +798,29 @@ public async Task Average_IntColumn_ReturnsDouble() Assert.AreEqual(2.1, results[^1].AvgQty, 0.01); } + [TestMethod] + public async Task Average_NullableIntColumn_CastsToFloat() + { + var query = Context.Orders + .Select(o => new + { + o.Id, + AvgCustomer = WindowFunction.Average(o.CustomerId, + Window.OrderBy(o.Id) + .RowsBetween(WindowFrameBound.UnboundedPreceding, WindowFrameBound.UnboundedFollowing)), + }) + .OrderBy(x => x.Id); + + var sql = query.ToQueryString(); + StringAssert.Contains(sql, "AVG"); + StringAssert.Contains(sql, "CAST"); + + var results = await query.ToListAsync(); + Assert.AreEqual(10, results.Count); + // AVG of CustomerId (five 1s + five 2s = 15/10) must be 1.5, not integer-divided to 1. + Assert.AreEqual(1.5, results[0].AvgCustomer!.Value, 0.01); + } + [TestMethod] public async Task Sum_WithoutFrame_UsesDefaultFrame() { From 6af1c35a4dd06869659f126142df6108b2315963 Mon Sep 17 00:00:00 2001 From: Koen Date: Mon, 1 Jun 2026 17:42:58 +0000 Subject: [PATCH 2/2] Make AVG nullable-cast test assertion provider-agnostic PostgreSQL renders the float cast as `::double precision` rather than the `CAST(... AS ...)` keyword, so the literal "CAST" assertion failed only on Postgres. Accept either cast idiom. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Infrastructure/WindowFunctionTestBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs b/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs index 442a89e..309569e 100644 --- a/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs +++ b/tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs @@ -813,7 +813,9 @@ public async Task Average_NullableIntColumn_CastsToFloat() var sql = query.ToQueryString(); StringAssert.Contains(sql, "AVG"); - StringAssert.Contains(sql, "CAST"); + // Float cast renders per-provider: CAST(... AS ...) on most, ::double precision on PostgreSQL. + Assert.IsTrue(sql.Contains("CAST(") || sql.Contains("::"), + $"AVG argument should be cast to floating point, but no cast was found in: {sql}"); var results = await query.ToListAsync(); Assert.AreEqual(10, results.Count);