From d00b4ec496b84cb077251a50eeeb0db3e34baf58 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 8 Dec 2020 18:32:21 -0800 Subject: [PATCH] Query: Implement optional dependent conditions An optional dependent considered present If all non-nullable non-PK properties have a non-null value, or If all non-nullable non-PK properties are shared with principal then at least one nullable non shared property has a non-null value If there are no non-shared properties then it is always present and act like a required dependent Resolves #23564 Resolves #21488 Resolves #23198 --- .../Query/RelationalEntityShaperExpression.cs | 11 +-- ...lationalSqlTranslatingExpressionVisitor.cs | 91 ++++++++++++++----- .../Query/SqlExpressionFactory.cs | 91 +++++++++---------- .../TableSplittingTestBase.cs | 18 ++++ .../GraphUpdates/GraphUpdatesTestBase.cs | 7 -- .../GraphUpdates/GraphUpdatesSqlServerTest.cs | 2 + ...NavigationsSharedTypeQuerySqlServerTest.cs | 46 +++++----- .../Query/OwnedQuerySqlServerTest.cs | 4 +- .../Query/QueryBugsTest.cs | 75 +++++++++++++++ .../TPTTableSplittingSqlServerTest.cs | 48 +++++++++- .../TableSplittingSqlServerTest.cs | 36 +++++++- 11 files changed, 311 insertions(+), 118 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs index 035b5353a64..2c52bbd30d7 100644 --- a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs +++ b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs @@ -120,12 +120,11 @@ protected override LambdaExpression GenerateMaterializationCondition(IEntityType .Aggregate((a, b) => AndAlso(a, b)); } - var allNonSharedProperties = GetNonSharedProperties(table, entityType); - if (allNonSharedProperties.Count != 0 - && allNonSharedProperties.All(p => p.IsNullable)) + var allNonSharedNonPkProperties = GetNonSharedNonPkProperties(table, entityType); + if (allNonSharedNonPkProperties.Count != 0 + && allNonSharedNonPkProperties.All(p => p.IsNullable)) { - var allNonSharedNullableProperties = allNonSharedProperties.Where(p => p.IsNullable).ToList(); - var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNullableProperties + var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNonPkProperties .Select( p => NotEqual( valueBufferParameter.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p), @@ -183,7 +182,7 @@ public override EntityShaperExpression Update(Expression valueBufferExpression) : this; } - private IReadOnlyList GetNonSharedProperties(ITableBase table, IEntityType entityType) + private IReadOnlyList GetNonSharedNonPkProperties(ITableBase table, IEntityType entityType) { var nonSharedProperties = new List(); var principalEntityTypes = new HashSet(); diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 8c4d1705a73..ab9b0e6886c 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1282,36 +1282,35 @@ private bool TryRewriteEntityEquality( { var nonNullEntityReference = (IsNullSqlConstantExpression(left) ? rightEntityReference : leftEntityReference)!; var entityType1 = nonNullEntityReference.EntityType; - - if (entityType1.GetViewOrTableMappings().FirstOrDefault()?.Table.IsOptional(entityType1) == true) + var table = entityType1.GetViewOrTableMappings().FirstOrDefault()?.Table; + if (table?.IsOptional(entityType1) == true) { + Expression? condition = null; // Optional dependent sharing table var requiredNonPkProperties = entityType1.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList(); if (requiredNonPkProperties.Count > 0) { - result = Visit( - requiredNonPkProperties.Select( - p => - { - var comparison = Expression.Call( - _objectEqualsMethodInfo, - Expression.Convert(CreatePropertyAccessExpression(nonNullEntityReference, p), typeof(object)), - Expression.Convert(Expression.Constant(null, p.ClrType.MakeNullable()), typeof(object))); - - return nodeType == ExpressionType.Equal - ? (Expression)comparison - : Expression.Not(comparison); - }).Aggregate( - (l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r))); - - return true; + condition = requiredNonPkProperties.Select( + p => + { + var comparison = Expression.Call( + _objectEqualsMethodInfo, + Expression.Convert(CreatePropertyAccessExpression(nonNullEntityReference, p), typeof(object)), + Expression.Convert(Expression.Constant(null, p.ClrType.MakeNullable()), typeof(object))); + + return nodeType == ExpressionType.Equal + ? (Expression)comparison + : Expression.Not(comparison); + }) + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r)); } - var allNonPkProperties = entityType1.GetProperties().Where(p => !p.IsPrimaryKey()).ToList(); - if (allNonPkProperties.Count > 0) + var allNonSharedNonPkProperties = GetNonSharedNonPkProperties(table, entityType1); + if (allNonSharedNonPkProperties.Count != 0 + && allNonSharedNonPkProperties.All(p => p.IsNullable)) { - result = Visit( - allNonPkProperties.Select( + var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNonPkProperties + .Select( p => { var comparison = Expression.Call( @@ -1322,9 +1321,19 @@ private bool TryRewriteEntityEquality( return nodeType == ExpressionType.Equal ? (Expression)comparison : Expression.Not(comparison); - }).Aggregate( - (l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r))); + }) + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r)); + + condition = condition == null + ? atLeastOneNonNullValueInNullablePropertyCondition + : nodeType == ExpressionType.Equal + ? Expression.OrElse(condition, atLeastOneNonNullValueInNullablePropertyCondition) + : Expression.AndAlso(condition, atLeastOneNonNullValueInNullablePropertyCondition); + } + if (condition != null) + { + result = Visit(condition); return true; } @@ -1411,6 +1420,40 @@ private bool TryRewriteEntityEquality( return true; } + private IReadOnlyList GetNonSharedNonPkProperties(ITableBase table, IEntityType entityType) + { + var nonSharedProperties = new List(); + var principalEntityTypes = new HashSet(); + GetPrincipalEntityTypes(table, entityType, principalEntityTypes); + foreach (var property in entityType.GetProperties()) + { + if (property.IsPrimaryKey()) + { + continue; + } + + var propertyMappings = table.FindColumn(property)!.PropertyMappings; + if (propertyMappings.Count() > 1 + && propertyMappings.Any(pm => principalEntityTypes.Contains(pm.TableMapping.EntityType))) + { + continue; + } + + nonSharedProperties.Add(property); + } + + return nonSharedProperties; + } + + private void GetPrincipalEntityTypes(ITableBase table, IEntityType entityType, HashSet entityTypes) + { + foreach (var linkingFk in table.GetRowInternalForeignKeys(entityType)) + { + entityTypes.Add(linkingFk.PrincipalEntityType); + GetPrincipalEntityTypes(table, linkingFk.PrincipalEntityType, entityTypes); + } + } + private Expression CreatePropertyAccessExpression(Expression target, IProperty property) { switch (target) diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index ebda99e7b00..72d96b86984 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -941,66 +941,63 @@ private void AddOptionalDependentConditions( ITableBase table) { SqlExpression? predicate = null; + var entityProjectionExpression = GetMappedEntityProjectionExpression(selectExpression); var requiredNonPkProperties = entityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()).ToList(); if (requiredNonPkProperties.Count > 0) { - var entityProjectionExpression = GetMappedEntityProjectionExpression(selectExpression); - predicate = IsNotNull(requiredNonPkProperties[0], entityProjectionExpression); + predicate = requiredNonPkProperties.Select(e => IsNotNull(e, entityProjectionExpression)).Aggregate((l, r) => AndAlso(l, r)); + } - if (requiredNonPkProperties.Count > 1) - { - predicate - = requiredNonPkProperties - .Skip(1) - .Aggregate( - predicate, (current, property) => - AndAlso( - IsNotNull(property, entityProjectionExpression), - current)); - } + var allNonSharedNonPkProperties = GetNonSharedNonPkProperties(table, entityType); + if (allNonSharedNonPkProperties.Count != 0 + && allNonSharedNonPkProperties.All(p => p.IsNullable)) + { + var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNonPkProperties + .Select(e => IsNotNull(e, entityProjectionExpression)) + .Aggregate((a, b) => OrElse(a, b)); + predicate = predicate == null + ? atLeastOneNonNullValueInNullablePropertyCondition + : AndAlso(predicate, atLeastOneNonNullValueInNullablePropertyCondition); + } + + if (predicate != null) + { selectExpression.ApplyPredicate(predicate); } - else + } + + private IReadOnlyList GetNonSharedNonPkProperties(ITableBase table, IEntityType entityType) + { + var nonSharedProperties = new List(); + var principalEntityTypes = new HashSet(); + GetPrincipalEntityTypes(table, entityType, principalEntityTypes); + foreach (var property in entityType.GetProperties()) { - var allNonPkProperties = entityType.GetProperties().Where(p => !p.IsPrimaryKey()).ToList(); - if (allNonPkProperties.Count > 0) + if (property.IsPrimaryKey()) { - var entityProjectionExpression = GetMappedEntityProjectionExpression(selectExpression); - predicate = IsNotNull(allNonPkProperties[0], entityProjectionExpression); - - if (allNonPkProperties.Count > 1) - { - predicate - = allNonPkProperties - .Skip(1) - .Aggregate( - predicate, (current, property) => - OrElse( - IsNotNull(property, entityProjectionExpression), - current)); - } - - selectExpression.ApplyPredicate(predicate); + continue; + } - // If there is no non-nullable property then we also need to add optional dependents which are acting as principal for - // other dependents. - foreach (var referencingFk in entityType.GetReferencingForeignKeys()) - { - if (referencingFk.PrincipalEntityType.IsAssignableFrom(entityType)) - { - continue; - } + var propertyMappings = table.FindColumn(property)!.PropertyMappings; + if (propertyMappings.Count() > 1 + && propertyMappings.Any(pm => principalEntityTypes.Contains(pm.TableMapping.EntityType))) + { + continue; + } - var otherSelectExpression = new SelectExpression(entityType, this); + nonSharedProperties.Add(property); + } - var sameTable = table.EntityTypeMappings.Any(m => m.EntityType == referencingFk.DeclaringEntityType) - && table.IsOptional(referencingFk.DeclaringEntityType); - AddInnerJoin(otherSelectExpression, referencingFk, sameTable ? table : null); + return nonSharedProperties; + } - selectExpression.ApplyUnion(otherSelectExpression, distinct: true); - } - } + private void GetPrincipalEntityTypes(ITableBase table, IEntityType entityType, HashSet entityTypes) + { + foreach (var linkingFk in table.GetRowInternalForeignKeys(entityType)) + { + entityTypes.Add(linkingFk.PrincipalEntityType); + GetPrincipalEntityTypes(table, linkingFk.PrincipalEntityType, entityTypes); } } diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index 2b44edd2a88..dea933e40f0 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -549,6 +549,24 @@ public virtual void Can_change_principal_and_dependent_instance_non_derived() } } + [ConditionalFact] + public virtual void Optional_dependent_materialized_when_no_properties() + { + using (CreateTestStore(OnModelCreating)) + { + using (var context = CreateContext()) + { + var vehicle = context.Set() + .Where(e => e.Name == "AIM-9M Sidewinder") + .OrderBy(e => e.Name) + .Include(e => e.Operator.Details).First(); + Assert.Equal(0, vehicle.SeatingCapacity); + Assert.Equal("Heat-seeking", vehicle.Operator.Details.Type); + Assert.Null(vehicle.Operator.Name); + } + } + } + protected virtual string DatabaseName { get; } = "TableSplittingTest"; protected TestStore TestStore { get; set; } protected abstract ITestStoreFactory TestStoreFactory { get; } diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs index 6696a32b8ea..34d47c7721d 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs @@ -1413,7 +1413,6 @@ public override int GetHashCode() protected class RequiredSingle1 : NotifyingEntity { private int _id; - private bool _bool; private Root _root; private RequiredSingle2 _single; @@ -1423,12 +1422,6 @@ public int Id set => SetWithNotify(value, ref _id); } - public bool Bool - { - get => _bool; - set => SetWithNotify(value, ref _bool); - } - public Root Root { get => _root; diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs index 3d1529d6b7e..56f8055e22e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs @@ -263,6 +263,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con r.OwnsOne(e => e.Single) .WithOwner(e => e.Back) .HasForeignKey(e => e.Id); + + r.Navigation(e => e.Single).IsRequired(); }); b.HasOne(e => e.OptionalSingle) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 70f2dd76309..9f08ab0b0db 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -32,7 +32,7 @@ LEFT JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Id]"); } @@ -55,7 +55,7 @@ LEFT JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id] @@ -64,9 +64,9 @@ INNER JOIN ( SELECT [l3].[Id] FROM [Level1] AS [l3] INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL) AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t1] ON [t].[Id] = [t1].[Id]"); } @@ -81,7 +81,7 @@ LEFT JOIN ( SELECT [l0].[Id] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[Level3_Name] @@ -90,9 +90,9 @@ INNER JOIN ( SELECT [l3].[Id] FROM [Level1] AS [l3] INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL) AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t1] ON [t].[Id] = [t1].[Id] GROUP BY [t1].[Level3_Name]"); } @@ -108,7 +108,7 @@ LEFT JOIN ( SELECT [l0].[Id] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[Level3_Name] @@ -117,9 +117,9 @@ INNER JOIN ( SELECT [l3].[Id] FROM [Level1] AS [l3] INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL) AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t1] ON [t].[Id] = [t1].[Id] GROUP BY [t1].[Level3_Name] HAVING MIN(COALESCE([t].[Id], 0)) > 0"); @@ -136,7 +136,7 @@ LEFT JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id] @@ -145,9 +145,9 @@ INNER JOIN ( SELECT [l3].[Id] FROM [Level1] AS [l3] INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL) AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t1] ON [t].[Id] = [t1].[Id] LEFT JOIN ( SELECT [l5].[Id], [l5].[Level3_Optional_Id], [l5].[Level3_Required_Id], [l5].[Level4_Name], [l5].[OneToMany_Optional_Inverse4Id], [l5].[OneToMany_Required_Inverse4Id], [l5].[OneToOne_Optional_PK_Inverse4Id] @@ -159,11 +159,11 @@ INNER JOIN ( SELECT [l7].[Id] FROM [Level1] AS [l7] INNER JOIN [Level1] AS [l8] ON [l7].[Id] = [l8].[Id] - WHERE [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL) AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t2] ON [l6].[Id] = [t2].[Id] - WHERE [l6].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l6].[Level2_Required_Id] IS NOT NULL + WHERE [l6].[Level2_Required_Id] IS NOT NULL AND [l6].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t3] ON [l5].[Id] = [t3].[Id] - WHERE [l5].[OneToMany_Required_Inverse4Id] IS NOT NULL AND [l5].[Level3_Required_Id] IS NOT NULL + WHERE [l5].[Level3_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse4Id] IS NOT NULL ) AS [t4] ON [t1].[Id] = [t4].[Id]"); } @@ -185,7 +185,7 @@ LEFT JOIN ( SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] FROM [Level1] AS [l1] INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] - WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL) AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l0].[Id] = [t].[Id] WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] @@ -198,7 +198,7 @@ LEFT JOIN ( SELECT [l4].[Id], [l4].[OneToOne_Required_PK_Date], [l4].[Level1_Optional_Id], [l4].[Level1_Required_Id], [l4].[Level2_Name], [l4].[OneToMany_Required_Inverse2Id] FROM [Level1] AS [l4] INNER JOIN [Level1] AS [l5] ON [l4].[Id] = [l5].[Id] - WHERE [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL) AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t2] ON [l3].[Id] = [t2].[Id] WHERE ([t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL) AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t3] ON [t1].[Id00] = [t3].[Level1_Optional_Id] @@ -221,7 +221,7 @@ LEFT JOIN ( SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToMany_Required_Inverse2Id] FROM [Level1] AS [l1] INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] - WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL) AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l0].[Id] = [t].[Id] WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] @@ -246,7 +246,7 @@ LEFT JOIN ( SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] FROM [Level1] AS [l1] INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] - WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL) AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l0].[Id] = [t].[Id] WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id]"); @@ -263,7 +263,7 @@ INNER JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l1].[Id] AS [Id0] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id], [t0].[Id] AS [Id0], [t0].[Id0] AS [Id00] @@ -272,9 +272,9 @@ INNER JOIN ( SELECT [l3].[Id], [l4].[Id] AS [Id0] FROM [Level1] AS [l3] INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL) AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL + WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t1] ON [t].[Id] = [t1].[OneToMany_Optional_Inverse3Id] ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0], [t1].[Id00]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 50c4536e153..3c463f6a18b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -1123,7 +1123,7 @@ SELECT [o3].[Id] FROM [OwnedPerson] AS [o3] WHERE [o3].[Discriminator] IN (N'Branch', N'LeafA') ) AS [t0] ON [o2].[Id] = [t0].[Id] - WHERE [o2].[BranchAddress_PlaceType] IS NOT NULL OR [o2].[BranchAddress_BranchName] IS NOT NULL + WHERE [o2].[BranchAddress_BranchName] IS NOT NULL OR [o2].[BranchAddress_PlaceType] IS NOT NULL ) AS [t1] ON [o].[Id] = [t1].[Id] LEFT JOIN ( SELECT [o4].[Id], [o4].[LeafBAddress_LeafBType], [o4].[LeafBAddress_PlaceType], [t2].[Id] AS [Id0], [o4].[Id] AS [Id1], [o4].[LeafBAddress_Country_Name], [o4].[LeafBAddress_Country_PlanetId] @@ -1133,7 +1133,7 @@ SELECT [o5].[Id] FROM [OwnedPerson] AS [o5] WHERE [o5].[Discriminator] = N'LeafB' ) AS [t2] ON [o4].[Id] = [t2].[Id] - WHERE [o4].[LeafBAddress_PlaceType] IS NOT NULL OR [o4].[LeafBAddress_LeafBType] IS NOT NULL + WHERE [o4].[LeafBAddress_LeafBType] IS NOT NULL OR [o4].[LeafBAddress_PlaceType] IS NOT NULL ) AS [t3] ON [o].[Id] = [t3].[Id] LEFT JOIN ( SELECT [o6].[Id], [o6].[LeafAAddress_LeafType], [o6].[LeafAAddress_PlaceType], [t4].[Id] AS [Id0], [o6].[Id] AS [Id1], [o6].[LeafAAddress_Country_Name], [o6].[LeafAAddress_Country_PlanetId] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 2a95c7d9868..48aa19c4819 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -10001,6 +10001,81 @@ public SqlExpression Translate( #endregion + #region Issue23198 + + [ConditionalFact] + public virtual void An_optional_dependent_without_any_columns_act_like_required_dependent() + { + using (CreateDatabase23198()) + { + using var context = new MyContext23198(_options); + var result = context.Set().ToList(); + + var root = Assert.Single(result); + + Assert.NotNull(root.AnOwnedTypeWithOwnedProperties); + Assert.Null(root.AnOwnedTypeWithOwnedProperties.AnOwnedTypeWithPrimitiveProperties1); + Assert.Null(root.AnOwnedTypeWithOwnedProperties.AnOwnedTypeWithPrimitiveProperties2); + + AssertSql( + @"SELECT [a].[Id], [a].[AnOwnedTypeWithOwnedProperties_AnOwnedTypeWithPrimitiveProperties1_Name], [a].[AnOwnedTypeWithOwnedProperties_AnOwnedTypeWithPrimitiveProperties2_Name] +FROM [AnAggregateRoot] AS [a]"); + } + } + + private class MyContext23198 : DbContext + { + public MyContext23198(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().OwnsOne(e => e.AnOwnedTypeWithOwnedProperties, + b => + { + b.OwnsOne(e => e.AnOwnedTypeWithPrimitiveProperties1); + b.OwnsOne(e => e.AnOwnedTypeWithPrimitiveProperties2); + }); + } + } + + public class AnAggregateRoot + { + public string Id { get; set; } + public AnOwnedTypeWithOwnedProperties AnOwnedTypeWithOwnedProperties { get; set; } + } + + public class AnOwnedTypeWithOwnedProperties + { + public AnOwnedTypeWithPrimitiveProperties1 AnOwnedTypeWithPrimitiveProperties1 { get; set; } + public AnOwnedTypeWithPrimitiveProperties2 AnOwnedTypeWithPrimitiveProperties2 { get; set; } + } + + public class AnOwnedTypeWithPrimitiveProperties1 + { + public string Name { get; set; } + } + + public class AnOwnedTypeWithPrimitiveProperties2 + { + public string Name { get; set; } + } + + private SqlServerTestStore CreateDatabase23198() + => CreateTestStore( + () => new MyContext23198(_options), + context => + { + context.Add(new AnAggregateRoot { Id = "1" }); + context.SaveChanges(); + + ClearLog(); + }); + + #endregion + private DbContextOptions _options; private SqlServerTestStore CreateTestStore( diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 8f83e1263d1..34308b9d2a0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -80,7 +80,7 @@ SELECT [p4].[Name] FROM [PoweredVehicles] AS [p4] INNER JOIN [CombustionEngines] AS [c6] ON [p4].[Name] = [c6].[VehicleName] ) AS [t6] ON [c5].[VehicleName] = [t6].[Name] - WHERE [c5].[FuelType] IS NOT NULL OR [c5].[Capacity] IS NOT NULL + WHERE [c5].[Capacity] IS NOT NULL OR [c5].[FuelType] IS NOT NULL ) AS [t7] ON [t5].[Name] = [t7].[VehicleName] ORDER BY [v].[Name]"); } @@ -142,7 +142,7 @@ SELECT [p].[Name] FROM [PoweredVehicles] AS [p] INNER JOIN [CombustionEngines] AS [c0] ON [p].[Name] = [c0].[VehicleName] ) AS [t] ON [c].[VehicleName] = [t].[Name] -WHERE [c].[FuelType] IS NOT NULL OR [c].[Capacity] IS NOT NULL"); +WHERE [c].[Capacity] IS NOT NULL OR [c].[FuelType] IS NOT NULL"); } public override void Can_query_shared_derived_nonhierarchy() @@ -157,7 +157,7 @@ SELECT [p].[Name] FROM [PoweredVehicles] AS [p] INNER JOIN [CombustionEngines] AS [c0] ON [p].[Name] = [c0].[VehicleName] ) AS [t] ON [c].[VehicleName] = [t].[Name] -WHERE [c].[FuelType] IS NOT NULL OR [c].[Capacity] IS NOT NULL"); +WHERE [c].[Capacity] IS NOT NULL OR [c].[FuelType] IS NOT NULL"); } public override void Can_query_shared_derived_nonhierarchy_all_required() @@ -172,7 +172,7 @@ SELECT [p].[Name] FROM [PoweredVehicles] AS [p] INNER JOIN [CombustionEngines] AS [c0] ON [p].[Name] = [c0].[VehicleName] ) AS [t] ON [c].[VehicleName] = [t].[Name] -WHERE [c].[FuelType] IS NOT NULL AND [c].[Capacity] IS NOT NULL"); +WHERE [c].[Capacity] IS NOT NULL AND [c].[FuelType] IS NOT NULL"); } public override void Can_change_dependent_instance_non_derived() @@ -248,5 +248,45 @@ FROM [Vehicles] AS [v1] ) AS [t0] ON [v].[Name] = [t0].[Name] WHERE [v].[Name] = N'Trek Pro Fit Madone 6 Series'"); } + + public override void Optional_dependent_materialized_when_no_properties() + { + base.Optional_dependent_materialized_when_no_properties(); + + AssertSql( + @"SELECT TOP(1) [v].[Name], [v].[SeatingCapacity], [c].[AttachedVehicleName], CASE + WHEN [c].[Name] IS NOT NULL THEN N'CompositeVehicle' + WHEN [p].[Name] IS NOT NULL THEN N'PoweredVehicle' +END AS [Discriminator], [t0].[Name], [t0].[Operator_Name], [t0].[LicenseType], [t0].[Discriminator], [t3].[Name], [t3].[Type] +FROM [Vehicles] AS [v] +LEFT JOIN [PoweredVehicles] AS [p] ON [v].[Name] = [p].[Name] +LEFT JOIN [CompositeVehicles] AS [c] ON [v].[Name] = [c].[Name] +LEFT JOIN ( + SELECT [v0].[Name], [v0].[Operator_Name], [l].[LicenseType], CASE + WHEN [l].[VehicleName] IS NOT NULL THEN N'LicensedOperator' + END AS [Discriminator] + FROM [Vehicles] AS [v0] + LEFT JOIN [LicensedOperators] AS [l] ON [v0].[Name] = [l].[VehicleName] + INNER JOIN ( + SELECT [v1].[Name] + FROM [Vehicles] AS [v1] + ) AS [t] ON [v0].[Name] = [t].[Name] +) AS [t0] ON [v].[Name] = [t0].[Name] +LEFT JOIN ( + SELECT [v2].[Name], [v2].[Type] + FROM [Vehicles] AS [v2] + INNER JOIN ( + SELECT [v3].[Name] + FROM [Vehicles] AS [v3] + INNER JOIN ( + SELECT [v4].[Name] + FROM [Vehicles] AS [v4] + ) AS [t1] ON [v3].[Name] = [t1].[Name] + ) AS [t2] ON [v2].[Name] = [t2].[Name] + WHERE [v2].[Type] IS NOT NULL +) AS [t3] ON [t0].[Name] = [t3].[Name] +WHERE [v].[Name] = N'AIM-9M Sidewinder' +ORDER BY [v].[Name]"); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs index dcc53e7e3a8..81b8aaf9f9b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs @@ -48,7 +48,7 @@ SELECT [v6].[Name] FROM [Vehicles] AS [v6] WHERE [v6].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t2] ON [v5].[Name] = [t2].[Name] - WHERE [v5].[Engine_Discriminator] IS NOT NULL AND [v5].[Computed] IS NOT NULL + WHERE [v5].[Computed] IS NOT NULL AND [v5].[Engine_Discriminator] IS NOT NULL ) AS [t3] ON [v].[Name] = [t3].[Name] LEFT JOIN ( SELECT [v7].[Name], [v7].[Capacity], [v7].[FuelTank_Discriminator], [v7].[FuelType], [v7].[GrainGeometry] @@ -148,7 +148,7 @@ INNER JOIN ( FROM [Vehicles] AS [v0] WHERE [v0].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t] ON [v].[Name] = [t].[Name] -WHERE [v].[FuelType] IS NOT NULL OR [v].[Capacity] IS NOT NULL +WHERE [v].[Capacity] IS NOT NULL OR [v].[FuelType] IS NOT NULL UNION SELECT [v1].[Name], [v1].[Capacity], [v1].[FuelType] FROM [Vehicles] AS [v1] @@ -162,7 +162,7 @@ WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t0] ON [v2].[Name] = [t0].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') ) AS [t1] ON [v1].[Name] = [t1].[Name] -WHERE [v1].[FuelType] IS NOT NULL OR [v1].[Capacity] IS NOT NULL"); +WHERE [v1].[Capacity] IS NOT NULL OR [v1].[FuelType] IS NOT NULL"); } public override void Can_query_shared_derived_nonhierarchy_all_required() @@ -177,7 +177,7 @@ INNER JOIN ( FROM [Vehicles] AS [v0] WHERE [v0].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t] ON [v].[Name] = [t].[Name] -WHERE [v].[FuelType] IS NOT NULL AND [v].[Capacity] IS NOT NULL +WHERE [v].[Capacity] IS NOT NULL AND [v].[FuelType] IS NOT NULL UNION SELECT [v1].[Name], [v1].[Capacity], [v1].[FuelType] FROM [Vehicles] AS [v1] @@ -191,7 +191,7 @@ WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t0] ON [v2].[Name] = [t0].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') ) AS [t1] ON [v1].[Name] = [t1].[Name] -WHERE [v1].[FuelType] IS NOT NULL AND [v1].[Capacity] IS NOT NULL"); +WHERE [v1].[Capacity] IS NOT NULL AND [v1].[FuelType] IS NOT NULL"); } public override void Can_change_dependent_instance_non_derived() @@ -242,6 +242,32 @@ FROM [Vehicles] AS [v0] WHERE [v].[Name] = N'Trek Pro Fit Madone 6 Series'"); } + public override void Optional_dependent_materialized_when_no_properties() + { + base.Optional_dependent_materialized_when_no_properties(); + + AssertSql( + @"SELECT TOP(1) [v].[Name], [v].[Discriminator], [v].[SeatingCapacity], [v].[AttachedVehicleName], [t].[Name], [t].[Operator_Discriminator], [t].[Operator_Name], [t].[LicenseType], [t1].[Name], [t1].[Type] +FROM [Vehicles] AS [v] +LEFT JOIN ( + SELECT [v0].[Name], [v0].[Operator_Discriminator], [v0].[Operator_Name], [v0].[LicenseType] + FROM [Vehicles] AS [v0] + INNER JOIN [Vehicles] AS [v1] ON [v0].[Name] = [v1].[Name] +) AS [t] ON [v].[Name] = [t].[Name] +LEFT JOIN ( + SELECT [v2].[Name], [v2].[Type] + FROM [Vehicles] AS [v2] + INNER JOIN ( + SELECT [v3].[Name] + FROM [Vehicles] AS [v3] + INNER JOIN [Vehicles] AS [v4] ON [v3].[Name] = [v4].[Name] + ) AS [t0] ON [v2].[Name] = [t0].[Name] + WHERE [v2].[Type] IS NOT NULL +) AS [t1] ON [t].[Name] = [t1].[Name] +WHERE [v].[Name] = N'AIM-9M Sidewinder' +ORDER BY [v].[Name]"); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder);