diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index ca6c54b58dc..1eea6f83eb1 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -199,6 +199,34 @@ protected override void ProcessEntityTypeAnnotations( } } + /// + /// Updates the complex type annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source complex type. + /// The target complex type that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessComplexTypeAnnotations( + Dictionary annotations, + IComplexType complexType, + RuntimeComplexType runtimeComplexType, + bool runtime) + { + base.ProcessComplexTypeAnnotations(annotations, complexType, runtimeComplexType, runtime); + + if (runtime) + { + annotations.Remove(RelationalAnnotationNames.TableMappings); + annotations.Remove(RelationalAnnotationNames.ViewMappings); + annotations.Remove(RelationalAnnotationNames.SqlQueryMappings); + annotations.Remove(RelationalAnnotationNames.FunctionMappings); + annotations.Remove(RelationalAnnotationNames.InsertStoredProcedureMappings); + annotations.Remove(RelationalAnnotationNames.DeleteStoredProcedureMappings); + annotations.Remove(RelationalAnnotationNames.UpdateStoredProcedureMappings); + annotations.Remove(RelationalAnnotationNames.DefaultMappings); + } + } + private static RuntimeEntityTypeMappingFragment Create( IEntityTypeMappingFragment entityTypeMappingFragment, RuntimeEntityType runtimeEntityType) diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs index 32391ba4ab2..1cd21ee06e7 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs @@ -247,6 +247,44 @@ public RelationalTypeMappingInfo( DbType = dbType; } + /// + /// Creates a new instance of . + /// + /// The CLR type in the model for which mapping is needed. + /// The type mapping configuration. + /// The type mapping for elements, if known. + /// The database type name. + /// The provider-specific relational type name, with any facets removed. + /// Specifies Unicode or ANSI mapping, or for default. + /// Specifies a size for the mapping, or for default. + /// Specifies a precision for the mapping, or for default. + /// Specifies a scale for the mapping, or for default. + public RelationalTypeMappingInfo( + Type type, + ITypeMappingConfiguration typeMappingConfiguration, + RelationalTypeMapping? elementTypeMapping = null, + string? storeTypeName = null, + string? storeTypeNameBase = null, + bool? unicode = null, + int? size = null, + int? precision = null, + int? scale = null) + { + _coreTypeMappingInfo = new TypeMappingInfo( + typeMappingConfiguration.GetValueConverter()?.ProviderClrType ?? type, + elementTypeMapping, + keyOrIndex: false, + unicode ?? typeMappingConfiguration.IsUnicode(), + size ?? typeMappingConfiguration.GetMaxLength(), + rowVersion: false, + precision ?? typeMappingConfiguration.GetPrecision(), + scale ?? typeMappingConfiguration.GetScale()); + + IsFixedLength = (bool?)typeMappingConfiguration[RelationalAnnotationNames.IsFixedLength]; + StoreTypeName = storeTypeName; + StoreTypeNameBase = storeTypeNameBase; + } + /// /// The core type mapping info. /// diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index c7eda39eb76..3a685cf279e 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -343,66 +343,34 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) { type = type.UnwrapNullableType(); var typeConfiguration = model.FindTypeMappingConfiguration(type); - RelationalTypeMappingInfo mappingInfo; - Type? providerClrType = null; - ValueConverter? customConverter = null; - if (typeConfiguration == null) + if (typeConfiguration != null) { - mappingInfo = new RelationalTypeMappingInfo(type, (RelationalTypeMapping?)elementMapping); - } - else - { - providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); - customConverter = typeConfiguration.GetValueConverter(); - - var isUnicode = typeConfiguration.IsUnicode(); - var scale = typeConfiguration.GetScale(); - var precision = typeConfiguration.GetPrecision(); - var size = typeConfiguration.GetMaxLength(); - + bool? unicode = null; + int? scale = null; + int? precision = null; + int? size = null; + string? storeTypeNameBase = null; var storeTypeName = (string?)typeConfiguration[RelationalAnnotationNames.ColumnType]; - string? storeTypeBaseName = null; if (storeTypeName != null) { - storeTypeBaseName = ParseStoreTypeName(storeTypeName, ref isUnicode, ref size, ref precision, ref scale); + storeTypeNameBase = ParseStoreTypeName(storeTypeName, ref unicode, ref size, ref precision, ref scale); } - var isFixedLength = (bool?)typeConfiguration[RelationalAnnotationNames.IsFixedLength]; - mappingInfo = new RelationalTypeMappingInfo( - customConverter?.ProviderClrType ?? type, - (RelationalTypeMapping?)elementMapping, - storeTypeName, - storeTypeBaseName, - keyOrIndex: false, - unicode: isUnicode, - size: size, - rowVersion: false, - fixedLength: isFixedLength, - precision: precision, - scale: scale); + var mappingInfo = new RelationalTypeMappingInfo(type, typeConfiguration, (RelationalTypeMapping?)elementMapping, + storeTypeName, storeTypeNameBase, unicode, size, precision, scale); + var providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); + return FindMappingWithConversion(mappingInfo, providerClrType, customConverter: typeConfiguration.GetValueConverter()); } - return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + return FindMappingWithConversion( + new RelationalTypeMappingInfo(type, (RelationalTypeMapping?)elementMapping), + providerClrType: null, + customConverter: null); } - /// - /// Finds the type mapping for a given representing - /// a field or a property of a CLR type. - /// - /// - /// - /// Note: Only call this method if there is no available, otherwise - /// call - /// - /// - /// Note: providers should typically not need to override this method. - /// - /// - /// The field or property. - /// The type mapping, or if none was found. + /// public override RelationalTypeMapping? FindMapping(MemberInfo member) { - // TODO: Remove this, see #11124 if (member.GetCustomAttribute(true) is ColumnAttribute attribute) { var storeTypeName = attribute.TypeName; @@ -417,7 +385,27 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) new RelationalTypeMappingInfo(member, null, storeTypeName, storeTypeNameBase, unicode, size, precision, scale), null); } - return FindMappingWithConversion(new RelationalTypeMappingInfo(member), null); + return FindMappingWithConversion(new RelationalTypeMappingInfo(member), null, null); + } + + /// + public override RelationalTypeMapping? FindMapping(MemberInfo member, IModel model, bool useAttributes) + { + if (useAttributes + && member.GetCustomAttribute(true) is ColumnAttribute attribute) + { + var storeTypeName = attribute.TypeName; + bool? unicode = null; + int? size = null; + int? precision = null; + int? scale = null; + var storeTypeNameBase = ParseStoreTypeName(storeTypeName, ref unicode, ref size, ref precision, ref scale); + + return FindMappingWithConversion( + new RelationalTypeMappingInfo(member, null, storeTypeName, storeTypeNameBase, unicode, size, precision, scale), null); + } + + return FindMappingWithConversion(new RelationalTypeMappingInfo(member), null, null); } /// diff --git a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs index b71ff1a5c52..fe15c2e5f5f 100644 --- a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs @@ -30,14 +30,14 @@ public interface ICSharpRuntimeAnnotationCodeGenerator /// /// Generates code to create the given annotations. /// - /// The entity type to which the annotations are applied. + /// The complex property to which the annotations are applied. /// Additional parameters used during code generation. void Generate(IComplexProperty complexProperty, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// - /// The entity type to which the annotations are applied. + /// The complex type to which the annotations are applied. /// Additional parameters used during code generation. void Generate(IComplexType complexType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index e65a07bb27d..49601b9d34c 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -236,7 +236,7 @@ void Validate(IConventionTypeBase typeBase) } var targetType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType( - clrProperty, conventionModel, out var targetOwned); + clrProperty, conventionModel, useAttributes: true, out var targetOwned); if (targetType == null && clrProperty.FindSetterProperty() == null) { diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 5dfc1bdfd20..76368c12a15 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -556,6 +556,7 @@ bool CanSetProviderValueComparer( /// 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. /// + [EntityFrameworkInternal] IConventionElementTypeBuilder? SetElementType(Type? elementType, bool fromDataAnnotation = false); /// @@ -564,5 +565,6 @@ bool CanSetProviderValueComparer( /// 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. /// + [EntityFrameworkInternal] bool CanSetElementType(Type? elementType, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs index 6d7baa64c25..5b8ccb167d9 100644 --- a/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -29,9 +30,13 @@ public class ComplexPropertyDiscoveryConvention : /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public ComplexPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies) + /// Whether the convention will use attributes found on the members. + public ComplexPropertyDiscoveryConvention( + ProviderConventionSetBuilderDependencies dependencies, + bool useAttributes = true) { Dependencies = dependencies; + UseAttributes = useAttributes; } /// @@ -39,33 +44,64 @@ public ComplexPropertyDiscoveryConvention(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - private void DiscoverComplexProperties( - IConventionTypeBaseBuilder typeBaseBuilder) + /// + /// A value indicating whether the convention will use attributes found on the members. + /// + protected virtual bool UseAttributes { get; } + + /// + /// Discovers complex properties on the given structural type. + /// + /// The type for which the properties will be discovered. + /// Additional information associated with convention execution. + protected virtual void DiscoverComplexProperties( + IConventionTypeBaseBuilder structuralTypeBuilder, + IConventionContext context) { - var typeBase = typeBaseBuilder.Metadata; - foreach (var candidateMember in typeBase.GetRuntimeProperties().Values) + var typeBase = structuralTypeBuilder.Metadata; + foreach (var candidateMember in GetMembers(typeBase)) { - TryConfigureComplexProperty(candidateMember, typeBase); + TryConfigureComplexProperty(candidateMember, typeBase, context); } } - private bool TryConfigureComplexProperty(MemberInfo? candidateMember, IConventionTypeBase typeBase) + private void TryConfigureComplexProperty(MemberInfo? candidateMember, IConventionTypeBase typeBase, IConventionContext context) { if (candidateMember == null - || !typeBase.IsInModel - || typeBase.IsIgnored(candidateMember.Name) - || typeBase.FindMember(candidateMember.Name) != null - || (candidateMember is PropertyInfo propertyInfo && propertyInfo.GetIndexParameters().Length != 0) + || !IsCandidateComplexProperty(candidateMember, typeBase, out var targetClrType)) + { + return; + } + + RemoveComplexCandidate(candidateMember.Name, typeBase.Builder); + + typeBase.Builder.ComplexProperty(candidateMember, targetClrType); + } + + /// + /// Returns a value indicating whether the given member is a complex property candidate. + /// + /// The member. + /// The type for which the properties will be discovered. + /// The complex type. + protected virtual bool IsCandidateComplexProperty( + MemberInfo memberInfo, IConventionTypeBase structuralType, [NotNullWhen(true)] out Type? targetClrType) + { + if (!structuralType.IsInModel + || structuralType.IsIgnored(memberInfo.Name) + || structuralType.FindMember(memberInfo.Name) != null + || (memberInfo is PropertyInfo propertyInfo && propertyInfo.GetIndexParameters().Length != 0) || !Dependencies.MemberClassifier.IsCandidateComplexProperty( - candidateMember, typeBase.Model, out var elementType, out var explicitlyConfigured)) + memberInfo, structuralType.Model, UseAttributes, out var elementType, out var explicitlyConfigured)) { + targetClrType = null; return false; } - var model = (Model)typeBase.Model; - var targetClrType = (elementType ?? candidateMember.GetMemberType()).UnwrapNullableType(); - if (typeBase.Model.Builder.IsIgnored(targetClrType) - || (typeBase is IReadOnlyComplexType complexType + var model = (Model)structuralType.Model; + targetClrType = (elementType ?? memberInfo.GetMemberType()).UnwrapNullableType(); + if (structuralType.Model.Builder.IsIgnored(targetClrType) + || (structuralType is IReadOnlyComplexType complexType && complexType.IsContainedBy(targetClrType))) { return false; @@ -74,26 +110,35 @@ private bool TryConfigureComplexProperty(MemberInfo? candidateMember, IConventio if (!explicitlyConfigured && model.FindIsComplexConfigurationSource(targetClrType) == null) { - AddComplexCandidate(candidateMember, typeBase.Builder); + AddComplexCandidate(memberInfo, structuralType.Builder); return false; } - RemoveComplexCandidate(candidateMember.Name, typeBase.Builder); - - return typeBase.Builder.ComplexProperty(candidateMember, targetClrType) != null; + return true; } + /// + /// Returns the CLR members from the given type that should be considered when discovering properties. + /// + /// The type for which the properties will be discovered. + /// The CLR members to be considered. + protected virtual IEnumerable GetMembers(IConventionTypeBase structuralType) + => structuralType is IConventionComplexType + ? structuralType.GetRuntimeProperties().Values.Cast() + .Concat(structuralType.GetRuntimeFields().Values) + : structuralType.GetRuntimeProperties().Values.Cast(); + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) - => DiscoverComplexProperties(entityTypeBuilder); + => DiscoverComplexProperties(entityTypeBuilder, context); /// public void ProcessComplexPropertyAdded( IConventionComplexPropertyBuilder propertyBuilder, IConventionContext context) - => DiscoverComplexProperties(propertyBuilder.Metadata.ComplexType.Builder); + => DiscoverComplexProperties(propertyBuilder.Metadata.ComplexType.Builder, context); /// public virtual void ProcessEntityTypeBaseTypeChanged( @@ -104,13 +149,13 @@ public virtual void ProcessEntityTypeBaseTypeChanged( { if (oldBaseType?.IsInModel == true) { - DiscoverComplexProperties(oldBaseType.Builder); + DiscoverComplexProperties(oldBaseType.Builder, context); } var entityType = entityTypeBuilder.Metadata; if (entityType.BaseType == newBaseType) { - DiscoverComplexProperties(entityType.Builder); + DiscoverComplexProperties(entityType.Builder, context); } } @@ -119,14 +164,14 @@ public void ProcessPropertyRemoved( IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property, IConventionContext context) - => TryConfigureComplexProperty(property.GetIdentifyingMemberInfo(), typeBaseBuilder.Metadata); + => TryConfigureComplexProperty(property.GetIdentifyingMemberInfo(), typeBaseBuilder.Metadata, context); /// public void ProcessSkipNavigationRemoved( IConventionEntityTypeBuilder entityTypeBuilder, IConventionSkipNavigation navigation, IConventionContext context) - => TryConfigureComplexProperty(navigation.GetIdentifyingMemberInfo(), entityTypeBuilder.Metadata); + => TryConfigureComplexProperty(navigation.GetIdentifyingMemberInfo(), entityTypeBuilder.Metadata, context); /// public virtual void ProcessNavigationRemoved( @@ -135,7 +180,7 @@ public virtual void ProcessNavigationRemoved( string navigationName, MemberInfo? memberInfo, IConventionContext context) - => TryConfigureComplexProperty(memberInfo, sourceEntityTypeBuilder.Metadata); + => TryConfigureComplexProperty(memberInfo, sourceEntityTypeBuilder.Metadata, context); /// public void ProcessEntityTypeMemberIgnored( @@ -169,26 +214,24 @@ public void ProcessModelFinalizing( { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { - DiscoverMissedComplexProperties(entityType); + DiscoverMissedComplexProperties(entityType, context); } } - private void DiscoverMissedComplexProperties(IConventionTypeBase typeBase) + private void DiscoverMissedComplexProperties(IConventionTypeBase typeBase, IConventionContext context) { var candidates = GetComplexCandidates(typeBase); if (candidates != null) { foreach (var candidatePair in candidates.OrderBy(v => v.Key)) { - TryConfigureComplexProperty(candidatePair.Value, typeBase); + TryConfigureComplexProperty(candidatePair.Value, typeBase, context); } - - typeBase.Builder.HasNoAnnotation(CoreAnnotationNames.ComplexCandidates); } foreach (var complexProperty in typeBase.GetComplexProperties()) { - DiscoverMissedComplexProperties(complexProperty.ComplexType); + DiscoverMissedComplexProperties(complexProperty.ComplexType, context); } } diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs index 0655edffd9a..1b2e93746f2 100644 --- a/src/EFCore/Metadata/Conventions/ConventionSet.cs +++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs @@ -592,6 +592,12 @@ public virtual void Replace(TImplementation newConvention) PropertyFieldChangedConventions.Add(propertyFieldChangedConvention); } + if (newConvention is IPropertyElementTypeChangedConvention propertyElementTypeChangedConvention + && !Replace(PropertyElementTypeChangedConventions, propertyElementTypeChangedConvention, oldConvetionType)) + { + PropertyElementTypeChangedConventions.Add(propertyElementTypeChangedConvention); + } + if (newConvention is IPropertyAnnotationChangedConvention propertyAnnotationChangedConvention && !Replace(PropertyAnnotationChangedConventions, propertyAnnotationChangedConvention, oldConvetionType)) { @@ -896,6 +902,11 @@ public virtual void Add(IConvention convention) PropertyFieldChangedConventions.Add(propertyFieldChangedConvention); } + if (convention is IPropertyElementTypeChangedConvention propertyElementTypeChangedConvention) + { + PropertyElementTypeChangedConventions.Add(propertyElementTypeChangedConvention); + } + if (convention is IPropertyAnnotationChangedConvention propertyAnnotationChangedConvention) { PropertyAnnotationChangedConventions.Add(propertyAnnotationChangedConvention); @@ -1209,6 +1220,11 @@ public virtual void Remove(Type conventionType) Remove(PropertyNullabilityChangedConventions, conventionType); } + if (typeof(IPropertyElementTypeChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(PropertyElementTypeChangedConventions, conventionType); + } + if (typeof(IPropertyFieldChangedConvention).IsAssignableFrom(conventionType)) { Remove(PropertyFieldChangedConventions, conventionType); diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs index 2cdbd9bed9c..8cc54e6b108 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs @@ -52,7 +52,7 @@ public virtual void ProcessEntityTypeAdded( var foreignKeyNavigations = new List(); var unconfiguredNavigations = new List(); var inverses = new List(); - foreach (var candidatePair in Dependencies.MemberClassifier.GetNavigationCandidates(entityType)) + foreach (var candidatePair in Dependencies.MemberClassifier.GetNavigationCandidates(entityType, useAttributes: true)) { var (targetType, shouldBeOwned) = candidatePair.Value; if (targetType != entityType.ClrType) @@ -442,7 +442,7 @@ var fkPropertiesOnDependentToPrincipal } private bool IsNavigationCandidate(PropertyInfo propertyInfo, IConventionEntityType entityType) - => Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(propertyInfo, out _); + => Dependencies.MemberClassifier.GetNavigationCandidates(entityType, useAttributes: true).TryGetValue(propertyInfo, out _); private static IReadOnlyList? FindCandidateDependentPropertiesThroughNavigation( IConventionForeignKeyBuilder relationshipBuilder, diff --git a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs index 2f31554e1e6..20e16eb1aa7 100644 --- a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs @@ -76,7 +76,7 @@ private void Process( var entityType = entityTypeBuilder.Metadata; var targetEntityType = targetEntityTypeBuilder.Metadata; var targetClrType = targetEntityType.ClrType; - var navigationCandidates = Dependencies.MemberClassifier.GetNavigationCandidates(targetEntityType); + var navigationCandidates = Dependencies.MemberClassifier.GetNavigationCandidates(targetEntityType, useAttributes: true); var inverseNavigationPropertyInfo = targetEntityType.GetRuntimeProperties().Values .FirstOrDefault( p => string.Equals(p.GetSimpleMemberName(), attribute.Property, StringComparison.Ordinal) diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index a083c858f16..ea91dc4c6d7 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -310,7 +310,7 @@ public virtual void ProcessEntityTypeMemberIgnored( private Type? FindCandidateNavigationWithAttributePropertyType(PropertyInfo propertyInfo, IConventionModel model) { - var targetClrType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo, model, out _); + var targetClrType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo, model, useAttributes: true, out _); return targetClrType != null && Attribute.IsDefined(propertyInfo, typeof(TAttribute), inherit: true) ? targetClrType @@ -318,7 +318,7 @@ public virtual void ProcessEntityTypeMemberIgnored( } private Type? FindCandidateNavigationWithAttributePropertyType(PropertyInfo propertyInfo, IConventionEntityType entityType) - => Dependencies.MemberClassifier.GetNavigationCandidates(entityType) + => Dependencies.MemberClassifier.GetNavigationCandidates(entityType, useAttributes: true) .TryGetValue(propertyInfo, out var target) && Attribute.IsDefined(propertyInfo, typeof(TAttribute), inherit: true) ? target.Type diff --git a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs index d5c23f624df..39ad79bfc4c 100644 --- a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs +++ b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs @@ -14,6 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; public class NonNullableReferencePropertyConvention : NonNullableConventionBase, IPropertyAddedConvention, IPropertyFieldChangedConvention, + IPropertyElementTypeChangedConvention, IComplexPropertyAddedConvention, IComplexPropertyFieldChangedConvention { @@ -77,13 +78,26 @@ public virtual void ProcessPropertyFieldChanged( } /// - public void ProcessComplexPropertyAdded( + public virtual void ProcessPropertyElementTypeChanged( + IConventionPropertyBuilder propertyBuilder, + IElementType? newElementType, + IElementType? oldElementType, + IConventionContext context) + { + if (newElementType != null) + { + Process(propertyBuilder); + } + } + + /// + public virtual void ProcessComplexPropertyAdded( IConventionComplexPropertyBuilder propertyBuilder, IConventionContext context) => Process(propertyBuilder); /// - public void ProcessComplexPropertyFieldChanged( + public virtual void ProcessComplexPropertyFieldChanged( IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo, diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index fc3cd2dd6aa..002d4e71cba 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -20,9 +20,13 @@ public class PropertyDiscoveryConvention : /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public PropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies) + /// Whether the convention will use attributes found on the members. + public PropertyDiscoveryConvention( + ProviderConventionSetBuilderDependencies dependencies, + bool useAttributes = true) { Dependencies = dependencies; + UseAttributes = useAttributes; } /// @@ -30,39 +34,16 @@ public PropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies depe /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + /// + /// A value indicating whether the convention will use attributes found on the members. + /// + protected virtual bool UseAttributes { get; } + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) - => Process(entityTypeBuilder); - - /// - public void ProcessComplexPropertyAdded( - IConventionComplexPropertyBuilder propertyBuilder, - IConventionContext context) - { - var complexType = propertyBuilder.Metadata.ComplexType; - var model = complexType.Model; - foreach (var propertyInfo in complexType.GetRuntimeProperties().Values) - { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model, out _)) - { - continue; - } - - complexType.Builder.Property(propertyInfo); - } - - foreach (var fieldInfo in complexType.GetRuntimeFields().Values) - { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(fieldInfo, model, out _)) - { - continue; - } - - complexType.Builder.Property(fieldInfo); - } - } + => DiscoverPrimitiveProperties(entityTypeBuilder, context); /// public virtual void ProcessEntityTypeBaseTypeChanged( @@ -75,26 +56,37 @@ public virtual void ProcessEntityTypeBaseTypeChanged( || oldBaseType != null) && entityTypeBuilder.Metadata.BaseType == newBaseType) { - Process(entityTypeBuilder); + DiscoverPrimitiveProperties(entityTypeBuilder, context); } } - private void Process(IConventionEntityTypeBuilder entityTypeBuilder) + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + => DiscoverPrimitiveProperties(propertyBuilder.Metadata.ComplexType.Builder, context); + + /// + /// Discovers properties on the given structural type. + /// + /// The type for which the properties will be discovered. + /// Additional information associated with convention execution. + protected virtual void DiscoverPrimitiveProperties( + IConventionTypeBaseBuilder structuralTypeBuilder, + IConventionContext context) { - var entityType = entityTypeBuilder.Metadata; - var model = entityType.Model; - foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) + var structuralType = structuralTypeBuilder.Metadata; + foreach (var propertyInfo in GetMembers(structuralType)) { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model, out var mapping) - || ((Model)model).FindIsComplexConfigurationSource(propertyInfo.GetMemberType().UnwrapNullableType()) != null) + if (!IsCandidatePrimitiveProperty(propertyInfo, structuralType, out var mapping)) { continue; } - var propertyBuilder = entityTypeBuilder.Property(propertyInfo); + var propertyBuilder = structuralTypeBuilder.Property(propertyInfo); if (mapping?.ElementTypeMapping != null) { - var elementType = propertyInfo.PropertyType.TryGetElementType(typeof(IEnumerable<>)); + var elementType = propertyInfo.GetMemberType().TryGetElementType(typeof(IEnumerable<>)); if (elementType != null) { propertyBuilder?.SetElementType(elementType); @@ -102,4 +94,26 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder) } } } + + /// + /// Returns the CLR members from the given type that should be considered when discovering properties. + /// + /// The type for which the properties will be discovered. + /// The CLR members to be considered. + protected virtual IEnumerable GetMembers(IConventionTypeBase structuralType) + => structuralType is IConventionComplexType + ? structuralType.GetRuntimeProperties().Values.Cast() + .Concat(structuralType.GetRuntimeFields().Values) + : structuralType.GetRuntimeProperties().Values.Cast(); + + /// + /// Returns a value indicating whether the given member is a primitive property candidate. + /// + /// The member. + /// The type for which the properties will be discovered. + /// The type mapping for the property. + protected virtual bool IsCandidatePrimitiveProperty( + MemberInfo memberInfo, IConventionTypeBase structuralType, out CoreTypeMapping? mapping) + => Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(memberInfo, structuralType.Model, UseAttributes, out mapping) + && ((Model)structuralType.Model).FindIsComplexConfigurationSource(memberInfo.GetMemberType().UnwrapNullableType()) == null; } diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index cd8d46c8192..fb7f3e51e70 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -29,9 +29,13 @@ public class RelationshipDiscoveryConvention : /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public RelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies) + /// Whether the convention will use attributes found on the members. + public RelationshipDiscoveryConvention( + ProviderConventionSetBuilderDependencies dependencies, + bool useAttributes = true) { Dependencies = dependencies; + UseAttributes = useAttributes; } /// @@ -39,24 +43,38 @@ public RelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - private void DiscoverRelationships( + /// + /// A value indicating whether the convention will use attributes found on the members. + /// + protected virtual bool UseAttributes { get; } + + /// + /// Discovers the relationships for the given entity type. + /// + /// The entity type builder. + /// Additional information associated with convention execution. + /// Whether to discover unmatched inverse navigations. + protected virtual void DiscoverRelationships( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context, - HashSet? otherInverseCandidateTypes = null) + bool discoverUnmatchedInverses = false) { - var relationshipCandidates = FindRelationshipCandidates(entityTypeBuilder, otherInverseCandidateTypes); + var unmatchedInverseCandidates = discoverUnmatchedInverses + ? Dependencies.MemberClassifier.GetInverseCandidateTypes(entityTypeBuilder.Metadata, UseAttributes).ToList() + : null; + var relationshipCandidates = FindRelationshipCandidates(entityTypeBuilder, unmatchedInverseCandidates); relationshipCandidates = RemoveIncompatibleWithExistingRelationships(relationshipCandidates, entityTypeBuilder); relationshipCandidates = RemoveInheritedInverseNavigations(relationshipCandidates); relationshipCandidates = RemoveSingleSidedBaseNavigations(relationshipCandidates, entityTypeBuilder); CreateRelationships(relationshipCandidates, entityTypeBuilder); - DiscoverUnidirectionalInverses(entityTypeBuilder, context, otherInverseCandidateTypes); + DiscoverUnidirectionalInverses(entityTypeBuilder, context, unmatchedInverseCandidates); } private IReadOnlyList FindRelationshipCandidates( IConventionEntityTypeBuilder entityTypeBuilder, - HashSet? otherInverseCandidateTypes) + List? otherInverseCandidateTypes) { var entityType = entityTypeBuilder.Metadata; var relationshipCandidates = new Dictionary(); @@ -68,7 +86,7 @@ private IReadOnlyList FindRelationshipCandidates( return relationshipCandidates.Values.ToList(); } - foreach (var candidateTuple in Dependencies.MemberClassifier.GetNavigationCandidates(entityType)) + foreach (var candidateTuple in Dependencies.MemberClassifier.GetNavigationCandidates(entityType, UseAttributes)) { var navigationPropertyInfo = candidateTuple.Key; var (targetClrType, shouldBeOwned) = candidateTuple.Value; @@ -192,7 +210,7 @@ private IReadOnlyList FindRelationshipCandidates( if (!entityType.IsKeyless) { - var inverseCandidates = Dependencies.MemberClassifier.GetNavigationCandidates(candidateTargetEntityType); + var inverseCandidates = Dependencies.MemberClassifier.GetNavigationCandidates(candidateTargetEntityType, UseAttributes); foreach (var (inversePropertyInfo, value) in inverseCandidates) { if (navigationPropertyInfo.IsSameAs(inversePropertyInfo) @@ -805,19 +823,21 @@ private void CreateRelationships( private void DiscoverUnidirectionalInverses( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context, - HashSet? otherInverseCandidateTypes) + List? otherInverseCandidateTypes) { var model = entityTypeBuilder.Metadata.Model; - if (otherInverseCandidateTypes != null) + if (otherInverseCandidateTypes == null) { - foreach (var inverseCandidateType in otherInverseCandidateTypes) + return; + } + + foreach (var inverseCandidateType in otherInverseCandidateTypes) + { + foreach (var inverseCandidateEntityType in model.FindEntityTypes(inverseCandidateType).ToList()) { - foreach (var inverseCandidateEntityType in model.FindEntityTypes(inverseCandidateType).ToList()) + if (inverseCandidateEntityType.IsInModel) { - if (inverseCandidateEntityType.IsInModel) - { - DiscoverRelationships(inverseCandidateEntityType.Builder, context); - } + DiscoverRelationships(inverseCandidateEntityType.Builder, context); } } } @@ -1011,11 +1031,7 @@ public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) { - DiscoverRelationships( - entityTypeBuilder, - context, - Dependencies.MemberClassifier.GetInverseCandidateTypes(entityTypeBuilder.Metadata).ToHashSet()); - + DiscoverRelationships(entityTypeBuilder, context, discoverUnmatchedInverses: true); if (!entityTypeBuilder.Metadata.IsInModel) { context.StopProcessing(); @@ -1084,10 +1100,7 @@ public virtual void ProcessForeignKeyRemoved( && foreignKey.IsOwnership && !entityTypeBuilder.Metadata.IsOwned()) { - DiscoverRelationships( - entityTypeBuilder, - context, - Dependencies.MemberClassifier.GetInverseCandidateTypes(entityTypeBuilder.Metadata).ToHashSet()); + DiscoverRelationships(entityTypeBuilder, context, discoverUnmatchedInverses: true); } } @@ -1107,7 +1120,7 @@ public virtual void ProcessNavigationRemoved( && IsCandidateNavigationProperty( sourceEntityTypeBuilder.Metadata, navigationName, memberInfo) && Dependencies.MemberClassifier.FindCandidateNavigationPropertyType( - memberInfo, targetEntityTypeBuilder.Metadata.Model, out _) + memberInfo, targetEntityTypeBuilder.Metadata.Model, UseAttributes, out _) != null) { Process(sourceEntityTypeBuilder.Metadata, navigationName, memberInfo, context); @@ -1303,12 +1316,7 @@ public virtual void ProcessNavigationAdded( public virtual void ProcessForeignKeyOwnershipChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) - { - var entityType = relationshipBuilder.Metadata.DeclaringEntityType; - DiscoverRelationships( - entityType.Builder, context, - Dependencies.MemberClassifier.GetInverseCandidateTypes(entityType).ToHashSet()); - } + => DiscoverRelationships(relationshipBuilder.Metadata.DeclaringEntityType.Builder, context, discoverUnmatchedInverses: true); // TODO: Rely on layering to remove these when no longer referenced #15898 private static bool IsImplicitlyCreatedUnusedType(IConventionEntityType entityType) diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 8cba9454a72..6d6e4fef2eb 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -510,9 +510,9 @@ protected virtual void ProcessServicePropertyAnnotations( } } - private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeTypeBase runtimeEntityType) + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeTypeBase runtimeStructuralType) { - var runtimeComplexProperty = runtimeEntityType.AddComplexProperty( + var runtimeComplexProperty = runtimeStructuralType.AddComplexProperty( complexProperty.Name, complexProperty.ClrType, complexProperty.ComplexType.Name, @@ -556,6 +556,9 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeT convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); } + CreateAnnotations( + complexType, runtimeComplexType, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexTypeAnnotations(annotations, source, target, runtime)); return runtimeComplexProperty; } @@ -584,6 +587,31 @@ protected virtual void ProcessComplexPropertyAnnotations( } } + /// + /// Updates the complex type annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source complex type. + /// The target complex type that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessComplexTypeAnnotations( + Dictionary annotations, + IComplexType complexType, + RuntimeComplexType runtimeComplexType, + bool runtime) + { + if (!runtime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key)) + { + annotations.Remove(key); + } + } + } + } + private static RuntimeKey Create(IKey key, RuntimeEntityType runtimeEntityType) => runtimeEntityType.AddKey(runtimeEntityType.FindProperties(key.Properties.Select(p => p.Name))!); diff --git a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs index c00e82e3237..5dd4ca08667 100644 --- a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -19,9 +21,13 @@ public class ServicePropertyDiscoveryConvention : /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public ServicePropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies) + /// Whether the convention will use attributes found on the members. + public ServicePropertyDiscoveryConvention( + ProviderConventionSetBuilderDependencies dependencies, + bool useAttributes = true) { Dependencies = dependencies; + UseAttributes = useAttributes; } /// @@ -29,6 +35,11 @@ public ServicePropertyDiscoveryConvention(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + /// + /// A value indicating whether the convention will use attributes found on the members. + /// + protected virtual bool UseAttributes { get; } + /// /// Called after an entity type is added to the model. /// @@ -37,7 +48,7 @@ public ServicePropertyDiscoveryConvention(ProviderConventionSetBuilderDependenci public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) - => Process(entityTypeBuilder); + => DiscoverServiceProperties(entityTypeBuilder, context); /// /// Called after the base type of an entity type changes. @@ -54,43 +65,72 @@ public virtual void ProcessEntityTypeBaseTypeChanged( { if (entityTypeBuilder.Metadata.BaseType == newBaseType) { - Process(entityTypeBuilder); + DiscoverServiceProperties(entityTypeBuilder, context); } } - private void Process(IConventionEntityTypeBuilder entityTypeBuilder) + /// + /// Discovers properties on the given structural type. + /// + /// The type for which the properties will be discovered. + /// Additional information associated with convention execution. + protected virtual void DiscoverServiceProperties( + IConventionTypeBaseBuilder structuralTypeBuilder, + IConventionContext context) { - var entityType = entityTypeBuilder.Metadata; - var model = entityType.Model; - DiscoverServiceProperties(entityType.GetRuntimeProperties().Values); - DiscoverServiceProperties(entityType.GetRuntimeFields().Values); + if (structuralTypeBuilder is not IConventionEntityTypeBuilder entityTypeBuilder) + { + return; + } - void DiscoverServiceProperties(IEnumerable members) + var entityType = entityTypeBuilder.Metadata; + foreach (var memberInfo in GetMembers(entityType)) { - foreach (var memberInfo in members) + if (!IsCandidateServiceProperty(memberInfo, entityType, out var factory)) { - if (!entityTypeBuilder.CanHaveServiceProperty(memberInfo) - || ((Model)model).FindIsComplexConfigurationSource(memberInfo.GetMemberType().UnwrapNullableType()) != null) - { - continue; - } + continue; + } + + entityTypeBuilder.ServiceProperty(memberInfo)?.HasParameterBinding( + (ServiceParameterBinding)factory.Bind(entityType, memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + } - var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(memberInfo, model); - if (factory == null) - { - continue; - } + /// + /// Returns the CLR members from the given type that should be considered when discovering properties. + /// + /// The type for which the properties will be discovered. + /// The CLR members to be considered. + protected virtual IEnumerable GetMembers(IConventionTypeBase structuralType) + => structuralType.GetRuntimeProperties().Values.Cast() + .Concat(structuralType.GetRuntimeFields().Values); - var memberType = memberInfo.GetMemberType(); - if (entityType.HasServiceProperties() - && entityType.GetServiceProperties().Any(p => p.ClrType == memberType)) - { - continue; - } + /// + /// Returns a value indicating whether the given member is a service property candidate. + /// + /// The member. + /// The type for which the properties will be discovered. + /// The parameter binding factory for the property. + protected virtual bool IsCandidateServiceProperty( + MemberInfo memberInfo, IConventionTypeBase structuralType, [NotNullWhen(true)] out IParameterBindingFactory? factory) + { + factory = null; + var model = (Model)structuralType.Model; + if (structuralType is not IConventionEntityType entityType + || !entityType.Builder.CanHaveServiceProperty(memberInfo) + || model.FindIsComplexConfigurationSource(memberInfo.GetMemberType().UnwrapNullableType()) != null) + { + return false; + } - entityTypeBuilder.ServiceProperty(memberInfo)?.HasParameterBinding( - (ServiceParameterBinding)factory.Bind(entityType, memberType, memberInfo.GetSimpleMemberName())); - } + factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(memberInfo, model, UseAttributes); + if (factory == null) + { + return false; } + + var memberType = memberInfo.GetMemberType(); + return !entityType.HasServiceProperties() + || !entityType.GetServiceProperties().Any(p => p.ClrType == memberType); } } diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index 5d969565376..17559381dc7 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -258,7 +258,7 @@ public static class CoreAnnotationNames /// 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 const string InverseNavigationCandidates = "RelationshipDiscoveryConvention:InverseNavigationCandidates"; + public const string InverseNavigationsNoAttribute = "RelationshipDiscoveryConvention:InverseNavigationsNoAttribute"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -268,6 +268,14 @@ public static class CoreAnnotationNames /// public const string NavigationCandidates = "RelationshipDiscoveryConvention:NavigationCandidates"; + /// + /// 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 const string NavigationCandidatesNoAttribute = "RelationshipDiscoveryConvention:NavigationCandidatesNoAttribute"; + /// /// 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 @@ -379,10 +387,11 @@ public static class CoreAnnotationNames ModelDependencies, ReadOnlyModel, PreUniquificationName, - InverseNavigations, DerivedTypes, - InverseNavigationCandidates, + InverseNavigations, + InverseNavigationsNoAttribute, NavigationCandidates, + NavigationCandidatesNoAttribute, ComplexCandidates, AmbiguousNavigations, AmbiguousField, diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs index 139aac305fd..152dd32154b 100644 --- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -24,7 +25,9 @@ public interface IMemberClassifier /// 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. /// - ImmutableSortedDictionary GetNavigationCandidates(IConventionEntityType entityType); + IReadOnlyDictionary GetNavigationCandidates( + IConventionEntityType entityType, + bool useAttributes); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -32,7 +35,11 @@ public interface IMemberClassifier /// 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. /// - Type? FindCandidateNavigationPropertyType(MemberInfo memberInfo, IConventionModel model, out bool? shouldBeOwned); + Type? FindCandidateNavigationPropertyType( + MemberInfo memberInfo, + IConventionModel model, + bool useAttributes, + out bool? shouldBeOwned); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -40,7 +47,11 @@ public interface IMemberClassifier /// 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 IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model, out CoreTypeMapping? typeMapping); + bool IsCandidatePrimitiveProperty( + MemberInfo memberInfo, + IConventionModel model, + bool useAttributes, + out CoreTypeMapping? typeMapping); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -51,6 +62,7 @@ public interface IMemberClassifier bool IsCandidateComplexProperty( MemberInfo memberInfo, IConventionModel model, + bool useAttributes, out Type? elementType, out bool explicitlyConfigured); @@ -60,7 +72,7 @@ bool IsCandidateComplexProperty( /// 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. /// - IReadOnlyCollection GetInverseCandidateTypes(IConventionEntityType entityType); + IReadOnlyCollection GetInverseCandidateTypes(IConventionEntityType entityType, bool useAttributes); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -68,5 +80,8 @@ bool IsCandidateComplexProperty( /// 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. /// - IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(MemberInfo memberInfo, IConventionModel model); + IParameterBindingFactory? FindServicePropertyCandidateBindingFactory( + MemberInfo memberInfo, + IConventionModel model, + bool useAttributes); } diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs index e8253b14625..b5f64ee791e 100644 --- a/src/EFCore/Metadata/Internal/MemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs @@ -39,35 +39,41 @@ public MemberClassifier( /// 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 ImmutableSortedDictionary GetNavigationCandidates( - IConventionEntityType entityType) + public virtual IReadOnlyDictionary GetNavigationCandidates( + IConventionEntityType entityType, + bool useAttributes) { - if (entityType.FindAnnotation(CoreAnnotationNames.NavigationCandidates)?.Value - is ImmutableSortedDictionary navigationCandidates) + var candidatesAnnotationName = useAttributes + ? CoreAnnotationNames.NavigationCandidates + : CoreAnnotationNames.NavigationCandidatesNoAttribute; + var inverseAnnotationName = useAttributes + ? CoreAnnotationNames.InverseNavigations + : CoreAnnotationNames.InverseNavigationsNoAttribute; + if (entityType.FindAnnotation(candidatesAnnotationName)?.Value + is OrderedDictionary navigationCandidates) { return navigationCandidates; } - var dictionaryBuilder = ImmutableSortedDictionary.CreateBuilder( - MemberInfoNameComparer.Instance); + navigationCandidates = new(); var model = entityType.Model; - if (model.FindAnnotation(CoreAnnotationNames.InverseNavigationCandidates)?.Value + if (model.FindAnnotation(inverseAnnotationName)?.Value is not Dictionary> inverseCandidatesLookup) { inverseCandidatesLookup = new Dictionary>(); - model.SetAnnotation(CoreAnnotationNames.InverseNavigationCandidates, inverseCandidatesLookup); + model.SetAnnotation(inverseAnnotationName, inverseCandidatesLookup); } foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) { - var targetType = FindCandidateNavigationPropertyType(propertyInfo, entityType.Model, out var shouldBeOwned); + var targetType = FindCandidateNavigationPropertyType(propertyInfo, entityType.Model, useAttributes, out var shouldBeOwned); if (targetType == null) { continue; } - dictionaryBuilder[propertyInfo] = (targetType, shouldBeOwned); + navigationCandidates.Insert(propertyInfo, (targetType, shouldBeOwned), MemberInfoNameComparer.Instance); if (!inverseCandidatesLookup.TryGetValue(targetType, out var inverseCandidates)) { @@ -78,12 +84,10 @@ public MemberClassifier( inverseCandidates.Add(entityType.ClrType); } - navigationCandidates = dictionaryBuilder.ToImmutable(); - if (!((Annotatable)entityType).IsReadOnly && entityType.IsInModel) { - entityType.Builder.HasAnnotation(CoreAnnotationNames.NavigationCandidates, navigationCandidates); + entityType.Builder.HasAnnotation(candidatesAnnotationName, navigationCandidates); } return navigationCandidates; @@ -95,9 +99,14 @@ public MemberClassifier( /// 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 IReadOnlyCollection GetInverseCandidateTypes(IConventionEntityType entityType) + public virtual IReadOnlyCollection GetInverseCandidateTypes( + IConventionEntityType entityType, + bool useAttributes) { - if (entityType.Model.FindAnnotation(CoreAnnotationNames.InverseNavigationCandidates)?.Value + var annotationName = useAttributes + ? CoreAnnotationNames.InverseNavigations + : CoreAnnotationNames.InverseNavigationsNoAttribute; + if (entityType.Model.FindAnnotation(annotationName)?.Value is not Dictionary> inverseCandidatesLookup || !inverseCandidatesLookup.TryGetValue(entityType.ClrType, out var inverseCandidates)) { @@ -116,6 +125,7 @@ is not Dictionary> inverseCandidatesLookup public virtual Type? FindCandidateNavigationPropertyType( MemberInfo memberInfo, IConventionModel model, + bool useAttributes, out bool? shouldBeOwned) { shouldBeOwned = null; @@ -125,11 +135,11 @@ is not Dictionary> inverseCandidatesLookup return targetSequenceType != null && (propertyInfo == null || propertyInfo.IsCandidateProperty(needsWrite: false)) - && IsCandidateNavigationPropertyType(targetSequenceType, memberInfo, (Model)model, out shouldBeOwned) + && IsCandidateNavigationPropertyType(targetSequenceType, memberInfo, (Model)model, useAttributes, out shouldBeOwned) ? targetSequenceType : (propertyInfo == null || propertyInfo.IsCandidateProperty(needsWrite: true)) - && IsCandidateNavigationPropertyType(targetType, memberInfo, (Model)model, out shouldBeOwned) + && IsCandidateNavigationPropertyType(targetType, memberInfo, (Model)model, useAttributes, out shouldBeOwned) ? targetType : null; } @@ -138,6 +148,7 @@ private bool IsCandidateNavigationPropertyType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type targetType, MemberInfo memberInfo, Model model, + bool useAttributes, out bool? shouldBeOwned) { shouldBeOwned = null; @@ -155,10 +166,12 @@ private bool IsCandidateNavigationPropertyType( shouldBeOwned = configurationType == TypeConfigurationType.OwnedEntityType; } + var memberType = memberInfo.GetMemberType(); return isConfiguredAsEntityType == true - || (targetType != typeof(object) - && _parameterBindingFactories.FindFactory(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()) == null - && _typeMappingSource.FindMapping(targetType, model) == null); + || targetType != typeof(object) + && (memberType != targetType + || (_parameterBindingFactories.FindFactory(memberType, memberInfo.GetSimpleMemberName()) == null + && _typeMappingSource.FindMapping(memberInfo, model, useAttributes) == null)); } /// @@ -167,7 +180,8 @@ private bool IsCandidateNavigationPropertyType( /// 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 IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model, out CoreTypeMapping? typeMapping) + public virtual bool IsCandidatePrimitiveProperty( + MemberInfo memberInfo, IConventionModel model, bool useAttributes, out CoreTypeMapping? typeMapping) { typeMapping = null; if (!memberInfo.IsCandidateProperty()) @@ -177,7 +191,8 @@ public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConvent var configurationType = ((Model)model).Configuration?.GetConfigurationType(memberInfo.GetMemberType()); return configurationType == TypeConfigurationType.Property - || (configurationType == null && (typeMapping = _typeMappingSource.FindMapping(memberInfo)) != null); + || (configurationType == null + && (typeMapping = _typeMappingSource.FindMapping(memberInfo, (IModel)model, useAttributes)) != null); } /// @@ -189,6 +204,7 @@ public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConvent public virtual bool IsCandidateComplexProperty( MemberInfo memberInfo, IConventionModel model, + bool useAttributes, out Type? elementType, out bool explicitlyConfigured) { @@ -233,7 +249,8 @@ private static bool IsCandidateComplexType(Type targetType, IConventionModel mod /// public virtual IParameterBindingFactory? FindServicePropertyCandidateBindingFactory( MemberInfo memberInfo, - IConventionModel model) + IConventionModel model, + bool useAttributes) { if (!memberInfo.IsCandidateProperty(publicOnly: false)) { @@ -250,7 +267,7 @@ private static bool IsCandidateComplexType(Type targetType, IConventionModel mod } if (memberInfo.IsCandidateProperty() - && _typeMappingSource.FindMapping(memberInfo.GetMemberType(), (IModel)model) != null) + && _typeMappingSource.FindMapping(memberInfo, (IModel)model, useAttributes) != null) { return null; } diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 9fbc6127d84..6dfad657c67 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -199,6 +199,7 @@ public virtual ModelConfigurationBuilder Properties( /// /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type /// to be used in queries that are not referencing property of this type. + /// Calling this won't affect whether properties of this type are discovered. /// /// /// @@ -226,6 +227,7 @@ public virtual TypeMappingConfigurationBuilder DefaultTypeMapping /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type /// to be used in queries that are not referencing property of this type. + /// Calling this won't affect whether properties of this type are discovered. /// /// /// diff --git a/src/EFCore/Storage/ITypeMappingSource.cs b/src/EFCore/Storage/ITypeMappingSource.cs index 944b3880cc6..6a54ce3d794 100644 --- a/src/EFCore/Storage/ITypeMappingSource.cs +++ b/src/EFCore/Storage/ITypeMappingSource.cs @@ -53,8 +53,23 @@ public interface ITypeMappingSource /// /// The field or property. /// The type mapping, or if none was found. + [Obsolete("Use overload with IModel")] CoreTypeMapping? FindMapping(MemberInfo member); + /// + /// Finds the type mapping for a given representing + /// a field or a property of a CLR type. + /// + /// + /// Note: Only call this method if there is no available, otherwise + /// call + /// + /// The field or property. + /// The model. + /// Whether the attributes found on the member can influence the type mapping. + /// The type mapping, or if none was found. + CoreTypeMapping? FindMapping(MemberInfo member, IModel model, bool useAttributes); + /// /// Finds the type mapping for a given . /// diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index 5cec9557220..b5bc6c884d4 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -216,6 +216,25 @@ public TypeMappingInfo( Scale = scale; } + /// + /// Creates a new instance of . + /// + /// The CLR type in the model for which mapping is needed. + /// The type mapping configuration. + /// The type mapping for elements, if known. + public TypeMappingInfo( + Type type, + ITypeMappingConfiguration typeMappingConfiguration, + CoreTypeMapping? elementTypeMapping = null) + : this(typeMappingConfiguration.GetValueConverter()?.ProviderClrType ?? type, + elementTypeMapping, + unicode: typeMappingConfiguration.IsUnicode(), + size: typeMappingConfiguration.GetMaxLength(), + precision: typeMappingConfiguration.GetPrecision(), + scale: typeMappingConfiguration.GetScale()) + { + } + /// /// Creates a new instance of . /// diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index 698deacced0..7e78f237e0b 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -222,13 +222,10 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// The type mapping, or if none was found. public override CoreTypeMapping? FindMapping(IElementType elementType) { - var providerClrType = elementType.GetProviderClrType(); - var customConverter = elementType.GetValueConverter(); - var resolvedMapping = FindMappingWithConversion( - new TypeMappingInfo( - elementType, elementType.IsUnicode(), elementType.GetMaxLength(), elementType.GetPrecision(), elementType.GetScale()), - providerClrType, customConverter); + new TypeMappingInfo(elementType), + providerClrType: elementType.GetProviderClrType(), + customConverter: elementType.GetValueConverter()); ValidateMapping(resolvedMapping, null); @@ -268,44 +265,24 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) { type = type.UnwrapNullableType(); var typeConfiguration = model.FindTypeMappingConfiguration(type); - TypeMappingInfo mappingInfo; - Type? providerClrType = null; - ValueConverter? customConverter = null; - if (typeConfiguration == null) - { - mappingInfo = new TypeMappingInfo(type, elementMapping); - } - else + if (typeConfiguration != null) { - providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); - customConverter = typeConfiguration.GetValueConverter(); - mappingInfo = new TypeMappingInfo( - customConverter?.ProviderClrType ?? type, - elementMapping, - unicode: typeConfiguration.IsUnicode(), - size: typeConfiguration.GetMaxLength(), - precision: typeConfiguration.GetPrecision(), - scale: typeConfiguration.GetScale()); + var mappingInfo = new TypeMappingInfo(type, typeConfiguration, elementMapping); + var providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); + return FindMappingWithConversion(mappingInfo, providerClrType, customConverter: typeConfiguration.GetValueConverter()); } - return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + return FindMappingWithConversion( + new TypeMappingInfo(type, elementMapping), + providerClrType: null, + customConverter: null); } - /// - /// Finds the type mapping for a given representing - /// a field or a property of a CLR type. - /// - /// - /// - /// Note: Only call this method if there is no available, otherwise - /// call - /// - /// - /// Note: providers should typically not need to override this method. - /// - /// - /// The field or property. - /// The type mapping, or if none was found. + /// public override CoreTypeMapping? FindMapping(MemberInfo member) - => FindMappingWithConversion(new TypeMappingInfo(member), null); + => FindMappingWithConversion(new TypeMappingInfo(member), null, null); + + /// + public override CoreTypeMapping? FindMapping(MemberInfo member, IModel model, bool useAttributes) + => FindMappingWithConversion(new TypeMappingInfo(member), null, null); } diff --git a/src/EFCore/Storage/TypeMappingSourceBase.cs b/src/EFCore/Storage/TypeMappingSourceBase.cs index 8a9589215f8..94740285a43 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -120,23 +120,13 @@ protected virtual void ValidateMapping( /// The type mapping, or if none was found. public abstract CoreTypeMapping? FindMapping(Type type, IModel model, CoreTypeMapping? elementMapping = null); - /// - /// Finds the type mapping for a given representing - /// a field or a property of a CLR type. - /// - /// - /// - /// Note: Only call this method if there is no available, otherwise - /// call - /// - /// - /// Note: providers should typically not need to override this method. - /// - /// - /// The field or property. - /// The type mapping, or if none was found. + /// public abstract CoreTypeMapping? FindMapping(MemberInfo member); + /// + public virtual CoreTypeMapping? FindMapping(MemberInfo member, IModel model, bool useAttributes) + => FindMapping(member); + /// /// Attempts to find a JSON-based type mapping for a collection of primitive types. /// diff --git a/src/Shared/OrderedDictionary.cs b/src/Shared/OrderedDictionary.cs index 76211c9002c..67d462ef0c5 100644 --- a/src/Shared/OrderedDictionary.cs +++ b/src/Shared/OrderedDictionary.cs @@ -312,6 +312,15 @@ public void Insert(int index, TKey key, TValue value) /// The key of the element to insert. /// The value of the element to insert. public void Insert(TKey key, TValue value) + => Insert(key, value, Comparer.Default); + + /// + /// Inserts the element in this sorted dictionary to the corresponding index using the default comparer. + /// + /// The key of the element to insert. + /// The value of the element to insert. + /// The comparer to use. + public void Insert(TKey key, TValue value, IComparer comparer) { var existingIndex = IndexOf(key, out var hashCode); if (existingIndex >= 0) @@ -319,7 +328,6 @@ public void Insert(TKey key, TValue value) throw new ArgumentException($"Key {key} is already present"); } - var comparer = Comparer.Default; for (var i = _count - 1; i >= 0; i--) { if (comparer.Compare(key, _entries[i].Key) >= 0) diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 0805530a9d2..9562b6d7072 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -213,7 +213,9 @@ public void Test_new_annotations_handled_for_properties() CoreAnnotationNames.DiscriminatorProperty, CoreAnnotationNames.DiscriminatorValue, CoreAnnotationNames.InverseNavigations, + CoreAnnotationNames.InverseNavigationsNoAttribute, CoreAnnotationNames.NavigationCandidates, + CoreAnnotationNames.NavigationCandidatesNoAttribute, CoreAnnotationNames.AmbiguousNavigations, CoreAnnotationNames.DuplicateServiceProperties, CoreAnnotationNames.AdHocModel, diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index 272dee06cd2..eb9b87c1e52 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -4,6 +4,7 @@ #nullable enable using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations.Schema; namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -265,6 +266,38 @@ public virtual void Can_set_collation_for_primitive_collection() Assert.Equal("Latin1_General_CI_AI", entityType.FindProperty("Charm")!.GetCollation()); } + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual void Can_avoid_attributes_when_discovering_properties(bool useAttributes) + { + var modelBuilder = CreateModelBuilder(c => c.Conventions.Replace( + s => new PropertyDiscoveryConvention( + s.GetService()!, useAttributes))); + modelBuilder.Entity(); + + if (useAttributes) + { + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(SqlVariantEntity))!; + + Assert.Equal([nameof(SqlVariantEntity.Id), nameof(SqlVariantEntity.Value),], + entityType.GetProperties().Select(p => p.Name)); + } + else + { + Assert.Equal(CoreStrings.PropertyNotAdded(nameof(SqlVariantEntity), nameof(SqlVariantEntity.Value), "object"), + Assert.Throws(modelBuilder.FinalizeModel).Message); + } + } + + protected class SqlVariantEntity + { + public int Id { get; set; } + [Column(TypeName = "sql_variant")] + public object? Value { get; set; } + } + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); } @@ -599,28 +632,28 @@ public void Adding_conflicting_check_constraint_to_derived_type_before_base_thro protected override TestModelBuilder CreateModelBuilder(Action? configure = null) => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - public class Parent + protected class Parent { public int Id { get; set; } public DisjointChildSubclass1? A { get; set; } public IList? B { get; set; } } - public abstract class ChildBase + protected abstract class ChildBase { public int Id { get; set; } } - public abstract class Child : ChildBase + protected abstract class Child : ChildBase { public string? Name { get; set; } } - public class DisjointChildSubclass1 : Child + protected class DisjointChildSubclass1 : Child { } - public class DisjointChildSubclass2 : Child + protected class DisjointChildSubclass2 : Child { } } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs index 15a4f023d18..74f4929083b 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs @@ -76,7 +76,7 @@ public virtual void Throws_when_keyless_type_property_is_not_added_or_ignored() modelBuilder.Entity(typeof(NonPrimitiveReferenceTypePropertyEntity)); Assert.Equal( - CoreStrings.PropertyNotAdded( + CoreStrings.NavigationNotAdded( typeof(NonPrimitiveReferenceTypePropertyEntity).ShortDisplayName(), nameof(NonPrimitiveReferenceTypePropertyEntity.Property), typeof(ICollection).ShortDisplayName()), diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs index 092cfad9c44..d304ced8ee1 100644 --- a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -40,7 +40,7 @@ public virtual void Can_set_complex_property_annotation() AlternateKey (Guid) Required Id (int) Required Name (string) - Notes (List) Element type: string", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true); + Notes (List) Element type: string Required", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true); } [ConditionalFact] @@ -1555,22 +1555,20 @@ public virtual void Can_call_Property_on_a_field() [ConditionalFact] public virtual void Can_ignore_a_field() { - var modelBuilder = CreateModelBuilder(); + var modelBuilder = CreateModelBuilder(c => c.ComplexProperties()); modelBuilder .Ignore() .Ignore() .Entity() .ComplexProperty( - e => e.EntityWithFields, b => - { - b.Property(e => e.Id); - b.Ignore(e => e.CompanyId); - }); + e => e.EntityWithFields, + b => b.Ignore(e => e.CompanyId)); var model = modelBuilder.FinalizeModel(); var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); Assert.Equal(5, complexProperty.ComplexType.GetProperties().Count()); + Assert.Single(complexProperty.ComplexType.GetComplexProperties()); } [ConditionalFact] diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index c0210174118..65a22615717 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -453,8 +453,7 @@ public virtual void Conventions_can_be_replaced() var modelBuilder = CreateModelBuilder( c => c.Conventions.Replace( - s => - new TestDbSetFindingConvention(s.GetService()!))); + s => new TestDbSetFindingConvention(s.GetService()!))); var model = modelBuilder.FinalizeModel(); @@ -1138,16 +1137,10 @@ public virtual void Properties_can_have_value_converter_configured_by_type() public virtual void Value_converter_configured_on_non_nullable_type_is_applied() { var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().HaveConversion, CustomValueComparer>(); - }); + c => c.Properties().HaveConversion, CustomValueComparer>()); modelBuilder.Entity( - b => - { - b.Property("Wierd"); - }); + b => b.Property("Wierd")); var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(Quarks))!; diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 22b8c92b564..ea89abada20 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -781,7 +781,7 @@ public class EntityWithFields public long[] CollectionId = null!; public int[] CollectionCompanyId = null!; public int[] CollectionTenantId = null!; - public KeylessEntityWithFields? KeylessEntity; + public KeylessEntityWithFields KeylessEntity = new(); } public class KeylessEntityWithFields