diff --git a/EFCore.Runtime.sln.DotSettings b/EFCore.Runtime.sln.DotSettings index 48d09f0557e..4d2620644a4 100644 --- a/EFCore.Runtime.sln.DotSettings +++ b/EFCore.Runtime.sln.DotSettings @@ -1,5 +1,6 @@  True + EF ..\EFCore.sln.DotSettings True True diff --git a/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs b/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs index c791a36575f..a04d5916b0c 100644 --- a/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs +++ b/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel; @@ -33,18 +34,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .Ignore(e => e.OneToMany_Required_Self) .Ignore(e => e.OneToMany_Required_Self_Inverse) .Ignore(e => e.OneToMany_Optional_Self) - .Ignore(e => e.OneToMany_Optional_Self_Inverse) - .Ignore(e => e.OneToMany_Required) - .Ignore(e => e.OneToMany_Optional); + .Ignore(e => e.OneToMany_Optional_Self_Inverse); var level1 = level1Builder.Metadata; + + ForeignKey level2Fk; var level2 = level1.Model.AddEntityType(typeof(Level2), nameof(Level1.OneToOne_Required_PK), level1); - var level2Fk = level2.AddForeignKey(level2.FindProperty(nameof(Level2.Id)), level1.FindPrimaryKey(), level1); - level2Fk.HasPrincipalToDependent(nameof(Level1.OneToOne_Required_PK)); - level2Fk.IsUnique = true; - level2Fk.DeleteBehavior = DeleteBehavior.Restrict; + using (var batch = ((Model)modelBuilder.Model).ConventionDispatcher.StartBatch()) + { + level2Fk = (ForeignKey)level2.AddForeignKey(level2.FindProperty(nameof(Level2.Id)), level1.FindPrimaryKey(), level1); + level2Fk.HasPrincipalToDependent(nameof(Level1.OneToOne_Required_PK)); + level2Fk.HasDependentToPrincipal(nameof(Level2.OneToOne_Required_PK_Inverse)); + level2Fk.IsUnique = true; + level2Fk.DeleteBehavior = DeleteBehavior.Restrict; + level2Fk = batch.Run(level2Fk); + } - Configure(new ReferenceOwnershipBuilder((EntityType)level1, (EntityType)level2, ((ForeignKey)level2Fk).Builder)); + Configure(new ReferenceOwnershipBuilder((EntityType)level1, level2Fk.DeclaringEntityType, level2Fk.Builder)); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); @@ -89,10 +95,7 @@ protected virtual void Configure(ReferenceOwnershipBuilder l2) .Ignore(e => e.OneToMany_Required_Self_Inverse) .Ignore(e => e.OneToMany_Optional_Self) .Ignore(e => e.OneToMany_Optional_Self_Inverse) - .Ignore(e => e.OneToMany_Required) - .Ignore(e => e.OneToMany_Required_Inverse) - .Ignore(e => e.OneToMany_Optional) - .Ignore(e => e.OneToMany_Optional_Inverse).OwnedEntityType; + .OwnedEntityType; l2.Property(e => e.Id).ValueGeneratedNever(); @@ -112,13 +115,28 @@ protected virtual void Configure(ReferenceOwnershipBuilder l2) .HasForeignKey(e => e.Level1_Optional_Id) .IsRequired(false); + l2.HasOne(e => e.OneToMany_Required_Inverse) + .WithMany(e => e.OneToMany_Required) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + l2.HasOne(e => e.OneToMany_Optional_Inverse) + .WithMany(e => e.OneToMany_Optional) + .IsRequired(false); + + ForeignKey level3Fk; var level3 = level2.Model.AddEntityType(typeof(Level3), nameof(Level2.OneToOne_Required_PK), level2); - var level3Fk = level3.AddForeignKey(level3.FindProperty(nameof(Level3.Id)), level2.FindPrimaryKey(), level2); - level3Fk.HasPrincipalToDependent(nameof(Level2.OneToOne_Required_PK)); - level3Fk.IsUnique = true; - level3Fk.DeleteBehavior = DeleteBehavior.Restrict; + using (var batch = ((Model)level2.Model).ConventionDispatcher.StartBatch()) + { + level3Fk = (ForeignKey)level3.AddForeignKey(level3.FindProperty(nameof(Level3.Id)), level2.FindPrimaryKey(), level2); + level3Fk.HasPrincipalToDependent(nameof(Level2.OneToOne_Required_PK)); + level3Fk.HasDependentToPrincipal(nameof(Level3.OneToOne_Required_PK_Inverse)); + level3Fk.IsUnique = true; + level3Fk.DeleteBehavior = DeleteBehavior.Restrict; + level3Fk = batch.Run(level3Fk); + } - Configure(new ReferenceOwnershipBuilder((EntityType)level2, (EntityType)level3, ((ForeignKey)level3Fk).Builder)); + Configure(new ReferenceOwnershipBuilder((EntityType)level2, level3Fk.DeclaringEntityType, level3Fk.Builder)); } protected virtual void Configure(ReferenceOwnershipBuilder l3) @@ -128,10 +146,7 @@ protected virtual void Configure(ReferenceOwnershipBuilder l3) .Ignore(e => e.OneToMany_Required_Self_Inverse) .Ignore(e => e.OneToMany_Optional_Self) .Ignore(e => e.OneToMany_Optional_Self_Inverse) - .Ignore(e => e.OneToMany_Required) - .Ignore(e => e.OneToMany_Required_Inverse) - .Ignore(e => e.OneToMany_Optional) - .Ignore(e => e.OneToMany_Optional_Inverse).OwnedEntityType; + .OwnedEntityType; l3.Property(e => e.Id).ValueGeneratedNever(); @@ -151,13 +166,28 @@ protected virtual void Configure(ReferenceOwnershipBuilder l3) .HasForeignKey(e => e.Level2_Optional_Id) .IsRequired(false); + l3.HasOne(e => e.OneToMany_Required_Inverse) + .WithMany(e => e.OneToMany_Required) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + l3.HasOne(e => e.OneToMany_Optional_Inverse) + .WithMany(e => e.OneToMany_Optional) + .IsRequired(false); + + ForeignKey level4Fk; var level4 = level3.Model.AddEntityType(typeof(Level4), nameof(Level3.OneToOne_Required_PK), level3); - var level4Fk = level4.AddForeignKey(level4.FindProperty(nameof(Level4.Id)), level3.FindPrimaryKey(), level3); - level4Fk.HasPrincipalToDependent(nameof(Level3.OneToOne_Required_PK)); - level4Fk.IsUnique = true; - level4Fk.DeleteBehavior = DeleteBehavior.Restrict; + using (var batch = ((Model)level3.Model).ConventionDispatcher.StartBatch()) + { + level4Fk = (ForeignKey)level4.AddForeignKey(level4.FindProperty(nameof(Level4.Id)), level3.FindPrimaryKey(), level3); + level4Fk.HasPrincipalToDependent(nameof(Level3.OneToOne_Required_PK)); + level4Fk.HasDependentToPrincipal(nameof(Level4.OneToOne_Required_PK_Inverse)); + level4Fk.IsUnique = true; + level4Fk.DeleteBehavior = DeleteBehavior.Restrict; + level4Fk = batch.Run(level4Fk); + } - Configure(new ReferenceOwnershipBuilder((EntityType)level3, (EntityType)level4, ((ForeignKey)level4Fk).Builder)); + Configure(new ReferenceOwnershipBuilder((EntityType)level3, level4Fk.DeclaringEntityType, level4Fk.Builder)); } protected virtual void Configure(ReferenceOwnershipBuilder l4) @@ -166,9 +196,7 @@ protected virtual void Configure(ReferenceOwnershipBuilder l4) .Ignore(e => e.OneToMany_Required_Self) .Ignore(e => e.OneToMany_Required_Self_Inverse) .Ignore(e => e.OneToMany_Optional_Self) - .Ignore(e => e.OneToMany_Optional_Self_Inverse) - .Ignore(e => e.OneToMany_Required_Inverse) - .Ignore(e => e.OneToMany_Optional_Inverse); + .Ignore(e => e.OneToMany_Optional_Self_Inverse); l4.Property(e => e.Id).ValueGeneratedNever(); @@ -187,6 +215,15 @@ protected virtual void Configure(ReferenceOwnershipBuilder l4) .WithOne(e => e.OneToOne_Optional_FK) .HasForeignKey(e => e.Level3_Optional_Id) .IsRequired(false); + + l4.HasOne(e => e.OneToMany_Required_Inverse) + .WithMany(e => e.OneToMany_Required) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + l4.HasOne(e => e.OneToMany_Optional_Inverse) + .WithMany(e => e.OneToMany_Optional) + .IsRequired(false); } protected override void Seed(ComplexNavigationsContext context) diff --git a/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs index 6f9f8d874f5..18e53b8e59d 100644 --- a/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs @@ -45,287 +45,17 @@ public override Task Key_equality_two_conditions_on_same_navigation(bool isAsync public override Task Level4_Include(bool isAsync) { - // Due to level 4 being owned, other tests using l4 as root could cause same query as this one to run + // Due to level 4 being weak, other tests using l4 as root could cause same query as this one to run // generating different SQL return Task.CompletedTask; } - #region #8172 - One-to-many not supported yet - - public override Task Multiple_SelectMany_with_string_based_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Where_navigation_property_to_collection_of_original_entity_type(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_Include1(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_Include2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigations_compared_to_each_other1(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigations_compared_to_each_other2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigations_compared_to_each_other3(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigations_compared_to_each_other4(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigations_compared_to_each_other5(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigation_with_same_navigation_compared_to_null(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multi_level_navigation_compared_to_null(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multi_level_navigation_with_same_navigation_compared_to_null(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multi_level_include_correct_PK_is_chosen_as_the_join_predicate_for_queries_that_join_same_table_multiple_times(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Required_navigation_with_Include_ThenInclude(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_nested_navigation_property_required(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_include_with_multiple_optional_navigations(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_SelectMany_calls(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_complex_includes(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_string_based_Include2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Contains_with_subquery_optional_navigation_and_constant_item(bool isAsync) - { - return Task.CompletedTask; - } - + //#12583 public override Task Include_with_groupjoin_skip_and_take(bool isAsync) { return Task.CompletedTask; } - public override Task SelectMany_navigation_property_and_projection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multi_level_include_one_to_many_optional_and_one_to_many_optional_produces_valid_sql(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_navigation_property_with_another_navigation_in_subquery(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_string_based_Include1(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_navigation_property(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Complex_multi_include_with_order_by_and_paging(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_nav_prop_collection_one_to_many_required(bool isAsync) - { - return Task.CompletedTask; - } - - public override void Data_reader_is_closed_correct_number_of_times_for_include_queries_on_optional_navigations() - { - } - - public override Task SelectMany_where_with_subquery(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Required_navigation_with_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Complex_multi_include_with_order_by_and_paging_joins_on_correct_key(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Complex_query_with_optional_navigations_and_client_side_evaluation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_navigation_property_and_filter_before(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_complex_include_select(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_navigation_and_Distinct(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Optional_navigation_with_Include_ThenInclude(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_Include_ThenInclude(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_nested_with_optional_navigation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_optional_navigation_with_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Where_navigation_property_to_collection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_SelectMany_with_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multiple_optional_navigation_with_string_based_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Where_navigation_property_to_collection2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Where_on_multilevel_reference_in_subquery_with_outer_projection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_navigation_property_and_filter_after(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Complex_multi_include_with_order_by_and_paging_joins_on_correct_key2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_navigation_filter_paging_and_explicit_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Comparing_collection_navigation_on_optional_reference_to_null(bool isAsync) - { - return Task.CompletedTask; - } - // Self-ref not supported public override Task Join_navigation_translated_to_subquery_self_ref(bool isAsync) { @@ -337,176 +67,21 @@ public override Task Multiple_complex_includes_self_ref(bool isAsync) return Task.CompletedTask; } - public override Task Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties(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_single_property(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Navigation_filter_navigation_grouping_ordering_by_group_key(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Manually_created_left_join_propagates_nullability_to_navigations(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join1(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task GroupJoin_with_complex_subquery_with_joins_does_not_get_flattened(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task GroupJoin_with_complex_subquery_with_joins_does_not_get_flattened2(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task GroupJoin_with_complex_subquery_with_joins_does_not_get_flattened3(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation_nested(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation_using_ef_property(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation_nested_anonymous(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation_count(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_navigation_composed(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_and_root_entity(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_collection_and_include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_navigation_and_collection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_optional_navigation_property_string_concat(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_multiple_orderbys_member(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_multiple_orderbys_property(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_multiple_orderbys_methodcall(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_multiple_orderbys_complex(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_multiple_orderbys_complex_repeated(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_groupby_in_subquery(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Multi_include_with_groupby_in_subquery(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_groupby_in_subquery_and_filter_before_groupby(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_collection_with_groupby_in_subquery_and_filter_after_groupby(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_reference_collection_order_by_reference_navigation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Optional_navigation_with_order_by_and_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Optional_navigation_with_Include_and_order(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_with_order_by_and_Include(bool isAsync) + public override Task Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties(bool isAsync) { return Task.CompletedTask; } - public override Task SelectMany_with_Include_and_order_by(bool isAsync) + public override Task Join_condition_optimizations_applied_correctly_when_anonymous_type_with_single_property(bool isAsync) { return Task.CompletedTask; } - public override Task Include_after_SelectMany_and_reference_navigation(bool isAsync) + public override Task Navigation_filter_navigation_grouping_ordering_by_group_key(bool isAsync) { return Task.CompletedTask; } @@ -520,27 +95,5 @@ public override Task Include_after_SelectMany_and_multiple_reference_navigations { return Task.CompletedTask; } - - public override Task Include_after_SelectMany_and_reference_navigation_with_another_SelectMany_with_Distinct(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_subquery_with_custom_projection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Null_check_in_anonymous_type_projection_should_not_be_removed(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Null_check_in_Dto_projection_should_not_be_removed(bool isAsync) - { - return Task.CompletedTask; - } - - #endregion } } diff --git a/src/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index ea58bdb931c..5cbd513a0c9 100644 --- a/src/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -72,6 +73,8 @@ public virtual void Query_for_base_type_loads_all_owned_navs() Assert.True(people.OfType().All(b => b.BranchAddress != null)); Assert.True(people.OfType().All(a => a.LeafAAddress != null)); Assert.True(people.OfType().All(b => b.LeafBAddress != null)); + + Assert.True(people.All(p => p.Orders.Count == (p.Id == 1 ? 2 : 1))); } } @@ -97,6 +100,8 @@ public virtual void Query_for_branch_type_loads_all_owned_navs() Assert.True(people.All(p => p.PersonAddress != null)); Assert.True(people.All(b => b.BranchAddress != null)); Assert.True(people.OfType().All(a => a.LeafAAddress != null)); + + Assert.True(people.All(p => p.Orders.Count == 1)); } } @@ -208,6 +213,38 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con Name = "USA" }); }); + + eb.OwnsMany( + p => p.Orders, ob => + { + ob.HasData( + new + { + Id = -10, + ClientId = 1 + }, + new + { + Id = -11, + ClientId = 1 + }, + new + { + Id = -20, + ClientId = 2 + }, + new + { + Id = -30, + ClientId = 3 + }, + new + { + Id = -40, + ClientId = 4 + } + ); + }); }); modelBuilder.Entity( @@ -324,6 +361,13 @@ protected class OwnedPerson { public int Id { get; set; } public OwnedAddress PersonAddress { get; set; } + public ICollection Orders { get; set; } + } + + protected class Order + { + public int Id { get; set; } + public OwnedPerson Client { get; set; } } protected class Branch : OwnedPerson diff --git a/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs b/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs index 30e95254ac4..dbac16ffc5e 100644 --- a/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs +++ b/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs @@ -41,19 +41,22 @@ public virtual void TraverseGraph( if (navigationValue != null) { + var targetEntityType = navigation.GetTargetType(); if (navigation.IsCollection()) { foreach (var relatedEntity in ((IEnumerable)navigationValue).Cast().ToList()) { + var targetEntry = targetEntityType.HasDefiningNavigation() + ? stateManager.GetOrCreateEntry(relatedEntity, targetEntityType) + : stateManager.GetOrCreateEntry(relatedEntity); TraverseGraph( - node.CreateNode(node, stateManager.GetOrCreateEntry(relatedEntity), navigation), + node.CreateNode(node, targetEntry, navigation), state, handleNode); } } else { - var targetEntityType = navigation.GetTargetType(); var targetEntry = targetEntityType.HasDefiningNavigation() ? stateManager.GetOrCreateEntry(navigationValue, targetEntityType) : stateManager.GetOrCreateEntry(navigationValue); @@ -91,12 +94,16 @@ public virtual async Task TraverseGraphAsync( if (navigationValue != null) { + var targetType = navigation.GetTargetType(); if (navigation.IsCollection()) { foreach (var relatedEntity in ((IEnumerable)navigationValue).Cast().ToList()) { + var targetEntry = targetType.HasDefiningNavigation() + ? stateManager.GetOrCreateEntry(relatedEntity, targetType) + : stateManager.GetOrCreateEntry(relatedEntity); await TraverseGraphAsync( - node.CreateNode(node, stateManager.GetOrCreateEntry(relatedEntity), navigation), + node.CreateNode(node, targetEntry, navigation), state, handleNode, cancellationToken); @@ -104,12 +111,11 @@ await TraverseGraphAsync( } else { - var targetType = navigation.GetTargetType(); - var entry = targetType.HasDefiningNavigation() + var targetEntry = targetType.HasDefiningNavigation() ? stateManager.GetOrCreateEntry(navigationValue, targetType) : stateManager.GetOrCreateEntry(navigationValue); await TraverseGraphAsync( - node.CreateNode(node, entry, navigation), + node.CreateNode(node, targetEntry, navigation), state, handleNode, cancellationToken); diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index cbf9264a5fb..2ebc91a6b71 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -95,7 +95,7 @@ public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavig if (ReferenceEquals(victimDependentEntry[navigation], newTargetEntry.Entity) && victimDependentEntry.StateManager - .TryGetEntry(victimDependentEntry.Entity, throwOnNonUniqueness: false) != null) + .TryGetEntry(victimDependentEntry.Entity, targetEntityType) != null) { SetNavigation(victimDependentEntry, navigation, null); } @@ -185,10 +185,10 @@ public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavig { stateManager.RecordReferencedUntrackedEntity(newValue, navigation, entry); entry.SetRelationshipSnapshotValue(navigation, newValue); - var targetEntry = targetEntityType.HasDefiningNavigation() + newTargetEntry = targetEntityType.HasDefiningNavigation() ? stateManager.GetOrCreateEntry(newValue, targetEntityType) : stateManager.GetOrCreateEntry(newValue); - _attacher.AttachGraph(targetEntry, EntityState.Added, forceStateWhenUnknownKey: false); + _attacher.AttachGraph(newTargetEntry, EntityState.Added, forceStateWhenUnknownKey: false); } } @@ -210,10 +210,11 @@ public virtual void NavigationCollectionChanged( var foreignKey = navigation.ForeignKey; var stateManager = entry.StateManager; var inverse = navigation.FindInverse(); + var targetEntityType = navigation.GetTargetType(); foreach (var oldValue in removed) { - var oldTargetEntry = stateManager.TryGetEntry(oldValue); + var oldTargetEntry = stateManager.TryGetEntry(oldValue, targetEntityType); if (oldTargetEntry != null && oldTargetEntry.EntityState != EntityState.Detached) @@ -228,7 +229,7 @@ public virtual void NavigationCollectionChanged( if (inverse != null && ReferenceEquals(oldTargetEntry[inverse], entry.Entity) - && oldTargetEntry.StateManager.TryGetEntry(oldTargetEntry.Entity, throwOnNonUniqueness: false) != null) + && oldTargetEntry.StateManager.TryGetEntry(oldTargetEntry.Entity, targetEntityType) != null) { SetNavigation(oldTargetEntry, inverse, null); } @@ -244,7 +245,9 @@ public virtual void NavigationCollectionChanged( foreach (var newValue in added) { - var newTargetEntry = stateManager.GetOrCreateEntry(newValue); + var newTargetEntry = targetEntityType.HasDefiningNavigation() + ? stateManager.GetOrCreateEntry(newValue, targetEntityType) + : stateManager.GetOrCreateEntry(newValue); if (newTargetEntry.EntityState != EntityState.Detached) { @@ -351,7 +354,7 @@ var targetDependentEntry ConditionallyNullForeignKeyProperties(targetDependentEntry, newPrincipalEntry, foreignKey); if (ReferenceEquals(targetDependentEntry[dependentToPrincipal], newPrincipalEntry.Entity) - && targetDependentEntry.StateManager.TryGetEntry(targetDependentEntry.Entity, throwOnNonUniqueness: false) != null) + && targetDependentEntry.StateManager.TryGetEntry(targetDependentEntry.Entity, foreignKey.DeclaringEntityType) != null) { SetNavigation(targetDependentEntry, dependentToPrincipal, null); } @@ -365,7 +368,7 @@ var targetDependentEntry } else if (oldPrincipalEntry != null && ReferenceEquals(entry[dependentToPrincipal], oldPrincipalEntry.Entity) - && entry.StateManager.TryGetEntry(entry.Entity, throwOnNonUniqueness: false) != null) + && entry.StateManager.TryGetEntry(entry.Entity, foreignKey.DeclaringEntityType) != null) { SetNavigation(entry, dependentToPrincipal, null); } @@ -632,7 +635,7 @@ private void InitialFixup( var dependents = ((IEnumerable)navigationValue).Cast().ToList(); foreach (var dependentEntity in dependents) { - var dependentEntry = stateManager.TryGetEntry(dependentEntity); + var dependentEntry = stateManager.TryGetEntry(dependentEntity, foreignKey.DeclaringEntityType); if (dependentEntry == null || dependentEntry.EntityState == EntityState.Detached) { @@ -763,7 +766,7 @@ private void ToDependentFixup( var oldDependent = principalToDependent == null ? null : principalEntry[principalToDependent]; var oldDependentEntry = oldDependent != null && !ReferenceEquals(dependentEntry.Entity, oldDependent) - ? dependentEntry.StateManager.TryGetEntry(oldDependent) + ? dependentEntry.StateManager.TryGetEntry(oldDependent, foreignKey.DeclaringEntityType) : dependentEntry.StateManager.GetDependentsUsingRelationshipSnapshot(principalEntry, foreignKey) .FirstOrDefault(); @@ -776,7 +779,7 @@ private void ToDependentFixup( var dependentToPrincipal = foreignKey.DependentToPrincipal; if (dependentToPrincipal != null && ReferenceEquals(oldDependentEntry[dependentToPrincipal], principalEntry.Entity) - && oldDependentEntry.StateManager.TryGetEntry(oldDependentEntry.Entity, throwOnNonUniqueness: false) != null) + && oldDependentEntry.StateManager.TryGetEntry(oldDependentEntry.Entity, foreignKey.DeclaringEntityType) != null) { SetNavigation(oldDependentEntry, dependentToPrincipal, null); } diff --git a/src/EFCore/Metadata/Conventions/Internal/PropertyMappingValidationConvention.cs b/src/EFCore/Metadata/Conventions/Internal/PropertyMappingValidationConvention.cs index 9d6b0aa8a0a..aaaaceb4952 100644 --- a/src/EFCore/Metadata/Conventions/Internal/PropertyMappingValidationConvention.cs +++ b/src/EFCore/Metadata/Conventions/Internal/PropertyMappingValidationConvention.cs @@ -46,9 +46,8 @@ public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder) foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { var unmappedProperty = entityType.GetProperties().FirstOrDefault( - p => - (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()) || !p.IsShadowProperty) - && !IsMappedPrimitiveProperty(p)); + p => (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()) || !p.IsShadowProperty) + && !IsMappedPrimitiveProperty(p)); if (unmappedProperty != null) { @@ -113,7 +112,10 @@ var isTargetWeakOrOwned || !targetType.GetTypeInfo().Equals(entityType.ClrType.GetTypeInfo())) && entityType.GetDerivedTypes().All( dt => dt.FindDeclaredNavigation(actualProperty.Name) == null) - && !entityType.IsInDefinitionPath(targetType)) + && (!entityType.IsInOwnershipPath(targetType) + || entityType.FindOwnership().PrincipalEntityType.ClrType.GetTypeInfo().Equals(targetType.GetTypeInfo())) + && (!entityType.IsInDefinitionPath(targetType) + || entityType.DefiningEntityType.ClrType.GetTypeInfo().Equals(targetType.GetTypeInfo()))) { throw new InvalidOperationException( CoreStrings.NavigationNotAdded( diff --git a/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs index 426b0859d76..3ed1b6ef5b4 100644 --- a/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs @@ -133,13 +133,11 @@ private IReadOnlyList FindRelationshipCandidates(Internal var targetOwnership = candidateTargetEntityType.FindOwnership(); if (targetOwnership != null && (targetOwnership.PrincipalEntityType != entityTypeBuilder.Metadata - || targetOwnership.PrincipalToDependent.Name != navigationPropertyInfo.Name)) + || targetOwnership.PrincipalToDependent.Name != navigationPropertyInfo.Name) + && (ownership == null + || ownership.PrincipalEntityType != candidateTargetEntityType)) { - if (ownership == null - || ownership.PrincipalEntityType != candidateTargetEntityType) - { - continue; - } + continue; } } else if (ownership != null @@ -181,10 +179,12 @@ private IReadOnlyList FindRelationshipCandidates(Internal || entityType.IsQueryType || (ownership != null && !candidateTargetEntityType.IsInOwnershipPath(entityTypeBuilder.Metadata) + && (!candidateTargetEntityType.HasDefiningNavigation() + || candidateTargetEntityType.DefiningEntityType != entityType) && (ownership.PrincipalEntityType != candidateTargetEntityType || ownership.PrincipalToDependent.Name != inversePropertyInfo.Name)) || (entityType.HasDefiningNavigation() - && !candidateTargetEntityType.IsInDefinitionPath(entityTypeBuilder.Metadata) + && !candidateTargetEntityType.IsInDefinitionPath(entityTypeBuilder.Metadata.ClrType) && (entityType.DefiningEntityType != candidateTargetEntityType || entityType.DefiningNavigationName != inversePropertyInfo.Name)) || !IsCandidateNavigationProperty( @@ -792,11 +792,18 @@ public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder rel foreach (var entityType in navigation.DeclaringEntityType.GetDerivedTypesInclusive()) { // Only run the convention if an ambiguity might have been removed - if (RemoveAmbiguous(entityType, navigation.GetTargetType().ClrType) - | RemoveAmbiguous(navigation.GetTargetType(), entityType.ClrType)) + var ambiguityRemoved = RemoveAmbiguous(entityType, navigation.GetTargetType().ClrType); + var targetAmbiguityRemoved = RemoveAmbiguous(navigation.GetTargetType(), entityType.ClrType); + + if (ambiguityRemoved) { DiscoverRelationships(entityType.Builder); } + + if (targetAmbiguityRemoved) + { + DiscoverRelationships(navigation.GetTargetType().Builder); + } } if (relationshipBuilder.Metadata.Builder == null) @@ -811,7 +818,7 @@ public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder rel /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder relationshipBuilder) + InternalRelationshipBuilder IForeignKeyOwnershipChangedConvention.Apply(InternalRelationshipBuilder relationshipBuilder) { DiscoverRelationships(relationshipBuilder.Metadata.DeclaringEntityType.Builder); return relationshipBuilder; diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index b254053d7f5..861909352b1 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -249,23 +249,44 @@ public static EntityType FindInDefinitionPath([NotNull] this EntityType entityTy /// directly from your code. This API may change or be removed in future releases. /// public static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) - => FindInDefinitionPath(entityType, targetType) != null; + => entityType.FindInDefinitionPath(targetType) != null; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] string targetTypeName) - => FindInDefinitionPath(entityType, targetTypeName) != null; + => entityType.FindInDefinitionPath(targetTypeName) != null; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] IEntityType targetType) - => targetType.ClrType == null - ? FindInDefinitionPath(entityType, targetType.Name) != null - : FindInDefinitionPath(entityType, targetType.ClrType) != null; + public static IEntityType FindInOwnershipPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) + { + var owner = entityType; + while (true) + { + var ownership = owner.FindOwnership(); + if (ownership == null) + { + return null; + } + + owner = ownership.PrincipalEntityType; + if (owner.ClrType == targetType) + { + return owner; + } + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static bool IsInOwnershipPath([NotNull] this EntityType entityType, [NotNull] Type targetType) + => entityType.FindInOwnershipPath(targetType) != null; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 372584a6555..e87caa71896 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -718,7 +718,22 @@ public virtual bool Ignore([NotNull] string name, ConfigurationSource configurat return false; } - if (foreignKey.DeclaringEntityType.Builder.RemoveForeignKey( + var isDependent = navigation.IsDependentToPrincipal(); + var navigationConfigurationSource = isDependent + ? foreignKey.GetDependentToPrincipalConfigurationSource() + : foreignKey.GetPrincipalToDependentConfigurationSource(); + if (foreignKey.GetConfigurationSource() != navigationConfigurationSource) + { + if (foreignKey.Builder.Navigations( + isDependent ? PropertyIdentity.None : (PropertyIdentity?)null, + isDependent ? (PropertyIdentity?)null : PropertyIdentity.None, + configurationSource) == null) + { + Metadata.Unignore(name); + return false; + } + } + else if (foreignKey.DeclaringEntityType.Builder.RemoveForeignKey( foreignKey, configurationSource, canOverrideSameSource: configurationSource == ConfigurationSource.Explicit) == null) { Metadata.Unignore(name); diff --git a/src/EFCore/Query/EntityQueryModelVisitor.cs b/src/EFCore/Query/EntityQueryModelVisitor.cs index 34db88c8f5f..69895a38f0e 100644 --- a/src/EFCore/Query/EntityQueryModelVisitor.cs +++ b/src/EFCore/Query/EntityQueryModelVisitor.cs @@ -323,7 +323,7 @@ protected virtual void OptimizeQueryModel( // Rewrite includes/navigations var includeCompiler = new IncludeCompiler(QueryCompilationContext, _querySourceTracingExpressionVisitorFactory); - includeCompiler.CompileIncludes(queryModel, TrackResults(queryModel), asyncQuery); + includeCompiler.CompileIncludes(queryModel, TrackResults(queryModel), asyncQuery, shouldThrow: false); queryModel.TransformExpressions(new CollectionNavigationSubqueryInjector(this).Visit); queryModel.TransformExpressions(new CollectionNavigationSetOperatorSubqueryInjector(this).Visit); @@ -340,7 +340,7 @@ protected virtual void OptimizeQueryModel( navigationRewritingExpressionVisitor.Rewrite(queryModel, parentQueryModel: null); - includeCompiler.CompileIncludes(queryModel, TrackResults(queryModel), asyncQuery); + includeCompiler.CompileIncludes(queryModel, TrackResults(queryModel), asyncQuery, shouldThrow: true); navigationRewritingExpressionVisitor.Rewrite(queryModel, parentQueryModel: null); diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/AdditionalFromClauseOptimizingQueryModelVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/AdditionalFromClauseOptimizingQueryModelVisitor.cs index c926bc3a652..13b274d9d3c 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/AdditionalFromClauseOptimizingQueryModelVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/AdditionalFromClauseOptimizingQueryModelVisitor.cs @@ -16,6 +16,17 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal /// public class AdditionalFromClauseOptimizingQueryModelVisitor : QueryModelVisitorBase { + private QueryCompilationContext _queryCompilationContext; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public AdditionalFromClauseOptimizingQueryModelVisitor(QueryCompilationContext queryCompilationContext) + { + _queryCompilationContext = queryCompilationContext; + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -57,6 +68,12 @@ public override void VisitAdditionalFromClause( ShiftBodyClauses(subQueryExpression.QueryModel, newSubQueryModel); + var entityType = _queryCompilationContext.FindEntityType(subQueryExpression.QueryModel.MainFromClause); + if (entityType != null) + { + _queryCompilationContext.AddOrUpdateMapping(newMainFromClause, entityType); + } + var newSubQueryExpression = new SubQueryExpression(newSubQueryModel); groupJoinClause.JoinClause.InnerSequence = newSubQueryExpression; diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/CollectionNavigationSubqueryInjector.cs b/src/EFCore/Query/ExpressionVisitors/Internal/CollectionNavigationSubqueryInjector.cs index 46c01d934c7..ba6e2c5d4c8 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/CollectionNavigationSubqueryInjector.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/CollectionNavigationSubqueryInjector.cs @@ -136,11 +136,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } - private static Expression InjectSubquery(Expression expression, INavigation collectionNavigation) + private Expression InjectSubquery(Expression expression, INavigation collectionNavigation) { var targetType = collectionNavigation.GetTargetType().ClrType; var mainFromClause = new MainFromClause(targetType.Name.Substring(0, 1).ToLowerInvariant(), targetType, expression); var selector = new QuerySourceReferenceExpression(mainFromClause); + _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(mainFromClause, collectionNavigation.GetTargetType()); var subqueryModel = new QueryModel(mainFromClause, new SelectClause(selector)); var subqueryExpression = new SubQueryExpression(subqueryModel); diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs index cbbe1ee4002..82260872acd 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs @@ -225,9 +225,6 @@ var querySourceReferenceFindingExpressionTreeVisitor var clonedParentQuerySourceReferenceExpression = (QuerySourceReferenceExpression)querySourceMapping.GetExpression(parentQuerySource); - var clonedParentQuerySource - = clonedParentQuerySourceReferenceExpression.ReferencedQuerySource; - var parentItemName = parentQuerySource.HasGeneratedItemName() ? navigation.DeclaringEntityType.DisplayName()[0].ToString().ToLowerInvariant() diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs index 41fa10852e8..fd43545bde7 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/NavigationRewritingExpressionVisitor.cs @@ -1023,6 +1023,7 @@ var optionalNavigationInChain var innerQuerySourceReferenceExpression = new QuerySourceReferenceExpression(_queryModel.MainFromClause); + _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(_queryModel.MainFromClause, targetEntityType); var leftKeyAccess = CreateKeyAccessExpression( querySourceReferenceExpression, @@ -1048,10 +1049,9 @@ var innerQuerySourceReferenceExpression var navigationJoin = navigationJoins - .FirstOrDefault( - nj => - nj.QuerySource == querySourceReferenceExpression.ReferencedQuerySource - && nj.Navigation == navigation); + .FirstOrDefault(nj => + nj.QuerySource == querySourceReferenceExpression.ReferencedQuerySource + && nj.Navigation == navigation); if (navigationJoin == null) { @@ -1123,6 +1123,7 @@ var groupJoinClause var groupJoinSubqueryModelMainFromClause = new MainFromClause(joinClause.ItemName + "_groupItem", joinClause.ItemType, groupReferenceExpression); var newQuerySourceReferenceExpression = new QuerySourceReferenceExpression(groupJoinSubqueryModelMainFromClause); + _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(groupJoinSubqueryModelMainFromClause, targetEntityType); var groupJoinSubqueryModel = new QueryModel( groupJoinSubqueryModelMainFromClause, @@ -1145,9 +1146,8 @@ var groupJoinClause querySourceMapping.AddMapping(groupJoinSubqueryMainFromClause, newQuerySourceReferenceExpression); groupJoinSubqueryModel.TransformExpressions( - e => - ReferenceReplacingExpressionVisitor - .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); + e => ReferenceReplacingExpressionVisitor + .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); } var defaultIfEmptySubquery = new SubQueryExpression(groupJoinSubqueryModel); @@ -1199,9 +1199,8 @@ private Expression RewriteSelectManyNavigationsIntoJoins( querySourceMapping.AddMapping(additionalFromClauseBeingProcessed, querySourceReferenceExpression); _queryModel.TransformExpressions( - e => - ReferenceReplacingExpressionVisitor - .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); + e => ReferenceReplacingExpressionVisitor + .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); AdjustIncludeAnnotations(querySourceMapping, additionalFromClauseBeingProcessed, querySourceReferenceExpression.ReferencedQuerySource); diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs index 154dab08e64..8833a1c8c52 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs @@ -277,7 +277,7 @@ var groupResultOperator .FromExpression) .QueryModel.ResultOperators.Last(); - var properties = MemberAccessBindingExpressionVisitor.GetPropertyPath( + MemberAccessBindingExpressionVisitor.GetPropertyPath( queryModel.SelectClause.Selector, _queryModelVisitor.QueryCompilationContext, out var qsre); if (qsre != null diff --git a/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs b/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs index 698585f1406..755dcbe4603 100644 --- a/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs @@ -144,6 +144,8 @@ var parentItemName var collectionQuerySourceReferenceExpression = new QuerySourceReferenceExpression(collectionQueryModel.MainFromClause); + _queryCompilationContext.AddOrUpdateMapping(collectionQueryModel.MainFromClause, navigation.GetTargetType()); + var joinQuerySourceReferenceExpression = CreateJoinToParentQuery( clonedParentQueryModel, diff --git a/src/EFCore/Query/Internal/IncludeCompiler.IncludeLoadTreeNode.cs b/src/EFCore/Query/Internal/IncludeCompiler.IncludeLoadTreeNode.cs index ca72f9d9ec0..581237b0f11 100644 --- a/src/EFCore/Query/Internal/IncludeCompiler.IncludeLoadTreeNode.cs +++ b/src/EFCore/Query/Internal/IncludeCompiler.IncludeLoadTreeNode.cs @@ -95,7 +95,8 @@ private Expression CompileCollectionInclude( collectionId = collectionIncludeId++; } - var targetType = Navigation.GetTargetType().ClrType; + var targetEntityType = Navigation.GetTargetType(); + var targetType = targetEntityType.ClrType; var mainFromClause = new MainFromClause( @@ -108,6 +109,8 @@ var mainFromClause var querySourceReferenceExpression = new QuerySourceReferenceExpression(mainFromClause); + queryCompilationContext.AddOrUpdateMapping(mainFromClause, targetEntityType); + var collectionQueryModel = new QueryModel( mainFromClause, diff --git a/src/EFCore/Query/Internal/IncludeCompiler.cs b/src/EFCore/Query/Internal/IncludeCompiler.cs index bf9290a5cf6..76ebe950085 100644 --- a/src/EFCore/Query/Internal/IncludeCompiler.cs +++ b/src/EFCore/Query/Internal/IncludeCompiler.cs @@ -69,7 +69,8 @@ public IncludeCompiler( public virtual void CompileIncludes( [NotNull] QueryModel queryModel, bool trackingQuery, - bool asyncQuery) + bool asyncQuery, + bool shouldThrow) { if (queryModel.GetOutputDataInfo() is StreamedScalarValueInfo) { @@ -78,7 +79,7 @@ public virtual void CompileIncludes( _targetQueryModel = _targetQueryModel ?? queryModel; - foreach (var includeLoadTree in CreateIncludeLoadTrees(queryModel)) + foreach (var includeLoadTree in CreateIncludeLoadTrees(queryModel, shouldThrow)) { includeLoadTree.Compile( _queryCompilationContext, @@ -127,7 +128,7 @@ public virtual void LogIgnoredIncludes() } } - private IEnumerable CreateIncludeLoadTrees(QueryModel queryModel) + private IEnumerable CreateIncludeLoadTrees(QueryModel queryModel, bool shouldThrow) { var querySourceTracingExpressionVisitor = _querySourceTracingExpressionVisitorFactory.Create(); @@ -176,7 +177,11 @@ var includeLoadTree includeLoadTrees.Add(includeLoadTree = new IncludeLoadTree(querySourceReferenceExpression)); } - PopulateIncludeLoadTree(includeResultOperator, includeLoadTree); + if (!TryPopulateIncludeLoadTree(includeResultOperator, includeLoadTree, shouldThrow)) + { + includeLoadTrees.Remove(includeLoadTree); + continue; + } _queryCompilationContext.Logger.NavigationIncluded(includeResultOperator); _includeResultOperators.Remove(includeResultOperator); @@ -185,7 +190,10 @@ var includeLoadTree return includeLoadTrees; } - private void PopulateIncludeLoadTree(IncludeResultOperator includeResultOperator, IncludeLoadTree includeLoadTree) + private bool TryPopulateIncludeLoadTree( + IncludeResultOperator includeResultOperator, + IncludeLoadTree includeLoadTree, + bool shouldThrow) { if (includeResultOperator.NavigationPaths != null) { @@ -194,7 +202,7 @@ private void PopulateIncludeLoadTree(IncludeResultOperator includeResultOperator includeLoadTree.AddLoadPath(navigationPath); } - return; + return true; } IEntityType entityType = null; @@ -222,17 +230,25 @@ private void PopulateIncludeLoadTree(IncludeResultOperator includeResultOperator if (entityType == null) { - throw new InvalidOperationException( - CoreStrings.IncludeNotSpecifiedDirectlyOnEntityType( - includeResultOperator.ToString(), - includeResultOperator.NavigationPropertyPaths.FirstOrDefault())); + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.IncludeNotSpecifiedDirectlyOnEntityType( + includeResultOperator.ToString(), + includeResultOperator.NavigationPropertyPaths.FirstOrDefault())); + } + + return false; } - WalkNavigations(entityType, includeResultOperator.NavigationPropertyPaths, includeLoadTree); + return WalkNavigations(entityType, includeResultOperator.NavigationPropertyPaths, includeLoadTree, shouldThrow); } - private static void WalkNavigations( - IEntityType entityType, IReadOnlyList navigationPropertyPaths, IncludeLoadTree includeLoadTree) + private static bool WalkNavigations( + IEntityType entityType, + IReadOnlyList navigationPropertyPaths, + IncludeLoadTree includeLoadTree, + bool shouldThrow) { var longestMatchFound = WalkNavigationsInternal( @@ -244,11 +260,18 @@ var longestMatchFound if (longestMatchFound.Depth < navigationPropertyPaths.Count) { - throw new InvalidOperationException( - CoreStrings.IncludeBadNavigation( - navigationPropertyPaths[longestMatchFound.Depth], - longestMatchFound.EntityType.DisplayName())); + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.IncludeBadNavigation( + navigationPropertyPaths[longestMatchFound.Depth], + longestMatchFound.EntityType.DisplayName())); + } + + return false; } + + return true; } private static (int Depth, IEntityType EntityType) WalkNavigationsInternal( diff --git a/src/EFCore/Query/Internal/QueryOptimizer.cs b/src/EFCore/Query/Internal/QueryOptimizer.cs index d23fb7ea687..c09d1de4fe4 100644 --- a/src/EFCore/Query/Internal/QueryOptimizer.cs +++ b/src/EFCore/Query/Internal/QueryOptimizer.cs @@ -53,7 +53,6 @@ void IFromClause.CopyFromSource(IFromClause source) private QueryCompilationContext _queryCompilationContext; private readonly TransformingQueryModelExpressionVisitor _transformingExpressionVisitor; - private readonly AdditionalFromClauseOptimizingQueryModelVisitor _additionalFromClauseOptimizingQueryModelVisitor; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -62,7 +61,6 @@ void IFromClause.CopyFromSource(IFromClause source) public QueryOptimizer() { _transformingExpressionVisitor = new TransformingQueryModelExpressionVisitor(this); - _additionalFromClauseOptimizingQueryModelVisitor = new AdditionalFromClauseOptimizingQueryModelVisitor(); } /// @@ -75,7 +73,7 @@ public virtual void Optimize( { _queryCompilationContext = queryCompilationContext; - _additionalFromClauseOptimizingQueryModelVisitor.VisitQueryModel(queryModel); + new AdditionalFromClauseOptimizingQueryModelVisitor(_queryCompilationContext).VisitQueryModel(queryModel); VisitQueryModel(queryModel); @@ -218,8 +216,6 @@ var subqueryInMainClauseWithoutResultOperatorsProjectingItsMainClause || emptyQueryModelWithResultOperatorThatIgnoresElementCountAndDistinctInSubquery || subqueryInMainClauseWithoutResultOperatorsProjectingItsMainClause) { - string itemName; - var querySourceMapping = new QuerySourceMapping(); var clonedSubQueryModel = subQueryModel.Clone(querySourceMapping); UpdateQueryAnnotations(subQueryModel, querySourceMapping); @@ -227,8 +223,8 @@ var subqueryInMainClauseWithoutResultOperatorsProjectingItsMainClause var innerMainFromClause = clonedSubQueryModel.MainFromClause; var isGeneratedNameOuter = fromClause.HasGeneratedItemName(); - itemName = innerMainFromClause.HasGeneratedItemName() - && !isGeneratedNameOuter + var itemName = innerMainFromClause.HasGeneratedItemName() + && !isGeneratedNameOuter ? fromClause.ItemName : innerMainFromClause.ItemName; @@ -299,7 +295,8 @@ public override void VisitResultOperator(ResultOperatorBase resultOperator, Quer } else { - var entityType = _queryCompilationContext.Model.FindEntityType(searchedItemType); + var entityType = _queryCompilationContext.Model.FindEntityType(searchedItemType) + ?? _queryCompilationContext.FindEntityType(queryModel.MainFromClause); if (entityType != null) { @@ -318,6 +315,8 @@ var newMainFromClause queryModel.MainFromClause = newMainFromClause; + _queryCompilationContext.AddOrUpdateMapping(newMainFromClause, entityType); + UpdateQuerySourceMapping( queryModel, oldQuerySource, @@ -401,13 +400,17 @@ private void UpdateQuerySourceMapping( querySourceMapping.AddMapping(oldQuerySource, newExpression); queryModel.TransformExpressions( - e => - ReferenceReplacingExpressionVisitor - .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); + e => ReferenceReplacingExpressionVisitor + .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); if (newExpression is QuerySourceReferenceExpression qsre) { var newQuerySource = qsre.ReferencedQuerySource; + var entityType = _queryCompilationContext.FindEntityType(oldQuerySource); + if (entityType != null) + { + _queryCompilationContext.AddOrUpdateMapping(newQuerySource, entityType); + } foreach (var queryAnnotation in _queryCompilationContext.QueryAnnotations.Where(qa => qa.QuerySource == oldQuerySource)) { queryAnnotation.QuerySource = newQuerySource; diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs index a481e098f1f..da85c64fd3f 100644 --- a/src/EFCore/Query/QueryCompilationContext.cs +++ b/src/EFCore/Query/QueryCompilationContext.cs @@ -195,7 +195,12 @@ public virtual IEntityType FindEntityType([NotNull] IQuerySource querySource) /// Gets the entity type mapped to the given query source /// public virtual void AddOrUpdateMapping([NotNull] IQuerySource querySource, [NotNull] IEntityType entityType) - => _querySourceEntityTypeMapping[Check.NotNull(querySource, nameof(querySource))] = entityType; + { + if (entityType.ClrType.IsAssignableFrom(querySource.ItemType)) + { + _querySourceEntityTypeMapping[Check.NotNull(querySource, nameof(querySource))] = entityType; + } + } /// /// Updates the query source mappings to the new query sources diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs index d0ce3c3bdf8..6dc4f6aed7c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs @@ -21,7 +21,7 @@ public override async Task Simple_owned_level1(bool isAsync) await base.Simple_owned_level1(isAsync); AssertSql( - @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToOne_Optional_PK_InverseId] + @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToMany_Optional_InverseId], [l1].[OneToMany_Required_InverseId], [l1].[OneToOne_Optional_PK_InverseId] FROM [Level1] AS [l1]"); } @@ -39,7 +39,7 @@ public override async Task Simple_owned_level1_level2(bool isAsync) await base.Simple_owned_level1_level2(isAsync); AssertSql( - @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[Level3_OneToOne_Optional_PK_InverseId] + @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToMany_Optional_InverseId], [l1].[OneToMany_Required_InverseId], [l1].[OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[Level3_OneToMany_Optional_InverseId], [l1].[Level3_OneToMany_Required_InverseId], [l1].[Level3_OneToOne_Optional_PK_InverseId] FROM [Level1] AS [l1]"); } @@ -69,7 +69,7 @@ public override async Task Simple_owned_level1_level2_level3(bool isAsync) await base.Simple_owned_level1_level2_level3(isAsync); AssertSql( - @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[Level3_OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level3_Optional_Id], [l1].[Level3_Required_Id], [l1].[Level4_Name], [l1].[Level4_OneToOne_Optional_PK_InverseId] + @"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToMany_Optional_InverseId], [l1].[OneToMany_Required_InverseId], [l1].[OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[Level3_OneToMany_Optional_InverseId], [l1].[Level3_OneToMany_Required_InverseId], [l1].[Level3_OneToOne_Optional_PK_InverseId], [l1].[Id], [l1].[Level3_Optional_Id], [l1].[Level3_Required_Id], [l1].[Level4_Name], [l1].[Level4_OneToMany_Optional_InverseId], [l1].[Level4_OneToMany_Required_InverseId], [l1].[Level4_OneToOne_Optional_PK_InverseId] FROM [Level1] AS [l1]"); } @@ -85,7 +85,7 @@ SELECT [t3].[Level2_Name] SELECT TOP(@__p_0) [t0].* FROM [Level1] AS [l1_inner] LEFT JOIN ( - SELECT [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToOne_Optional_PK_InverseId] + SELECT [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToOne_Optional_PK_InverseId] FROM [Level1] AS [t] WHERE [t].[Id] IS NOT NULL ) AS [t0] ON [l1_inner].[Id] = [t0].[Level1_Optional_Id] @@ -133,6 +133,26 @@ WHERE [t].[Id] IS NOT NULL ) AS [t0] ON [l1].[Id] = [t0].[Level1_Optional_Id]"); } + public override async Task SelectMany_with_Include1(bool isAsync) + { + await base.SelectMany_with_Include1(isAsync); + + AssertSql( + @"SELECT [l1.OneToMany_Optional].[Id], [l1.OneToMany_Optional].[OneToOne_Required_PK_Date], [l1.OneToMany_Optional].[Level1_Optional_Id], [l1.OneToMany_Optional].[Level1_Required_Id], [l1.OneToMany_Optional].[Level2_Name], [l1.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional].[OneToOne_Optional_PK_InverseId] +FROM [Level1] AS [l1] +INNER JOIN [Level1] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId] +ORDER BY [l1.OneToMany_Optional].[Id]", + // + @"SELECT [l1.OneToMany_Optional.OneToMany_Optional].[Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Optional_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Required_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level3_Name], [l1.OneToMany_Optional.OneToMany_Optional].[Level3_OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[Level3_OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[Level3_OneToOne_Optional_PK_InverseId] +FROM [Level1] AS [l1.OneToMany_Optional.OneToMany_Optional] +INNER JOIN ( + SELECT DISTINCT [l1.OneToMany_Optional0].[Id] + FROM [Level1] AS [l10] + INNER JOIN [Level1] AS [l1.OneToMany_Optional0] ON [l10].[Id] = [l1.OneToMany_Optional0].[OneToMany_Optional_InverseId] +) AS [t] ON [l1.OneToMany_Optional.OneToMany_Optional].[Level3_OneToMany_Optional_InverseId] = [t].[Id] +ORDER BY [t].[Id]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 4e36055525c..bf46334ce74 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -57,7 +57,8 @@ SELECT [b.LeafBAddress].* FROM [OwnedPerson] AS [b.LeafBAddress] WHERE [b.LeafBAddress].[Discriminator] = N'LeafB' ) AS [t5] ON [b].[Id] = [t5].[Id] -WHERE [a].[Discriminator] = N'LeafA'"); +WHERE [a].[Discriminator] = N'LeafA' +ORDER BY [a].[Id]"); } public override void Query_for_base_type_loads_all_owned_navs() @@ -108,7 +109,57 @@ SELECT [o.PersonAddress.Country].* FROM [OwnedPerson] AS [o.PersonAddress.Country] WHERE [o.PersonAddress.Country].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') ) AS [t6] ON [t5].[Id] = [t6].[Id] -WHERE [o].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson')"); +WHERE [o].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') +ORDER BY [o].[Id]", + // + @"SELECT [o.Orders].[Id], [o.Orders].[ClientId] +FROM [Order] AS [o.Orders] +INNER JOIN ( + SELECT DISTINCT [o0].[Id] + FROM [OwnedPerson] AS [o0] + LEFT JOIN ( + SELECT [l.LeafBAddress0].* + FROM [OwnedPerson] AS [l.LeafBAddress0] + WHERE [l.LeafBAddress0].[Discriminator] = N'LeafB' + ) AS [t7] ON [o0].[Id] = [t7].[Id] + LEFT JOIN ( + SELECT [l.LeafBAddress.Country0].* + FROM [OwnedPerson] AS [l.LeafBAddress.Country0] + WHERE [l.LeafBAddress.Country0].[Discriminator] = N'LeafB' + ) AS [t8] ON [t7].[Id] = [t8].[Id] + LEFT JOIN ( + SELECT [l.LeafAAddress0].* + FROM [OwnedPerson] AS [l.LeafAAddress0] + WHERE [l.LeafAAddress0].[Discriminator] = N'LeafA' + ) AS [t9] ON [o0].[Id] = [t9].[Id] + LEFT JOIN ( + SELECT [l.LeafAAddress.Country0].* + FROM [OwnedPerson] AS [l.LeafAAddress.Country0] + WHERE [l.LeafAAddress.Country0].[Discriminator] = N'LeafA' + ) AS [t10] ON [t9].[Id] = [t10].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress0].* + FROM [OwnedPerson] AS [b.BranchAddress0] + WHERE [b.BranchAddress0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t11] ON [o0].[Id] = [t11].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress.Country0].* + FROM [OwnedPerson] AS [b.BranchAddress.Country0] + WHERE [b.BranchAddress.Country0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t12] ON [t11].[Id] = [t12].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress0].* + FROM [OwnedPerson] AS [o.PersonAddress0] + WHERE [o.PersonAddress0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t13] ON [o0].[Id] = [t13].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress.Country0].* + FROM [OwnedPerson] AS [o.PersonAddress.Country0] + WHERE [o.PersonAddress.Country0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t14] ON [t13].[Id] = [t14].[Id] + WHERE [o0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') +) AS [t15] ON [o.Orders].[ClientId] = [t15].[Id] +ORDER BY [t15].[Id]"); } public override void No_ignored_include_warning_when_implicit_load() @@ -158,7 +209,47 @@ SELECT [o.PersonAddress.Country].* FROM [OwnedPerson] AS [o.PersonAddress.Country] WHERE [o.PersonAddress.Country].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') ) AS [t4] ON [t3].[Id] = [t4].[Id] -WHERE [o].[Discriminator] IN (N'LeafA', N'Branch')"); +WHERE [o].[Discriminator] IN (N'LeafA', N'Branch') +ORDER BY [o].[Id]", + // + @"SELECT [o.Orders].[Id], [o.Orders].[ClientId] +FROM [Order] AS [o.Orders] +INNER JOIN ( + SELECT DISTINCT [o0].[Id] + FROM [OwnedPerson] AS [o0] + LEFT JOIN ( + SELECT [l.LeafAAddress0].* + FROM [OwnedPerson] AS [l.LeafAAddress0] + WHERE [l.LeafAAddress0].[Discriminator] = N'LeafA' + ) AS [t5] ON [o0].[Id] = [t5].[Id] + LEFT JOIN ( + SELECT [l.LeafAAddress.Country0].* + FROM [OwnedPerson] AS [l.LeafAAddress.Country0] + WHERE [l.LeafAAddress.Country0].[Discriminator] = N'LeafA' + ) AS [t6] ON [t5].[Id] = [t6].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress0].* + FROM [OwnedPerson] AS [b.BranchAddress0] + WHERE [b.BranchAddress0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t7] ON [o0].[Id] = [t7].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress.Country0].* + FROM [OwnedPerson] AS [b.BranchAddress.Country0] + WHERE [b.BranchAddress.Country0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t8] ON [t7].[Id] = [t8].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress0].* + FROM [OwnedPerson] AS [o.PersonAddress0] + WHERE [o.PersonAddress0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t9] ON [o0].[Id] = [t9].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress.Country0].* + FROM [OwnedPerson] AS [o.PersonAddress.Country0] + WHERE [o.PersonAddress.Country0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t10] ON [t9].[Id] = [t10].[Id] + WHERE [o0].[Discriminator] IN (N'LeafA', N'Branch') +) AS [t11] ON [o.Orders].[ClientId] = [t11].[Id] +ORDER BY [t11].[Id]"); } public override void Query_for_leaf_type_loads_all_owned_navs() @@ -166,7 +257,7 @@ public override void Query_for_leaf_type_loads_all_owned_navs() base.Query_for_leaf_type_loads_all_owned_navs(); AssertSql( - @"SELECT [o].[Id], [o].[Discriminator], [t].[Id], [t0].[Id], [t0].[LeafAAddress_Country_Name], [t1].[Id], [t2].[Id], [t2].[BranchAddress_Country_Name], [t3].[Id], [t4].[Id], [t4].[PersonAddress_Country_Name] + @"SELECT [o].[Id], [o].[Discriminator], [t].[Id], [t0].[Id], [t0].[LeafAAddress_Country_Name], [t1].[Id], [t2].[Id], [t2].[BranchAddress_Country_Name], [t3].[Id], [t4].[Id], [t4].[PersonAddress_Country_Name] FROM [OwnedPerson] AS [o] LEFT JOIN ( SELECT [l.LeafAAddress].* @@ -198,7 +289,47 @@ SELECT [o.PersonAddress.Country].* FROM [OwnedPerson] AS [o.PersonAddress.Country] WHERE [o.PersonAddress.Country].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') ) AS [t4] ON [t3].[Id] = [t4].[Id] -WHERE [o].[Discriminator] = N'LeafA'"); +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id]", + // + @"SELECT [o.Orders].[Id], [o.Orders].[ClientId] +FROM [Order] AS [o.Orders] +INNER JOIN ( + SELECT DISTINCT [o0].[Id] + FROM [OwnedPerson] AS [o0] + LEFT JOIN ( + SELECT [l.LeafAAddress0].* + FROM [OwnedPerson] AS [l.LeafAAddress0] + WHERE [l.LeafAAddress0].[Discriminator] = N'LeafA' + ) AS [t5] ON [o0].[Id] = [t5].[Id] + LEFT JOIN ( + SELECT [l.LeafAAddress.Country0].* + FROM [OwnedPerson] AS [l.LeafAAddress.Country0] + WHERE [l.LeafAAddress.Country0].[Discriminator] = N'LeafA' + ) AS [t6] ON [t5].[Id] = [t6].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress0].* + FROM [OwnedPerson] AS [b.BranchAddress0] + WHERE [b.BranchAddress0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t7] ON [o0].[Id] = [t7].[Id] + LEFT JOIN ( + SELECT [b.BranchAddress.Country0].* + FROM [OwnedPerson] AS [b.BranchAddress.Country0] + WHERE [b.BranchAddress.Country0].[Discriminator] IN (N'LeafA', N'Branch') + ) AS [t8] ON [t7].[Id] = [t8].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress0].* + FROM [OwnedPerson] AS [o.PersonAddress0] + WHERE [o.PersonAddress0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t9] ON [o0].[Id] = [t9].[Id] + LEFT JOIN ( + SELECT [o.PersonAddress.Country0].* + FROM [OwnedPerson] AS [o.PersonAddress.Country0] + WHERE [o.PersonAddress.Country0].[Discriminator] IN (N'LeafB', N'LeafA', N'Branch', N'OwnedPerson') + ) AS [t10] ON [t9].[Id] = [t10].[Id] + WHERE [o0].[Discriminator] = N'LeafA' +) AS [t11] ON [o.Orders].[ClientId] = [t11].[Id] +ORDER BY [t11].[Id]"); } public override void Query_when_group_by() diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 0af0947d648..1a2413154bb 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -251,6 +251,7 @@ public virtual void Owned_types_can_be_mapped_to_different_tables() bb.OwnsOne( b => b.AlternateLabel, tb => { + tb.Ignore(l => l.Book); tb.HasConstraintName("AlternateLabelFK"); tb.ToTable("TT", "TS"); tb.ForSqlServerIsMemoryOptimized(); @@ -262,10 +263,18 @@ public virtual void Owned_types_can_be_mapped_to_different_tables() .ToTable("ST11", "SS11"); ((Navigation)ab.OwnedEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))).AddAnnotation("Foo", "Bar"); }); + tb.OwnsOne( + l => l.SpecialBookLabel, sb => + { + sb.ToTable("ST2", "SS2"); + sb.OwnsOne(s => s.AnotherBookLabel) + .ToTable("AT21", "AS21"); + }); }); bb.OwnsOne( b => b.Label, lb => { + lb.Ignore(l => l.Book); lb.ToTable("LT", "LS"); lb.OwnsOne( l => l.SpecialBookLabel, sb => @@ -274,20 +283,13 @@ public virtual void Owned_types_can_be_mapped_to_different_tables() sb.OwnsOne(a => a.AnotherBookLabel) .ToTable("AT11", "AS11"); }); - }); - bb.OwnsOne(b => b.Label).OwnsOne( - l => l.AnotherBookLabel, ab => - { - ab.ToTable("AT2", "AS2"); - ab.OwnsOne(a => a.SpecialBookLabel) - .ToTable("ST21", "SS21"); - }); - bb.OwnsOne(b => b.AlternateLabel).OwnsOne( - l => l.SpecialBookLabel, sb => - { - sb.ToTable("ST2", "SS2"); - sb.OwnsOne(s => s.AnotherBookLabel) - .ToTable("AT21", "AS21"); + lb.OwnsOne( + l => l.AnotherBookLabel, ab => + { + ab.ToTable("AT2", "AS2"); + ab.OwnsOne(a => a.SpecialBookLabel) + .ToTable("ST21", "SS21"); + }); }); }); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index bd88ed252d7..3874e859b4d 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Metadata; @@ -625,15 +624,33 @@ public virtual void Can_configure_chained_ownerships() modelBuilder.Entity().OwnsOne( b => b.Label, bb => { - bb.OwnsOne(l => l.AnotherBookLabel, ab => { ab.OwnsOne(l => l.SpecialBookLabel); }); - bb.OwnsOne(l => l.SpecialBookLabel, sb => { sb.OwnsOne(l => l.AnotherBookLabel); }); + bb.Ignore(l => l.Book); + bb.OwnsOne(l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.OwnsOne(l => l.SpecialBookLabel).Ignore(l => l.Book).Ignore(s => s.BookLabel); + }); + bb.OwnsOne(l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.OwnsOne(l => l.AnotherBookLabel).Ignore(l => l.Book); + }); }); modelBuilder.Entity().OwnsOne( b => b.AlternateLabel, bb => { - bb.OwnsOne(l => l.SpecialBookLabel, sb => { sb.OwnsOne(l => l.AnotherBookLabel); }); - bb.OwnsOne(l => l.AnotherBookLabel, ab => { ab.OwnsOne(l => l.SpecialBookLabel); }); + bb.Ignore(l => l.Book); + bb.OwnsOne(l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.OwnsOne(l => l.AnotherBookLabel).Ignore(l => l.Book); + }); + bb.OwnsOne(l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.OwnsOne(l => l.SpecialBookLabel).Ignore(l => l.Book).Ignore(s => s.BookLabel); + }); }); modelBuilder.Validate(); @@ -647,16 +664,42 @@ public virtual void Can_configure_chained_ownerships_different_order() var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().OwnsOne(b => b.Label, bb => - { bb.OwnsOne(l => l.AnotherBookLabel, ab => { ab.OwnsOne(l => l.SpecialBookLabel); }); }); + { + bb.OwnsOne(l => l.AnotherBookLabel, ab => + { + ab.OwnsOne(l => l.SpecialBookLabel).Ignore(s => s.BookLabel).Ignore(l => l.Book); + ab.Ignore(l => l.Book); + }); + bb.Ignore(l => l.Book); + }); modelBuilder.Entity().OwnsOne(b => b.AlternateLabel, bb => - { bb.OwnsOne(l => l.AnotherBookLabel, ab => { ab.OwnsOne(l => l.SpecialBookLabel); }); }); + { + bb.OwnsOne(l => l.AnotherBookLabel, ab => + { + ab.OwnsOne(l => l.SpecialBookLabel).Ignore(s => s.BookLabel).Ignore(l => l.Book); + ab.Ignore(l => l.Book); + }); + bb.Ignore(l => l.Book); + }); modelBuilder.Entity().OwnsOne(b => b.Label, bb => - { bb.OwnsOne(l => l.SpecialBookLabel, sb => { sb.OwnsOne(l => l.AnotherBookLabel); }); }); + { + bb.OwnsOne(l => l.SpecialBookLabel, sb => + { + sb.OwnsOne(l => l.AnotherBookLabel).Ignore(l => l.Book); + sb.Ignore(l => l.Book); + }); + }); modelBuilder.Entity().OwnsOne(b => b.AlternateLabel, bb => - { bb.OwnsOne(l => l.SpecialBookLabel, sb => { sb.OwnsOne(l => l.AnotherBookLabel); }); }); + { + bb.OwnsOne(l => l.SpecialBookLabel, sb => + { + sb.OwnsOne(l => l.AnotherBookLabel).Ignore(l => l.Book); + sb.Ignore(l => l.Book); + }); + }); modelBuilder.Validate(); @@ -669,8 +712,8 @@ public virtual void Can_configure_all_ownerships_with_one_call() var modelBuilder = CreateModelBuilder(); modelBuilder.Owned(); - modelBuilder.Entity().OwnsOne(b => b.Label); - modelBuilder.Entity().OwnsOne(b => b.AlternateLabel); + modelBuilder.Entity().OwnsOne(b => b.Label).Ignore(l => l.Book); + modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).Ignore(l => l.Book); modelBuilder.Validate(); @@ -684,8 +727,8 @@ public virtual void Can_configure_all_ownerships_with_one_call_afterwards() modelBuilder.Entity(); modelBuilder.Owned(); - modelBuilder.Entity().OwnsOne(b => b.Label); - modelBuilder.Entity().OwnsOne(b => b.AlternateLabel); + modelBuilder.Entity().OwnsOne(b => b.Label).Ignore(l => l.Book); + modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).Ignore(l => l.Book); modelBuilder.Validate(); @@ -696,9 +739,12 @@ protected virtual void VerifyOwnedBookLabelModel(IMutableModel model) { var bookOwnership1 = model.FindEntityType(typeof(Book)).FindNavigation(nameof(Book.Label)).ForeignKey; var bookOwnership2 = model.FindEntityType(typeof(Book)).FindNavigation(nameof(Book.AlternateLabel)).ForeignKey; + Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); Assert.Equal(1, bookOwnership1.DeclaringEntityType.GetForeignKeys().Count()); Assert.Equal(1, bookOwnership1.DeclaringEntityType.GetForeignKeys().Count()); + Assert.Null(bookOwnership1.DependentToPrincipal); + Assert.Null(bookOwnership2.DependentToPrincipal); var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation( nameof(BookLabel.AnotherBookLabel)).ForeignKey; @@ -709,6 +755,11 @@ protected virtual void VerifyOwnedBookLabelModel(IMutableModel model) var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation( nameof(BookLabel.SpecialBookLabel)).ForeignKey; + Assert.Null(bookLabel1Ownership1.DependentToPrincipal); + Assert.Equal(nameof(SpecialBookLabel.BookLabel), bookLabel1Ownership2.DependentToPrincipal.Name); + Assert.Null(bookLabel2Ownership1.DependentToPrincipal); + Assert.Equal(nameof(SpecialBookLabel.BookLabel), bookLabel2Ownership2.DependentToPrincipal.Name); + var bookLabel1Ownership1Subownership = bookLabel1Ownership1.DeclaringEntityType.FindNavigation( nameof(BookLabel.SpecialBookLabel)).ForeignKey; var bookLabel1Ownership2Subownership = bookLabel1Ownership2.DeclaringEntityType.FindNavigation( @@ -717,6 +768,7 @@ protected virtual void VerifyOwnedBookLabelModel(IMutableModel model) nameof(BookLabel.SpecialBookLabel)).ForeignKey; var bookLabel2Ownership2Subownership = bookLabel2Ownership2.DeclaringEntityType.FindNavigation( nameof(BookLabel.AnotherBookLabel)).ForeignKey; + Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); Assert.Equal(1, bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys().Count()); @@ -727,6 +779,10 @@ protected virtual void VerifyOwnedBookLabelModel(IMutableModel model) Assert.Equal(1, bookLabel1Ownership2Subownership.DeclaringEntityType.GetForeignKeys().Count()); Assert.Equal(1, bookLabel2Ownership1Subownership.DeclaringEntityType.GetForeignKeys().Count()); Assert.Equal(1, bookLabel2Ownership2Subownership.DeclaringEntityType.GetForeignKeys().Count()); + Assert.Equal(nameof(SpecialBookLabel.AnotherBookLabel), bookLabel1Ownership1Subownership.DependentToPrincipal.Name); + Assert.Equal(nameof(AnotherBookLabel.SpecialBookLabel), bookLabel1Ownership2Subownership.DependentToPrincipal.Name); + Assert.Equal(nameof(SpecialBookLabel.AnotherBookLabel), bookLabel2Ownership1Subownership.DependentToPrincipal.Name); + Assert.Equal(nameof(AnotherBookLabel.SpecialBookLabel), bookLabel2Ownership2Subownership.DependentToPrincipal.Name); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel)));