From 15bc370703ee500c4c7ff1a4a670c97a829b839b Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 19 Jun 2018 12:11:54 -0700 Subject: [PATCH] Fix to #12314 - SumAsync throw Exception when used over float? Problem was that in order to return 0 from empty Sum of nullable values, we convert them to non-nullable (to produce 0) and then convert back to nullable type. This works without issue for sync path, but in async simple casting like that doesn't work. Fix is to call method that adds Task.ContinueWith() call that casts the result to the correct type in the async scenario. --- ...ensions.cs => RelationalTaskExtensions.cs} | 29 ++- .../RelationalResultOperatorHandler.cs | 18 +- .../Query/AsyncGearsOfWarQueryTestBase.cs | 10 + .../Query/AsyncQueryTestBase.cs | 210 ++++++++++-------- .../Query/AsyncSimpleQueryTestBase.cs | 6 + .../Query/GearsOfWarQueryFixtureBase.cs | 1 + .../GearsOfWarModel/GearsOfWarData.cs | 6 +- .../TestModels/GearsOfWarModel/Mission.cs | 1 + .../Query/AsyncSimpleQuerySqlServerTest.cs | 16 ++ .../Query/GearsOfWarQuerySqlServerTest.cs | 22 +- 10 files changed, 212 insertions(+), 107 deletions(-) rename src/EFCore.Relational/Extensions/{TaskExtensions.cs => RelationalTaskExtensions.cs} (55%) diff --git a/src/EFCore.Relational/Extensions/TaskExtensions.cs b/src/EFCore.Relational/Extensions/RelationalTaskExtensions.cs similarity index 55% rename from src/EFCore.Relational/Extensions/TaskExtensions.cs rename to src/EFCore.Relational/Extensions/RelationalTaskExtensions.cs index 1606cca8898..6cb86b148c5 100644 --- a/src/EFCore.Relational/Extensions/TaskExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalTaskExtensions.cs @@ -3,7 +3,7 @@ namespace System.Threading.Tasks { - internal static class TaskExtensions + internal static class RelationalTaskExtensions { public static Task Cast(this Task task) where TDerived : T @@ -31,5 +31,32 @@ public static Task Cast(this Task task) return taskCompletionSource.Task; } + + public static Task CastToNullable(this Task task) + where T : struct + { + var taskCompletionSource = new TaskCompletionSource(); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + // ReSharper disable once PossibleNullReferenceException + taskCompletionSource.TrySetException(t.Exception.InnerExceptions); + } + else if (t.IsCanceled) + { + taskCompletionSource.TrySetCanceled(); + } + else + { + taskCompletionSource.TrySetResult((T?)t.Result); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return taskCompletionSource.Task; + } } } diff --git a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index f916b25176c..5c20d5e7eea 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs @@ -963,6 +963,9 @@ var sqlTranslatingExpressionVisitor return handlerContext.EvalOnClient(); } + private static readonly MethodInfo _castToNullableMethodInfo + = typeof(RelationalTaskExtensions).GetRuntimeMethods().Where(m => m.Name == nameof(RelationalTaskExtensions.CastToNullable)).Single(); + private static Expression HandleSum(HandlerContext handlerContext) { if (!handlerContext.QueryModelVisitor.RequiresClientProjection @@ -990,10 +993,21 @@ var clientExpression .MakeGenericMethod(sumExpression.Type.UnwrapNullableType()) .Invoke(null, new object[] { handlerContext, /*throwOnNullResult:*/ false }); - return - sumExpression.Type.IsNullableType() + if (handlerContext.QueryModelVisitor.QueryCompilationContext.IsAsyncQuery + && !(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue12314", out var isEnabled) && isEnabled)) + { + return sumExpression.Type.IsNullableType() + ? Expression.Call( + _castToNullableMethodInfo.MakeGenericMethod(sumExpression.Type.UnwrapNullableType()), + clientExpression) + : clientExpression; + } + else + { + return sumExpression.Type.IsNullableType() ? Expression.Convert(clientExpression, sumExpression.Type) : clientExpression; + } } } diff --git a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs index d8f76e87ccc..e067666ed77 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs @@ -1691,5 +1691,15 @@ await AssertQuery( assertOrder: true, elementAsserter: CollectionAsserter(e => e.Nickname, (e, a) => Assert.Equal(e.Nickname, a.Nickname))); } + + [ConditionalFact] + public virtual async Task Sum_with_no_data_nullable_double() + { + using (var ctx = CreateContext()) + { + var result = await ctx.Missions.Where(m => m.CodeName == "Operation Foobar").Select(m => m.Rating).SumAsync(); + Assert.Equal(0, result); + } + } } } diff --git a/src/EFCore.Specification.Tests/Query/AsyncQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncQueryTestBase.cs index 03202cb8baa..90fb411a18a 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncQueryTestBase.cs @@ -21,219 +21,249 @@ public abstract class AsyncQueryTestBase : IClassFixture // one argument - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( + Func, Task> query, + Action asserter = null, + int entryCount = 0) + where TItem1 : class + => AssertSingleResult(query, query, asserter, entryCount); + + protected virtual Task AssertSingleResult( + Func, Task> actualQuery, + Func, Task> expectedQuery, + Action asserter = null, + int entryCount = 0) + where TItem1 : class + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + + protected virtual Task AssertSingleResult( + Func, Task> query, + Action asserter = null, + int entryCount = 0) + where TItem1 : class + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( + Func, Task> actualQuery, + Func, Task> expectedQuery, + Action asserter = null, + int entryCount = 0) + where TItem1 : class + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + + protected virtual Task AssertSingleResult( Func, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, Task> actualQuery, Func, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); // two arguments - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> actualQuery, Func, IQueryable, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> actualQuery, Func, IQueryable, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> actualQuery, Func, IQueryable, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, Task> actualQuery, Func, IQueryable, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - protected virtual async Task AssertSingleResult( + protected virtual Task AssertSingleResult( Func, IQueryable, Task> actualQuery, Func, IQueryable, Task> expectedQuery, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); // three arguments - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class where TItem3 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, IQueryable, Task> query, Action asserter = null, int entryCount = 0) where TItem1 : class where TItem2 : class where TItem3 : class - => await AssertSingleResult(query, query, asserter, entryCount); + => AssertSingleResult(query, query, asserter, entryCount); - public virtual async Task AssertSingleResult( + public virtual Task AssertSingleResult( Func, IQueryable, IQueryable, Task> actualQuery, Func, IQueryable, IQueryable, Task> expectedQuery, Action asserter = null, @@ -241,22 +271,22 @@ public virtual async Task AssertSingleResult( where TItem1 : class where TItem2 : class where TItem3 : class - => await Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); + => Fixture.QueryAsserter.AssertSingleResult(actualQuery, expectedQuery, asserter, entryCount); #endregion #region AssertQuery - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable> query, Func elementSorter = null, Action elementAsserter = null, bool assertOrder = false, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable> actualQuery, Func, IQueryable> expectedQuery, Func elementSorter = null, @@ -264,9 +294,9 @@ public virtual async Task AssertQuery( bool assertOrder = false, int entryCount = 0) where TItem1 : class - => await Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable, IQueryable> query, Func elementSorter = null, Action elementAsserter = null, @@ -274,9 +304,9 @@ public virtual async Task AssertQuery( int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable, IQueryable> actualQuery, Func, IQueryable, IQueryable> expectedQuery, Func elementSorter = null, @@ -285,9 +315,9 @@ public virtual async Task AssertQuery( int entryCount = 0) where TItem1 : class where TItem2 : class - => await Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable, IQueryable, IQueryable> query, Func elementSorter = null, Action elementAsserter = null, @@ -296,9 +326,9 @@ public virtual async Task AssertQuery( where TItem1 : class where TItem2 : class where TItem3 : class - => await Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(query, query, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); - public virtual async Task AssertQuery( + public virtual Task AssertQuery( Func, IQueryable, IQueryable, IQueryable> actualQuery, Func, IQueryable, IQueryable, IQueryable> expectedQuery, Func elementSorter = null, @@ -308,89 +338,89 @@ public virtual async Task AssertQuery( where TItem1 : class where TItem2 : class where TItem3 : class - => await Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); + => Fixture.QueryAsserter.AssertQuery(actualQuery, expectedQuery, elementSorter, elementAsserter, assertOrder, entryCount, isAsync: true); #endregion #region AssertQueryScalar - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> actualQuery, Func, IQueryable> expectedQuery, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(actualQuery, expectedQuery, assertOrder); + => AssertQueryScalar(actualQuery, expectedQuery, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> actualQuery, Func, IQueryable> expectedQuery, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(actualQuery, expectedQuery, assertOrder); + => AssertQueryScalar(actualQuery, expectedQuery, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> actualQuery, Func, IQueryable> expectedQuery, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(actualQuery, expectedQuery, assertOrder); + => AssertQueryScalar(actualQuery, expectedQuery, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class - => await AssertQueryScalar(query, query, assertOrder); + => AssertQueryScalar(query, query, assertOrder); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> query, bool assertOrder = false) where TItem1 : class where TResult : struct - => await Fixture.QueryAsserter.AssertQueryScalar(query, query, assertOrder, isAsync: true); + => Fixture.QueryAsserter.AssertQueryScalar(query, query, assertOrder, isAsync: true); - public virtual async Task AssertQueryScalar( + public virtual Task AssertQueryScalar( Func, IQueryable> actualQuery, Func, IQueryable> expectedQuery, bool assertOrder = false) where TItem1 : class where TResult : struct - => await Fixture.QueryAsserter.AssertQueryScalar(actualQuery, expectedQuery, assertOrder, isAsync: true); + => Fixture.QueryAsserter.AssertQueryScalar(actualQuery, expectedQuery, assertOrder, isAsync: true); public virtual void AssertQueryScalar( Func, IQueryable, IQueryable> query, diff --git a/src/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs index 4cb8c3f1074..51cdf361e74 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs @@ -3808,5 +3808,11 @@ public virtual async Task Cast_to_same_Type_CountAsync_works() { await AssertSingleResult(cs => cs.Cast().CountAsync()); } + + [ConditionalFact] + public virtual async Task Sum_with_no_data_nullable() + { + await AssertSingleResult(os => os.Where(o => o.OrderID < 0).Select(o => (int?)o.OrderID).SumAsync()); + } } } diff --git a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs index 18f81413a84..131b7302c50 100644 --- a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs +++ b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs @@ -150,6 +150,7 @@ protected GearsOfWarQueryFixtureBase() { Assert.Equal(e.Id, a.Id); Assert.Equal(e.CodeName, a.CodeName); + Assert.Equal(e.Rating, a.Rating); Assert.Equal(e.Timeline, a.Timeline); } } diff --git a/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs b/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs index 88350d33f37..559054623ae 100644 --- a/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs +++ b/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs @@ -104,9 +104,9 @@ public static IReadOnlyList CreateSquads() public static IReadOnlyList CreateMissions() => new List { - new Mission { Id = 1, CodeName = "Lightmass Offensive", Timeline = new DateTimeOffset(2, 1, 2, 10, 0, 0, new TimeSpan(1, 30, 0)) }, - new Mission { Id = 2, CodeName = "Hollow Storm", Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)) }, - new Mission { Id = 3, CodeName = "Halvo Bay defense", Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan()) } + new Mission { Id = 1, CodeName = "Lightmass Offensive", Rating = 2.1, Timeline = new DateTimeOffset(2, 1, 2, 10, 0, 0, new TimeSpan(1, 30, 0)) }, + new Mission { Id = 2, CodeName = "Hollow Storm", Rating = 4.2, Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)) }, + new Mission { Id = 3, CodeName = "Halvo Bay defense", Rating = null, Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan()) } }; public static IReadOnlyList CreateSquadMissions() diff --git a/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs b/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs index 50ca5d05811..a2240b0a812 100644 --- a/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs +++ b/src/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs @@ -11,6 +11,7 @@ public class Mission public int Id { get; set; } public string CodeName { get; set; } + public double? Rating { get; set; } public DateTimeOffset Timeline { get; set; } public virtual ICollection ParticipatingSquads { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs index 1ca35879ab5..f512f61a5a1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncSimpleQuerySqlServerTest.cs @@ -214,5 +214,21 @@ await AssertQuery( os => os.Select(o => new { o.Customer.City, Count = o.OrderDetails.Count() }), elementSorter: e => e.City + " " + e.Count); } + + [Fact] + public async Task Sum_with_no_data_nullable_legacy_behavior() + { + AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue12314", true); + + try + { + await Assert.ThrowsAsync( + async () => await AssertSingleResult(os => os.Where(o => o.OrderID < 0).Select(o => (int?)o.OrderID).SumAsync())); + } + finally + { + AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue12314", false); + } + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 1afebc703ba..20d0f35000b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -3037,7 +3037,7 @@ public override void Where_datetimeoffset_now() base.Where_datetimeoffset_now(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE [m].[Timeline] <> SYSDATETIMEOFFSET()"); } @@ -3047,7 +3047,7 @@ public override void Where_datetimeoffset_utcnow() base.Where_datetimeoffset_utcnow(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE [m].[Timeline] <> CAST(SYSUTCDATETIME() AS datetimeoffset)"); } @@ -3060,7 +3060,7 @@ public override void Where_datetimeoffset_date_component() AssertSql( @"@__Date_0='0001-01-01T00:00:00' -SELECT [m].[Id], [m].[CodeName], [m].[Timeline] +SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE CONVERT(date, [m].[Timeline]) > @__Date_0"); } @@ -3070,7 +3070,7 @@ public override void Where_datetimeoffset_year_component() base.Where_datetimeoffset_year_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(year, [m].[Timeline]) = 2"); } @@ -3080,7 +3080,7 @@ public override void Where_datetimeoffset_month_component() base.Where_datetimeoffset_month_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(month, [m].[Timeline]) = 1"); } @@ -3090,7 +3090,7 @@ public override void Where_datetimeoffset_dayofyear_component() base.Where_datetimeoffset_dayofyear_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(dayofyear, [m].[Timeline]) = 2"); } @@ -3100,7 +3100,7 @@ public override void Where_datetimeoffset_day_component() base.Where_datetimeoffset_day_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(day, [m].[Timeline]) = 2"); } @@ -3110,7 +3110,7 @@ public override void Where_datetimeoffset_hour_component() base.Where_datetimeoffset_hour_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(hour, [m].[Timeline]) = 10"); } @@ -3120,7 +3120,7 @@ public override void Where_datetimeoffset_minute_component() base.Where_datetimeoffset_minute_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(minute, [m].[Timeline]) = 0"); } @@ -3130,7 +3130,7 @@ public override void Where_datetimeoffset_second_component() base.Where_datetimeoffset_second_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(second, [m].[Timeline]) = 0"); } @@ -3140,7 +3140,7 @@ public override void Where_datetimeoffset_millisecond_component() base.Where_datetimeoffset_millisecond_component(); AssertSql( - @"SELECT [m].[Id], [m].[CodeName], [m].[Timeline] + @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] WHERE DATEPART(millisecond, [m].[Timeline]) = 0"); }