From c3e891f20c6f68b6fed4534e3f5536ff08bc1eda Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Mon, 12 Mar 2018 17:56:06 -0700 Subject: [PATCH] Fix to issues around entity comparisons in query: #11022 - GroupJoin with an entity object as a key fails #10974 - Query: Include Collection group by reference throws exception #11245 - Query: compilation error for queries with join where the inner key is navigation - Added step to the QueryOptimizer that converts entity qsre comparison to key comparisons instead for order by join and group join, - Improved logic of join/groupjoin inner key navigation rewrite to correctly handle case when navigation is the final operator (i.e. no scalar is projected at the end), - Improved "requires materialization" logic to correctly mark qsre projected out of subquery that is a result of nav rewrite of join/groupjoin inner key. --- .../Query/AsyncGearsOfWarQueryTestBase.cs | 144 ++++++++++ .../Query/GearsOfWarQueryTestBase.cs | 183 +++++++++++++ src/EFCore/Query/EntityQueryModelVisitor.cs | 121 ++++++++ .../NavigationRewritingExpressionVisitor.cs | 12 +- src/EFCore/Query/QueryCompilationContext.cs | 52 +++- .../Query/GearsOfWarQuerySqlServerTest.cs | 258 ++++++++++++++++++ 6 files changed, 766 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs index 840cb29e70e..6f574c4a033 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncGearsOfWarQueryTestBase.cs @@ -1372,5 +1372,149 @@ await AssertQuery( }), elementSorter: e => e.c); } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys() + { + await AssertQuery( + ws => from w1 in ws + join w2 in ws on w1 equals w2 + select new { Name1 = w1.Name, Name2 = w2.Name }, + elementSorter: e => e.Name1 + " " + e.Name2); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_composite_key() + { + await AssertQuery( + gs => from g1 in gs + join g2 in gs on g1 equals g2 + select new { GearName1 = g1.FullName, GearName2 = g2.FullName }, + elementSorter: e => e.GearName1 + " " + e.GearName2); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_inheritance() + { + await AssertQuery( + gs => from g in gs + join o in gs.OfType() on g equals o + select new { GearName = g.FullName, OfficerName = o.FullName }, + elementSorter: e => e.GearName + " " + e.OfficerName); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_outer_key_is_navigation() + { + await AssertQuery( + ws => from w1 in ws + join w2 in ws on w1.SynergyWith equals w2 + select new { Name1 = w1.Name, Name2 = w2.Name }, + elementSorter: e => e.Name1 + " " + e.Name2); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_inner_key_is_navigation() + { + await AssertQuery( + (cs, gs) => + from c in cs + join g in gs on c equals g.AssignedCity + select new { CityName = c.Name, GearNickname = g.Nickname }, + e => e.CityName + " " + e.GearNickname); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_inner_key_is_navigation_composite_key() + { + await AssertQuery( + (gs, ts) => + from g in gs + join t in ts.Where(tt => tt.Note == "Cole's Tag" || tt.Note == "Dom's Tag") on g equals t.Gear + select new { g.Nickname, t.Note }, + elementSorter: e => e.Nickname + " " + e.Note); + } + + [ConditionalFact] + public virtual async Task Join_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + await AssertQuery( + (ss, ws) => + from s in ss + join w in ws.Where(ww => ww.IsAutomatic) on s equals w.Owner.Squad + select new { SquadName = s.Name, WeaponName = w.Name }, + elementSorter: e => e.SquadName + " " + e.WeaponName); + } + + [ConditionalFact] + public virtual async Task GroupJoin_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + await AssertQuery( + (ss, ws) => + from s in ss + join w in ws on s equals w.Owner.Squad into grouping + from w in grouping.DefaultIfEmpty() + select new { SquadName = s.Name, WeaponName = w.Name }, + (ss, ws) => + from s in ss + join w in ws on s equals Maybe(w.Owner, () => w.Owner.Squad) into grouping + from w in grouping.DefaultIfEmpty() + select new { SquadName = s.Name, WeaponName = Maybe(w, () => w.Name) }, + elementSorter: e => e.SquadName + " " + e.WeaponName); + } + + [ConditionalFact] + public virtual async Task Include_with_group_by_on_entity_qsre() + { + using (var ctx = CreateContext()) + { + var query = ctx.Squads.Include(s => s.Members).GroupBy(s => s); + var results = await query.ToListAsync(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Members.Count > 0); + } + } + } + } + + [ConditionalFact] + public virtual async Task Include_with_group_by_on_entity_qsre_with_composite_key() + { + using (var ctx = CreateContext()) + { + var query = ctx.Gears.Include(g => g.Weapons).GroupBy(g => g); + var results = await query.ToListAsync(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Weapons.Count > 0); + } + } + } + } + + [ConditionalFact] + public virtual async Task Include_with_group_by_on_entity_navigation() + { + using (var ctx = CreateContext()) + { + var query = ctx.Factions.OfType().Include(lh => lh.Leaders).GroupBy(lh => lh.Commander.DefeatedBy); + var results = await query.ToListAsync(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Leaders.Count > 0); + } + } + } + } } } diff --git a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 09c7e9e10fa..2b411e62891 100644 --- a/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -4365,6 +4365,189 @@ public virtual void Negated_bool_ternary_inside_anonymous_type_in_projection() elementSorter: e => e.c); } + [ConditionalFact] + public virtual void Order_by_entity_qsre() + { + AssertQuery( + gs => gs.OrderBy(g => g.AssignedCity).ThenByDescending(g => g.Nickname).Select(f => f.FullName), + gs => gs.OrderBy(g => Maybe(g.AssignedCity, () => g.AssignedCity.Name)).ThenByDescending(g => g.Nickname).Select(f => f.FullName), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Order_by_entity_qsre_with_inheritance() + { + AssertQuery( + lls => lls.OfType().OrderBy(lc => lc.HighCommand).ThenBy(lc => lc.Name).Select(lc => lc.Name), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Order_by_entity_qsre_composite_key() + { + AssertQuery( + ws => ws.OrderBy(w => w.Owner).Select(w => w.Name), + ws => ws.OrderBy(w => Maybe(w.Owner, () => w.Owner.Nickname)).ThenBy(w => MaybeScalar(w.Owner, () => w.Owner.SquadId)).Select(w => w.Name), + assertOrder: true); + } + + [ConditionalFact] + public virtual void Order_by_entity_qsre_with_other_orderbys() + { + AssertQuery( + ws => ws.OrderBy(w => w.IsAutomatic).ThenByDescending(w => w.Owner).ThenBy(w => w.SynergyWith).ThenBy(w => w.Name), + ws => ws + .OrderBy(w => w.IsAutomatic) + .ThenByDescending(w => Maybe(w.Owner, () => w.Owner.Nickname)) + .ThenByDescending(w => MaybeScalar(w.Owner, () => w.Owner.SquadId)) + .ThenBy(w => MaybeScalar(w.SynergyWith, () => w.SynergyWith.Id)) + .ThenBy(w => w.Name)); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys() + { + AssertQuery( + ws => from w1 in ws + join w2 in ws on w1 equals w2 + select new { Name1 = w1.Name, Name2 = w2.Name }, + elementSorter: e => e.Name1 + " " + e.Name2); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_composite_key() + { + AssertQuery( + gs => from g1 in gs + join g2 in gs on g1 equals g2 + select new { GearName1 = g1.FullName, GearName2 = g2.FullName }, + elementSorter: e => e.GearName1 + " " + e.GearName2); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_inheritance() + { + AssertQuery( + gs => from g in gs + join o in gs.OfType() on g equals o + select new { GearName = g.FullName, OfficerName = o.FullName }, + elementSorter: e => e.GearName + " " + e.OfficerName); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_outer_key_is_navigation() + { + AssertQuery( + ws => from w1 in ws + join w2 in ws on w1.SynergyWith equals w2 + select new { Name1 = w1.Name, Name2 = w2.Name }, + elementSorter: e => e.Name1 + " " + e.Name2); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_inner_key_is_navigation() + { + AssertQuery( + (cs, gs) => + from c in cs + join g in gs on c equals g.AssignedCity + select new { CityName = c.Name, GearNickname = g.Nickname }, + e => e.CityName + " " + e.GearNickname); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_inner_key_is_navigation_composite_key() + { + AssertQuery( + (gs, ts) => + from g in gs + join t in ts.Where(tt => tt.Note == "Cole's Tag" || tt.Note == "Dom's Tag") on g equals t.Gear + select new { g.Nickname, t.Note }, + elementSorter: e => e.Nickname + " " + e.Note); + } + + [ConditionalFact] + public virtual void Join_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + AssertQuery( + (ss, ws) => + from s in ss + join w in ws.Where(ww => ww.IsAutomatic) on s equals w.Owner.Squad + select new { SquadName = s.Name, WeaponName = w.Name }, + elementSorter: e => e.SquadName + " " + e.WeaponName); + } + + [ConditionalFact] + public virtual void GroupJoin_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + AssertQuery( + (ss, ws) => + from s in ss + join w in ws on s equals w.Owner.Squad into grouping + from w in grouping.DefaultIfEmpty() + select new { SquadName = s.Name, WeaponName = w.Name }, + (ss, ws) => + from s in ss + join w in ws on s equals Maybe(w.Owner, () => w.Owner.Squad) into grouping + from w in grouping.DefaultIfEmpty() + select new { SquadName = s.Name, WeaponName = Maybe(w, () => w.Name) }, + elementSorter: e => e.SquadName + " " + e.WeaponName); + } + + [ConditionalFact] + public virtual void Include_with_group_by_on_entity_qsre() + { + using (var ctx = CreateContext()) + { + var query = ctx.Squads.Include(s => s.Members).GroupBy(s => s); + var results = query.ToList(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Members.Count > 0); + } + } + } + } + + [ConditionalFact] + public virtual void Include_with_group_by_on_entity_qsre_with_composite_key() + { + using (var ctx = CreateContext()) + { + var query = ctx.Gears.Include(g => g.Weapons).GroupBy(g => g); + var results = query.ToList(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Weapons.Count > 0); + } + } + } + } + + [ConditionalFact] + public virtual void Include_with_group_by_on_entity_navigation() + { + using (var ctx = CreateContext()) + { + var query = ctx.Factions.OfType().Include(lh => lh.Leaders).GroupBy(lh => lh.Commander.DefeatedBy); + var results = query.ToList(); + + foreach (var result in results) + { + foreach (var grouping in result) + { + Assert.True(grouping.Leaders.Count > 0); + } + } + } + } + // Remember to add any new tests to Async version of this test class protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); diff --git a/src/EFCore/Query/EntityQueryModelVisitor.cs b/src/EFCore/Query/EntityQueryModelVisitor.cs index 35a16de4616..944082d1679 100644 --- a/src/EFCore/Query/EntityQueryModelVisitor.cs +++ b/src/EFCore/Query/EntityQueryModelVisitor.cs @@ -17,6 +17,7 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -313,6 +314,8 @@ protected virtual void OptimizeQueryModel( navigationRewritingExpressionVisitor.Rewrite(queryModel, parentQueryModel: null); + new EntityQsreToKeyAccessConvertingVisitor(QueryCompilationContext).VisitQueryModel(queryModel); + includeCompiler.RewriteCollectionQueries(); includeCompiler.LogIgnoredIncludes(); @@ -482,6 +485,124 @@ protected override void VisitResultOperators(ObservableCollection(this).Visit); + + base.VisitQueryModel(queryModel); + } + + public override void VisitOrderByClause(OrderByClause orderByClause, QueryModel queryModel, int index) + { + var newOrderings = new List(); + + var changed = false; + foreach (var ordering in orderByClause.Orderings) + { + if (ordering.Expression is QuerySourceReferenceExpression qsre + && TryGetEntityPrimaryKeys(qsre.ReferencedQuerySource, out var keys)) + { + changed = true; + foreach (var key in keys) + { + newOrderings.Add(new Ordering(qsre.CreateEFPropertyExpression(key), ordering.OrderingDirection)); + } + } + else + { + newOrderings.Add(ordering); + } + } + + if (changed) + { + orderByClause.Orderings.Clear(); + foreach (var newOrdering in newOrderings) + { + orderByClause.Orderings.Add(newOrdering); + } + } + + base.VisitOrderByClause(orderByClause, queryModel, index); + } + + public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index) + { +#pragma warning disable IDE0019 // Use pattern matching + var outerKeyQsre = joinClause.OuterKeySelector as QuerySourceReferenceExpression; +#pragma warning restore IDE0019 // Use pattern matching + + var innerKeyQsre = joinClause.InnerKeySelector as QuerySourceReferenceExpression; + var innerKeySubquery = joinClause.InnerKeySelector as SubQueryExpression; + + // if inner key is a subquery (i.e. it contains a navigation) we can only perform the optimization if the key is not composite + // otherwise we would have to clone the entire subquery for each key property in order be able to fully translate the key selector + if (outerKeyQsre != null + && TryGetEntityPrimaryKeys(outerKeyQsre.ReferencedQuerySource, out var keyProperties) + && (innerKeyQsre != null || (keyProperties.Count == 1 && IsNavigationSubquery(innerKeySubquery)))) + { + joinClause.OuterKeySelector = outerKeyQsre.CreateKeyAccessExpression(keyProperties); + + if (innerKeyQsre != null) + { + joinClause.InnerKeySelector = innerKeyQsre.CreateKeyAccessExpression(keyProperties); + } + else + { + var innerSubquerySelectorQsre = (QuerySourceReferenceExpression)innerKeySubquery.QueryModel.SelectClause.Selector; + innerKeySubquery.QueryModel.SelectClause.Selector = innerSubquerySelectorQsre.CreateKeyAccessExpression(keyProperties); + joinClause.InnerKeySelector = new SubQueryExpression(innerKeySubquery.QueryModel); + } + } + + base.VisitJoinClause(joinClause, queryModel, index); + } + + public override void VisitGroupJoinClause(GroupJoinClause groupJoinClause, QueryModel queryModel, int index) + { + VisitJoinClause(groupJoinClause.JoinClause, queryModel, index); + + base.VisitGroupJoinClause(groupJoinClause, queryModel, index); + } + + private bool IsNavigationSubquery(SubQueryExpression subQueryExpression) + => subQueryExpression != null + ? subQueryExpression.QueryModel.BodyClauses.OfType().Where(c => c.Predicate is NullSafeEqualExpression).Any() + && subQueryExpression.QueryModel.SelectClause.Selector is QuerySourceReferenceExpression selectorQsre + && subQueryExpression.QueryModel.ResultOperators.Count == 1 + && subQueryExpression.QueryModel.ResultOperators[0] is FirstResultOperator firstResultOperator + && firstResultOperator.ReturnDefaultWhenEmpty + : false; + + private bool TryGetEntityPrimaryKeys(IQuerySource querySource, out IReadOnlyList keyProperties) + { + var entityType + = _queryCompilationContext.FindEntityType(querySource) + ?? _queryCompilationContext.Model + .FindEntityType(querySource.ItemType); + + if (entityType != null) + { + keyProperties = entityType.FindPrimaryKey().Properties; + + return true; + } + + keyProperties = new List(); + + return false; + } + } + /// /// Converts the results of the query from a single result to a series of results. /// diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs index b0e96569da6..47dbac66b8e 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs @@ -722,7 +722,7 @@ private Expression RewriteNavigationProperties( { var translated = CreateSubqueryForNavigations( outerQuerySourceReferenceExpression, - navigations, + properties, propertyCreator); return translated; @@ -884,9 +884,10 @@ var parentDeclaringExpression private Expression CreateSubqueryForNavigations( Expression outerQuerySourceReferenceExpression, - ICollection navigations, + IReadOnlyList properties, Func propertyCreator) { + var navigations = properties.OfType().ToList(); var firstNavigation = navigations.First(); var targetEntityType = firstNavigation.GetTargetType(); @@ -930,7 +931,12 @@ var mainFromClause selectClauseExpression, (current, navigation) => Expression.Property(current, navigation.Name)); - subQueryModel.SelectClause = new SelectClause(propertyCreator(selectClauseExpression)); + subQueryModel.SelectClause = new SelectClause(selectClauseExpression); + + if (properties.Count > navigations.Count) + { + subQueryModel.SelectClause = new SelectClause(propertyCreator(subQueryModel.SelectClause.Selector)); + } if (navigations.Count > 1) { diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs index 02d16c43a47..0260a81a3a7 100644 --- a/src/EFCore/Query/QueryCompilationContext.cs +++ b/src/EFCore/Query/QueryCompilationContext.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -513,10 +514,14 @@ var requiresMaterializationExpressionVisitor var setResultOperatorsCompensatingVisitor = new SetResultOperatorsCompensatingVisitor(this); setResultOperatorsCompensatingVisitor.VisitQueryModel(queryModel); + var subQueryInJoinInnerKeySelectorCompensatingVisitor = new SubQueryInJoinInnerKeySelectorCompensatingVisitor(); + subQueryInJoinInnerKeySelectorCompensatingVisitor.VisitQueryModel(queryModel); + _querySourcesRequiringMaterialization.UnionWith( querySourcesRequiringMaterialization .Concat(groupJoinCompensatingVisitor.QuerySources) - .Concat(blockedMemberPushdownCompensatingVisitor.QuerySources)); + .Concat(blockedMemberPushdownCompensatingVisitor.QuerySources) + .Concat(subQueryInJoinInnerKeySelectorCompensatingVisitor.QuerySources)); } } @@ -632,6 +637,51 @@ protected override void VisitResultOperators(ObservableCollection QuerySources { get; } = new HashSet(); + + public override void VisitQueryModel(QueryModel queryModel) + { + queryModel.TransformExpressions(new TransformingQueryModelExpressionVisitor(this).Visit); + + base.VisitQueryModel(queryModel); + } + + public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index) + { + if (joinClause.InnerKeySelector is SubQueryExpression innerKeySubquery + && IsNavigationSubquery(innerKeySubquery)) + { + MarkForMaterialization(((QuerySourceReferenceExpression)innerKeySubquery.QueryModel.SelectClause.Selector).ReferencedQuerySource); + } + + base.VisitJoinClause(joinClause, queryModel, index); + } + + public override void VisitGroupJoinClause(GroupJoinClause groupJoinClause, QueryModel queryModel, int index) + { + VisitJoinClause(groupJoinClause.JoinClause, queryModel, index); + + base.VisitGroupJoinClause(groupJoinClause, queryModel, index); + } + + private bool IsNavigationSubquery(SubQueryExpression subQueryExpression) + => subQueryExpression != null + ? subQueryExpression.QueryModel.BodyClauses.OfType().Where(c => c.Predicate is NullSafeEqualExpression).Any() + && subQueryExpression.QueryModel.SelectClause.Selector is QuerySourceReferenceExpression selectorQsre + && subQueryExpression.QueryModel.ResultOperators.Count == 1 + && subQueryExpression.QueryModel.ResultOperators[0] is FirstResultOperator firstResultOperator + && firstResultOperator.ReturnDefaultWhenEmpty + : false; + + private void MarkForMaterialization(IQuerySource querySource) + { + RequiresMaterializationExpressionVisitor.HandleUnderlyingQuerySources(querySource, MarkForMaterialization); + QuerySources.Add(querySource); + } + } + /// /// Determine whether or not a query source requires materialization. /// diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index deba6502ede..ed6b0308ec8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -6131,6 +6131,264 @@ WHERE [t.Gear].[Discriminator] IN (N'Officer', N'Gear') ) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId])"); } + public override void Order_by_entity_qsre() + { + base.Order_by_entity_qsre(); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +LEFT JOIN [Cities] AS [g.AssignedCity] ON [g].[AssignedCityName] = [g.AssignedCity].[Name] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') +ORDER BY [g.AssignedCity].[Name], [g].[Nickname] DESC"); + } + + public override void Order_by_entity_qsre_with_inheritance() + { + base.Order_by_entity_qsre_with_inheritance(); + + AssertSql( + @"SELECT [lc].[Name] +FROM [LocustLeaders] AS [lc] +INNER JOIN [LocustHighCommands] AS [lc.HighCommand] ON [lc].[HighCommandId] = [lc.HighCommand].[Id] +WHERE [lc].[Discriminator] = N'LocustCommander' +ORDER BY [lc.HighCommand].[Id], [lc].[Name]"); + } + + public override void Order_by_entity_qsre_composite_key() + { + base.Order_by_entity_qsre_composite_key(); + + AssertSql( + @"SELECT [w].[Name] +FROM [Weapons] AS [w] +LEFT JOIN ( + SELECT [w.Owner].* + FROM [Gears] AS [w.Owner] + WHERE [w.Owner].[Discriminator] IN (N'Officer', N'Gear') +) AS [t] ON [w].[OwnerFullName] = [t].[FullName] +ORDER BY [t].[Nickname], [t].[SquadId]"); + } + + public override void Order_by_entity_qsre_with_other_orderbys() + { + base.Order_by_entity_qsre_with_other_orderbys(); + + AssertSql( + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +LEFT JOIN [Weapons] AS [w.SynergyWith] ON [w].[SynergyWithId] = [w.SynergyWith].[Id] +LEFT JOIN ( + SELECT [w.Owner].* + FROM [Gears] AS [w.Owner] + WHERE [w.Owner].[Discriminator] IN (N'Officer', N'Gear') +) AS [t] ON [w].[OwnerFullName] = [t].[FullName] +ORDER BY [w].[IsAutomatic], [t].[Nickname] DESC, [t].[SquadId] DESC, [w.SynergyWith].[Id], [w].[Name]"); + } + + public override void Join_on_entity_qsre_keys() + { + base.Join_on_entity_qsre_keys(); + + AssertSql( + @"SELECT [w1].[Name] AS [Name1], [w2].[Name] AS [Name2] +FROM [Weapons] AS [w1] +INNER JOIN [Weapons] AS [w2] ON [w1].[Id] = [w2].[Id]"); + } + + public override void Join_on_entity_qsre_keys_composite_key() + { + base.Join_on_entity_qsre_keys_composite_key(); + + AssertSql( + @"SELECT [g1].[FullName] AS [GearName1], [g2].[FullName] AS [GearName2] +FROM [Gears] AS [g1] +INNER JOIN [Gears] AS [g2] ON ([g1].[Nickname] = [g2].[Nickname]) AND ([g1].[SquadId] = [g2].[SquadId]) +WHERE [g1].[Discriminator] IN (N'Officer', N'Gear') AND [g2].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Join_on_entity_qsre_keys_inheritance() + { + base.Join_on_entity_qsre_keys_inheritance(); + + AssertSql( + @"SELECT [g].[FullName] AS [GearName], [o].[FullName] AS [OfficerName] +FROM [Gears] AS [g] +INNER JOIN [Gears] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([o].[Discriminator] = N'Officer')"); + } + + public override void Join_on_entity_qsre_keys_outer_key_is_navigation() + { + base.Join_on_entity_qsre_keys_outer_key_is_navigation(); + + AssertSql( + @"SELECT [w1].[Name] AS [Name1], [w2].[Name] AS [Name2] +FROM [Weapons] AS [w1] +LEFT JOIN [Weapons] AS [w1.SynergyWith] ON [w1].[SynergyWithId] = [w1.SynergyWith].[Id] +INNER JOIN [Weapons] AS [w2] ON [w1.SynergyWith].[Id] = [w2].[Id]"); + } + + public override void Join_on_entity_qsre_keys_inner_key_is_navigation() + { + base.Join_on_entity_qsre_keys_inner_key_is_navigation(); + + AssertSql( + @"SELECT [c].[Name] AS [CityName], [g].[Nickname] AS [GearNickname] +FROM [Cities] AS [c] +INNER JOIN [Gears] AS [g] ON [c].[Name] = ( + SELECT TOP(1) [subQuery0].[Name] + FROM [Cities] AS [subQuery0] + WHERE [subQuery0].[Name] = [g].[AssignedCityName] +) +WHERE [g].[Discriminator] IN (N'Officer', N'Gear')"); + } + + public override void Join_on_entity_qsre_keys_inner_key_is_navigation_composite_key() + { + base.Join_on_entity_qsre_keys_inner_key_is_navigation_composite_key(); + + AssertContainsSql( + @"SELECT [t].[GearNickName], [t].[GearSquadId], [t].[Note] +FROM ( + SELECT [tt].* + FROM [Tags] AS [tt] + WHERE [tt].[Note] IN (N'Cole''s Tag', N'Dom''s Tag') +) AS [t]", + // + @"@_outer_GearNickName='Dom' (Size = 450) +@_outer_GearSquadId='1' (Nullable = true) + +SELECT TOP(1) [subQuery].[Nickname], [subQuery].[SquadId], [subQuery].[AssignedCityName], [subQuery].[CityOrBirthName], [subQuery].[Discriminator], [subQuery].[FullName], [subQuery].[HasSoulPatch], [subQuery].[LeaderNickname], [subQuery].[LeaderSquadId], [subQuery].[Rank] +FROM [Gears] AS [subQuery] +WHERE [subQuery].[Discriminator] IN (N'Officer', N'Gear') AND (([subQuery].[Nickname] = @_outer_GearNickName) AND ([subQuery].[SquadId] = @_outer_GearSquadId))", + // + @"@_outer_GearNickName='Cole Train' (Size = 450) +@_outer_GearSquadId='1' (Nullable = true) + +SELECT TOP(1) [subQuery].[Nickname], [subQuery].[SquadId], [subQuery].[AssignedCityName], [subQuery].[CityOrBirthName], [subQuery].[Discriminator], [subQuery].[FullName], [subQuery].[HasSoulPatch], [subQuery].[LeaderNickname], [subQuery].[LeaderSquadId], [subQuery].[Rank] +FROM [Gears] AS [subQuery] +WHERE [subQuery].[Discriminator] IN (N'Officer', N'Gear') AND (([subQuery].[Nickname] = @_outer_GearNickName) AND ([subQuery].[SquadId] = @_outer_GearSquadId))", + // + @"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')"); + } + + public override void Join_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + base.Join_on_entity_qsre_keys_inner_key_is_nested_navigation(); + + AssertSql( + @"SELECT [s].[Name] AS [SquadName], [t].[Name] AS [WeaponName] +FROM [Squads] AS [s] +INNER JOIN ( + SELECT [ww].* + FROM [Weapons] AS [ww] + WHERE [ww].[IsAutomatic] = 1 +) AS [t] ON [s].[Id] = ( + SELECT TOP(1) [subQuery.Squad0].[Id] + FROM [Gears] AS [subQuery0] + INNER JOIN [Squads] AS [subQuery.Squad0] ON [subQuery0].[SquadId] = [subQuery.Squad0].[Id] + WHERE [subQuery0].[Discriminator] IN (N'Officer', N'Gear') AND ([subQuery0].[FullName] = [t].[OwnerFullName]) +)"); + } + + public override void GroupJoin_on_entity_qsre_keys_inner_key_is_nested_navigation() + { + base.GroupJoin_on_entity_qsre_keys_inner_key_is_nested_navigation(); + + AssertSql( + @"SELECT [s].[Name] AS [SquadName], [w].[Name] AS [WeaponName] +FROM [Squads] AS [s] +LEFT JOIN [Weapons] AS [w] ON [s].[Id] = ( + SELECT TOP(1) [subQuery.Squad0].[Id] + FROM [Gears] AS [subQuery0] + INNER JOIN [Squads] AS [subQuery.Squad0] ON [subQuery0].[SquadId] = [subQuery.Squad0].[Id] + WHERE [subQuery0].[Discriminator] IN (N'Officer', N'Gear') AND ([subQuery0].[FullName] = [w].[OwnerFullName]) +)"); + } + + public override void Include_with_group_by_on_entity_qsre() + { + base.Include_with_group_by_on_entity_qsre(); + + AssertSql( + @"SELECT [s].[Id], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +ORDER BY [s].[Id]", + // + @"SELECT [s.Members].[Nickname], [s.Members].[SquadId], [s.Members].[AssignedCityName], [s.Members].[CityOrBirthName], [s.Members].[Discriminator], [s.Members].[FullName], [s.Members].[HasSoulPatch], [s.Members].[LeaderNickname], [s.Members].[LeaderSquadId], [s.Members].[Rank] +FROM [Gears] AS [s.Members] +INNER JOIN ( + SELECT [s0].[Id] + FROM [Squads] AS [s0] +) AS [t] ON [s.Members].[SquadId] = [t].[Id] +WHERE [s.Members].[Discriminator] IN (N'Officer', N'Gear') +ORDER BY [t].[Id]"); + } + + public override void Include_with_group_by_on_entity_qsre_with_composite_key() + { + base.Include_with_group_by_on_entity_qsre_with_composite_key(); + + 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') +ORDER BY [g].[Nickname], [g].[SquadId], [g].[FullName]", + // + @"SELECT [g.Weapons].[Id], [g.Weapons].[AmmunitionType], [g.Weapons].[IsAutomatic], [g.Weapons].[Name], [g.Weapons].[OwnerFullName], [g.Weapons].[SynergyWithId] +FROM [Weapons] AS [g.Weapons] +INNER JOIN ( + SELECT [g0].[FullName], [g0].[Nickname], [g0].[SquadId] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Officer', N'Gear') +) AS [t] ON [g.Weapons].[OwnerFullName] = [t].[FullName] +ORDER BY [t].[Nickname], [t].[SquadId], [t].[FullName]"); + } + + public override void Include_with_group_by_on_entity_navigation() + { + base.Include_with_group_by_on_entity_navigation(); + + AssertSql( + @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOrBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank] +FROM [Factions] AS [f] +LEFT JOIN ( + SELECT [l.Commander].* + FROM [LocustLeaders] AS [l.Commander] + WHERE [l.Commander].[Discriminator] = N'LocustCommander' +) AS [t] ON [f].[CommanderName] = [t].[Name] +LEFT JOIN ( + SELECT [l.Commander.DefeatedBy].* + FROM [Gears] AS [l.Commander.DefeatedBy] + WHERE [l.Commander.DefeatedBy].[Discriminator] IN (N'Officer', N'Gear') +) AS [t0] ON ([t].[DefeatedByNickname] = [t0].[Nickname]) AND ([t].[DefeatedBySquadId] = [t0].[SquadId]) +WHERE [f].[Discriminator] = N'LocustHorde' +ORDER BY [t0].[Nickname], [t0].[SquadId], [f].[Id]", + // + @"SELECT [l.Leaders].[Name], [l.Leaders].[Discriminator], [l.Leaders].[LocustHordeId], [l.Leaders].[ThreatLevel], [l.Leaders].[DefeatedByNickname], [l.Leaders].[DefeatedBySquadId], [l.Leaders].[HighCommandId] +FROM [LocustLeaders] AS [l.Leaders] +INNER JOIN ( + SELECT DISTINCT [f0].[Id], [t2].[Nickname], [t2].[SquadId] + FROM [Factions] AS [f0] + LEFT JOIN ( + SELECT [l.Commander0].* + FROM [LocustLeaders] AS [l.Commander0] + WHERE [l.Commander0].[Discriminator] = N'LocustCommander' + ) AS [t1] ON [f0].[CommanderName] = [t1].[Name] + LEFT JOIN ( + SELECT [l.Commander.DefeatedBy0].* + FROM [Gears] AS [l.Commander.DefeatedBy0] + WHERE [l.Commander.DefeatedBy0].[Discriminator] IN (N'Officer', N'Gear') + ) AS [t2] ON ([t1].[DefeatedByNickname] = [t2].[Nickname]) AND ([t1].[DefeatedBySquadId] = [t2].[SquadId]) + WHERE [f0].[Discriminator] = N'LocustHorde' +) AS [t3] ON [l.Leaders].[LocustHordeId] = [t3].[Id] +WHERE [l.Leaders].[Discriminator] IN (N'LocustCommander', N'LocustLeader') +ORDER BY [t3].[Nickname], [t3].[SquadId], [t3].[Id]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);