From d87837ab9204548aef5562415b33f04c3267a7fd Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 30 Jul 2020 14:12:36 -0700 Subject: [PATCH] Metadata: Configure backing field by attribute on skip navigation --- .../Builders/IConventionNavigationBuilder.cs | 35 +---- .../IConventionPropertyBaseBuilder.cs | 85 ++++++++++++ .../Builders/IConventionPropertyBuilder.cs | 35 +---- .../IConventionServicePropertyBuilder.cs | 27 ++-- .../IConventionSkipNavigationBuilder.cs | 35 +---- .../Conventions/BackingFieldConvention.cs | 87 ++++++------ .../ProviderConventionSetBuilder.cs | 5 +- .../NavigationAttributeConventionBase.cs | 48 ++++++- ...vigationBackingFieldAttributeConvention.cs | 34 +++++ .../Internal/InternalNavigationBuilder.cs | 33 ++++- .../Internal/InternalPropertyBuilder.cs | 47 ++++++- .../InternalServicePropertyBuilder.cs | 124 ++++++++++-------- .../Internal/InternalSkipNavigationBuilder.cs | 36 ++++- test/EFCore.Tests/ApiConsistencyTest.cs | 16 ++- .../ModelBuilding/ManyToManyTestBase.cs | 20 ++- test/EFCore.Tests/ModelBuilding/TestModel.cs | 8 ++ 16 files changed, 448 insertions(+), 227 deletions(-) create mode 100644 src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs create mode 100644 src/EFCore/Metadata/Conventions/SkipNavigationBackingFieldAttributeConvention.cs diff --git a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs index 39db36bf639..16cae240379 100644 --- a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs @@ -15,22 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// not used in application code. /// /// - public interface IConventionNavigationBuilder : IConventionAnnotatableBuilder + public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation being configured. /// new IConventionNavigation Metadata { get; } - /// - /// Returns a value indicating whether the backing field can be set for this navigation - /// from the given configuration source. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] string fieldName, bool fromDataAnnotation = false); - /// /// Sets the backing field to use for this navigation. /// @@ -40,16 +31,7 @@ public interface IConventionNavigationBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionNavigationBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this navigation - /// from the given configuration source. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + new IConventionNavigationBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this navigation. @@ -60,16 +42,7 @@ public interface IConventionNavigationBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionNavigationBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be set for this navigation - /// from the current configuration source. - /// - /// The to use for this navigation. - /// Indicates whether the configuration was specified using a data annotation. - /// if the can be set for this navigation. - bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + new IConventionNavigationBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); /// /// Sets the to use for this navigation. @@ -80,7 +53,7 @@ public interface IConventionNavigationBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionNavigationBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + new IConventionNavigationBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation can be configured to be automatically included in a query diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs new file mode 100644 index 00000000000..c614176b8ee --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs @@ -0,0 +1,85 @@ +// 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.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders +{ + /// + /// + /// Provides a simple API surface for configuring an from conventions. + /// + /// + /// This interface is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder + { + /// + /// Gets the property-like object being configured. + /// + new IConventionPropertyBase Metadata { get; } + + /// + /// Sets the backing field to use for this property-like object. + /// + /// The field name. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionPropertyBaseBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); + + /// + /// Sets the backing field to use for this property-like object. + /// + /// The field. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionPropertyBaseBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the backing field can be set for this property-like object + /// from the current configuration source. + /// + /// The field name. + /// Indicates whether the configuration was specified using a data annotation. + /// if the backing field can be set for this property-like object. + bool CanSetField([CanBeNull] string fieldName, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the backing field can be set for this property-like object + /// from the current configuration source. + /// + /// The field. + /// Indicates whether the configuration was specified using a data annotation. + /// if the backing field can be set for this property-like object. + bool CanSetField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + + /// + /// Sets the to use for this property-like object. + /// + /// The to use for this property-like object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionPropertyBaseBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be set for this property-like object + /// from the current configuration source. + /// + /// The to use for this property-like object. + /// Indicates whether the configuration was specified using a data annotation. + /// if the can be set for this property-like object. + bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + } +} diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 7752f977fa3..fef9d241c7e 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -19,7 +19,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// not used in application code. /// /// - public interface IConventionPropertyBuilder : IConventionAnnotatableBuilder + public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder { /// /// Gets the property being configured. @@ -117,7 +117,7 @@ public interface IConventionPropertyBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); + new IConventionPropertyBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this property. @@ -128,25 +128,7 @@ public interface IConventionPropertyBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this property - /// from the current configuration source. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] string fieldName, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this property - /// from the current configuration source. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + new IConventionPropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); /// /// Sets the to use for this property. @@ -157,16 +139,7 @@ public interface IConventionPropertyBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be set for this property - /// from the current configuration source. - /// - /// The to use for this property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the can be set for this property. - bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + new IConventionPropertyBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Configures the maximum length of data that can be stored in this property. diff --git a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs index 309787af13c..5894004a12b 100644 --- a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// not used in application code. /// /// - public interface IConventionServicePropertyBuilder : IConventionAnnotatableBuilder + public interface IConventionServicePropertyBuilder : IConventionPropertyBaseBuilder { /// /// Gets the service property being configured. @@ -31,7 +31,7 @@ public interface IConventionServicePropertyBuilder : IConventionAnnotatableBuild /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionServicePropertyBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); + new IConventionServicePropertyBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this property. @@ -42,25 +42,18 @@ public interface IConventionServicePropertyBuilder : IConventionAnnotatableBuild /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionServicePropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + new IConventionServicePropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the backing field can be set for this property - /// from the current configuration source. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] string fieldName, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this property - /// from the current configuration source. + /// Sets the to use for this property. /// - /// The field. + /// The to use for this property. /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + new IConventionServicePropertyBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Sets the for this property. diff --git a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs index 03b220e78c9..43654470e6b 100644 --- a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// not used in application code. /// /// - public interface IConventionSkipNavigationBuilder : IConventionAnnotatableBuilder + public interface IConventionSkipNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation property being configured. @@ -31,7 +31,7 @@ public interface IConventionSkipNavigationBuilder : IConventionAnnotatableBuilde /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionSkipNavigationBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); + new IConventionSkipNavigationBuilder HasField([CanBeNull] string fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this navigation. @@ -42,25 +42,7 @@ public interface IConventionSkipNavigationBuilder : IConventionAnnotatableBuilde /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionSkipNavigationBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this navigation - /// from the given configuration source. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] string fieldName, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the backing field can be set for this navigation - /// from the given configuration source. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// if the backing field can be set for this property. - bool CanSetField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); + new IConventionSkipNavigationBuilder HasField([CanBeNull] FieldInfo fieldInfo, bool fromDataAnnotation = false); /// /// Sets the to use for this navigation. @@ -71,16 +53,7 @@ public interface IConventionSkipNavigationBuilder : IConventionAnnotatableBuilde /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionSkipNavigationBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be set for this navigation - /// from the given configuration source. - /// - /// The to use for this navigation. - /// Indicates whether the configuration was specified using a data annotation. - /// if the can be set for this property. - bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + new IConventionSkipNavigationBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Sets the foreign key. diff --git a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs index 31ffe0372f0..56736df6059 100644 --- a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs +++ b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs @@ -57,11 +57,7 @@ public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) { - var field = GetFieldToSet(propertyBuilder.Metadata); - if (field != null) - { - propertyBuilder.HasField(field); - } + DiscoverField(propertyBuilder); } /// @@ -69,11 +65,49 @@ public virtual void ProcessNavigationAdded( IConventionNavigationBuilder navigationBuilder, IConventionContext context) { - var navigation = navigationBuilder.Metadata; - var field = GetFieldToSet(navigation); - if (field != null) + DiscoverField(navigationBuilder); + } + + /// + public virtual void ProcessSkipNavigationAdded( + IConventionSkipNavigationBuilder skipNavigationBuilder, + IConventionContext context) + { + DiscoverField(skipNavigationBuilder); + } + + /// + public virtual void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { - navigation.Builder.HasField(field); + foreach (var property in entityType.GetDeclaredProperties()) + { + var ambiguousField = property.FindAnnotation(CoreAnnotationNames.AmbiguousField); + if (ambiguousField != null) + { + if (property.GetFieldName() == null) + { + throw new InvalidOperationException((string)ambiguousField.Value); + } + + property.RemoveAnnotation(CoreAnnotationNames.AmbiguousField); + } + } + } + } + + private void DiscoverField(IConventionPropertyBaseBuilder conventionPropertyBaseBuilder) + { + if (ConfigurationSource.Convention.Overrides(conventionPropertyBaseBuilder.Metadata.GetFieldInfoConfigurationSource())) + { + var field = GetFieldToSet(conventionPropertyBaseBuilder.Metadata); + if (field != null) + { + conventionPropertyBaseBuilder.HasField(field); + } } } @@ -239,40 +273,5 @@ private static int PrefixBinarySearch(KeyValuePair[] array, string right = middle - 1; } } - - /// - public virtual void ProcessModelFinalizing( - IConventionModelBuilder modelBuilder, - IConventionContext context) - { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) - { - foreach (var property in entityType.GetDeclaredProperties()) - { - var ambiguousField = property.FindAnnotation(CoreAnnotationNames.AmbiguousField); - if (ambiguousField != null) - { - if (property.GetFieldName() == null) - { - throw new InvalidOperationException((string)ambiguousField.Value); - } - - property.RemoveAnnotation(CoreAnnotationNames.AmbiguousField); - } - } - } - } - - /// - public virtual void ProcessSkipNavigationAdded( - IConventionSkipNavigationBuilder skipNavigationBuilder, - IConventionContext context) - { - var field = GetFieldToSet(skipNavigationBuilder.Metadata); - if (field != null) - { - skipNavigationBuilder.HasField(field); - } - } } } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 7ccbb2e18bf..57c411a4942 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -114,6 +114,7 @@ public virtual ConventionSet CreateConventionSet() var timestampAttributeConvention = new TimestampAttributeConvention(Dependencies); var backingFieldAttributeConvention = new BackingFieldAttributeConvention(Dependencies); + conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention); conventionSet.PropertyAddedConventions.Add(backingFieldConvention); conventionSet.PropertyAddedConventions.Add(concurrencyCheckAttributeConvention); conventionSet.PropertyAddedConventions.Add(databaseGeneratedAttributeConvention); @@ -122,7 +123,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.PropertyAddedConventions.Add(maxLengthAttributeConvention); conventionSet.PropertyAddedConventions.Add(stringLengthAttributeConvention); conventionSet.PropertyAddedConventions.Add(timestampAttributeConvention); - conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention); conventionSet.PropertyAddedConventions.Add(keyAttributeConvention); conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); @@ -170,8 +170,8 @@ public virtual ConventionSet CreateConventionSet() var requiredNavigationAttributeConvention = new RequiredNavigationAttributeConvention(Dependencies); var nonNullableNavigationConvention = new NonNullableNavigationConvention(Dependencies); - conventionSet.NavigationAddedConventions.Add(backingFieldConvention); conventionSet.NavigationAddedConventions.Add(new NavigationBackingFieldAttributeConvention(Dependencies)); + conventionSet.NavigationAddedConventions.Add(backingFieldConvention); conventionSet.NavigationAddedConventions.Add(requiredNavigationAttributeConvention); conventionSet.NavigationAddedConventions.Add(nonNullableNavigationConvention); conventionSet.NavigationAddedConventions.Add(inversePropertyAttributeConvention); @@ -180,6 +180,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.NavigationAddedConventions.Add(foreignKeyAttributeConvention); var manyToManyJoinEntityTypeConvention = new ManyToManyJoinEntityTypeConvention(Dependencies); + conventionSet.SkipNavigationAddedConventions.Add(new SkipNavigationBackingFieldAttributeConvention(Dependencies)); conventionSet.SkipNavigationAddedConventions.Add(backingFieldConvention); conventionSet.SkipNavigationAddedConventions.Add(manyToManyJoinEntityTypeConvention); diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index 44a098f64db..43b5cf836e0 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -24,6 +24,7 @@ public abstract class NavigationAttributeConventionBase : IEntityTypeBaseTypeChangedConvention, IEntityTypeMemberIgnoredConvention, INavigationAddedConvention, + ISkipNavigationAddedConvention, IForeignKeyPrincipalEndChangedConvention where TAttribute : Attribute { @@ -171,6 +172,23 @@ public virtual void ProcessNavigationAdded( } } + /// + public virtual void ProcessSkipNavigationAdded( + IConventionSkipNavigationBuilder skipNavigationBuilder, + IConventionContext context) + { + var skipNavigation = skipNavigationBuilder.Metadata; + var attributes = GetAttributes(skipNavigation.DeclaringEntityType, skipNavigation); + foreach (var attribute in attributes) + { + ProcessSkipNavigationAdded(skipNavigationBuilder, attribute, context); + if (((IReadableConventionContext)context).ShouldStopProcessing()) + { + break; + } + } + } + /// public virtual void ProcessForeignKeyPrincipalEndChanged( IConventionForeignKeyBuilder relationshipBuilder, @@ -234,8 +252,24 @@ private Type FindCandidateNavigationWithAttributePropertyType([NotNull] Property protected static IEnumerable GetAttributes( [NotNull] IConventionEntityType entityType, [NotNull] IConventionNavigation navigation) where TCustomAttribute : Attribute + => GetAttributes(entityType, navigation.GetIdentifyingMemberInfo()); + + /// + /// Returns the attributes applied to the given skip navigation. + /// + /// The entity type. + /// The skip navigation. + /// The attribute type to look for. + /// The attributes applied to the given skip navigation. + protected static IEnumerable GetAttributes( + [NotNull] IConventionEntityType entityType, [NotNull] IConventionSkipNavigation skipNavigation) + where TCustomAttribute : Attribute + => GetAttributes(entityType, skipNavigation.GetIdentifyingMemberInfo()); + + private static IEnumerable GetAttributes( + [NotNull] IConventionEntityType entityType, [NotNull] MemberInfo memberInfo) + where TCustomAttribute : Attribute { - var memberInfo = navigation.GetIdentifyingMemberInfo(); if (!entityType.HasClrType() || memberInfo == null) { @@ -313,6 +347,18 @@ public virtual void ProcessNavigationAdded( [NotNull] IConventionContext context) => throw new NotImplementedException(); + /// + /// Called after a skip navigation property that has an attribute is added to an entity type. + /// + /// The builder for the navigation. + /// The attribute. + /// Additional information associated with convention execution. + public virtual void ProcessSkipNavigationAdded( + [NotNull] IConventionSkipNavigationBuilder skipNavigationBuilder, + [NotNull] TAttribute attribute, + [NotNull] IConventionContext context) + => throw new NotImplementedException(); + /// /// Called after a navigation property that has an attribute is ignored. /// diff --git a/src/EFCore/Metadata/Conventions/SkipNavigationBackingFieldAttributeConvention.cs b/src/EFCore/Metadata/Conventions/SkipNavigationBackingFieldAttributeConvention.cs new file mode 100644 index 00000000000..532c71305a2 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/SkipNavigationBackingFieldAttributeConvention.cs @@ -0,0 +1,34 @@ +// 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 JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that configures a skip navigation property as having a backing field + /// based on the attribute. + /// + public class SkipNavigationBackingFieldAttributeConvention : NavigationAttributeConventionBase + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public SkipNavigationBackingFieldAttributeConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + public override void ProcessSkipNavigationAdded( + IConventionSkipNavigationBuilder skipNavigationBuilder, + BackingFieldAttribute attribute, + IConventionContext context) + { + skipNavigationBuilder.HasField(attribute.Name, fromDataAnnotation: true); + } + } +} diff --git a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs index 4212cd0493e..fd270bf0a08 100644 --- a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs @@ -87,6 +87,12 @@ public virtual InternalNavigationBuilder AutoInclude(bool? autoInclude, Configur return null; } + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + IConventionNavigation IConventionNavigationBuilder.Metadata { [DebuggerStepThrough] @@ -95,10 +101,17 @@ IConventionNavigation IConventionNavigationBuilder.Metadata /// [DebuggerStepThrough] - bool IConventionNavigationBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionNavigationBuilder IConventionNavigationBuilder.UsePropertyAccessMode( @@ -108,11 +121,18 @@ IConventionNavigationBuilder IConventionNavigationBuilder.UsePropertyAccessMode( /// [DebuggerStepThrough] - bool IConventionNavigationBuilder.CanSetField(string fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string fieldName, bool fromDataAnnotation) => CanSetField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(string fieldName, bool fromDataAnnotation) + => HasField( + fieldName, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionNavigationBuilder IConventionNavigationBuilder.HasField(string fieldName, bool fromDataAnnotation) @@ -122,11 +142,18 @@ IConventionNavigationBuilder IConventionNavigationBuilder.HasField(string fieldN /// [DebuggerStepThrough] - bool IConventionNavigationBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(FieldInfo fieldInfo, bool fromDataAnnotation) + => HasField( + fieldInfo, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionNavigationBuilder IConventionNavigationBuilder.HasField(FieldInfo fieldInfo, bool fromDataAnnotation) diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 9bfa6617c93..65e35c85ac0 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -616,6 +616,18 @@ public virtual InternalPropertyBuilder Attach([NotNull] InternalEntityTypeBuilde return newPropertyBuilder; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -685,6 +697,15 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, => CanSetIsConcurrencyToken( concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(string fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -694,6 +715,15 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, IConventionPropertyBuilder IConventionPropertyBuilder.HasField(string fieldName, bool fromDataAnnotation) => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(FieldInfo fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -709,7 +739,7 @@ IConventionPropertyBuilder IConventionPropertyBuilder.HasField(FieldInfo fieldIn /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBuilder.CanSetField(string fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string fieldName, bool fromDataAnnotation) => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -718,9 +748,20 @@ bool IConventionPropertyBuilder.CanSetField(string fieldName, bool fromDataAnnot /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -738,7 +779,7 @@ IConventionPropertyBuilder IConventionPropertyBuilder.UsePropertyAccessMode( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs index a12889a4c1a..461ad8a6f88 100644 --- a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class InternalServicePropertyBuilder : AnnotatableBuilder, IConventionServicePropertyBuilder + public class InternalServicePropertyBuilder : InternalPropertyBaseBuilder, IConventionServicePropertyBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,41 +33,8 @@ public InternalServicePropertyBuilder([NotNull] ServiceProperty property, [NotNu /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalServicePropertyBuilder HasField([CanBeNull] string fieldName, ConfigurationSource configurationSource) - { - if (Metadata.FieldInfo?.GetSimpleMemberName() == fieldName - || configurationSource.Overrides(Metadata.GetFieldInfoConfigurationSource())) - { - Metadata.SetField(fieldName, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetField([CanBeNull] string fieldName, ConfigurationSource? configurationSource) - { - if (fieldName != null - && configurationSource.Overrides(Metadata.GetFieldInfoConfigurationSource())) - { - var fieldInfo = PropertyBase.GetFieldInfo( - fieldName, Metadata.DeclaringType, Metadata.Name, - shouldThrow: false); - return fieldInfo != null - && PropertyBase.IsCompatible( - fieldInfo, Metadata.ClrType, Metadata.DeclaringType.ClrType, Metadata.Name, - shouldThrow: false); - } - - return Metadata.FieldInfo?.GetSimpleMemberName() == fieldName; - } + public new virtual InternalServicePropertyBuilder HasField([CanBeNull] string fieldName, ConfigurationSource configurationSource) + => (InternalServicePropertyBuilder)base.HasField(fieldName, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -75,17 +42,8 @@ public virtual bool CanSetField([CanBeNull] string fieldName, ConfigurationSourc /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalServicePropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, ConfigurationSource configurationSource) - { - if (configurationSource.Overrides(Metadata.GetFieldInfoConfigurationSource()) - || Equals(Metadata.FieldInfo, fieldInfo)) - { - Metadata.SetFieldInfo(fieldInfo, configurationSource); - return this; - } - - return null; - } + public new virtual InternalServicePropertyBuilder HasField([CanBeNull] FieldInfo fieldInfo, ConfigurationSource configurationSource) + => (InternalServicePropertyBuilder)base.HasField(fieldInfo, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -93,13 +51,9 @@ public virtual InternalServicePropertyBuilder HasField([CanBeNull] FieldInfo fie /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetField([CanBeNull] FieldInfo fieldInfo, ConfigurationSource? configurationSource) - => (configurationSource.Overrides(Metadata.GetFieldInfoConfigurationSource()) - && (fieldInfo == null - || PropertyBase.IsCompatible( - fieldInfo, Metadata.ClrType, Metadata.DeclaringType.ClrType, Metadata.Name, - shouldThrow: false))) - || Equals(Metadata.FieldInfo, fieldInfo); + public new virtual InternalServicePropertyBuilder UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) + => (InternalServicePropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -162,6 +116,14 @@ public virtual InternalServicePropertyBuilder Attach([NotNull] InternalEntityTyp return newPropertyBuilder; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata => Metadata; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -170,6 +132,24 @@ public virtual InternalServicePropertyBuilder Attach([NotNull] InternalEntityTyp /// IConventionServiceProperty IConventionServicePropertyBuilder.Metadata => Metadata; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(string fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(FieldInfo fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -194,7 +174,7 @@ IConventionServicePropertyBuilder IConventionServicePropertyBuilder.HasField(Fie /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionServicePropertyBuilder.CanSetField(string fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string fieldName, bool fromDataAnnotation) => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -203,9 +183,41 @@ bool IConventionServicePropertyBuilder.CanSetField(string fieldName, bool fromDa /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionServicePropertyBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionServicePropertyBuilder IConventionServicePropertyBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs index a73b38799a4..d8ae5bf3ea8 100644 --- a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs @@ -319,11 +319,31 @@ public virtual InternalSkipNavigationBuilder AutoInclude(bool? autoInclude, Conf return null; } - IConventionSkipNavigation IConventionSkipNavigationBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; } + IConventionSkipNavigation IConventionSkipNavigationBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(string fieldName, bool fromDataAnnotation) + => HasField( + fieldName, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.HasField(FieldInfo fieldInfo, bool fromDataAnnotation) + => HasField( + fieldInfo, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionSkipNavigationBuilder IConventionSkipNavigationBuilder.HasField(string fieldName, bool fromDataAnnotation) @@ -340,18 +360,26 @@ IConventionSkipNavigationBuilder IConventionSkipNavigationBuilder.HasField(Field /// [DebuggerStepThrough] - bool IConventionSkipNavigationBuilder.CanSetField(string fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string fieldName, bool fromDataAnnotation) => CanSetField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionSkipNavigationBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo fieldInfo, bool fromDataAnnotation) => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + [DebuggerStepThrough] + IConventionPropertyBaseBuilder IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionSkipNavigationBuilder IConventionSkipNavigationBuilder.UsePropertyAccessMode( @@ -362,7 +390,7 @@ IConventionSkipNavigationBuilder IConventionSkipNavigationBuilder.UsePropertyAcc /// [DebuggerStepThrough] - bool IConventionSkipNavigationBuilder.CanSetPropertyAccessMode( + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index c16f4a85f8f..6c2aa597ef6 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -129,7 +129,19 @@ public override bool TryGetProviderOptionsDelegate(out Action MetadataMethodExceptions { get; } = new HashSet @@ -142,7 +154,7 @@ public override bool TryGetProviderOptionsDelegate(out Action(); + modelBuilder.Ignore(); + modelBuilder.Entity() + .HasMany(e => e.Dependents) + .WithMany(e => e.ManyToManyPrincipals); + + var model = modelBuilder.FinalizeModel(); + + Assert.Equal("_randomField", model.FindEntityType(typeof(ManyToManyNavPrincipal)).FindSkipNavigation("Dependents").GetFieldName()); + } + [ConditionalFact] public virtual void Navigation_properties_can_set_access_mode_using_navigation_names() { @@ -353,8 +369,8 @@ public virtual void Navigation_properties_can_set_access_mode_using_navigation_n var model = modelBuilder.FinalizeModel(); - var principal = (IEntityType)model.FindEntityType(typeof(ManyToManyNavPrincipal)); - var dependent = (IEntityType)model.FindEntityType(typeof(NavDependent)); + var principal = model.FindEntityType(typeof(ManyToManyNavPrincipal)); + var dependent = model.FindEntityType(typeof(NavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode()); Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode()); diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index c775958543c..fd5825f4861 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -881,9 +881,17 @@ private class OneToOneNavPrincipal private class ManyToManyNavPrincipal { + private readonly List _randomField; + + public ManyToManyNavPrincipal() + { + _randomField = new List(); + } + public int Id { get; set; } public string Name { get; set; } + [BackingField("_randomField")] public List Dependents { get; set; } }