From 8bb7ad866cb1ca2aeb78726cf485cbe293fbfab1 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 22 Dec 2021 18:24:44 +0000 Subject: [PATCH] Christmas crackers: Add Property/Reference/etc overloads that take IProperty/INavigation Small stuff that I have wanted to do for ages. Fixes #7390. --- .../Internal/IdValueGenerator.cs | 2 +- src/EFCore/ChangeTracking/CollectionEntry.cs | 4 +- src/EFCore/ChangeTracking/CollectionEntry`.cs | 4 +- src/EFCore/ChangeTracking/EntityEntry.cs | 129 +++++++++-- src/EFCore/ChangeTracking/EntityEntry`.cs | 93 ++++++-- src/EFCore/ChangeTracking/NavigationEntry.cs | 43 ++-- src/EFCore/ChangeTracking/ReferenceEntry.cs | 2 +- .../CustomPartitionKeyIdGenerator.cs | 2 +- .../BuiltInDataTypesTestBase.cs | 4 +- .../ValueConvertersEndToEndTestBase.cs | 2 +- .../ChangeTracking/EntityEntryTest.cs | 207 +++++++++++++++++- .../ChangeTracking/SkipMemberEntryTest.cs | 70 +++++- .../ChangeTracking/TrackGraphTestBase.cs | 18 +- 13 files changed, 486 insertions(+), 94 deletions(-) diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs index 7177dc8b833..26a4dc2e855 100644 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs +++ b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs @@ -61,7 +61,7 @@ protected override object NextValue(EntityEntry entry) continue; } - var value = entry.Property(property.Name).CurrentValue; + var value = entry.Property(property).CurrentValue; var converter = property.GetTypeMapping().Converter; if (converter != null) diff --git a/src/EFCore/ChangeTracking/CollectionEntry.cs b/src/EFCore/ChangeTracking/CollectionEntry.cs index aed757475d1..4155b069830 100644 --- a/src/EFCore/ChangeTracking/CollectionEntry.cs +++ b/src/EFCore/ChangeTracking/CollectionEntry.cs @@ -47,8 +47,8 @@ public CollectionEntry(InternalEntityEntry internalEntry, string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public CollectionEntry(InternalEntityEntry internalEntry, INavigation navigation) - : base(internalEntry, navigation) + public CollectionEntry(InternalEntityEntry internalEntry, INavigationBase navigationBase) + : base(internalEntry, navigationBase, collection: true) { LocalDetectChanges(); } diff --git a/src/EFCore/ChangeTracking/CollectionEntry`.cs b/src/EFCore/ChangeTracking/CollectionEntry`.cs index 84463fb3ae4..b848c0f3764 100644 --- a/src/EFCore/ChangeTracking/CollectionEntry`.cs +++ b/src/EFCore/ChangeTracking/CollectionEntry`.cs @@ -45,8 +45,8 @@ public CollectionEntry(InternalEntityEntry internalEntry, string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public CollectionEntry(InternalEntityEntry internalEntry, INavigation navigation) - : base(internalEntry, navigation) + public CollectionEntry(InternalEntityEntry internalEntry, INavigationBase navigationBase) + : base(internalEntry, navigationBase) { } diff --git a/src/EFCore/ChangeTracking/EntityEntry.cs b/src/EFCore/ChangeTracking/EntityEntry.cs index b0d74f76eee..3c500ddce15 100644 --- a/src/EFCore/ChangeTracking/EntityEntry.cs +++ b/src/EFCore/ChangeTracking/EntityEntry.cs @@ -129,15 +129,39 @@ public virtual DbContext Context public virtual IEntityType Metadata => InternalEntry.EntityType; + /// + /// Provides access to change tracking information and operations for a given property or navigation of this entity. + /// + /// + /// See Accessing tracked entities in EF Core for more information and + /// examples. + /// + /// The property or navigation to access information and operations for. + /// An object that exposes change tracking information and operations for the given property. + public virtual MemberEntry Member(IPropertyBase propertyBase) + { + Check.NotNull(propertyBase, nameof(propertyBase)); + + return propertyBase switch + { + IProperty property => new PropertyEntry(InternalEntry, property), + INavigationBase navigation => navigation.IsCollection + ? new CollectionEntry(InternalEntry, navigation) + : new ReferenceEntry(InternalEntry, (INavigation)navigation), + _ => throw new InvalidOperationException( + CoreStrings.PropertyNotFound(propertyBase.Name, InternalEntry.EntityType.DisplayName())) + }; + } + /// /// Provides access to change tracking information and operations for a given - /// property or navigation property of this entity. + /// property or navigation of this entity. /// /// /// See Accessing tracked entities in EF Core for more information and /// examples. /// - /// The property to access information and operations for. + /// The property or navigation to access information and operations for. /// An object that exposes change tracking information and operations for the given property. public virtual MemberEntry Member(string propertyName) { @@ -163,9 +187,8 @@ public virtual MemberEntry Member(string propertyName) } /// - /// Provides access to change tracking information and operations for all - /// properties and navigation properties of this entity. - /// + /// Provides access to change tracking information and operations for all properties and navigations of this entity. + /// - /// /// See Accessing tracked entities in EF Core for more information and /// examples. @@ -174,15 +197,33 @@ public virtual IEnumerable Members => Properties.Cast().Concat(Navigations); /// - /// Provides access to change tracking information and operations for a given - /// navigation property of this entity. + /// Provides access to change tracking information and operations for a given navigation of this entity. /// /// /// See Accessing tracked entities in EF Core /// and Changing foreign keys and navigations /// for more information and examples. /// - /// The property to access information and operations for. + /// The navigation to access information and operations for. + /// An object that exposes change tracking information and operations for the given property. + public virtual NavigationEntry Navigation(INavigationBase navigationBase) + { + Check.NotNull(navigationBase, nameof(navigationBase)); + + return navigationBase.IsCollection + ? new CollectionEntry(InternalEntry, navigationBase) + : new ReferenceEntry(InternalEntry, (INavigation)navigationBase); + } + + /// + /// Provides access to change tracking information and operations for a given navigation of this entity. + /// + /// + /// See Accessing tracked entities in EF Core + /// and Changing foreign keys and navigations + /// for more information and examples. + /// + /// The navigation to access information and operations for. /// An object that exposes change tracking information and operations for the given property. public virtual NavigationEntry Navigation(string propertyName) { @@ -234,8 +275,23 @@ public virtual IEnumerable Navigations } /// - /// Provides access to change tracking information and operations for a given - /// property of this entity. + /// Provides access to change tracking information and operations for a given property of this entity. + /// + /// + /// See Accessing tracked entities in EF Core for more information and + /// examples. + /// + /// The property to access information and operations for. + /// An object that exposes change tracking information and operations for the given property. + public virtual PropertyEntry Property(IProperty property) + { + Check.NotNull(property, nameof(property)); + + return new PropertyEntry(InternalEntry, property); + } + + /// + /// Provides access to change tracking information and operations for a given property of this entity. /// /// /// See Accessing tracked entities in EF Core for more information and @@ -263,17 +319,36 @@ public virtual IEnumerable Properties /// /// Provides access to change tracking and loading information for a reference (i.e. non-collection) - /// navigation property that associates this entity to another entity. + /// navigation that associates this entity to another entity. /// /// /// See Accessing tracked entities in EF Core /// and Changing foreign keys and navigations /// for more information and examples. /// - /// The name of the navigation property. + /// The reference navigation. /// - /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// An object that exposes change tracking information and operations for the given navigation. + /// + public virtual ReferenceEntry Reference(INavigationBase navigation) + { + Check.NotNull(navigation, nameof(navigation)); + + return new ReferenceEntry(InternalEntry, (INavigation)navigation); + } + + /// + /// Provides access to change tracking and loading information for a reference (i.e. non-collection) + /// navigation that associates this entity to another entity. + /// + /// + /// See Accessing tracked entities in EF Core + /// and Changing foreign keys and navigations + /// for more information and examples. + /// + /// The name of the navigation. + /// + /// An object that exposes change tracking information and operations for the given navigation. /// public virtual ReferenceEntry Reference(string propertyName) { @@ -297,17 +372,37 @@ public virtual IEnumerable References /// /// Provides access to change tracking and loading information for a collection - /// navigation property that associates this entity to a collection of another entities. + /// navigation that associates this entity to a collection of another entities. + /// + /// + /// See Accessing tracked entities in EF Core + /// and Changing foreign keys and navigations + /// for more information and examples. + /// + /// The collection navigation. + /// + /// An object that exposes change tracking information and operations for the given navigation. + /// + public virtual CollectionEntry Collection(INavigationBase navigation) + { + Check.NotNull(navigation, nameof(navigation)); + + return new CollectionEntry(InternalEntry, navigation); + } + + /// + /// Provides access to change tracking and loading information for a collection + /// navigation that associates this entity to a collection of another entities. /// /// /// See Accessing tracked entities in EF Core /// and Changing foreign keys and navigations /// for more information and examples. /// - /// The name of the navigation property. + /// The name of the navigation. /// /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// given navigation. /// public virtual CollectionEntry Collection(string propertyName) { diff --git a/src/EFCore/ChangeTracking/EntityEntry`.cs b/src/EFCore/ChangeTracking/EntityEntry`.cs index a5e1d2dd814..050be2e5439 100644 --- a/src/EFCore/ChangeTracking/EntityEntry`.cs +++ b/src/EFCore/ChangeTracking/EntityEntry`.cs @@ -41,16 +41,14 @@ public EntityEntry(InternalEntityEntry internalEntry) => (TEntity)base.Entity; /// - /// Provides access to change tracking information and operations for a given - /// property of this entity. + /// Provides access to change tracking information and operations for a given property of this entity. /// /// /// See Accessing tracked entities in EF Core for more information and /// examples. /// /// - /// A lambda expression representing the property to access information and operations for - /// (t => t.Property1). + /// A lambda expression representing the property to access information and operations for. /// /// An object that exposes change tracking information and operations for the given property. public virtual PropertyEntry Property( @@ -71,12 +69,10 @@ public virtual PropertyEntry Property( /// for more information and examples. /// /// - /// A lambda expression representing the property to access information and operations for - /// (t => t.Property1). + /// A lambda expression representing the reference navigation to access information and operations for. /// /// - /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// An object that exposes change tracking information and operations for the given navigation property. /// public virtual ReferenceEntry Reference( Expression> propertyExpression) @@ -97,12 +93,10 @@ public virtual ReferenceEntry Reference( /// for more information and examples. /// /// - /// A lambda expression representing the property to access information and operations for - /// (t => t.Property1). + /// A lambda expression representing the collection navigation to access information and operations for. /// /// - /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// An object that exposes change tracking information and operations for the given navigation property. /// public virtual CollectionEntry Collection( Expression>> propertyExpression) @@ -113,9 +107,70 @@ public virtual CollectionEntry Collection( return new CollectionEntry(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName()); } + /// + /// Provides access to change tracking information and operations for a given property of this entity. + /// + /// + /// See Accessing tracked entities in EF Core for more information and + /// examples. + /// + /// The type of the property. + /// The property to access information and operations for. + /// An object that exposes change tracking information and operations for the given property. + public virtual PropertyEntry Property(IProperty property) + { + Check.NotNull(property, nameof(property)); + + ValidateType(property); + + return new PropertyEntry(InternalEntry, property); + } + /// /// Provides access to change tracking and loading information for a reference (i.e. non-collection) - /// navigation property that associates this entity to another entity. + /// navigation that associates this entity to another entity. + /// + /// + /// See Accessing tracked entities in EF Core + /// and Changing foreign keys and navigations + /// for more information and examples. + /// + /// The reference navigation. + /// + /// An object that exposes change tracking information and operations for the given navigation property. + /// + public virtual ReferenceEntry Reference(INavigationBase navigation) + where TProperty : class + { + Check.NotNull(navigation, nameof(navigation)); + + return new ReferenceEntry(InternalEntry, (INavigation)navigation); + } + + /// + /// Provides access to change tracking and loading information for a collection + /// navigation property that associates this entity to a collection of another entities. + /// + /// + /// See Accessing tracked entities in EF Core + /// and Changing foreign keys and navigations + /// for more information and examples. + /// + /// The collection navigation. + /// + /// An object that exposes change tracking information and operations for the given navigation property. + /// + public virtual CollectionEntry Collection(INavigationBase navigation) + where TProperty : class + { + Check.NotNull(navigation, nameof(navigation)); + + return new CollectionEntry(InternalEntry, navigation); + } + + /// + /// Provides access to change tracking and loading information for a reference (i.e. non-collection) + /// navigation that associates this entity to another entity. /// /// /// See Accessing tracked entities in EF Core @@ -124,8 +179,7 @@ public virtual CollectionEntry Collection( /// /// The name of the navigation property. /// - /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// An object that exposes change tracking information and operations for the given navigation property. /// public virtual ReferenceEntry Reference(string propertyName) where TProperty : class @@ -146,8 +200,7 @@ public virtual ReferenceEntry Reference(string pr /// /// The name of the navigation property. /// - /// An object that exposes change tracking information and operations for the - /// given navigation property. + /// An object that exposes change tracking information and operations for the given navigation property. /// public virtual CollectionEntry Collection(string propertyName) where TProperty : class @@ -158,8 +211,7 @@ public virtual CollectionEntry Collection(string } /// - /// Provides access to change tracking information and operations for a given - /// property of this entity. + /// Provides access to change tracking information and operations for a given property of this entity. /// /// /// See Accessing tracked entities in EF Core for more information and @@ -168,8 +220,7 @@ public virtual CollectionEntry Collection(string /// The type of the property. /// The property to access information and operations for. /// An object that exposes change tracking information and operations for the given property. - public virtual PropertyEntry Property( - string propertyName) + public virtual PropertyEntry Property(string propertyName) { Check.NotEmpty(propertyName, nameof(propertyName)); diff --git a/src/EFCore/ChangeTracking/NavigationEntry.cs b/src/EFCore/ChangeTracking/NavigationEntry.cs index eb2dd1ccd6b..de6178f1a26 100644 --- a/src/EFCore/ChangeTracking/NavigationEntry.cs +++ b/src/EFCore/ChangeTracking/NavigationEntry.cs @@ -29,7 +29,7 @@ public abstract class NavigationEntry : MemberEntry /// [EntityFrameworkInternal] protected NavigationEntry(InternalEntityEntry internalEntry, string name, bool collection) - : this(internalEntry, GetNavigation(internalEntry, name, collection)) + : this(internalEntry, GetNavigation(internalEntry, name), collection) { } @@ -40,12 +40,29 @@ protected NavigationEntry(InternalEntityEntry internalEntry, string name, bool c /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected NavigationEntry(InternalEntityEntry internalEntry, INavigationBase navigation) - : base(internalEntry, navigation) + protected NavigationEntry(InternalEntityEntry internalEntry, INavigationBase navigationBase, bool collection) + : base(internalEntry, navigationBase) { + if (collection + && !navigationBase.IsCollection) + { + throw new InvalidOperationException( + CoreStrings.CollectionIsReference( + navigationBase.Name, internalEntry.EntityType.DisplayName(), + nameof(ChangeTracking.EntityEntry.Collection), nameof(ChangeTracking.EntityEntry.Reference))); + } + + if (!collection + && navigationBase.IsCollection) + { + throw new InvalidOperationException( + CoreStrings.ReferenceIsCollection( + navigationBase.Name, internalEntry.EntityType.DisplayName(), + nameof(ChangeTracking.EntityEntry.Reference), nameof(ChangeTracking.EntityEntry.Collection))); + } } - private static INavigationBase GetNavigation(InternalEntityEntry internalEntry, string name, bool collection) + private static INavigationBase GetNavigation(InternalEntityEntry internalEntry, string name) { var navigation = (INavigationBase?)internalEntry.EntityType.FindNavigation(name) ?? internalEntry.EntityType.FindSkipNavigation(name); @@ -64,24 +81,6 @@ private static INavigationBase GetNavigation(InternalEntityEntry internalEntry, throw new InvalidOperationException(CoreStrings.PropertyNotFound(name, internalEntry.EntityType.DisplayName())); } - if (collection - && !navigation.IsCollection) - { - throw new InvalidOperationException( - CoreStrings.CollectionIsReference( - name, internalEntry.EntityType.DisplayName(), - nameof(ChangeTracking.EntityEntry.Collection), nameof(ChangeTracking.EntityEntry.Reference))); - } - - if (!collection - && navigation.IsCollection) - { - throw new InvalidOperationException( - CoreStrings.ReferenceIsCollection( - name, internalEntry.EntityType.DisplayName(), - nameof(ChangeTracking.EntityEntry.Reference), nameof(ChangeTracking.EntityEntry.Collection))); - } - return navigation; } diff --git a/src/EFCore/ChangeTracking/ReferenceEntry.cs b/src/EFCore/ChangeTracking/ReferenceEntry.cs index 30a570b384a..9fbe44d2919 100644 --- a/src/EFCore/ChangeTracking/ReferenceEntry.cs +++ b/src/EFCore/ChangeTracking/ReferenceEntry.cs @@ -49,7 +49,7 @@ public ReferenceEntry(InternalEntityEntry internalEntry, string name) /// [EntityFrameworkInternal] public ReferenceEntry(InternalEntityEntry internalEntry, INavigation navigation) - : base(internalEntry, navigation) + : base(internalEntry, navigation, collection: false) { LocalDetectChanges(); diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdGenerator.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdGenerator.cs index 72372423660..530cb38419c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdGenerator.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdGenerator.cs @@ -36,7 +36,7 @@ protected override object NextValue(EntityEntry entry) continue; } - var value = entry.Property(property.Name).CurrentValue; + var value = entry.Property(property).CurrentValue; var converter = property.GetTypeMapping().Converter; if (converter != null) diff --git a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs index 269a41b3440..46dd9c8011d 100644 --- a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs @@ -467,7 +467,7 @@ private void QueryBuiltInDataTypesTest(EntityEntry source) } Assert.Equal( - source.Property(propertyEntry.Metadata.Name).CurrentValue, + source.Property(propertyEntry.Metadata).CurrentValue, propertyEntry.CurrentValue); } } @@ -838,7 +838,7 @@ private void QueryBuiltInNullableDataTypesTest(EntityEntry sou } Assert.Equal( - source.Property(propertyEntry.Metadata.Name).CurrentValue, + source.Property(propertyEntry.Metadata).CurrentValue, propertyEntry.CurrentValue); } } diff --git a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs index b9aa7d183b4..8a944aed994 100644 --- a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs +++ b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs @@ -217,7 +217,7 @@ private static void SetPropertyValues(DbContext context, ConvertingEntity entity testValues[3] = null; } - var propertyEntry = entry.Property(property.Name); + var propertyEntry = entry.Property(property); if (previousValueIndex >= 0 && property.FindAnnotation("Relational:DefaultValue") == null) diff --git a/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs b/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs index 1d2ff24d0ba..b5f52b7ab6a 100644 --- a/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs @@ -311,6 +311,27 @@ public void Can_get_generic_property_entry_by_name() Assert.Equal("Monkey", context.Entry(entity).Property("Monkey").Metadata.Name); } + [ConditionalFact] + public void Can_get_property_entry_by_IProperty() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var property = context.Entry(entity).Metadata.FindProperty("Monkey")!; + + Assert.Same(property, context.Entry(entity).Property(property).Metadata); + Assert.Same(property, context.Entry((object)entity).Property(property).Metadata); + } + + [ConditionalFact] + public void Can_get_generic_property_entry_by_IProperty() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var property = context.Entry(entity).Metadata.FindProperty("Monkey")!; + + Assert.Same(property, context.Entry(entity).Property(property).Metadata); + } + [ConditionalFact] public void Throws_when_wrong_generic_type_is_used_while_getting_property_entry_by_name() { @@ -404,6 +425,27 @@ public void Can_get_generic_reference_entry_by_lambda() Assert.Equal("Garcia", context.Entry(entity).Reference(e => e.Garcia).Metadata.Name); } + [ConditionalFact] + public void Can_get_reference_entry_by_INavigationBase() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Garcia")!; + + Assert.Same(navigationBase, context.Entry(entity).Reference(navigationBase).Metadata); + Assert.Same(navigationBase, context.Entry((object)entity).Reference(navigationBase).Metadata); + } + + [ConditionalFact] + public void Can_get_generic_reference_entry_by_INavigationBase() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Garcia")!; + + Assert.Same(navigationBase, context.Entry(entity).Reference(navigationBase).Metadata); + } + [ConditionalFact] public void Throws_when_wrong_reference_name_is_used_while_getting_property_entry_by_name() { @@ -461,24 +503,45 @@ public void Throws_when_accessing_collection_as_reference() CoreStrings.ReferenceIsCollection( "Monkeys", entity.GetType().Name, nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Reference("Monkeys").Metadata.Name).Message); + Assert.Throws(() => context.Entry(entity).Reference("Monkeys")).Message); + Assert.Equal( CoreStrings.ReferenceIsCollection( "Monkeys", entity.GetType().Name, nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry((object)entity).Reference("Monkeys").Metadata.Name) - .Message); + Assert.Throws(() => context.Entry((object)entity).Reference("Monkeys")).Message); + Assert.Equal( CoreStrings.ReferenceIsCollection( "Monkeys", entity.GetType().Name, nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Reference("Monkeys").Metadata.Name) - .Message); + Assert.Throws(() => context.Entry(entity).Reference("Monkeys")).Message); + + Assert.Equal( + CoreStrings.ReferenceIsCollection( + "Monkeys", entity.GetType().Name, + nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), + Assert.Throws(() => context.Entry(entity).Reference(e => e.Monkeys)).Message); + + var navigationBase = context.Entry(entity).Navigation("Monkeys").Metadata; + Assert.Equal( CoreStrings.ReferenceIsCollection( "Monkeys", entity.GetType().Name, nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Reference(e => e.Monkeys).Metadata.Name).Message); + Assert.Throws(() => context.Entry(entity).Reference(navigationBase)).Message); + + Assert.Equal( + CoreStrings.ReferenceIsCollection( + "Monkeys", entity.GetType().Name, + nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), + Assert.Throws(() => context.Entry((object)entity).Reference(navigationBase)).Message); + + Assert.Equal( + CoreStrings.ReferenceIsCollection( + "Monkeys", entity.GetType().Name, + nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), + Assert.Throws(() => context.Entry(entity).Reference(navigationBase)).Message); } [ConditionalFact] @@ -509,6 +572,28 @@ public void Can_get_generic_collection_entry_by_lambda() Assert.Equal("Monkeys", context.Entry(entity).Collection(e => e.Monkeys).Metadata.Name); } + + [ConditionalFact] + public void Can_get_collection_entry_by_INavigationBase() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Monkeys")!; + + Assert.Same(navigationBase, context.Entry(entity).Collection(navigationBase).Metadata); + Assert.Same(navigationBase, context.Entry((object)entity).Collection(navigationBase).Metadata); + } + + [ConditionalFact] + public void Can_get_generic_collection_entry_by_INavigationBase() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Monkeys")!; + + Assert.Same(navigationBase, context.Entry(entity).Collection(navigationBase).Metadata); + } + [ConditionalFact] public void Throws_when_wrong_collection_name_is_used_while_getting_property_entry_by_name() { @@ -563,19 +648,39 @@ public void Throws_when_accessing_reference_as_collection() CoreStrings.CollectionIsReference( "Garcia", entity.GetType().Name, nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), - Assert.Throws(() => context.Entry(entity).Collection("Garcia").Metadata.Name).Message); + Assert.Throws(() => context.Entry(entity).Collection("Garcia")).Message); + Assert.Equal( CoreStrings.CollectionIsReference( "Garcia", entity.GetType().Name, nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), - Assert.Throws(() => context.Entry((object)entity).Collection("Garcia").Metadata.Name) - .Message); + Assert.Throws(() => context.Entry((object)entity).Collection("Garcia")).Message); + Assert.Equal( CoreStrings.CollectionIsReference( "Garcia", entity.GetType().Name, nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), - Assert.Throws(() => context.Entry(entity).Collection("Garcia").Metadata.Name) - .Message); + Assert.Throws(() => context.Entry(entity).Collection("Garcia")).Message); + + var navigationBase = context.Entry(entity).Navigation("Garcia").Metadata; + + Assert.Equal( + CoreStrings.CollectionIsReference( + "Garcia", entity.GetType().Name, + nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), + Assert.Throws(() => context.Entry(entity).Collection(navigationBase)).Message); + + Assert.Equal( + CoreStrings.CollectionIsReference( + "Garcia", entity.GetType().Name, + nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), + Assert.Throws(() => context.Entry((object)entity).Collection(navigationBase)).Message); + + Assert.Equal( + CoreStrings.CollectionIsReference( + "Garcia", entity.GetType().Name, + nameof(EntityEntry.Collection), nameof(EntityEntry.Reference)), + Assert.Throws(() => context.Entry(entity).Collection(navigationBase)).Message); } [ConditionalFact] @@ -637,6 +742,54 @@ public void Can_get_collection_entry_by_name_using_Member() Assert.IsType(entry); } + [ConditionalFact] + public void Can_get_property_entry_by_IPropertyBase_using_Member() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var propertyBase = (IPropertyBase)context.Entry(entity).Metadata.FindProperty("Monkey")!; + + var entry = context.Entry(entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + } + + [ConditionalFact] + public void Can_get_reference_entry_by_IPropertyBase_using_Member() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var propertyBase = (IPropertyBase)context.Entry(entity).Metadata.FindNavigation("Garcia")!; + + var entry = context.Entry(entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + } + + [ConditionalFact] + public void Can_get_collection_entry_by_IPropertyBase_using_Member() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var propertyBase = (IPropertyBase)context.Entry(entity).Metadata.FindNavigation("Monkeys")!; + + var entry = context.Entry(entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + } + [ConditionalFact] public void Throws_when_wrong_property_name_is_used_while_getting_property_entry_by_name_using_Navigation() { @@ -682,6 +835,38 @@ public void Can_get_collection_entry_by_name_using_Navigation() Assert.IsType(entry); } + [ConditionalFact] + public void Can_get_reference_entry_by_INavigationBase_using_Navigation() + { + using var context = new FreezerContext(); + var entity = context.Add(new Chunky()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Garcia")!; + + var entry = context.Entry(entity).Navigation(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Navigation(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + } + + [ConditionalFact] + public void Can_get_collection_entry_by_INavigationBase_using_Navigation() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindNavigation("Monkeys")!; + + var entry = context.Entry(entity).Navigation(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Navigation(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + } + [ConditionalFact] public void Throws_when_accessing_property_as_navigation() { diff --git a/test/EFCore.Tests/ChangeTracking/SkipMemberEntryTest.cs b/test/EFCore.Tests/ChangeTracking/SkipMemberEntryTest.cs index 046a2af1b58..0c57d77ae67 100644 --- a/test/EFCore.Tests/ChangeTracking/SkipMemberEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/SkipMemberEntryTest.cs @@ -12,9 +12,8 @@ public void Can_get_back_reference_collection() { using var context = new FreezerContext(); var entity = new Cherry(); - context.Add(entity); - var entityEntry = context.Entry(entity); + var entityEntry = context.Add(entity); Assert.Same(entityEntry.Entity, entityEntry.Member("Chunkies").EntityEntry.Entity); } @@ -23,9 +22,9 @@ public void Can_get_metadata_collection() { using var context = new FreezerContext(); var entity = new Cherry(); - context.Add(entity); - Assert.Equal("Chunkies", context.Entry(entity).Member("Chunkies").Metadata.Name); + var entityEntry = context.Add(entity); + Assert.Equal("Chunkies", entityEntry.Member("Chunkies").Metadata.Name); } [ConditionalFact] @@ -58,6 +57,69 @@ public void Can_get_and_set_current_value_collection() Assert.Null(collection.CurrentValue); } + [ConditionalFact] + public void Can_get_skip_collection_entry_by_name_using_Member() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + + var entry = context.Entry(entity).Member("Chunkies"); + Assert.Equal("Chunkies", entry.Metadata.Name); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Member("Chunkies"); + Assert.Equal("Chunkies", entry.Metadata.Name); + Assert.IsType(entry); + } + + [ConditionalFact] + public void Can_get_skip_collection_entry_by_IPropertyBase_using_Member() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var propertyBase = (IPropertyBase)context.Entry(entity).Metadata.FindSkipNavigation("Chunkies")!; + + var entry = context.Entry(entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Member(propertyBase); + Assert.Same(propertyBase, entry.Metadata); + Assert.IsType(entry); + } + + + [ConditionalFact] + public void Can_get_skip_collection_entry_by_name_using_Collection() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + + var entry = context.Entry(entity).Collection("Chunkies"); + Assert.Equal("Chunkies", entry.Metadata.Name); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Collection("Chunkies"); + Assert.Equal("Chunkies", entry.Metadata.Name); + Assert.IsType(entry); + } + + [ConditionalFact] + public void Can_get_skip_collection_entry_by_INavigationBase_using_Collection() + { + using var context = new FreezerContext(); + var entity = context.Add(new Cherry()).Entity; + var navigationBase = (INavigationBase)context.Entry(entity).Metadata.FindSkipNavigation("Chunkies")!; + + var entry = context.Entry(entity).Collection(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + + entry = context.Entry((object)entity).Collection(navigationBase); + Assert.Same(navigationBase, entry.Metadata); + Assert.IsType(entry); + } + private class Chunky { public int Id { get; set; } diff --git a/test/EFCore.Tests/ChangeTracking/TrackGraphTestBase.cs b/test/EFCore.Tests/ChangeTracking/TrackGraphTestBase.cs index 07815e83aa1..308a430ee64 100644 --- a/test/EFCore.Tests/ChangeTracking/TrackGraphTestBase.cs +++ b/test/EFCore.Tests/ChangeTracking/TrackGraphTestBase.cs @@ -253,9 +253,9 @@ public void Can_attach_parent_with_owned_dependent(bool useAttach, bool setPrinc Assert.Equal(expectedDependentState, dependentEntry2b.State); Assert.Equal(1, sweet.Id); - Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); + Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); } [ConditionalTheory] @@ -318,9 +318,9 @@ public void Can_attach_owned_dependent_with_reference_to_parent(bool useAttach, Assert.Equal(expectedDependentState, dependentEntry2b.State); Assert.Equal(1, dreams.Sweet.Id); - Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); + Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); } [ConditionalFact] @@ -695,9 +695,9 @@ public void Can_add_owned_dependent_with_reference_to_parent(bool useAdd, bool s Assert.Equal(expectedDependentState, dependentEntry2b.State); Assert.Equal(1, dreams.Sweet.Id); - Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); - Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0].Name).CurrentValue); + Assert.Equal(1, dependentEntry.Property(dependentEntry.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2a.Property(dependentEntry2a.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); + Assert.Equal(1, dependentEntry2b.Property(dependentEntry2b.Metadata.FindPrimaryKey().Properties[0]).CurrentValue); } [ConditionalTheory] // Issue #12590