diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs index 9c626091988..4cc4640978d 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs @@ -42,5 +42,45 @@ public static IEnumerable GetViewOrTableMappings(this IEntity /// public static IReadOnlyList GetTptDiscriminatorValues(this IReadOnlyEntityType entityType) => entityType.GetConcreteDerivedTypesInclusive().Select(et => et.ShortName()).ToList(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IReadOnlyList GetNonPrincipalSharedNonPkProperties(this IEntityType entityType, ITableBase table) + { + var nonPrincipalSharedProperties = new List(); + var principalEntityTypes = new HashSet(); + PopulatePrincipalEntityTypes(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; + } + + nonPrincipalSharedProperties.Add(property); + } + + return nonPrincipalSharedProperties; + + static void PopulatePrincipalEntityTypes(ITableBase table, IEntityType entityType, HashSet entityTypes) + { + foreach (var linkingFk in table.GetRowInternalForeignKeys(entityType)) + { + entityTypes.Add(linkingFk.PrincipalEntityType); + PopulatePrincipalEntityTypes(table, linkingFk.PrincipalEntityType, entityTypes); + } + } + } } } diff --git a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs index c9f51ade037..44c0b24f7bc 100644 --- a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs +++ b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs @@ -117,12 +117,12 @@ 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 allNonPrincipalSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table); + // We don't need condition for nullable property if there exist at least one required property which is non shared. + if (allNonPrincipalSharedNonPkProperties.Count != 0 + && allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable)) { - var allNonSharedNullableProperties = allNonSharedProperties.Where(p => p.IsNullable).ToList(); - var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNullableProperties + var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties .Select( p => NotEqual( valueBufferParameter.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p), @@ -179,39 +179,5 @@ public override EntityShaperExpression Update(Expression valueBufferExpression) ? new RelationalEntityShaperExpression(EntityType, valueBufferExpression, IsNullable, MaterializationCondition) : this; } - - private IReadOnlyList GetNonSharedProperties(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); - } - } } } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 66b2e4c860f..3c3596927e4 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1282,36 +1282,36 @@ 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 allNonPrincipalSharedNonPkProperties = entityType1.GetNonPrincipalSharedNonPkProperties(table); + // We don't need condition for nullable property if there exist at least one required property which is non shared. + if (allNonPrincipalSharedNonPkProperties.Count != 0 + && allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable)) { - result = Visit( - allNonPkProperties.Select( + var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties + .Select( p => { var comparison = Expression.Call( @@ -1322,9 +1322,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; } diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index eeaef9a508f..1fa9a3da2b6 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -938,66 +938,30 @@ 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); - - if (requiredNonPkProperties.Count > 1) - { - predicate - = requiredNonPkProperties - .Skip(1) - .Aggregate( - predicate, (current, property) => - AndAlso( - IsNotNull(property, entityProjectionExpression), - current)); - } - - selectExpression.ApplyPredicate(predicate); + predicate = requiredNonPkProperties.Select(e => IsNotNull(e, entityProjectionExpression)).Aggregate((l, r) => AndAlso(l, r)); } - else - { - var allNonPkProperties = entityType.GetProperties().Where(p => !p.IsPrimaryKey()).ToList(); - if (allNonPkProperties.Count > 0) - { - 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); - // 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 otherSelectExpression = new SelectExpression(entityType, this); + var allNonSharedNonPkProperties = entityType.GetNonPrincipalSharedNonPkProperties(table); + // We don't need condition for nullable property if there exist at least one required property which is non shared. + if (allNonSharedNonPkProperties.Count != 0 + && allNonSharedNonPkProperties.All(p => p.IsNullable)) + { + var atLeastOneNonNullValueInNullablePropertyCondition = allNonSharedNonPkProperties + .Select(e => IsNotNull(e, entityProjectionExpression)) + .Aggregate((a, b) => OrElse(a, b)); - var sameTable = table.EntityTypeMappings.Any(m => m.EntityType == referencingFk.DeclaringEntityType) - && table.IsOptional(referencingFk.DeclaringEntityType); - AddInnerJoin(otherSelectExpression, referencingFk, sameTable ? table : null); + predicate = predicate == null + ? atLeastOneNonNullValueInNullablePropertyCondition + : AndAlso(predicate, atLeastOneNonNullValueInNullablePropertyCondition); + } - selectExpression.ApplyUnion(otherSelectExpression, distinct: true); - } - } + if (predicate != null) + { + selectExpression.ApplyPredicate(predicate); } } diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index e92bdf21c8c..cd2a979a711 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -531,6 +531,23 @@ await InitializeAsync( } } + [ConditionalFact] + public virtual async Task Optional_dependent_materialized_when_no_properties() + { + await InitializeAsync(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 override string StoreName { get; } = "TableSplittingTest"; protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 6230aa89b42..1c2e2c19774 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 [t1] ON [l2].[Id] = [t1].[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 [t0] ON [t].[Id] = [t0].[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 [t1] ON [l2].[Id] = [t1].[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 [t0] ON [t].[Id] = [t0].[Id] GROUP BY [t0].[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 [t1] ON [l2].[Id] = [t1].[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 [t0] ON [t].[Id] = [t0].[Id] GROUP BY [t0].[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 [t1] ON [l2].[Id] = [t1].[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 [t0] ON [t].[Id] = [t0].[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 [t4] ON [l6].[Id] = [t4].[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 [t2] ON [t0].[Id] = [t2].[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 [t3] ON [l3].[Id] = [t3].[Id] WHERE ([t3].[OneToOne_Required_PK_Date] IS NOT NULL AND [t3].[Level1_Required_Id] IS NOT NULL) AND [t3].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t2] ON [t1].[Id00] = [t2].[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], [t1].[Id] AS [Id0], [t1].[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 [t1] ON [l2].[Id] = [t1].[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 [t0] ON [t].[Id] = [t0].[OneToMany_Optional_Inverse3Id] ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t0].[Id], [t0].[Id0], [t0].[Id00]"); } @@ -290,13 +290,13 @@ INNER JOIN ( SELECT DISTINCT [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].[OneToMany_Optional_Inverse2Id] LEFT JOIN ( SELECT [l2].[Id], [l2].[OneToOne_Required_PK_Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Level2_Name], [l2].[OneToMany_Optional_Inverse2Id], [l2].[OneToMany_Required_Inverse2Id], [l2].[OneToOne_Optional_PK_Inverse2Id], [l3].[Id] AS [Id0] FROM [Level1] AS [l2] INNER JOIN [Level1] AS [l3] ON [l2].[Id] = [l3].[Id] - WHERE [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL) AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l].[Id] = [t0].[OneToMany_Optional_Inverse2Id] 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 ORDER BY [l].[Id], [t].[Id], [t0].[Id], [t0].[Id0]"); @@ -313,13 +313,13 @@ INNER JOIN ( SELECT DISTINCT [l0].[Id], [l0].[Level2_Name] AS [Name], [l0].[OneToMany_Optional_Inverse2Id] AS [FK] 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].[FK] LEFT JOIN ( SELECT [l2].[Id], [l2].[OneToOne_Required_PK_Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Level2_Name], [l2].[OneToMany_Optional_Inverse2Id], [l2].[OneToMany_Required_Inverse2Id], [l2].[OneToOne_Optional_PK_Inverse2Id], [l3].[Id] AS [Id0] FROM [Level1] AS [l2] INNER JOIN [Level1] AS [l3] ON [l2].[Id] = [l3].[Id] - WHERE [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToOne_Required_PK_Date] IS NOT NULL) + WHERE ([l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL) AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t0] ON [l].[Id] = [t0].[OneToMany_Optional_Inverse2Id] ORDER BY [l].[Id], [t].[Id], [t].[Name], [t].[FK], [t0].[Id], [t0].[Id0]"); } @@ -343,7 +343,7 @@ OUTER APPLY ( SELECT TOP(3) [l0].[Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Required_Inverse2Id], [l2].[Id] AS [Id0] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l2] ON [l0].[Id] = [l2].[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)) AND ([t].[Id] = [l0].[OneToMany_Required_Inverse2Id]) + 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) AND ([t].[Id] = [l0].[OneToMany_Required_Inverse2Id]) ORDER BY [l0].[Id] ) AS [t1] INNER JOIN [Level1] AS [l1] ON [t1].[Level1_Required_Id] = [l1].[Id] @@ -371,7 +371,7 @@ OUTER APPLY ( SELECT [l0].[Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Required_Inverse2Id], [l2].[Id] AS [Id0] FROM [Level1] AS [l0] INNER JOIN [Level1] AS [l2] ON [l0].[Id] = [l2].[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)) AND ([t].[Id] = [l0].[OneToMany_Required_Inverse2Id]) + 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) AND ([t].[Id] = [l0].[OneToMany_Required_Inverse2Id]) ORDER BY [l0].[Id] OFFSET 1 ROWS FETCH NEXT 3 ROWS ONLY ) AS [t1] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index a139574fa46..fd03a1ce844 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 [t1] ON [o2].[Id] = [t1].[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 [t0] ON [o].[Id] = [t0].[Id] LEFT JOIN ( SELECT [o4].[Id], [o4].[LeafBAddress_LeafBType], [o4].[LeafBAddress_PlaceType], [t3].[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 [t3] ON [o4].[Id] = [t3].[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 [t2] ON [o].[Id] = [t2].[Id] LEFT JOIN ( SELECT [o6].[Id], [o6].[LeafAAddress_LeafType], [o6].[LeafAAddress_PlaceType], [t5].[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 531db16e307..eb27e2fd685 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -10023,6 +10023,74 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) #endregion + #region Issue23198 + + [ConditionalFact] + public virtual async Task An_optional_dependent_without_any_columns_act_like_required_dependent() + { + var contextFactory = await InitializeAsync( + seed: ctx => + { + ctx.Add(new AnAggregateRoot { Id = "1" }); + ctx.SaveChanges(); + }); + using var context = contextFactory.CreateContext(); + + 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; } + } + + #endregion + protected override string StoreName => "QueryBugsTest"; protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index a0dcdf2c10a..792dde7ef72 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -81,7 +81,7 @@ SELECT [p4].[Name] FROM [PoweredVehicles] AS [p4] INNER JOIN [CombustionEngines] AS [c6] ON [p4].[Name] = [c6].[VehicleName] ) AS [t7] ON [c5].[VehicleName] = [t7].[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 [t6] ON [t4].[Name] = [t6].[VehicleName] ORDER BY [v].[Name]"); } @@ -143,7 +143,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 async Task Can_query_shared_derived_nonhierarchy() @@ -158,7 +158,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 async Task Can_query_shared_derived_nonhierarchy_all_required() @@ -173,7 +173,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 async Task Can_change_dependent_instance_non_derived() @@ -249,5 +249,45 @@ FROM [Vehicles] AS [v1] ) AS [t0] ON [v].[Name] = [t0].[Name] WHERE [v].[Name] = N'Trek Pro Fit Madone 6 Series'"); } + + public override async Task Optional_dependent_materialized_when_no_properties() + { + await 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], [t1].[Name], [t1].[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 [t3] ON [v3].[Name] = [t3].[Name] + ) AS [t2] ON [v2].[Name] = [t2].[Name] + WHERE [v2].[Type] IS NOT NULL +) AS [t1] ON [t0].[Name] = [t1].[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 9a0a4c58f9c..9a7308694a6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs @@ -49,7 +49,7 @@ SELECT [v6].[Name] FROM [Vehicles] AS [v6] WHERE [v6].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t3] ON [v5].[Name] = [t3].[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 [t2] ON [v].[Name] = [t2].[Name] LEFT JOIN ( SELECT [v7].[Name], [v7].[Capacity], [v7].[FuelTank_Discriminator], [v7].[FuelType], [v7].[GrainGeometry] @@ -149,7 +149,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] @@ -163,7 +163,7 @@ WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t1] ON [v2].[Name] = [t1].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') ) AS [t0] ON [v1].[Name] = [t0].[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 async Task Can_query_shared_derived_nonhierarchy_all_required() @@ -178,7 +178,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] @@ -192,7 +192,7 @@ WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') ) AS [t1] ON [v2].[Name] = [t1].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') ) AS [t0] ON [v1].[Name] = [t0].[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 async Task Can_change_dependent_instance_non_derived() @@ -243,6 +243,32 @@ FROM [Vehicles] AS [v0] WHERE [v].[Name] = N'Trek Pro Fit Madone 6 Series'"); } + public override async Task Optional_dependent_materialized_when_no_properties() + { + await 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], [t0].[Name], [t0].[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 [t1] ON [v2].[Name] = [t1].[Name] + WHERE [v2].[Type] IS NOT NULL +) AS [t0] ON [t].[Name] = [t0].[Name] +WHERE [v].[Name] = N'AIM-9M Sidewinder' +ORDER BY [v].[Name]"); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder);