diff --git a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs index 75c91ae03ce..f92c322ec23 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs @@ -1605,6 +1605,15 @@ await AssertIncludeQuery( new List { new ExpectedInclude(o => o.Reports, "Reports") }); } + [ConditionalFact] + public virtual async Task Include_collection_with_complex_OrderBy3() + { + await AssertIncludeQuery( + os => os.OfType() + .Include(o => o.Reports) + .OrderBy(o => o.Weapons.OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault()), + new List { new ExpectedInclude(o => o.Reports, "Reports") }); + } [ConditionalFact] public virtual async Task Correlated_collection_with_complex_OrderBy() diff --git a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 7dc9c39b5e5..96ab9d73405 100644 --- a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -617,6 +617,16 @@ public virtual void Where_enum_has_flag() [ConditionalFact] public virtual void Where_enum_has_flag_subquery() + { + AssertQuery( + gs => gs.Where(g => g.Rank.HasFlag(gs.OrderBy(x => x.Nickname).ThenBy(x => x.SquadId).Select(x => x.Rank).FirstOrDefault()))); + + AssertQuery( + gs => gs.Where(g => MilitaryRank.Corporal.HasFlag(gs.OrderBy(x => x.Nickname).ThenBy(x => x.SquadId).Select(x => x.Rank).FirstOrDefault()))); + } + + [ConditionalFact] + public virtual void Where_enum_has_flag_subquery_with_pushdown() { AssertQuery( gs => gs.Where(g => g.Rank.HasFlag(gs.OrderBy(x => x.Nickname).ThenBy(x => x.SquadId).FirstOrDefault().Rank))); @@ -1090,6 +1100,13 @@ public virtual void Optional_Navigation_Null_Coalesce_To_Clr_Type() [ConditionalFact] public virtual void Where_subquery_boolean() + { + AssertQuery( + gs => gs.Where(g => g.Weapons.OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault())); + } + + [ConditionalFact] + public virtual void Where_subquery_boolean_with_pushdown() { AssertQuery( gs => gs.Where(g => g.Weapons.OrderBy(w => w.Id).FirstOrDefault().IsAutomatic)); @@ -1097,6 +1114,13 @@ public virtual void Where_subquery_boolean() [ConditionalFact] public virtual void Where_subquery_distinct_firstordefault_boolean() + { + AssertQuery( + gs => gs.Where(g => g.HasSoulPatch && g.Weapons.Distinct().OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault())); + } + + [ConditionalFact] + public virtual void Where_subquery_distinct_firstordefault_boolean_with_pushdown() { AssertQuery( gs => gs.Where(g => g.HasSoulPatch && g.Weapons.Distinct().OrderBy(w => w.Id).FirstOrDefault().IsAutomatic)); @@ -1111,7 +1135,23 @@ public virtual void Where_subquery_distinct_first_boolean() } [ConditionalFact] - public virtual void Where_subquery_distinct_singleordefault_boolean() + public virtual void Where_subquery_distinct_singleordefault_boolean1() + { + AssertQuery( + gs => gs.OrderBy(g => g.Nickname).Where(g => g.HasSoulPatch && g.Weapons.Where(w => w.Name.Contains("Lancer")).Distinct().Select(w => w.IsAutomatic).SingleOrDefault()), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Where_subquery_distinct_singleordefault_boolean2() + { + AssertQuery( + gs => gs.OrderBy(g => g.Nickname).Where(g => g.HasSoulPatch && g.Weapons.Where(w => w.Name.Contains("Lancer")).Select(w => w.IsAutomatic).Distinct().SingleOrDefault()), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Where_subquery_distinct_singleordefault_boolean_with_pushdown() { AssertQuery( gs => gs.OrderBy(g => g.Nickname).Where(g => g.HasSoulPatch && g.Weapons.Where(w => w.Name.Contains("Lancer")).Distinct().SingleOrDefault().IsAutomatic), @@ -1155,6 +1195,13 @@ public virtual void Where_subquery_distinct_last_boolean() [ConditionalFact] public virtual void Where_subquery_distinct_orderby_firstordefault_boolean() + { + AssertQuery( + gs => gs.Where(g => g.HasSoulPatch && g.Weapons.Distinct().OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault())); + } + + [ConditionalFact] + public virtual void Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown() { AssertQuery( gs => gs.Where(g => g.HasSoulPatch && g.Weapons.Distinct().OrderBy(w => w.Id).FirstOrDefault().IsAutomatic)); @@ -4785,6 +4832,16 @@ public virtual void Include_collection_with_complex_OrderBy2() new List { new ExpectedInclude(o => o.Reports, "Reports") }); } + [ConditionalFact] + public virtual void Include_collection_with_complex_OrderBy3() + { + AssertIncludeQuery( + os => os.OfType() + .Include(o => o.Reports) + .OrderBy(o => o.Weapons.OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault()), + new List { new ExpectedInclude(o => o.Reports, "Reports") }); + } + [ConditionalFact] public virtual void Correlated_collection_with_complex_OrderBy() { @@ -4823,6 +4880,83 @@ public virtual void Cast_to_derived_type_after_OfType_works() AssertQuery( gs => gs.OfType().Cast()); } + + [ConditionalFact] + public virtual void Select_subquery_boolean() + { + AssertQueryScalar( + gs => gs.Select(g => g.Weapons.OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault())); + } + + [ConditionalFact] + public virtual void Select_subquery_boolean_with_pushdown() + { + AssertQueryScalar( + gs => gs.Select(g => g.Weapons.OrderBy(w => w.Id).FirstOrDefault().IsAutomatic)); + } + + [ConditionalFact] + public virtual void Select_subquery_boolean_empty() + { + AssertQueryScalar( + gs => gs.Select(g => g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).Select(w => w.IsAutomatic).FirstOrDefault())); + } + + [ConditionalFact] + public virtual void Select_subquery_boolean_empty_with_pushdown() + { + AssertQueryScalar( + gs => gs.Select(g => (bool?)g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).FirstOrDefault().IsAutomatic), + gs => gs.Select(g => (bool?)null)); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean1() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => g.Weapons.Where(w => w.Name.Contains("Lancer")).Distinct().Select(w => w.IsAutomatic).SingleOrDefault()), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean2() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => g.Weapons.Where(w => w.Name.Contains("Lancer")).Select(w => w.IsAutomatic).Distinct().SingleOrDefault()), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean_with_pushdown() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => g.Weapons.Where(w => w.Name.Contains("Lancer")).Distinct().SingleOrDefault().IsAutomatic), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean_empty1() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => g.Weapons.Where(w => w.Name == "BFG").Distinct().Select(w => w.IsAutomatic).SingleOrDefault())); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean_empty2() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => g.Weapons.Where(w => w.Name == "BFG").Select(w => w.IsAutomatic).Distinct().SingleOrDefault())); + } + + [ConditionalFact] + public virtual void Select_subquery_distinct_singleordefault_boolean_empty_with_pushdown() + { + AssertQueryScalar( + gs => gs.Where(g => g.HasSoulPatch).Select(g => (bool?)g.Weapons.Where(w => w.Name == "BFG").Distinct().SingleOrDefault().IsAutomatic), + gs => gs.Where(g => g.HasSoulPatch).Select(g => (bool?)null), + assertOrder: true); + } + // Remember to add any new tests to Async version of this test class protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/SubQueryMemberPushDownExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/SubQueryMemberPushDownExpressionVisitor.cs index 61fdde0c255..1f17b44f851 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/SubQueryMemberPushDownExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/SubQueryMemberPushDownExpressionVisitor.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; @@ -55,6 +56,24 @@ ro is ConcatResultOperator _queryCompilationContext.UpdateMapping(querySourceMapping); subQueryModel.SelectClause.Selector = VisitMember(memberExpression.Update(subQueryModel.SelectClause.Selector)); + + var selector = subQueryModel.SelectClause.Selector; + if (IsFirstSingleLastOrDefault(subQueryExpression.QueryModel.ResultOperators.LastOrDefault()) + && !selector.Type.IsNullableType()) + { + var oldType = selector.Type; + subQueryModel.SelectClause.Selector + = Expression.Convert( + selector, + selector.Type.MakeNullable()); + + subQueryModel.ResultTypeOverride = subQueryModel.SelectClause.Selector.Type; + + return Expression.Convert( + new SubQueryExpression(subQueryModel), + oldType); + } + subQueryModel.ResultTypeOverride = subQueryModel.SelectClause.Selector.Type; return new SubQueryExpression(subQueryModel); @@ -72,15 +91,33 @@ ro is ConcatResultOperator var newSubQueryExpression = new SubQueryExpression(queryModel); var mainFromClause = new MainFromClause(queryModel.GetNewName("subquery"), queryModel.SelectClause.Selector.Type, newSubQueryExpression); - var selector = Expression.MakeMemberAccess( + Expression selector = Expression.MakeMemberAccess( new QuerySourceReferenceExpression(mainFromClause), memberExpression.Member); - var subqueryModel = new QueryModel(mainFromClause, new SelectClause(selector)); - subqueryModel.ResultOperators.Add(finalResultOperator); - var subqueryExpression = new SubQueryExpression(subqueryModel); - - return subqueryExpression; + if (IsFirstSingleLastOrDefault(finalResultOperator) + && !selector.Type.IsNullableType()) + { + var oldType = selector.Type; + selector = Expression.Convert( + selector, + selector.Type.MakeNullable()); + + var subqueryModel = new QueryModel(mainFromClause, new SelectClause(selector)); + subqueryModel.ResultOperators.Add(finalResultOperator); + + return Expression.Convert( + new SubQueryExpression(subqueryModel), + oldType); + } + else + { + var subqueryModel = new QueryModel(mainFromClause, new SelectClause(selector)); + subqueryModel.ResultOperators.Add(finalResultOperator); + var subqueryExpression = new SubQueryExpression(subqueryModel); + + return subqueryExpression; + } } } } @@ -88,6 +125,11 @@ ro is ConcatResultOperator return memberExpression.Update(newExpression); } + private bool IsFirstSingleLastOrDefault(ResultOperatorBase resultOperator) + => (resultOperator is FirstResultOperator first && first.ReturnDefaultWhenEmpty) + || (resultOperator is SingleResultOperator single && single.ReturnDefaultWhenEmpty) + || (resultOperator is LastResultOperator last && last.ReturnDefaultWhenEmpty); + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -121,5 +163,31 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return newMethodCallExpression; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected override Expression VisitUnary(UnaryExpression unaryExpression) + { + // remove double convert that could be introduced in the member pushdown (when we introduce cast to nullable for FirstOrDefault cases) + if (unaryExpression.NodeType == ExpressionType.Convert + || unaryExpression.NodeType == ExpressionType.ConvertChecked) + { + var newOperand = Visit(unaryExpression.Operand); + if (newOperand is UnaryExpression innerUnaryExpression + && (innerUnaryExpression.NodeType == ExpressionType.Convert + || innerUnaryExpression.NodeType == ExpressionType.ConvertChecked) + && innerUnaryExpression.Operand.Type == unaryExpression.Type + && innerUnaryExpression.Operand.Type != typeof(object)) + { + return innerUnaryExpression.Operand; + } + + return unaryExpression.Update(newOperand); + } + + return base.VisitUnary(unaryExpression); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGearsOfWarQuerySqlServerTest.cs index 9a4d7f8e9d4..a02b6ecc85b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGearsOfWarQuerySqlServerTest.cs @@ -2221,6 +2221,37 @@ public override async Task Include_collection_with_complex_OrderBy2() @"SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOrBirthName], [o].[Discriminator], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank] FROM [Gears] AS [o] WHERE [o].[Discriminator] = N'Officer' +ORDER BY ( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE [o].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id] +), [o].[Nickname], [o].[SquadId]", + // + @"SELECT [o.Reports].[Nickname], [o.Reports].[SquadId], [o.Reports].[AssignedCityName], [o.Reports].[CityOrBirthName], [o.Reports].[Discriminator], [o.Reports].[FullName], [o.Reports].[HasSoulPatch], [o.Reports].[LeaderNickname], [o.Reports].[LeaderSquadId], [o.Reports].[Rank] +FROM [Gears] AS [o.Reports] +INNER JOIN ( + SELECT [o0].[Nickname], [o0].[SquadId], ( + SELECT TOP(1) [w0].[IsAutomatic] + FROM [Weapons] AS [w0] + WHERE [o0].[FullName] = [w0].[OwnerFullName] + ORDER BY [w0].[Id] + ) AS [c] + FROM [Gears] AS [o0] + WHERE [o0].[Discriminator] = N'Officer' +) AS [t] ON ([o.Reports].[LeaderNickname] = [t].[Nickname]) AND ([o.Reports].[LeaderSquadId] = [t].[SquadId]) +WHERE [o.Reports].[Discriminator] IN (N'Officer', N'Gear') +ORDER BY [t].[c], [t].[Nickname], [t].[SquadId]"); + } + + public override async Task Include_collection_with_complex_OrderBy3() + { + await base.Include_collection_with_complex_OrderBy3(); + + AssertSql( + @"SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOrBirthName], [o].[Discriminator], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank] +FROM [Gears] AS [o] +WHERE [o].[Discriminator] = N'Officer' ORDER BY COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 95c06d034a0..a76b2af9221 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -784,6 +784,40 @@ WHERE [x].[Discriminator] IN (N'Officer', N'Gear') ), 0))"); } + public override void Where_enum_has_flag_subquery_with_pushdown() + { + base.Where_enum_has_flag_subquery_with_pushdown(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND (([g].[Rank] & ( + SELECT TOP(1) [x].[Rank] + FROM [Gears] AS [x] + WHERE [x].[Discriminator] IN (N'Officer', N'Gear') + ORDER BY [x].[Nickname], [x].[SquadId] +)) = ( + SELECT TOP(1) [x].[Rank] + FROM [Gears] AS [x] + WHERE [x].[Discriminator] IN (N'Officer', N'Gear') + ORDER BY [x].[Nickname], [x].[SquadId] +))", + // + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ((1 & ( + SELECT TOP(1) [x].[Rank] + FROM [Gears] AS [x] + WHERE [x].[Discriminator] IN (N'Officer', N'Gear') + ORDER BY [x].[Nickname], [x].[SquadId] +)) = ( + SELECT TOP(1) [x].[Rank] + FROM [Gears] AS [x] + WHERE [x].[Discriminator] IN (N'Officer', N'Gear') + ORDER BY [x].[Nickname], [x].[SquadId] +))"); + } + public override void Where_enum_has_flag_subquery_client_eval() { base.Where_enum_has_flag_subquery_client_eval(); @@ -1517,6 +1551,21 @@ ORDER BY [w].[Id] ), 0) = 1)"); } + public override void Where_subquery_boolean_with_pushdown() + { + base.Where_subquery_boolean_with_pushdown(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND (( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id] +) = 1)"); + } + public override void Where_subquery_distinct_firstordefault_boolean() { base.Where_subquery_distinct_firstordefault_boolean(); @@ -1535,6 +1584,24 @@ ORDER BY [t].[Id] ), 0) = 1))"); } + public override void Where_subquery_distinct_firstordefault_boolean_with_pushdown() + { + base.Where_subquery_distinct_firstordefault_boolean_with_pushdown(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND (([g].[HasSoulPatch] = 1) AND (( + SELECT TOP(1) [t].[IsAutomatic] + FROM ( + SELECT DISTINCT [w].* + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ) AS [t] + ORDER BY [t].[Id] +) = 1))"); + } + public override void Where_subquery_distinct_first_boolean() { base.Where_subquery_distinct_first_boolean(); @@ -1566,9 +1633,61 @@ FROM [Weapons] AS [w0] ORDER BY [t0].[Id]"); } - public override void Where_subquery_distinct_singleordefault_boolean() + public override void Where_subquery_distinct_singleordefault_boolean1() + { + base.Where_subquery_distinct_singleordefault_boolean1(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1) +ORDER BY [g].[Nickname]", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]"); + } + + public override void Where_subquery_distinct_singleordefault_boolean2() + { + base.Where_subquery_distinct_singleordefault_boolean2(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1) +ORDER BY [g].[Nickname]", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName])", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName])"); + } + + public override void Where_subquery_distinct_singleordefault_boolean_with_pushdown() { - base.Where_subquery_distinct_singleordefault_boolean(); + base.Where_subquery_distinct_singleordefault_boolean_with_pushdown(); AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -1629,6 +1748,24 @@ ORDER BY [t].[Id] ), 0) = 1))"); } + public override void Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown() + { + base.Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown(); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND (([g].[HasSoulPatch] = 1) AND (( + SELECT TOP(1) [t].[IsAutomatic] + FROM ( + SELECT DISTINCT [w].* + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ) AS [t] + ORDER BY [t].[Id] +) = 1))"); + } + public override void Where_subquery_union_firstordefault_boolean() { base.Where_subquery_union_firstordefault_boolean(); @@ -6776,6 +6913,37 @@ public override void Include_collection_with_complex_OrderBy2() @"SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOrBirthName], [o].[Discriminator], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank] FROM [Gears] AS [o] WHERE [o].[Discriminator] = N'Officer' +ORDER BY ( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE [o].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id] +), [o].[Nickname], [o].[SquadId]", + // + @"SELECT [o.Reports].[Nickname], [o.Reports].[SquadId], [o.Reports].[AssignedCityName], [o.Reports].[CityOrBirthName], [o.Reports].[Discriminator], [o.Reports].[FullName], [o.Reports].[HasSoulPatch], [o.Reports].[LeaderNickname], [o.Reports].[LeaderSquadId], [o.Reports].[Rank] +FROM [Gears] AS [o.Reports] +INNER JOIN ( + SELECT [o0].[Nickname], [o0].[SquadId], ( + SELECT TOP(1) [w0].[IsAutomatic] + FROM [Weapons] AS [w0] + WHERE [o0].[FullName] = [w0].[OwnerFullName] + ORDER BY [w0].[Id] + ) AS [c] + FROM [Gears] AS [o0] + WHERE [o0].[Discriminator] = N'Officer' +) AS [t] ON ([o.Reports].[LeaderNickname] = [t].[Nickname]) AND ([o.Reports].[LeaderSquadId] = [t].[SquadId]) +WHERE [o.Reports].[Discriminator] IN (N'Officer', N'Gear') +ORDER BY [t].[c], [t].[Nickname], [t].[SquadId]"); + } + + public override void Include_collection_with_complex_OrderBy3() + { + base.Include_collection_with_complex_OrderBy3(); + + AssertSql( + @"SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOrBirthName], [o].[Discriminator], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank] +FROM [Gears] AS [o] +WHERE [o].[Discriminator] = N'Officer' ORDER BY COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] @@ -6890,6 +7058,222 @@ FROM [Gears] AS [g] WHERE [g].[Discriminator] = N'Officer'"); } + public override void Select_subquery_boolean() + { + base.Select_subquery_boolean(); + + AssertSql( + @"SELECT CAST(COALESCE(( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id] +), 0) AS bit) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Select_subquery_boolean_with_pushdown() + { + base.Select_subquery_boolean_with_pushdown(); + + AssertSql( + @"SELECT ( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id] +) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Select_subquery_boolean_empty() + { + base.Select_subquery_boolean_empty(); + + AssertSql( + @"SELECT CAST(COALESCE(( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE ([w].[Name] = N'BFG') AND ([g].[FullName] = [w].[OwnerFullName]) + ORDER BY [w].[Id] +), 0) AS bit) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Select_subquery_boolean_empty_with_pushdown() + { + base.Select_subquery_boolean_empty_with_pushdown(); + + AssertSql( + @"SELECT ( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE ([w].[Name] = N'BFG') AND ([g].[FullName] = [w].[OwnerFullName]) + ORDER BY [w].[Id] +) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Select_subquery_distinct_singleordefault_boolean1() + { + base.Select_subquery_distinct_singleordefault_boolean1(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]"); + } + + public override void Select_subquery_distinct_singleordefault_boolean2() + { + base.Select_subquery_distinct_singleordefault_boolean2(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName])", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE (CHARINDEX(N'Lancer', [w0].[Name]) > 0) AND (@_outer_FullName = [w0].[OwnerFullName])"); + } + + public override void Select_subquery_distinct_singleordefault_boolean_with_pushdown() + { + base.Select_subquery_distinct_singleordefault_boolean_with_pushdown(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT TOP(2) [t1].[IsAutomatic] +FROM ( + SELECT DISTINCT [w1].* + FROM [Weapons] AS [w1] + WHERE (CHARINDEX(N'Lancer', [w1].[Name]) > 0) AND (@_outer_FullName = [w1].[OwnerFullName]) +) AS [t1]", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT TOP(2) [t1].[IsAutomatic] +FROM ( + SELECT DISTINCT [w1].* + FROM [Weapons] AS [w1] + WHERE (CHARINDEX(N'Lancer', [w1].[Name]) > 0) AND (@_outer_FullName = [w1].[OwnerFullName]) +) AS [t1]"); + } + + public override void Select_subquery_distinct_singleordefault_boolean_empty1() + { + base.Select_subquery_distinct_singleordefault_boolean_empty1(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]"); + } + + public override void Select_subquery_distinct_singleordefault_boolean_empty2() + { + base.Select_subquery_distinct_singleordefault_boolean_empty2(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName])", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT DISTINCT TOP(2) [w0].[IsAutomatic] +FROM [Weapons] AS [w0] +WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName])"); + } + + public override void Select_subquery_distinct_singleordefault_boolean_empty_with_pushdown() + { + base.Select_subquery_distinct_singleordefault_boolean_empty_with_pushdown(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[HasSoulPatch] = 1)", + // + @"@_outer_FullName='Damon Baird' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]", + // + @"@_outer_FullName='Marcus Fenix' (Size = 450) + +SELECT TOP(2) [t0].[IsAutomatic] +FROM ( + SELECT DISTINCT [w0].* + FROM [Weapons] AS [w0] + WHERE ([w0].[Name] = N'BFG') AND (@_outer_FullName = [w0].[OwnerFullName]) +) AS [t0]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/WarningsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/WarningsSqlServerTest.cs index 093636ebb29..30f6ba911aa 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/WarningsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/WarningsSqlServerTest.cs @@ -51,7 +51,7 @@ public override void FirstOrDefault_without_orderby_and_filter_issues_warning_su Assert.Contains( CoreStrings.LogFirstWithoutOrderByAndFilter.GenerateMessage( - "(from Order _1 in [c].Orders select [_1].OrderID).FirstOrDefault()"), Fixture.TestSqlLoggerFactory.Log); + "(from Order _1 in [c].Orders select (Nullable)[_1].OrderID).FirstOrDefaul..."), Fixture.TestSqlLoggerFactory.Log); } public override void FirstOrDefault_without_orderby_but_with_filter_doesnt_issue_warning()