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