From f22487abff8c55aaab8b0b7269848fe56514ca25 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 1 Aug 2019 18:52:30 -0700 Subject: [PATCH] Query: Throw exception for cycle in include path when no tracking query Resolves #16225 --- ...ingExpressionVisitor.ExpressionVisitors.cs | 56 ++++++-- .../NavigationExpandingExpressionVisitor.cs | 4 +- .../Query/ComplexNavigationsQueryTestBase.cs | 53 -------- .../ComplexNavigationsWeakQueryTestBase.cs | 4 - .../Query/GearsOfWarQueryTestBase.cs | 73 +++-------- .../Query/GearsOfWarQuerySqlServerTest.cs | 123 ------------------ 6 files changed, 67 insertions(+), 246 deletions(-) diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index 9cf4ed7f7c0..2c932c9b53b 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -288,11 +288,15 @@ private static Expression CreateKeyAccessExpression(Expression target, IReadOnly private class IncludeExpandingExpressionVisitor : ExpandingExpressionVisitor { + private readonly bool _isTracking; + public IncludeExpandingExpressionVisitor( NavigationExpandingExpressionVisitor navigationExpandingExpressionVisitor, - NavigationExpansionExpression source) + NavigationExpansionExpression source, + bool tracking) : base(navigationExpandingExpressionVisitor, source) { + _isTracking = tracking; } public override Expression Visit(Expression expression) @@ -302,7 +306,7 @@ public override Expression Visit(Expression expression) case NavigationTreeExpression navigationTreeExpression: if (navigationTreeExpression.Value is EntityReference entityReference) { - return ExpandIncludes(navigationTreeExpression, entityReference); + return ExpandInclude(navigationTreeExpression, entityReference); } if (navigationTreeExpression.Value is NewExpression newExpression) @@ -315,7 +319,7 @@ public override Expression Visit(Expression expression) break; case OwnedNavigationReference ownedNavigationReference: - return ExpandIncludes(ownedNavigationReference, ownedNavigationReference.EntityReference); + return ExpandInclude(ownedNavigationReference, ownedNavigationReference.EntityReference); } return base.Visit(expression); @@ -350,7 +354,7 @@ protected override Expression VisitNew(NewExpression newExpression) { var argument = newExpression.Arguments[i]; arguments[i] = argument is EntityReference entityReference - ? ExpandIncludes(argument, entityReference) + ? ExpandInclude(argument, entityReference) : Visit(argument); } @@ -375,7 +379,7 @@ private bool ReconstructAnonymousType(Expression currentRoot, NewExpression newE if (argument is EntityReference entityReference) { changed = true; - arguments[i] = ExpandIncludes(newRoot, entityReference); + arguments[i] = ExpandInclude(newRoot, entityReference); } else if (argument is NewExpression innerNewExpression) { @@ -403,7 +407,35 @@ private bool ReconstructAnonymousType(Expression currentRoot, NewExpression newE return changed; } - private Expression ExpandIncludes(Expression root, EntityReference entityReference) + private Expression ExpandInclude(Expression root, EntityReference entityReference) + { + if (!_isTracking) + { + VerifyNoCycles(entityReference.IncludePaths); + } + + return ExpandIncludesHelper(root, entityReference); + } + + private void VerifyNoCycles(IncludeTreeNode includeTreeNode) + { + foreach (var keyValuePair in includeTreeNode) + { + var navigation = keyValuePair.Key; + var referenceIncludeTreeNode = keyValuePair.Value; + var inverseNavigation = navigation.FindInverse(); + if (inverseNavigation != null + && referenceIncludeTreeNode.ContainsKey(inverseNavigation)) + { + throw new InvalidOperationException("There is a cycle in include path in no tracking query" + + $"{navigation.Name}->{inverseNavigation.Name}"); + } + + VerifyNoCycles(referenceIncludeTreeNode); + } + } + + private Expression ExpandIncludesHelper(Expression root, EntityReference entityReference) { var result = root; var convertedRoot = root; @@ -426,7 +458,7 @@ private Expression ExpandIncludes(Expression root, EntityReference entityReferen { var elementType = navigation.ClrType.TryGetSequenceType(); var collectionSelectorParameter = Expression.Parameter(elementType); - var nestedInclude = ExpandIncludes(collectionSelectorParameter, includedEntityReference); + var nestedInclude = ExpandIncludesHelper(collectionSelectorParameter, includedEntityReference); if (nestedInclude is IncludeExpression) { @@ -443,7 +475,7 @@ private Expression ExpandIncludes(Expression root, EntityReference entityReferen } else { - included = ExpandIncludes(included, includedEntityReference); + included = ExpandIncludesHelper(included, includedEntityReference); } } else @@ -453,7 +485,7 @@ private Expression ExpandIncludes(Expression root, EntityReference entityReferen { var navigationTreeExpression = (NavigationTreeExpression)included; var innerEntityReference = (EntityReference)navigationTreeExpression.Value; - included = ExpandIncludes(navigationTreeExpression, innerEntityReference); + included = ExpandIncludesHelper(navigationTreeExpression, innerEntityReference); } } @@ -467,17 +499,19 @@ private Expression ExpandIncludes(Expression root, EntityReference entityReferen private class IncludeApplyingExpressionVisitor : ExpressionVisitor { private readonly NavigationExpandingExpressionVisitor _visitor; + private readonly bool _isTracking; - public IncludeApplyingExpressionVisitor(NavigationExpandingExpressionVisitor visitor) + public IncludeApplyingExpressionVisitor(NavigationExpandingExpressionVisitor visitor, bool tracking) { _visitor = visitor; + _isTracking = tracking; } public override Expression Visit(Expression expression) { if (expression is NavigationExpansionExpression navigationExpansionExpression) { - var innerVisitor = new IncludeExpandingExpressionVisitor(_visitor, navigationExpansionExpression); + var innerVisitor = new IncludeExpandingExpressionVisitor(_visitor, navigationExpansionExpression, _isTracking); var pendingSelector = innerVisitor.Visit(navigationExpansionExpression.PendingSelector); pendingSelector = _visitor.Visit(pendingSelector); pendingSelector = Visit(pendingSelector); diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index cd9bcfcbf51..1309fd36d6d 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -23,6 +23,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal public partial class NavigationExpandingExpressionVisitor : ExpressionVisitor { private readonly IModel _model; + private readonly bool _isTracking; private readonly PendingSelectorExpandingExpressionVisitor _pendingSelectorExpandingExpressionVisitor; private readonly SubqueryMemberPushdownExpressionVisitor _subqueryMemberPushdownExpressionVisitor; private readonly ReducingExpressionVisitor _reducingExpressionVisitor; @@ -34,6 +35,7 @@ public partial class NavigationExpandingExpressionVisitor : ExpressionVisitor public NavigationExpandingExpressionVisitor(QueryCompilationContext queryCompilationContext) { _model = queryCompilationContext.Model; + _isTracking = queryCompilationContext.IsTracking; _pendingSelectorExpandingExpressionVisitor = new PendingSelectorExpandingExpressionVisitor(this); _subqueryMemberPushdownExpressionVisitor = new SubqueryMemberPushdownExpressionVisitor(); _reducingExpressionVisitor = new ReducingExpressionVisitor(); @@ -56,7 +58,7 @@ public virtual Expression Expand(Expression query) { var result = Visit(query); result = _pendingSelectorExpandingExpressionVisitor.Visit(result); - result = new IncludeApplyingExpressionVisitor(this).Visit(result); + result = new IncludeApplyingExpressionVisitor(this, _isTracking).Visit(result); return Reduce(result); } diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 97d9bbb32cb..8356ef3b08a 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -206,21 +206,6 @@ public virtual Task Key_equality_two_conditions_on_same_navigation2(bool isAsync (e, a) => Assert.Equal(e.Id, a.Id)); } - [ConditionalFact] - public virtual void Data_reader_is_closed_correct_number_of_times_for_include_queries_on_optional_navigations() - { - using (var context = CreateContext()) - { - // reader for the last include is not opened because there is no data one level below - we should only try to close connection as many times as we tried to open it - // if we close it too many times, consecutive query will not work - // see issue #1457 for more details - context.LevelOne.Include(e => e.OneToMany_Optional1).ThenInclude(e => e.OneToMany_Optional2) - .ThenInclude(e => e.OneToMany_Optional_Inverse3.OneToMany_Optional2).ToList(); - - context.LevelOne.ToList(); - } - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Multi_level_include_one_to_many_optional_and_one_to_many_optional_produces_valid_sql(bool isAsync) @@ -262,44 +247,6 @@ public virtual Task Multi_level_include_correct_PK_is_chosen_as_the_join_predica elementSorter: e => e.Id); } - [ConditionalFact] - public virtual void Multi_level_include_reads_key_values_from_data_reader_rather_than_incorrect_reader_deep_into_the_stack() - { - using (var context = CreateContext()) - { - // #1433 - context.LevelOne.Include(e => e.OneToMany_Optional1).ToList(); - context.LevelOne.Include(e => e.OneToMany_Optional_Self1).ToList(); - - // #1478 - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToMany_Optional_Self_Inverse1.OneToOne_Optional_FK1).ToList(); - - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToMany_Optional_Self_Inverse1.OneToOne_Optional_PK1).ToList(); - - // #1487 - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToOne_Optional_PK1.OneToOne_Optional_FK2).ToList(); - - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToOne_Optional_PK1.OneToOne_Optional_FK_Inverse2).ToList(); - - // #1488 - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToOne_Optional_PK1.OneToOne_Required_FK2).ToList(); - - context.LevelOne - .Include(e => e.OneToMany_Optional1) - .ThenInclude(e => e.OneToMany_Optional_Inverse2.OneToOne_Optional_PK1.OneToOne_Required_FK_Inverse2).ToList(); - } - } - [ConditionalFact] public virtual void Multi_level_include_with_short_circuiting() { diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs index aaa1498db31..c2d7175855e 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs @@ -67,10 +67,6 @@ public override Task Multiple_complex_includes_self_ref(bool isAsync) return Task.CompletedTask; } - public override void Multi_level_include_reads_key_values_from_data_reader_rather_than_incorrect_reader_deep_into_the_stack() - { - } - public override Task Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties(bool isAsync) { return Task.CompletedTask; diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index f12c5751331..3bd06e0708c 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -82,21 +82,15 @@ public virtual Task ToString_guid_property_projection(bool isAsync) }); } - [ConditionalTheory(Skip = "Issue#16225")] - [MemberData(nameof(IsAsyncData))] - public virtual Task Include_multiple_one_to_one_and_one_to_many_self_reference(bool isAsync) + [ConditionalFact] + public virtual void Include_multiple_one_to_one_and_one_to_many_self_reference() { - var expectedIncludes = new List + using (var context = CreateContext()) { - new ExpectedInclude(w => w.Owner, "Owner"), - new ExpectedInclude(g => g.Weapons, "Weapons", "Owner"), - new ExpectedInclude(o => o.Weapons, "Weapons", "Owner") - }; + Assert.Throws( + () => context.Weapons.Include(w => w.Owner.Weapons).ToList()); - return AssertIncludeQuery( - isAsync, - ws => ws.Include(w => w.Owner.Weapons), - expectedIncludes); + } } [ConditionalTheory] @@ -116,22 +110,14 @@ public virtual Task Include_multiple_one_to_one_optional_and_one_to_one_required expectedIncludes); } - [ConditionalTheory(Skip = "Issue#16225")] - [MemberData(nameof(IsAsyncData))] - public virtual Task Include_multiple_one_to_one_and_one_to_one_and_one_to_many(bool isAsync) + [ConditionalFact] + public virtual void Include_multiple_one_to_one_and_one_to_one_and_one_to_many() { - var expectedIncludes = new List + using (var context = CreateContext()) { - new ExpectedInclude(t => t.Gear, "Gear"), - new ExpectedInclude(g => g.Squad, "Squad", "Gear"), - new ExpectedInclude(o => o.Squad, "Squad", "Gear"), - new ExpectedInclude(s => s.Members, "Members", "Gear.Squad") - }; - - return AssertIncludeQuery( - isAsync, - ts => ts.Include(t => t.Gear.Squad.Members), - expectedIncludes); + Assert.Throws( + () => context.Tags.Include(t => t.Gear.Squad.Members).ToList()); + } } [ConditionalTheory] @@ -184,39 +170,18 @@ public virtual Task Include_using_alternate_key(bool isAsync) expectedIncludes); } - [ConditionalTheory(Skip = "Issue#16225")] - [MemberData(nameof(IsAsyncData))] - public virtual Task Include_multiple_include_then_include(bool isAsync) + [ConditionalFact] + public virtual void Include_multiple_include_then_include() { - var expectedIncludes = new List + using (var context = CreateContext()) { - new ExpectedInclude(g => g.AssignedCity, "AssignedCity"), - new ExpectedInclude(o => o.AssignedCity, "AssignedCity"), - new ExpectedInclude(c => c.BornGears, "BornGears", "AssignedCity"), - new ExpectedInclude(g => g.Tag, "Tag", "AssignedCity.BornGears"), - new ExpectedInclude(o => o.Tag, "Tag", "AssignedCity.BornGears"), - new ExpectedInclude(c => c.StationedGears, "StationedGears", "AssignedCity"), - new ExpectedInclude(g => g.Tag, "Tag", "AssignedCity.StationedGears"), - new ExpectedInclude(o => o.Tag, "Tag", "AssignedCity.StationedGears"), - new ExpectedInclude(g => g.CityOfBirth, "CityOfBirth"), - new ExpectedInclude(o => o.CityOfBirth, "CityOfBirth"), - new ExpectedInclude(c => c.BornGears, "BornGears", "CityOfBirth"), - new ExpectedInclude(g => g.Tag, "Tag", "CityOfBirth.BornGears"), - new ExpectedInclude(o => o.Tag, "Tag", "CityOfBirth.BornGears"), - new ExpectedInclude(c => c.StationedGears, "StationedGears", "CityOfBirth"), - new ExpectedInclude(g => g.Tag, "Tag", "CityOfBirth.StationedGears"), - new ExpectedInclude(o => o.Tag, "Tag", "CityOfBirth.StationedGears") - }; - - return AssertIncludeQuery( - isAsync, - gs => gs.Include(g => g.AssignedCity.BornGears).ThenInclude(g => g.Tag) + Assert.Throws( + () => context.Gears.Include(g => g.AssignedCity.BornGears).ThenInclude(g => g.Tag) .Include(g => g.AssignedCity.StationedGears).ThenInclude(g => g.Tag) .Include(g => g.CityOfBirth.BornGears).ThenInclude(g => g.Tag) .Include(g => g.CityOfBirth.StationedGears).ThenInclude(g => g.Tag) - .OrderBy(g => g.Nickname), - expectedIncludes, - assertOrder: true); + .OrderBy(g => g.Nickname).ToList()); + } } [ConditionalTheory] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index d2f9414e2fa..f39b44fd8dc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -46,65 +46,6 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') ORDER BY [t].[Id], [w].[Id]"); } - public override async Task Include_multiple_one_to_one_and_one_to_many_self_reference(bool isAsync) - { - await base.Include_multiple_one_to_one_and_one_to_many_self_reference(isAsync); - - AssertSql( - @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOrBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] -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].[FullName]", - // - @"SELECT [w.Owner.Weapons].[Id], [w.Owner.Weapons].[AmmunitionType], [w.Owner.Weapons].[IsAutomatic], [w.Owner.Weapons].[Name], [w.Owner.Weapons].[OwnerFullName], [w.Owner.Weapons].[SynergyWithId] -FROM [Weapons] AS [w.Owner.Weapons] -INNER JOIN ( - SELECT DISTINCT [t0].[FullName] - FROM [Weapons] AS [w0] - LEFT JOIN ( - SELECT [w.Owner0].* - FROM [Gears] AS [w.Owner0] - WHERE [w.Owner0].[Discriminator] IN (N'Officer', N'Gear') - ) AS [t0] ON [w0].[OwnerFullName] = [t0].[FullName] -) AS [t1] ON [w.Owner.Weapons].[OwnerFullName] = [t1].[FullName] -ORDER BY [t1].[FullName]"); - } - - public override async Task Include_multiple_one_to_one_and_one_to_one_and_one_to_many(bool isAsync) - { - await base.Include_multiple_one_to_one_and_one_to_one_and_one_to_many(isAsync); - - AssertSql( - @"SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[Note], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOrBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t.Gear.Squad].[Id], [t.Gear.Squad].[InternalNumber], [t.Gear.Squad].[Name] -FROM [Tags] AS [t] -LEFT JOIN ( - SELECT [t.Gear].* - FROM [Gears] AS [t.Gear] - WHERE [t.Gear].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId]) -LEFT JOIN [Squads] AS [t.Gear.Squad] ON [t0].[SquadId] = [t.Gear.Squad].[Id] -ORDER BY [t.Gear.Squad].[Id]", - // - @"SELECT [t.Gear.Squad.Members].[Nickname], [t.Gear.Squad.Members].[SquadId], [t.Gear.Squad.Members].[AssignedCityName], [t.Gear.Squad.Members].[CityOrBirthName], [t.Gear.Squad.Members].[Discriminator], [t.Gear.Squad.Members].[FullName], [t.Gear.Squad.Members].[HasSoulPatch], [t.Gear.Squad.Members].[LeaderNickname], [t.Gear.Squad.Members].[LeaderSquadId], [t.Gear.Squad.Members].[Rank] -FROM [Gears] AS [t.Gear.Squad.Members] -INNER JOIN ( - SELECT DISTINCT [t.Gear.Squad0].[Id] - FROM [Tags] AS [t1] - LEFT JOIN ( - SELECT [t.Gear0].* - FROM [Gears] AS [t.Gear0] - WHERE [t.Gear0].[Discriminator] IN (N'Officer', N'Gear') - ) AS [t2] ON ([t1].[GearNickName] = [t2].[Nickname]) AND ([t1].[GearSquadId] = [t2].[SquadId]) - LEFT JOIN [Squads] AS [t.Gear.Squad0] ON [t2].[SquadId] = [t.Gear.Squad0].[Id] -) AS [t3] ON [t.Gear.Squad.Members].[SquadId] = [t3].[Id] -WHERE [t.Gear.Squad.Members].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t3].[Id]"); - } - public override async Task Include_multiple_one_to_one_optional_and_one_to_one_required(bool isAsync) { await base.Include_multiple_one_to_one_optional_and_one_to_one_required(isAsync); @@ -166,70 +107,6 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Nickname] = N'Marcu ORDER BY [g].[Nickname], [g].[SquadId], [w].[Id]"); } - public override async Task Include_multiple_include_then_include(bool isAsync) - { - await base.Include_multiple_include_then_include(isAsync); - - AssertSql( - @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location], [g.CityOfBirth].[Nation], [g.AssignedCity].[Name], [g.AssignedCity].[Location], [g.AssignedCity].[Nation] -FROM [Gears] AS [g] -INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOrBirthName] = [g.CityOfBirth].[Name] -LEFT JOIN [Cities] AS [g.AssignedCity] ON [g].[AssignedCityName] = [g.AssignedCity].[Name] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [g].[Nickname], [g.AssignedCity].[Name], [g.CityOfBirth].[Name]", - // - @"SELECT [g.AssignedCity.BornGears].[Nickname], [g.AssignedCity.BornGears].[SquadId], [g.AssignedCity.BornGears].[AssignedCityName], [g.AssignedCity.BornGears].[CityOrBirthName], [g.AssignedCity.BornGears].[Discriminator], [g.AssignedCity.BornGears].[FullName], [g.AssignedCity.BornGears].[HasSoulPatch], [g.AssignedCity.BornGears].[LeaderNickname], [g.AssignedCity.BornGears].[LeaderSquadId], [g.AssignedCity.BornGears].[Rank], [g.Tag].[Id], [g.Tag].[GearNickName], [g.Tag].[GearSquadId], [g.Tag].[Note] -FROM [Gears] AS [g.AssignedCity.BornGears] -LEFT JOIN [Tags] AS [g.Tag] ON ([g.AssignedCity.BornGears].[Nickname] = [g.Tag].[GearNickName]) AND ([g.AssignedCity.BornGears].[SquadId] = [g.Tag].[GearSquadId]) -INNER JOIN ( - SELECT DISTINCT [g.AssignedCity0].[Name], [g0].[Nickname] - FROM [Gears] AS [g0] - INNER JOIN [Cities] AS [g.CityOfBirth0] ON [g0].[CityOrBirthName] = [g.CityOfBirth0].[Name] - LEFT JOIN [Cities] AS [g.AssignedCity0] ON [g0].[AssignedCityName] = [g.AssignedCity0].[Name] - WHERE [g0].[Discriminator] IN (N'Officer', N'Gear') -) AS [t] ON [g.AssignedCity.BornGears].[CityOrBirthName] = [t].[Name] -WHERE [g.AssignedCity.BornGears].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t].[Nickname], [t].[Name]", - // - @"SELECT [g.AssignedCity.StationedGears].[Nickname], [g.AssignedCity.StationedGears].[SquadId], [g.AssignedCity.StationedGears].[AssignedCityName], [g.AssignedCity.StationedGears].[CityOrBirthName], [g.AssignedCity.StationedGears].[Discriminator], [g.AssignedCity.StationedGears].[FullName], [g.AssignedCity.StationedGears].[HasSoulPatch], [g.AssignedCity.StationedGears].[LeaderNickname], [g.AssignedCity.StationedGears].[LeaderSquadId], [g.AssignedCity.StationedGears].[Rank], [g.Tag0].[Id], [g.Tag0].[GearNickName], [g.Tag0].[GearSquadId], [g.Tag0].[Note] -FROM [Gears] AS [g.AssignedCity.StationedGears] -LEFT JOIN [Tags] AS [g.Tag0] ON ([g.AssignedCity.StationedGears].[Nickname] = [g.Tag0].[GearNickName]) AND ([g.AssignedCity.StationedGears].[SquadId] = [g.Tag0].[GearSquadId]) -INNER JOIN ( - SELECT DISTINCT [g.AssignedCity1].[Name], [g1].[Nickname] - FROM [Gears] AS [g1] - INNER JOIN [Cities] AS [g.CityOfBirth1] ON [g1].[CityOrBirthName] = [g.CityOfBirth1].[Name] - LEFT JOIN [Cities] AS [g.AssignedCity1] ON [g1].[AssignedCityName] = [g.AssignedCity1].[Name] - WHERE [g1].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON [g.AssignedCity.StationedGears].[AssignedCityName] = [t0].[Name] -WHERE [g.AssignedCity.StationedGears].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t0].[Nickname], [t0].[Name]", - // - @"SELECT [g.CityOfBirth.BornGears].[Nickname], [g.CityOfBirth.BornGears].[SquadId], [g.CityOfBirth.BornGears].[AssignedCityName], [g.CityOfBirth.BornGears].[CityOrBirthName], [g.CityOfBirth.BornGears].[Discriminator], [g.CityOfBirth.BornGears].[FullName], [g.CityOfBirth.BornGears].[HasSoulPatch], [g.CityOfBirth.BornGears].[LeaderNickname], [g.CityOfBirth.BornGears].[LeaderSquadId], [g.CityOfBirth.BornGears].[Rank], [g.Tag1].[Id], [g.Tag1].[GearNickName], [g.Tag1].[GearSquadId], [g.Tag1].[Note] -FROM [Gears] AS [g.CityOfBirth.BornGears] -LEFT JOIN [Tags] AS [g.Tag1] ON ([g.CityOfBirth.BornGears].[Nickname] = [g.Tag1].[GearNickName]) AND ([g.CityOfBirth.BornGears].[SquadId] = [g.Tag1].[GearSquadId]) -INNER JOIN ( - SELECT DISTINCT [g.CityOfBirth2].[Name], [g2].[Nickname], [g.AssignedCity2].[Name] AS [Name0] - FROM [Gears] AS [g2] - INNER JOIN [Cities] AS [g.CityOfBirth2] ON [g2].[CityOrBirthName] = [g.CityOfBirth2].[Name] - LEFT JOIN [Cities] AS [g.AssignedCity2] ON [g2].[AssignedCityName] = [g.AssignedCity2].[Name] - WHERE [g2].[Discriminator] IN (N'Officer', N'Gear') -) AS [t1] ON [g.CityOfBirth.BornGears].[CityOrBirthName] = [t1].[Name] -WHERE [g.CityOfBirth.BornGears].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t1].[Nickname], [t1].[Name0], [t1].[Name]", - // - @"SELECT [g.CityOfBirth.StationedGears].[Nickname], [g.CityOfBirth.StationedGears].[SquadId], [g.CityOfBirth.StationedGears].[AssignedCityName], [g.CityOfBirth.StationedGears].[CityOrBirthName], [g.CityOfBirth.StationedGears].[Discriminator], [g.CityOfBirth.StationedGears].[FullName], [g.CityOfBirth.StationedGears].[HasSoulPatch], [g.CityOfBirth.StationedGears].[LeaderNickname], [g.CityOfBirth.StationedGears].[LeaderSquadId], [g.CityOfBirth.StationedGears].[Rank], [g.Tag2].[Id], [g.Tag2].[GearNickName], [g.Tag2].[GearSquadId], [g.Tag2].[Note] -FROM [Gears] AS [g.CityOfBirth.StationedGears] -LEFT JOIN [Tags] AS [g.Tag2] ON ([g.CityOfBirth.StationedGears].[Nickname] = [g.Tag2].[GearNickName]) AND ([g.CityOfBirth.StationedGears].[SquadId] = [g.Tag2].[GearSquadId]) -INNER JOIN ( - SELECT DISTINCT [g.CityOfBirth3].[Name], [g3].[Nickname], [g.AssignedCity3].[Name] AS [Name0] - FROM [Gears] AS [g3] - INNER JOIN [Cities] AS [g.CityOfBirth3] ON [g3].[CityOrBirthName] = [g.CityOfBirth3].[Name] - LEFT JOIN [Cities] AS [g.AssignedCity3] ON [g3].[AssignedCityName] = [g.AssignedCity3].[Name] - WHERE [g3].[Discriminator] IN (N'Officer', N'Gear') -) AS [t2] ON [g.CityOfBirth.StationedGears].[AssignedCityName] = [t2].[Name] -WHERE [g.CityOfBirth.StationedGears].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t2].[Nickname], [t2].[Name0], [t2].[Name]"); - } public override async Task Include_navigation_on_derived_type(bool isAsync) {