diff --git a/src/EFCore/ChangeTracking/EntityEntry.cs b/src/EFCore/ChangeTracking/EntityEntry.cs index 16d917f2ce7..a7a7e1d72f8 100644 --- a/src/EFCore/ChangeTracking/EntityEntry.cs +++ b/src/EFCore/ChangeTracking/EntityEntry.cs @@ -203,10 +203,18 @@ public virtual NavigationEntry Navigation([NotNull] string propertyName) /// navigation properties of this entity. /// public virtual IEnumerable Navigations - => InternalEntry.EntityType.GetNavigations().Select( - navigation => navigation.IsCollection - ? (NavigationEntry)new CollectionEntry(InternalEntry, navigation) - : new ReferenceEntry(InternalEntry, navigation)); + { + get + { + var entityType = InternalEntry.EntityType; + return entityType.GetNavigations() + .Concat(entityType.GetSkipNavigations()) + .Select( + navigation => navigation.IsCollection + ? (NavigationEntry)new CollectionEntry(InternalEntry, navigation.Name) + : new ReferenceEntry(InternalEntry, navigation.Name)); + } + } /// /// Provides access to change tracking information and operations for a given @@ -273,8 +281,16 @@ public virtual CollectionEntry Collection([NotNull] string propertyName) /// collection navigation properties of this entity. /// public virtual IEnumerable Collections - => InternalEntry.EntityType.GetNavigations().Where(n => n.IsCollection) - .Select(navigation => new CollectionEntry(InternalEntry, navigation)); + { + get + { + var entityType = InternalEntry.EntityType; + return entityType.GetNavigations() + .Concat(entityType.GetSkipNavigations()) + .Where(navigation => navigation.IsCollection) + .Select(navigation => new CollectionEntry(InternalEntry, navigation.Name)); + } + } /// /// diff --git a/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs b/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs index fd2773b413f..06bc8e0a424 100644 --- a/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs +++ b/src/EFCore/ChangeTracking/Internal/EntityEntryGraphIterator.cs @@ -93,7 +93,8 @@ public virtual async Task TraverseGraphAsync( } var internalEntityEntry = node.GetInfrastructure(); - var navigations = internalEntityEntry.EntityType.GetNavigations(); + var navigations = internalEntityEntry.EntityType.GetNavigations() + .Concat(internalEntityEntry.EntityType.GetSkipNavigations()); var stateManager = internalEntityEntry.StateManager; foreach (var navigation in navigations) diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs index c4be54752c2..3842c0d0b53 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs @@ -85,7 +85,9 @@ public virtual void Unsubscribe(InternalEntityEntry entry) if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) { - foreach (var navigation in entityType.GetNavigations().Where(n => n.IsCollection)) + foreach (var navigation in entityType.GetNavigations() + .Concat(entityType.GetSkipNavigations()) + .Where(n => n.IsCollection)) { AsINotifyCollectionChanged(entry, navigation, entityType, changeTrackingStrategy).CollectionChanged -= entry.HandleINotifyCollectionChanged; diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index c6e240c9d8f..c5cd850250d 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -634,7 +634,8 @@ public static IProperty GetProperty([NotNull] this IEntityType entityType, [NotN var property = entityType.FindProperty(name); if (property == null) { - if (entityType.FindNavigation(name) != null) + if (entityType.FindNavigation(name) != null + || entityType.FindSkipNavigation(name) != null) { throw new InvalidOperationException( CoreStrings.PropertyIsNavigation( diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index fc01e962cd6..cb67c23ea4c 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -419,12 +419,19 @@ public static IEnumerable GetNotificationProperties( { yield return navigation; } + + foreach (var navigation in entityType.GetSkipNavigations()) + { + yield return navigation; + } } else { // ReSharper disable once AssignNullToNotNullAttribute - var property = (IPropertyBase)entityType.FindProperty(propertyName) - ?? entityType.FindNavigation(propertyName); + var property = entityType.FindProperty(propertyName) + ?? entityType.FindNavigation(propertyName) + ?? (IPropertyBase)entityType.FindSkipNavigation(propertyName); + if (property != null) { yield return property; diff --git a/test/EFCore.InMemory.FunctionalTests/ManyToManyTrackingInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/ManyToManyTrackingInMemoryTest.cs index cf72a45a197..4fc318659d3 100644 --- a/test/EFCore.InMemory.FunctionalTests/ManyToManyTrackingInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ManyToManyTrackingInMemoryTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -22,10 +23,24 @@ protected override void ExecuteWithStrategyInTransaction( Action nestedTestOperation2 = null, Action nestedTestOperation3 = null) { - base.ExecuteWithStrategyInTransaction(testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + base.ExecuteWithStrategyInTransaction( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + Fixture.Reseed(); } + protected override async Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + { + await base.ExecuteWithStrategyInTransactionAsync( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + + await Fixture.ReseedAsync(); + } + protected override bool SupportsDatabaseDefaults => false; diff --git a/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs b/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs index 7610d00545a..24666d65680 100644 --- a/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -17,11 +18,13 @@ namespace Microsoft.EntityFrameworkCore public abstract class ManyToManyTrackingTestBase : IClassFixture where TFixture : ManyToManyTrackingTestBase.ManyToManyTrackingFixtureBase { - [ConditionalFact] - public virtual void Can_insert_many_to_many_composite_with_navs() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_composite_with_navs(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -66,18 +69,34 @@ public virtual void Can_insert_many_to_many_composite_with_navs() rightEntities[0].CompositeKeySkipFull.Add(leftEntities[1]); // 21 - 12 rightEntities[0].CompositeKeySkipFull.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Key1 > 7700).Include(e => e.LeafSkipFull).ToList(); + var queryable = context.Set().Where(e => e.Key1 > 7700).Include(e => e.LeafSkipFull); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries() @@ -410,11 +429,13 @@ static void ValidateJoinNavigations(DbContext context) } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_composite_shared_with_navs() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_composite_shared_with_navs(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -459,18 +480,34 @@ public virtual void Can_insert_many_to_many_composite_shared_with_navs() rightEntities[0].CompositeKeySkipShared.Add(leftEntities[1]); // 21 - 12 rightEntities[0].CompositeKeySkipShared.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Key1 > 7700).Include(e => e.RootSkipShared).ToList(); + var queryable = context.Set().Where(e => e.Key1 > 7700).Include(e => e.RootSkipShared); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries() @@ -727,11 +764,13 @@ static void ValidateNavigations(List ones, List } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_composite_additional_pk_with_navs() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_composite_additional_pk_with_navs(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -776,18 +815,34 @@ public virtual void Can_insert_many_to_many_composite_additional_pk_with_navs() rightEntities[0].CompositeKeySkipFull.Add(leftEntities[1]); // 21 - 12 rightEntities[0].CompositeKeySkipFull.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Key1 > 7700).Include(e => e.ThreeSkipFull).ToList(); + var queryable = context.Set().Where(e => e.Key1 > 7700).Include(e => e.ThreeSkipFull); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries() @@ -1121,11 +1176,13 @@ static void ValidateJoinNavigations(DbContext context) } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_self_shared() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_self_shared(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -1152,18 +1209,34 @@ public virtual void Can_insert_many_to_many_self_shared() rightEntities[0].SelfSkipSharedRight.Add(leftEntities[1]); // 21 - 12 rightEntities[0].SelfSkipSharedRight.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.SelfSkipSharedLeft).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.SelfSkipSharedLeft); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(6, results.Count); var leftEntities = context.ChangeTracker.Entries() @@ -1297,11 +1370,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_with_navs() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_with_navs(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -1328,18 +1403,34 @@ public virtual void Can_insert_many_to_many_with_navs() rightEntities[0].TwoSkipFull.Add(leftEntities[1]); // 21 - 12 rightEntities[0].TwoSkipFull.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipFull).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipFull); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -1484,11 +1575,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_with_inheritance() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_with_inheritance(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -1515,18 +1608,34 @@ public virtual void Can_insert_many_to_many_with_inheritance() rightEntities[0].OneSkip.Add(leftEntities[1]); // 21 - 12 rightEntities[0].OneSkip.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.BranchSkip).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.BranchSkip); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -1654,11 +1763,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_self_with_payload() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_self_with_payload(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -1685,18 +1796,34 @@ public virtual void Can_insert_many_to_many_self_with_payload() rightEntities[0].SelfSkipPayloadRight.Add(leftEntities[1]); // 21 - 12 rightEntities[0].SelfSkipPayloadRight.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities, postSave: false); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities, postSave: true); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.SelfSkipPayloadLeft).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.SelfSkipPayloadLeft); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(6, results.Count); var leftEntities = context.ChangeTracker.Entries() @@ -1875,11 +2002,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_shared_with_payload() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_shared_with_payload(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -1906,18 +2035,34 @@ public virtual void Can_insert_many_to_many_shared_with_payload() rightEntities[0].OneSkipPayloadFullShared.Add(leftEntities[1]); // 21 - 12 rightEntities[0].OneSkipPayloadFullShared.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities, postSave: false); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities, postSave: true); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipPayloadFullShared).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipPayloadFullShared); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -2078,11 +2223,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_shared() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_shared(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -2109,18 +2256,34 @@ public virtual void Can_insert_many_to_many_shared() rightEntities[0].OneSkipShared.Add(leftEntities[1]); // 21 - 12 rightEntities[0].OneSkipShared.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.TwoSkipShared).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.TwoSkipShared); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -2248,11 +2411,13 @@ static void ValidateFixup( } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_with_payload() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_with_payload(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -2279,18 +2444,34 @@ public virtual void Can_insert_many_to_many_with_payload() rightEntities[0].OneSkipPayloadFull.Add(leftEntities[1]); // 21 - 12 rightEntities[0].OneSkipPayloadFull.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities, postSave: false); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities, postSave: true); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipPayloadFull).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.ThreeSkipPayloadFull); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -2588,11 +2769,13 @@ static void ValidateJoinNavigations(DbContext context) } } - [ConditionalFact] - public virtual void Can_insert_many_to_many() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -2619,18 +2802,34 @@ public virtual void Can_insert_many_to_many() rightEntities[0].OneSkip.Add(leftEntities[1]); // 21 - 12 rightEntities[0].OneSkip.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.TwoSkip).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.TwoSkip); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id).ToList(); @@ -2843,11 +3042,13 @@ static void ValidateNavigations(List ones, List twos) } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_fully_by_convention() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_fully_by_convention(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -2870,18 +3071,34 @@ public virtual void Can_insert_many_to_many_fully_by_convention() rightEntities[0].As.Add(leftEntities[1]); // 21 - 12 rightEntities[0].As.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); - context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.Bs).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.Bs); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); Assert.Equal(11, context.ChangeTracker.Entries().Count()); @@ -2920,11 +3137,13 @@ static void ValidateFixup(DbContext context, IList leftEnti } } - [ConditionalFact] - public virtual void Can_insert_many_to_many_fully_by_convention_generated_keys() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_fully_by_convention_generated_keys(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -2947,17 +3166,34 @@ public virtual void Can_insert_many_to_many_fully_by_convention_generated_keys() rightEntities[0].Lefts.Add(leftEntities[1]); // 21 - 12 rightEntities[0].Lefts.Add(leftEntities[2]); // 21 - 13 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Include(e => e.Rights).ToList(); + var queryable = context.Set().Include(e => e.Rights); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); Assert.Equal(11, context.ChangeTracker.Entries().Count()); @@ -2997,14 +3233,16 @@ static void ValidateFixup(DbContext context, IList leftEntiti } [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual void Can_Attach_or_Update_a_many_to_many_with_mixed_set_and_unset_keys(bool useUpdate) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + public virtual async Task Can_Attach_or_Update_a_many_to_many_with_mixed_set_and_unset_keys(bool useUpdate, bool async) { var existingLeftId = -1; var existingRightId = -1; - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var left = context.Set().CreateInstance(); var right = context.Set().CreateInstance(); @@ -3014,13 +3252,21 @@ public virtual void Can_Attach_or_Update_a_many_to_many_with_mixed_set_and_unset left.Rights.Add(right); } - context.AddRange(left, right); - context.SaveChanges(); + if (async) + { + await context.AddRangeAsync(left, right); + await context.SaveChangesAsync(); + } + else + { + context.AddRange(left, right); + context.SaveChanges(); + } existingLeftId = left.Id; existingRightId = right.Id; }, - context => + async context => { var leftEntities = new[] { @@ -3085,13 +3331,21 @@ public virtual void Can_Attach_or_Update_a_many_to_many_with_mixed_set_and_unset : EntityState.Added, rightEntry.State); } - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Include(e => e.Rights).ToList(); + var queryable = context.Set().Include(e => e.Rights); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); Assert.Equal(11, context.ChangeTracker.Entries().Count()); @@ -3130,11 +3384,13 @@ static void ValidateFixup(DbContext context, IList leftEntiti } } - [ConditionalFact] - public virtual void Initial_tracking_uses_skip_navigations() + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Initial_tracking_uses_skip_navigations(bool async) { - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var leftEntities = new[] { @@ -3153,17 +3409,32 @@ public virtual void Initial_tracking_uses_skip_navigations() leftEntities[0].Bs.Add(rightEntities[1]); // 11 - 22 leftEntities[0].Bs.Add(rightEntities[2]); // 11 - 23 - context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + } ValidateFixup(context, leftEntities, rightEntities); - context.SaveChanges(); + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } ValidateFixup(context, leftEntities, rightEntities); }, - context => + async context => { - var results = context.Set().Where(e => e.Id > 7700).Include(e => e.Bs).ToList(); + var queryable = context.Set().Where(e => e.Id > 7700).Include(e => e.Bs); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); Assert.Equal(3, results.Count); var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Id) @@ -3355,6 +3626,15 @@ protected virtual void ExecuteWithStrategyInTransaction( CreateContext, UseTransaction, testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + protected virtual Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, UseTransaction, + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) { } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs index 6681610c76e..baa5372c5b1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore @@ -14,9 +15,10 @@ public ManyToManyTrackingProxySqlServerTest(ManyToManyTrackingProxySqlServerFixt { } - public override void Can_insert_many_to_many_shared_with_payload() + public override Task Can_insert_many_to_many_shared_with_payload(bool async) { // Mutable properties aren't proxyable on Dictionary + return Task.CompletedTask; } public override void Can_update_many_to_many_shared_with_payload() diff --git a/test/EFCore.Tests/ChangeTracking/SkipCollectionEntryTest.cs b/test/EFCore.Tests/ChangeTracking/SkipCollectionEntryTest.cs index 996f5860454..478b497c0e9 100644 --- a/test/EFCore.Tests/ChangeTracking/SkipCollectionEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/SkipCollectionEntryTest.cs @@ -10,6 +10,36 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking { public class SkipCollectionEntryTest { + [ConditionalFact] + public void Can_get_all_member_entries() + { + using var context = new FreezerContext(); + + Assert.Equal( + new List { "Id", "Cherries" }, + context.Attach(new Chunky()).Members.Select(e => e.Metadata.Name).ToList()); + } + + [ConditionalFact] + public void Can_get_all_navigation_entries() + { + using var context = new FreezerContext(); + + Assert.Equal( + new List { "Cherries" }, + context.Attach(new Chunky()).Navigations.Select(e => e.Metadata.Name).ToList()); + } + + [ConditionalFact] + public void Can_get_all_collection_entries() + { + using var context = new FreezerContext(); + + Assert.Equal( + new List { "Cherries" }, + context.Attach(new Chunky()).Collections.Select(e => e.Metadata.Name).ToList()); + } + [ConditionalTheory] [InlineData(false)] [InlineData(true)]