diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 1f9f21fe697..04823e65d56 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -459,6 +460,11 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str { CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className, nullable); + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + CreateComplexProperty(complexProperty, mainBuilder, methodBuilder, namespaces, className, nullable); + } + var foreignKeyNumber = 1; foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) { @@ -527,7 +533,7 @@ private void CreateEntityType( Create(entityType, parameters); - var propertyVariables = new Dictionary(); + var propertyVariables = new Dictionary(); foreach (var property in entityType.GetDeclaredProperties()) { Create(property, propertyVariables, parameters); @@ -538,6 +544,17 @@ private void CreateEntityType( Create(property, parameters); } + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + mainBuilder + .Append(_code.Identifier(complexProperty.Name, capitalize: true)) + .Append("ComplexProperty") + .Append(".Create") + .Append("(") + .Append(entityTypeVariable) + .AppendLine(");"); + } + foreach (var key in entityType.GetDeclaredKeys()) { Create(key, propertyVariables, parameters, nullable); @@ -663,7 +680,44 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator private void Create( IProperty property, - Dictionary propertyVariables, + Dictionary propertyVariables, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); + propertyVariables[property] = variableName; + + Create(property, variableName, propertyVariables, parameters); + + CreateAnnotations( + property, + _annotationCodeGenerator.Generate, + parameters with { TargetName = variableName }); + + parameters.MainBuilder.AppendLine(); + } + + private void Create( + IComplexTypeProperty property, + Dictionary propertyVariables, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); + propertyVariables[property] = variableName; + + Create(property, variableName, propertyVariables, parameters); + + CreateAnnotations( + property, + _annotationCodeGenerator.Generate, + parameters with { TargetName = variableName }); + + parameters.MainBuilder.AppendLine(); + } + + private void Create( + IPrimitivePropertyBase property, + string variableName, + Dictionary propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var valueGeneratorFactoryType = (Type?)property[CoreAnnotationNames.ValueGeneratorFactoryType]; @@ -672,7 +726,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueGenerator( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasValueGeneratorFactory))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasValueGeneratorFactory))); } var valueComparerType = (Type?)property[CoreAnnotationNames.ValueComparerType]; @@ -681,7 +735,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueComparer( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } var providerValueComparerType = (Type?)property[CoreAnnotationNames.ProviderValueComparerType]; @@ -690,7 +744,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueComparer( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } var valueConverterType = GetValueConverterType(property); @@ -699,7 +753,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueConverter( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } if (property is IConventionProperty conventionProperty @@ -707,12 +761,9 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelTypeMapping( - property.DeclaringEntityType.ShortName(), property.Name, "Customize()", parameters.ClassName)); + property.DeclaringType.ShortName(), property.Name, "Customize()", parameters.ClassName)); } - var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); - propertyVariables[property] = variableName; - var mainBuilder = parameters.MainBuilder; mainBuilder .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddProperty(") @@ -870,16 +921,9 @@ private void Create( mainBuilder .AppendLine(");") .DecrementIndent(); - - CreateAnnotations( - property, - _annotationCodeGenerator.Generate, - parameters with { TargetName = variableName }); - - mainBuilder.AppendLine(); } - private static Type? GetValueConverterType(IProperty property) + private static Type? GetValueConverterType(IPrimitivePropertyBase property) { var annotation = property.FindAnnotation(CoreAnnotationNames.ValueConverterType); if (annotation != null) @@ -925,9 +969,8 @@ private void Create( } return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - property.DeclaringEntityType.DisplayName(), property.Name, "ValueConverterType")) + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + property.DeclaringType.DisplayName(), property.Name, "ValueConverterType")) : null; } @@ -1004,7 +1047,7 @@ private void FindProperties( IEnumerable properties, IndentedStringBuilder mainBuilder, bool nullable, - Dictionary? propertyVariables = null) + Dictionary? propertyVariables = null) { mainBuilder.Append("new[] { "); var first = true; @@ -1057,9 +1100,10 @@ private void Create( PropertyBaseParameters(property, parameters, skipType: true); + AddNamespace(property.ClrType, parameters.Namespaces); mainBuilder .AppendLine(",") - .Append("serviceType: typeof(" + property.ClrType.DisplayName(fullName: true, compilable: true) + ")"); + .Append("serviceType: typeof(" + _code.Reference(property.ClrType) + ")"); mainBuilder .AppendLine(");") @@ -1075,7 +1119,7 @@ private void Create( private void Create( IKey key, - Dictionary propertyVariables, + Dictionary propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, bool nullable) { @@ -1109,7 +1153,7 @@ private void Create( private void Create( IIndex index, - Dictionary propertyVariables, + Dictionary propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, bool nullable) { @@ -1148,6 +1192,144 @@ private void Create( mainBuilder.AppendLine(); } + private void CreateComplexProperty( + IComplexProperty complexProperty, + IndentedStringBuilder mainBuilder, + IndentedStringBuilder methodBuilder, + SortedSet namespaces, + string topClassName, + bool nullable) + { + mainBuilder + .Append("private static class ") + .Append(_code.Identifier(complexProperty.Name, capitalize: true)) + .Append("ComplexProperty") + .AppendLine("{"); + + var complexType = complexProperty.ComplexType; + using (mainBuilder.Indent()) + { + var declaringTypeVariable = "declaringEntityType"; + mainBuilder.AppendLine() + .Append("public static RuntimeComplexProperty Create") + .Append($"(RuntimeEntityType {declaringTypeVariable})") + .AppendLine("{"); + + using (mainBuilder.Indent()) + { + const string complexPropertyVariable = "complexProperty"; + var variables = new HashSet + { + declaringTypeVariable, + complexPropertyVariable + }; + + mainBuilder + .Append("var ").Append(complexPropertyVariable).Append(" = ") + .Append(declaringTypeVariable).Append(".AddComplexProperty(") + .IncrementIndent() + .Append(_code.Literal(complexProperty.Name)) + .AppendLine(",") + .Append(_code.Literal(complexProperty.ClrType)); + + AddNamespace(complexProperty.ClrType, namespaces); + + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( + declaringTypeVariable, + topClassName, + mainBuilder, + methodBuilder, + namespaces, + variables, + nullable); + + PropertyBaseParameters(complexProperty, parameters, skipType: true); + + if (complexProperty.IsNullable) + { + mainBuilder.AppendLine(",") + .Append("nullable: ") + .Append(_code.Literal(true)); + } + + if (complexProperty.IsCollection) + { + mainBuilder.AppendLine(",") + .Append("collection: ") + .Append(_code.Literal(true)); + } + + var changeTrackingStrategy = complexType.GetChangeTrackingStrategy(); + if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) + { + namespaces.Add(typeof(ChangeTrackingStrategy).Namespace!); + + mainBuilder.AppendLine(",") + .Append("changeTrackingStrategy: ") + .Append(_code.Literal(changeTrackingStrategy)); + } + + var indexerPropertyInfo = complexType.FindIndexerPropertyInfo(); + if (indexerPropertyInfo != null) + { + mainBuilder.AppendLine(",") + .Append("indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(") + .Append(_code.Literal(complexType.ClrType)) + .Append(")"); + } + + if (complexType.IsPropertyBag) + { + mainBuilder.AppendLine(",") + .Append("propertyBag: ") + .Append(_code.Literal(true)); + } + + mainBuilder + .AppendLine(");") + .AppendLine() + .DecrementIndent(); + + var complexPropertyParameters = parameters with { TargetName = complexPropertyVariable }; + var propertyVariables = new Dictionary(); + foreach (var property in complexType.GetProperties()) + { + Create(property, propertyVariables, complexPropertyParameters); + } + + foreach (var nestedComplexProperty in complexType.GetComplexProperties()) + { + mainBuilder + .Append(_code.Identifier(nestedComplexProperty.Name, capitalize: true)) + .Append("ComplexProperty") + .Append(".Create") + .Append("(") + .Append(complexPropertyVariable) + .AppendLine(");"); + } + + CreateAnnotations( + complexProperty, + _annotationCodeGenerator.Generate, + complexPropertyParameters); + + mainBuilder + .Append("return ") + .Append(complexPropertyVariable) + .AppendLine(";"); + } + + mainBuilder.AppendLine("}"); + } + + foreach (var nestedComplexProperty in complexType.GetComplexProperties()) + { + CreateComplexProperty(nestedComplexProperty, mainBuilder, methodBuilder, namespaces, topClassName, nullable); + } + + mainBuilder.AppendLine("}"); + } + private void CreateForeignKey( IForeignKey foreignKey, int foreignKeyNumber, diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 40032248f1c..80143d3243c 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -714,10 +714,10 @@ protected virtual ModelBuilder VisitForeignKeys( uniquifier: NavigationUniquifier); var leftSkipNavigation = leftEntityType.AddSkipNavigation( - leftNavigationPropertyName, null, rightEntityType, collection: true, onDependent: false); + leftNavigationPropertyName, memberInfo: null, targetEntityType: rightEntityType, collection: true, onDependent: false); leftSkipNavigation.SetForeignKey(fks[0]); var rightSkipNavigation = rightEntityType.AddSkipNavigation( - rightNavigationPropertyName, null, leftEntityType, collection: true, onDependent: false); + rightNavigationPropertyName, memberInfo: null, targetEntityType: leftEntityType, collection: true, onDependent: false); rightSkipNavigation.SetForeignKey(fks[1]); leftSkipNavigation.SetInverse(rightSkipNavigation); rightSkipNavigation.SetInverse(leftSkipNavigation); diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 088bc3c0865..8b660d4fafd 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable once CheckNamespace @@ -1055,7 +1056,7 @@ public static void SetDefaultValue(this IMutableProperty property, object? value /// /// The property. /// A flag indicating whether the property is capable of storing only fixed-length data, such as strings. - public static bool? IsFixedLength(this IReadOnlyProperty property) + public static bool? IsFixedLength(this IReadOnlyPrimitivePropertyBase property) => (bool?)property.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.Value; /// @@ -1103,10 +1104,10 @@ public static void SetIsFixedLength(this IMutableProperty property, bool? fixedL fromDataAnnotation)?.Value; /// - /// Gets the for . + /// Gets the for . /// /// The property. - /// The for . + /// The for . public static ConfigurationSource? GetIsFixedLengthConfigurationSource(this IConventionProperty property) => property.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.GetConfigurationSource(); diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs index a1240ea6ddc..24ff53947f3 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs @@ -17,6 +17,45 @@ public interface IConventionCheckConstraintBuilder : IConventionAnnotatableBuild /// new IConventionCheckConstraint Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionCheckConstraintBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionCheckConstraintBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionCheckConstraintBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the database name of the check constraint. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs index dd7f26dd28b..57a68e49323 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs @@ -18,6 +18,45 @@ public interface IConventionDbFunctionBuilder : IConventionAnnotatableBuilder /// new IConventionDbFunction Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionDbFunctionBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the name of the database function. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs index 0f12ad0fa8c..0b4a924be72 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionDbFunctionParameterBuilder : IConventionAnnotatableB /// new IConventionDbFunctionParameter Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the store type of the function parameter in the database. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs index 2cfb880b98c..4a96d08ac73 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionSequenceBuilder : IConventionAnnotatableBuilder /// new IConventionSequence Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionSequenceBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionSequenceBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionSequenceBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the type of values returned by the sequence. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs index 5e33363857a..9b6b114d65b 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuild /// new IConventionStoredProcedure Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the name of the stored procedure. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs index 3daf64fe4c8..671ec1ddf15 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs @@ -18,6 +18,45 @@ public interface IConventionStoredProcedureParameterBuilder : IConventionAnnotat /// new IConventionStoredProcedureParameter Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures the parameter name. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs index b8723de8784..8fe535803e3 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionStoredProcedureResultColumnBuilder : IConventionAnno /// new IConventionStoredProcedureResultColumn Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures the result column name. /// diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs index cdfed28f535..9b24471ff22 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs @@ -31,13 +31,7 @@ public RelationalColumnAttributeConvention( /// protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, ColumnAttribute attribute, @@ -59,4 +53,13 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasColumnOrder(attribute.Order, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + ColumnAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs index daebc058db8..6a2f474354a 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs @@ -29,13 +29,7 @@ public RelationalColumnCommentAttributeConvention( /// protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, CommentAttribute attribute, @@ -47,4 +41,13 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasComment(attribute.Comment, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + CommentAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs index 33f2b1ab240..9d55cb81b37 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs @@ -43,4 +43,13 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasJsonPropertyName(attribute.Name, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + JsonPropertyNameAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs index 9b154b2ded5..3226db5a8dd 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RelationalTableAttributeConvention : EntityTypeAttributeConventionBase +public class RelationalTableAttributeConvention : TypeAttributeConventionBase { /// /// Creates a new instance of . diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs index 45dd1764914..ba202eb015e 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RelationalTableCommentAttributeConvention : EntityTypeAttributeConventionBase +public class RelationalTableCommentAttributeConvention : TypeAttributeConventionBase { /// /// Creates a new instance of . diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs index 5ef202fa24f..cddb8f80c31 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs @@ -174,6 +174,24 @@ IConventionCheckConstraint IConventionCheckConstraintBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs index ea8b52edbc7..5ccc41eb578 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs @@ -246,6 +246,39 @@ IConventionDbFunction IConventionDbFunctionBuilder.Metadata get => Metadata; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs index f455fc063c3..3d1bacc7501 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs @@ -127,6 +127,24 @@ IConventionDbFunctionParameter IConventionDbFunctionParameterBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasStoreType( diff --git a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs index 9b3a1b6372a..1424a93c02e 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs @@ -195,6 +195,24 @@ IConventionSequence IConventionSequenceBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionSequenceBuilder? IConventionSequenceBuilder.HasType(Type? type, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs index f437eab0f70..df64a52ece3 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs @@ -420,6 +420,24 @@ IConventionStoredProcedure IConventionStoredProcedureBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs index d9ac813a5cf..69579f077bf 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs @@ -100,6 +100,24 @@ IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.M get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs index a42e64c867e..82bcd8afc3f 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs @@ -65,6 +65,24 @@ IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBui get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName( diff --git a/src/EFCore.Relational/Storage/IRelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/IRelationalTypeMappingSource.cs index 36c67076e86..e0165b2e609 100644 --- a/src/EFCore.Relational/Storage/IRelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/IRelationalTypeMappingSource.cs @@ -29,19 +29,19 @@ namespace Microsoft.EntityFrameworkCore.Storage; public interface IRelationalTypeMappingSource : ITypeMappingSource { /// - /// Finds the type mapping for a given . + /// Finds the type mapping for a given . /// /// The property. /// The type mapping, or if none was found. - new RelationalTypeMapping? FindMapping(IProperty property); + new RelationalTypeMapping? FindMapping(IPrimitivePropertyBase property); /// /// 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: Only call this method if there is no available, otherwise + /// call /// /// The field or property. /// The type mapping, or if none was found. @@ -51,8 +51,8 @@ public interface IRelationalTypeMappingSource : ITypeMappingSource /// Finds the type mapping for a given . /// /// - /// Note: Only call this method if there is no - /// or available, otherwise call + /// Note: Only call this method if there is no + /// or available, otherwise call /// or /// /// The CLR type. @@ -63,8 +63,8 @@ public interface IRelationalTypeMappingSource : ITypeMappingSource /// Finds the type mapping for a given , taking pre-convention configuration into the account. /// /// - /// Note: Only call this method if there is no , - /// otherwise call . + /// Note: Only call this method if there is no , + /// otherwise call . /// /// The CLR type. /// The model. @@ -75,8 +75,8 @@ public interface IRelationalTypeMappingSource : ITypeMappingSource /// Finds the type mapping for a given database type name. /// /// - /// Note: Only call this method if there is no available, otherwise - /// call + /// Note: Only call this method if there is no available, otherwise + /// call /// /// The database type name. /// The type mapping, or if none was found. @@ -86,8 +86,8 @@ public interface IRelationalTypeMappingSource : ITypeMappingSource /// Finds the type mapping for a given and additional facets. /// /// - /// Note: Only call this method if there is no available, otherwise - /// call + /// Note: Only call this method if there is no available, otherwise + /// call /// /// The CLR type. /// The database type name. diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs index af74ab9d33f..0c38144231c 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs @@ -21,7 +21,7 @@ public readonly record struct RelationalTypeMappingInfo /// Creates a new instance of . /// /// The property for which mapping is needed. - public RelationalTypeMappingInfo(IProperty property) + public RelationalTypeMappingInfo(IPrimitivePropertyBase property) : this(property.GetPrincipals()) { } @@ -46,7 +46,7 @@ public RelationalTypeMappingInfo(IProperty property) /// Specifies a scale for the mapping, in case one isn't found at the core level, or for default. /// public RelationalTypeMappingInfo( - IReadOnlyList principals, + IReadOnlyList principals, string? storeTypeName = null, string? storeTypeNameBase = null, bool? fallbackUnicode = null, diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index 4b9e64b1423..e1f261a0d1c 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -87,7 +87,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) private RelationalTypeMapping? FindMappingWithConversion( in RelationalTypeMappingInfo mappingInfo, - IReadOnlyList? principals) + IReadOnlyList? principals) { Type? providerClrType = null; ValueConverter? customConverter = null; @@ -193,7 +193,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) /// /// The property. /// The type mapping, or if none was found. - public override CoreTypeMapping? FindMapping(IProperty property) + public override CoreTypeMapping? FindMapping(IPrimitivePropertyBase property) { var principals = property.GetPrincipals(); @@ -406,7 +406,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) } /// - RelationalTypeMapping? IRelationalTypeMappingSource.FindMapping(IProperty property) + RelationalTypeMapping? IRelationalTypeMappingSource.FindMapping(IPrimitivePropertyBase property) => (RelationalTypeMapping?)FindMapping(property); /// diff --git a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs index 4aabe0eac85..d5b61954ce0 100644 --- a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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. /// - protected override int GetPropertyCount(IEntityType entityType) - => entityType.ShadowPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.ShadowPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 9fa4eccb2ba..f4754a74aa4 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -19,7 +19,6 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public sealed partial class InternalEntityEntry : IUpdateEntry { - // ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly StateData _stateData; private OriginalValues _originalValues; private RelationshipsSnapshot _relationshipsSnapshot; @@ -39,10 +38,10 @@ public InternalEntityEntry( object entity) { StateManager = stateManager; - EntityType = entityType; + EntityType = (IRuntimeEntityType)entityType; Entity = entity; - _shadowValues = entityType.GetEmptyShadowValuesFactory()(); - _stateData = new StateData(entityType.PropertyCount(), entityType.NavigationCount()); + _shadowValues = EntityType.EmptyShadowValuesFactory(); + _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); MarkShadowPropertiesNotSet(entityType); } @@ -60,10 +59,10 @@ public InternalEntityEntry( in ValueBuffer valueBuffer) { StateManager = stateManager; - EntityType = entityType; + EntityType = (IRuntimeEntityType)entityType; Entity = entity; - _shadowValues = ((IRuntimeEntityType)entityType).ShadowValuesFactory(valueBuffer); - _stateData = new StateData(entityType.PropertyCount(), entityType.NavigationCount()); + _shadowValues = EntityType.ShadowValuesFactory(valueBuffer); + _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); } /// @@ -107,7 +106,7 @@ void IUpdateEntry.SetPropertyModified(IProperty property) /// 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 IEntityType EntityType { [DebuggerStepThrough] get; } + public IRuntimeEntityType EntityType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -271,7 +270,7 @@ private bool PrepareForAdd(EntityState newState) if (EntityState == EntityState.Modified) { _stateData.FlagAllProperties( - EntityType.PropertyCount(), PropertyFlag.Modified, + EntityType.PropertyCount, PropertyFlag.Modified, flagged: false); } @@ -309,7 +308,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc if (newState == EntityState.Modified && modifyProperties) { - _stateData.FlagAllProperties(entityType.PropertyCount(), PropertyFlag.Modified, flagged: true); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Modified, flagged: true); // Hot path; do not use LINQ foreach (var property in entityType.GetProperties()) @@ -329,7 +328,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc if (newState == EntityState.Unchanged) { _stateData.FlagAllProperties( - entityType.PropertyCount(), PropertyFlag.Modified, + EntityType.PropertyCount, PropertyFlag.Modified, flagged: false); } @@ -372,7 +371,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc || newState == EntityState.Detached) && HasConceptualNull) { - _stateData.FlagAllProperties(entityType.PropertyCount(), PropertyFlag.Null, flagged: false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Null, flagged: false); } if (oldState == EntityState.Detached @@ -1473,9 +1472,9 @@ public void AcceptChanges() _temporaryValues = new SidecarValues(); } - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsStoreGenerated, false); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsTemporary, false); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.Unknown, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsStoreGenerated, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsTemporary, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Unknown, false); var currentState = EntityState; switch (currentState) @@ -1695,7 +1694,7 @@ public void DiscardStoreGeneratedValues() if (!_storeGeneratedValues.IsEmpty) { _storeGeneratedValues = new SidecarValues(); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsStoreGenerated, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsStoreGenerated, false); } } @@ -2002,6 +2001,9 @@ public DebugView DebugView IUpdateEntry? IUpdateEntry.SharedIdentityEntry => SharedIdentityEntry; + IEntityType IUpdateEntry.EntityType + => EntityType; + private enum CurrentValueType { Normal, diff --git a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs index 5069c7bc6e5..792d4839856 100644 --- a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs +++ b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs @@ -37,7 +37,7 @@ public KeyPropagator( { Check.DebugAssert(property.IsForeignKey(), $"property {property} is not part of an FK"); - var generationProperty = property.FindGenerationProperty(); + var generationProperty = (IProperty?)property.FindGenerationProperty(); var principalEntry = TryPropagateValue(entry, property, generationProperty); if (principalEntry == null @@ -72,7 +72,7 @@ public KeyPropagator( { Check.DebugAssert(property.IsForeignKey(), $"property {property} is not part of an FK"); - var generationProperty = property.FindGenerationProperty(); + var generationProperty = (IProperty?)property.FindGenerationProperty(); var principalEntry = TryPropagateValue(entry, property, generationProperty); if (principalEntry == null diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs index bb617e971c2..d2fa8816177 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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. /// - protected override int GetPropertyCount(IEntityType entityType) - => entityType.OriginalValueCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.OriginalValueCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs index 72bcd6319a4..8c4bb0e69f8 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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. /// - protected override int GetPropertyCount(IEntityType entityType) - => entityType.RelationshipPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.RelationshipPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs index cf6b4ee81d9..02a03f14ae4 100644 --- a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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. /// - protected override int GetPropertyCount(IEntityType entityType) - => entityType.ShadowPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.ShadowPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs index d8030894f67..40fcd76ae1b 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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. /// - protected override int GetPropertyCount(IEntityType entityType) - => entityType.StoreGeneratedCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.StoreGeneratedCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index 04499ea5858..c69e316928a 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -21,11 +21,11 @@ public abstract class SnapshotFactoryFactory /// 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 Func CreateEmpty(IEntityType entityType) - => GetPropertyCount(entityType) == 0 + public virtual Func CreateEmpty(IRuntimeTypeBase typeBase) + => GetPropertyCount(typeBase) == 0 ? (() => Snapshot.Empty) : Expression.Lambda>( - CreateConstructorExpression(entityType, null!)) + CreateConstructorExpression(typeBase, null!)) .Compile(); /// @@ -35,15 +35,15 @@ public virtual Func CreateEmpty(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual Expression CreateConstructorExpression( - IEntityType entityType, + IRuntimeTypeBase typeBase, ParameterExpression? parameter) { - var count = GetPropertyCount(entityType); + var count = GetPropertyCount(typeBase); var types = new Type[count]; var propertyBases = new IPropertyBase?[count]; - foreach (var propertyBase in entityType.GetPropertiesAndNavigations()) + foreach (var propertyBase in typeBase.GetSnapshottableMembers()) { var index = GetPropertyIndex(propertyBase); if (index >= 0) @@ -62,7 +62,7 @@ protected virtual Expression CreateConstructorExpression( { snapshotExpressions.Add( CreateSnapshotExpression( - entityType.ClrType, + typeBase.ClrType, parameter, types.Skip(i).Take(Snapshot.MaxGenericTypes).ToArray(), propertyBases.Skip(i).Take(Snapshot.MaxGenericTypes).ToList())); @@ -77,7 +77,7 @@ protected virtual Expression CreateConstructorExpression( } else { - constructorExpression = CreateSnapshotExpression(entityType.ClrType, parameter, types, propertyBases); + constructorExpression = CreateSnapshotExpression(typeBase.ClrType, parameter, types, propertyBases); } return constructorExpression; @@ -257,7 +257,7 @@ protected virtual Expression CreateReadValueExpression( /// 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. /// - protected abstract int GetPropertyCount(IEntityType entityType); + protected abstract int GetPropertyCount(IRuntimeTypeBase typeBase); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs index 756a5a70d31..66f8fc1c536 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.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 Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// @@ -17,9 +19,9 @@ public abstract class SnapshotFactoryFactory : SnapshotFactoryFactory /// 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 Func Create(IEntityType entityType) + public virtual Func Create(IRuntimeTypeBase typeBase) { - if (GetPropertyCount(entityType) == 0) + if (GetPropertyCount(typeBase) == 0) { return _ => Snapshot.Empty; } @@ -27,7 +29,7 @@ public virtual Func Create(IEntityType entityType) var parameter = Expression.Parameter(typeof(TInput), "source"); return Expression.Lambda>( - CreateConstructorExpression(entityType, parameter), + CreateConstructorExpression(typeBase, parameter), parameter) .Compile(); } diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 2fbdcb43c89..93488a0fdf9 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -275,8 +275,9 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity, IEntityType? public virtual InternalEntityEntry CreateEntry(IDictionary values, IEntityType entityType) { var i = 0; - var valuesArray = new object?[entityType.PropertyCount()]; - var shadowPropertyValuesArray = new object?[entityType.ShadowPropertyCount()]; + var runtimeEntityType = (IRuntimeEntityType)entityType; + var valuesArray = new object?[runtimeEntityType.PropertyCount]; + var shadowPropertyValuesArray = new object?[runtimeEntityType.ShadowPropertyCount]; foreach (var property in entityType.GetProperties()) { valuesArray[i++] = values.TryGetValue(property.Name, out var value) diff --git a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs index bcc7cbaa812..dcaedb947db 100644 --- a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs +++ b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs @@ -17,7 +17,7 @@ public static class ValueComparerExtensions /// 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 static ValueComparer? ToNullableComparer(this ValueComparer? valueComparer, IReadOnlyProperty property) + public static ValueComparer? ToNullableComparer(this ValueComparer? valueComparer, IReadOnlyPrimitivePropertyBase property) { if (valueComparer == null || !property.ClrType.IsNullableValueType() diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs index 7f97dffbe13..450a5196d32 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs @@ -68,6 +68,44 @@ public virtual void Generate(IEntityType entityType, CSharpRuntimeAnnotationCode GenerateSimpleAnnotations(parameters); } + /// + public virtual void Generate(IComplexProperty complexProperty, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key) + && key != CoreAnnotationNames.DiscriminatorMappingComplete) + { + annotations.Remove(key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + + /// + public virtual void Generate(IComplexType complexType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key) + && key != CoreAnnotationNames.DiscriminatorMappingComplete) + { + annotations.Remove(key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + /// public virtual void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { @@ -86,6 +124,24 @@ public virtual void Generate(IProperty property, CSharpRuntimeAnnotationCodeGene GenerateSimpleAnnotations(parameters); } + /// + public virtual void Generate(IComplexTypeProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key)) + { + annotations.Remove(key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + /// public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs index a13bd7f5f25..3cbce4374ec 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs @@ -82,8 +82,8 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters( public bool IsRuntime { get; init; } /// - /// Gets or sets a value indicating whther nullable reference types are enabled. + /// Gets or sets a value indicating whether nullable reference types are enabled. /// - /// A value indicating whther nullable reference types are enabled. + /// A value indicating whether nullable reference types are enabled. public bool UseNullableReferenceTypes { get; init; } } diff --git a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs index 6d960da09f7..cc7e3a231e4 100644 --- a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs @@ -27,6 +27,20 @@ public interface ICSharpRuntimeAnnotationCodeGenerator /// Additional parameters used during code generation. void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// + /// Generates code to create the given annotations. + /// + /// The entity type 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. + /// Additional parameters used during code generation. + void Generate(IComplexType complexType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// /// Generates code to create the given annotations. /// @@ -34,6 +48,13 @@ public interface ICSharpRuntimeAnnotationCodeGenerator /// Additional parameters used during code generation. void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// + /// Generates code to create the given annotations. + /// + /// The property to which the annotations are applied. + /// Additional parameters used during code generation. + void Generate(IComplexTypeProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// /// Generates code to create the given annotations. /// diff --git a/src/EFCore/Diagnostics/ComplexPropertyEventData.cs b/src/EFCore/Diagnostics/ComplexPropertyEventData.cs new file mode 100644 index 00000000000..c38393b9860 --- /dev/null +++ b/src/EFCore/Diagnostics/ComplexPropertyEventData.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Diagnostics; + +/// +/// A event payload class for events that have +/// a property. +/// +/// +/// See Logging, events, and diagnostics for more information and examples. +/// +public class ComplexPropertyEventData : EventData +{ + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The property. + public ComplexPropertyEventData( + EventDefinitionBase eventDefinition, + Func messageGenerator, + IReadOnlyComplexProperty property) + : base(eventDefinition, messageGenerator) + { + Property = property; + } + + /// + /// The property. + /// + public virtual IReadOnlyComplexProperty Property { get; } +} diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 2d4415d1c1a..f20bee9677d 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -120,6 +120,7 @@ private enum Id MappedEntityTypeIgnoredWarning, MappedNavigationIgnoredWarning, MappedPropertyIgnoredWarning, + MappedComplexPropertyIgnoredWarning, // ChangeTracking events DetectChangesStarting = CoreBaseId + 800, @@ -557,7 +558,7 @@ private static EventId MakeModelValidationId(Id id) /// This event is in the category. /// /// - /// This event uses the payload when used with a . + /// This event uses the payload when used with a . /// /// /// See Modeling entity types and relationships for more information and @@ -566,6 +567,23 @@ private static EventId MakeModelValidationId(Id id) /// public static readonly EventId MappedPropertyIgnoredWarning = MakeModelId(Id.MappedPropertyIgnoredWarning); + /// + /// A property was first mapped explicitly and then ignored. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + /// See Modeling entity types and relationships for more information and + /// examples. + /// + /// + public static readonly EventId MappedComplexPropertyIgnoredWarning = MakeModelId(Id.MappedComplexPropertyIgnoredWarning); + /// /// An index was not created as the properties are already covered. /// diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index 4c98d57152b..068edb23793 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -3049,7 +3049,7 @@ private static string MappedNavigationIgnoredWarning(EventDefinitionBase definit /// The property. public static void MappedPropertyIgnoredWarning( this IDiagnosticsLogger diagnostics, - IProperty property) + IPrimitivePropertyBase property) { var definition = CoreResources.LogMappedPropertyIgnored(diagnostics); @@ -3060,7 +3060,7 @@ public static void MappedPropertyIgnoredWarning( if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) { - var eventData = new PropertyEventData(definition, MappedPropertyIgnoredWarning, property); + var eventData = new PrimitivePropertyEventData(definition, MappedPropertyIgnoredWarning, property); diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); } @@ -3069,7 +3069,38 @@ public static void MappedPropertyIgnoredWarning( private static string MappedPropertyIgnoredWarning(EventDefinitionBase definition, EventData payload) { var d = (EventDefinition)definition; - var p = (PropertyEventData)payload; + var p = (PrimitivePropertyEventData)payload; + return d.GenerateMessage(p.Property.DeclaringType.ShortName(), p.Property.Name); + } + + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The property. + public static void MappedComplexPropertyIgnoredWarning( + this IDiagnosticsLogger diagnostics, + IComplexProperty property) + { + var definition = CoreResources.LogMappedComplexPropertyIgnored(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, property.DeclaringType.ShortName(), property.Name); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new ComplexPropertyEventData(definition, MappedComplexPropertyIgnoredWarning, property); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string MappedComplexPropertyIgnoredWarning(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (ComplexPropertyEventData)payload; return d.GenerateMessage(p.Property.DeclaringType.ShortName(), p.Property.Name); } diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index 389f76a963b..cbe8c5523ff 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -70,6 +70,15 @@ public abstract class LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogMappedPropertyIgnored; + /// + /// 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. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogMappedComplexPropertyIgnored; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Diagnostics/PrimitivePropertyEventData.cs b/src/EFCore/Diagnostics/PrimitivePropertyEventData.cs new file mode 100644 index 00000000000..eb9856dd50d --- /dev/null +++ b/src/EFCore/Diagnostics/PrimitivePropertyEventData.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Diagnostics; + +/// +/// A event payload class for events that have +/// a property. +/// +/// +/// See Logging, events, and diagnostics for more information and examples. +/// +public class PrimitivePropertyEventData : EventData +{ + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The property. + public PrimitivePropertyEventData( + EventDefinitionBase eventDefinition, + Func messageGenerator, + IReadOnlyPrimitivePropertyBase property) + : base(eventDefinition, messageGenerator) + { + Property = property; + } + + /// + /// The property. + /// + public virtual IReadOnlyPrimitivePropertyBase Property { get; } +} diff --git a/src/EFCore/Infrastructure/AnnotatableBuilder.cs b/src/EFCore/Infrastructure/AnnotatableBuilder.cs index 7bb11fdfdaf..b2e74bf565e 100644 --- a/src/EFCore/Infrastructure/AnnotatableBuilder.cs +++ b/src/EFCore/Infrastructure/AnnotatableBuilder.cs @@ -103,7 +103,7 @@ protected AnnotatableBuilder(TMetadata metadata, TModelBuilder modelBuilder) object? value, ConfigurationSource configurationSource) => value == null - ? RemoveAnnotation(name, configurationSource) + ? HasNoAnnotation(name, configurationSource) : HasAnnotation(name, value, configurationSource, canOverrideSameSource: true); /// @@ -143,7 +143,7 @@ private static bool CanSetAnnotationValue( /// The name of the annotation to remove. /// The configuration source of the annotation to be set. /// The same builder so that multiple calls can be chained. - public virtual AnnotatableBuilder? RemoveAnnotation( + public virtual AnnotatableBuilder? HasNoAnnotation( string name, ConfigurationSource configurationSource) { @@ -238,7 +238,7 @@ bool IConventionAnnotatableBuilder.CanSetAnnotation(string name, object? value, /// [DebuggerStepThrough] IConventionAnnotatableBuilder? IConventionAnnotatableBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) - => RemoveAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + => HasNoAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index c014af1e32c..39c5446d464 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -164,6 +164,7 @@ protected virtual void ValidatePropertyMapping( clrProperties.ExceptWith( ((IEnumerable)entityType.GetProperties()) + .Concat(entityType.GetComplexProperties()) .Concat(entityType.GetNavigations()) .Concat(entityType.GetSkipNavigations()) .Concat(entityType.GetServiceProperties()).Select(p => p.Name)); @@ -476,7 +477,7 @@ protected virtual void ValidateNoCycles( } /// - /// Validates the mapping/configuration of primary key nullability in the model. + /// Validates that all trackable entity types have a primary key. /// /// The model to validate. /// The logger to use. diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs index 0f30401cf12..06067bc1fc7 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs @@ -268,7 +268,10 @@ protected virtual IMutableSkipNavigation WithLeftManyNavigation(string? inverseN return ((EntityType)DeclaringEntityType).Builder.HasSkipNavigation( navigationMember, (EntityType)RelatedEntityType, - ConfigurationSource.Explicit)!.Metadata; + foreignKey.PrincipalToDependent?.ClrType, + ConfigurationSource.Explicit, + collection: true, + onDependent: false)!.Metadata; } } @@ -320,6 +323,7 @@ private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigation var skipNavigation = RelatedEntityType.FindSkipNavigation(navigationName); if (skipNavigation != null) { + ((SkipNavigation)skipNavigation).UpdateConfigurationSource(ConfigurationSource.Explicit); return skipNavigation; } } @@ -328,7 +332,9 @@ private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigation return ((EntityType)RelatedEntityType).Builder.HasSkipNavigation( navigationMember, (EntityType)DeclaringEntityType, - ConfigurationSource.Explicit)!.Metadata; + ConfigurationSource.Explicit, + collection: true, + onDependent: false)!.Metadata; } } diff --git a/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs new file mode 100644 index 00000000000..5c14c9f6aa2 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting property defaults before conventions run. +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertiesConfigurationBuilder +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexPropertiesConfigurationBuilder(ComplexPropertyConfiguration property) + { + Check.NotNull(property, nameof(property)); + + Configuration = property; + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + protected virtual ComplexPropertyConfiguration Configuration { get; } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs new file mode 100644 index 00000000000..21024c36705 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting property defaults before conventions run. +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertiesConfigurationBuilder : ComplexPropertiesConfigurationBuilder +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexPropertiesConfigurationBuilder(ComplexPropertyConfiguration property) + : base(property) + { + } +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs new file mode 100644 index 00000000000..73aa0eb07d5 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -0,0 +1,456 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring an . +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertyBuilder : + IInfrastructure, IInfrastructure +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) + { + PropertyBuilder = ((ComplexProperty)complexProperty).Builder; + TypeBuilder = ((ComplexProperty)complexProperty).ComplexType.Builder; + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexPropertyBuilder PropertyBuilder { [DebuggerStepThrough] get; } + + /// + /// 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. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexTypeBuilder TypeBuilder { [DebuggerStepThrough] get; } + + /// + /// Gets the internal builder being used to configure the complex property. + /// + IConventionComplexPropertyBuilder IInfrastructure.Instance + => PropertyBuilder; + + /// + /// Gets the internal builder being used to configure the complex type. + /// + IConventionComplexTypeBuilder IInfrastructure.Instance + => TypeBuilder; + + /// + /// The complex property being configured. + /// + public virtual IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + /// + /// Adds or updates an annotation on the complex property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + PropertyBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Adds or updates an annotation on the complex type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + TypeBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder IsRequired(bool required = true) + { + PropertyBuilder.IsRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(string propertyName) + => new( + TypeBuilder.Property( + Check.NotEmpty(propertyName, nameof(propertyName)), + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(string propertyName) + => new( + TypeBuilder.Property( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(Type propertyType, string propertyName) + => new( + TypeBuilder.Property( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// Indexer properties are stored in the entity using + /// an indexer + /// supplying the provided property name. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder IndexerProperty + <[DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] TProperty>(string propertyName) + => new( + TypeBuilder.IndexerProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// Indexer properties are stored in the entity using + /// an indexer + /// supplying the provided property name. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder IndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + string propertyName) + { + Check.NotNull(propertyType, nameof(propertyType)); + + return new( + TypeBuilder.IndexerProperty( + propertyType, + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + } + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + TypeBuilder.ComplexProperty( + propertyType: null, + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + TypeBuilder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) + => new( + TypeBuilder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName)); + + return this; + } + + /// + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// and navigations from the complex type that were added by convention. + /// + /// The name of the property to be removed from the complex type. + public virtual ComplexPropertyBuilder Ignore(string propertyName) + { + Check.NotEmpty(propertyName, nameof(propertyName)); + + TypeBuilder.Ignore(propertyName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the to be used for this entity type. + /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// + /// The change tracking strategy to be used. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + { + TypeBuilder.HasChangeTrackingStrategy(changeTrackingStrategy, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// entity type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + PropertyBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for all properties of this complex type. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for all properties of this complex type as described in the enum. + /// + /// + /// Calling this method overrides for all properties of this complex type any access mode that was + /// set on the model. + /// + /// + /// The to use for properties of this complex type. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + TypeBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs new file mode 100644 index 00000000000..f47d42f32b2 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -0,0 +1,251 @@ +// 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.Builders; + +/// +/// Provides a simple API for configuring an . +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +/// The complex type being configured. +public class ComplexPropertyBuilder<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TComplex> + : ComplexPropertyBuilder +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) + : base(complexProperty) + { + } + + /// + /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same typeBuilder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => (ComplexPropertyBuilder)base.HasPropertyAnnotation(annotation, value); + + /// + /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same typeBuilder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => (ComplexPropertyBuilder)base.HasTypeAnnotation(annotation, value); + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder IsRequired(bool required = true) + => (ComplexPropertyBuilder)base.IsRequired(required); + + /// + /// Returns an object that can be used to configure a property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(Expression> propertyExpression) + => new(TypeBuilder.Property( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)! + .Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty(string propertyName, Action buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, string propertyName, Action buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Expression> propertyExpression) + => new(TypeBuilder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression)); + + return this; + } + + /// + /// Excludes the given property from the entity type. This method is typically used to remove properties + /// or navigations from the entity type that were added by convention. + /// + /// + /// A lambda expression representing the property to be ignored + /// (blog => blog.Url). + /// + public virtual ComplexPropertyBuilder Ignore(Expression> propertyExpression) + => (ComplexPropertyBuilder)base.Ignore( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess().GetSimpleMemberName()); + + /// + /// Excludes the given property from the entity type. This method is typically used to remove properties + /// or navigations from the entity type that were added by convention. + /// + /// The name of the property to be removed from the entity type. + public new virtual ComplexPropertyBuilder Ignore(string propertyName) + => (ComplexPropertyBuilder)base.Ignore(propertyName); + + /// + /// Configures the to be used for this entity type. + /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// + /// The change tracking strategy to be used. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => (ComplexPropertyBuilder)base.HasChangeTrackingStrategy(changeTrackingStrategy); + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// entity type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexPropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); + + /// + /// Sets the to use for all properties of this entity type. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for all properties of this entity type as described in the enum. + /// + /// + /// Calling this method overrides for all properties of this entity type any access mode that was + /// set on the model. + /// + /// + /// The to use for properties of this entity type. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexPropertyBuilder)base.UseDefaultPropertyAccessMode(propertyAccessMode); +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs new file mode 100644 index 00000000000..45dfa201894 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs @@ -0,0 +1,695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexTypePropertyBuilder : IInfrastructure +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexTypePropertyBuilder(IMutableComplexTypeProperty property) + { + Check.NotNull(property, nameof(property)); + + Builder = ((ComplexTypeProperty)property).Builder; + } + + /// + /// The internal builder being used to configure the property. + /// + IConventionComplexTypePropertyBuilder IInfrastructure.Instance + => Builder; + + private InternalComplexTypePropertyBuilder Builder { get; } + + /// + /// The property being configured. + /// + public virtual IMutableComplexTypeProperty Metadata + => Builder.Metadata; + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsRequired(bool required = true) + { + Builder.IsRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasMaxLength(int maxLength) + { + Builder.HasMaxLength(maxLength, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public virtual ComplexTypePropertyBuilder HasSentinel(object? sentinel) + { + Builder.HasSentinel(sentinel, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasPrecision(int precision, int scale) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + Builder.HasScale(scale, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasPrecision(int precision) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsUnicode(bool unicode = true) + { + Builder.IsUnicode(unicode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property as and + /// . + /// + /// + /// Database providers can choose to interpret this in different way, but it is commonly used + /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsRowVersion() + { + Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit); + Builder.IsConcurrencyToken(true, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + { + Builder.HasValueGenerator(typeof(TGenerator), ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + { + Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a factory for creating a to use to generate values + /// for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// This factory will be invoked once to create a single instance of the value generator, and + /// this will be used to generate values for this property in all instances of the complex type. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A delegate that will be used to create value generator instances. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) + { + Check.NotNull(factory, nameof(factory)); + + Builder.HasValueGenerator(factory, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => HasValueGeneratorFactory(typeof(TFactory)); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + { + Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property should be used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this complex type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// A value indicating whether this property is a concurrency token. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsConcurrencyToken(bool concurrencyToken = true) + { + Builder.IsConcurrencyToken(concurrencyToken, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to never have a value generated by the database when an instance of this + /// complex type is saved. + /// + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// Note that values may still be generated by a client-side value generator, if one is set explicitly or by a convention. + /// + public virtual ComplexTypePropertyBuilder ValueGeneratedNever() + { + Builder.ValueGenerated(ValueGenerated.Never, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated only when saving a new entity, unless a non-null, + /// non-temporary value has been set, in which case the set value will be saved instead. The value + /// may be generated by a client-side value generator or may be generated by the database as part + /// of saving the entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnAdd() + { + Builder.ValueGenerated(ValueGenerated.OnAdd, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated when saving a new or existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + { + Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdate() + { + Builder.ValueGenerated(ValueGenerated.OnUpdate, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated under certain conditions when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdateSometimes() + { + Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasField(string fieldName) + { + Check.NotEmpty(fieldName, nameof(fieldName)); + + Builder.HasField(fieldName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => HasConversion(typeof(TConversion)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? conversionType) + { + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion(converter, null, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer) + => HasConversion(typeof(TConversion), valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion(typeof(TConversion), valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer) + => HasConversion(conversionType, valueComparer, null); + + // DynamicallyAccessedMemberTypes.PublicParameterlessConstructor + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => HasConversion(converter, valueComparer, null); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer, ValueComparer? providerComparer) + { + Builder.HasConversion(converter, ConfigurationSource.Explicit); + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + where TComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer), typeof(TProviderComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => HasConversion(conversionType, comparerType, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparerType, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs new file mode 100644 index 00000000000..00de6f623cf --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs @@ -0,0 +1,610 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexTypePropertyBuilder : ComplexTypePropertyBuilder +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public ComplexTypePropertyBuilder(IMutableComplexTypeProperty property) + : base(property) + { + } + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => (ComplexTypePropertyBuilder)base.HasAnnotation(annotation, value); + + /// + /// Configures whether this property must have a value assigned or whether null is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsRequired(bool required = true) + => (ComplexTypePropertyBuilder)base.IsRequired(required); + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasMaxLength(int maxLength) + => (ComplexTypePropertyBuilder)base.HasMaxLength(maxLength); + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public new virtual ComplexTypePropertyBuilder HasSentinel(object? sentinel) + => (ComplexTypePropertyBuilder)base.HasSentinel(sentinel); + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => (ComplexTypePropertyBuilder)base.HasPrecision(precision, scale); + + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasPrecision(int precision) + => (ComplexTypePropertyBuilder)base.HasPrecision(precision); + + /// + /// Configures the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => (ComplexTypePropertyBuilder)base.IsUnicode(unicode); + + /// + /// Configures the property as and + /// . + /// + /// + /// Database providers can choose to interpret this in different way, but it is commonly used + /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsRowVersion() + => (ComplexTypePropertyBuilder)base.IsRowVersion(); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + => (ComplexTypePropertyBuilder)base.HasValueGenerator(); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting null does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + => (ComplexTypePropertyBuilder)base.HasValueGenerator(valueGeneratorType); + + /// + /// Configures a factory for creating a to use to generate values + /// for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// This factory will be invoked once to create a single instance of the value generator, and + /// this will be used to generate values for this property in all instances of the complex type. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A delegate that will be used to create value generator instances. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) + => (ComplexTypePropertyBuilder)base.HasValueGenerator(factory); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => (ComplexTypePropertyBuilder)base.HasValueGeneratorFactory(); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + => (ComplexTypePropertyBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType); + + /// + /// Configures whether this property should be used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this complex type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// A value indicating whether this property is a concurrency token. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsConcurrencyToken(bool concurrencyToken = true) + => (ComplexTypePropertyBuilder)base.IsConcurrencyToken(concurrencyToken); + + /// + /// Configures a property to never have a value generated when an instance of this + /// complex type is saved. + /// + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// Note that temporary values may still be generated for use internally before a + /// new entity is saved. + /// + public new virtual ComplexTypePropertyBuilder ValueGeneratedNever() + => (ComplexTypePropertyBuilder)base.ValueGeneratedNever(); + + /// + /// Configures a property to have a value generated only when saving a new entity, unless a non-null, + /// non-temporary value has been set, in which case the set value will be saved instead. The value + /// may be generated by a client-side value generator or may be generated by the database as part + /// of saving the entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnAdd() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnAdd(); + + /// + /// Configures a property to have a value generated when saving a new or existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnAddOrUpdate(); + + /// + /// Configures a property to have a value generated when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnUpdate(); + + /// + /// Configures a property to have a value generated under certain conditions when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdateSometimes() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnUpdateSometimes(); + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasField(string fieldName) + => (ComplexTypePropertyBuilder)base.HasField(fieldName); + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexTypePropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerClrType) + => (ComplexTypePropertyBuilder)base.HasConversion(providerClrType); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression)))); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion((ValueConverter?)converter); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => (ComplexTypePropertyBuilder)base.HasConversion(converter); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression))), + valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression))), + valueComparer, + providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => HasConversion((ValueConverter?)converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion((ValueConverter?)converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + where TComparer : ValueComparer + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, comparerType); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, comparerType, providerComparerType); +} diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 4937b1b67ee..65299bcc607 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -31,7 +31,14 @@ public EntityTypeBuilder(IMutableEntityType entityType) Builder = ((EntityType)entityType).Builder; } - private InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get; } + /// + /// 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. + /// + [EntityFrameworkInternal] + protected virtual InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get; } /// /// Gets the internal builder being used to configure the entity type. @@ -205,6 +212,139 @@ public virtual PropertyBuilder IndexerProperty( Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); } + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the entity type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + Builder.ComplexProperty( + propertyType: null, + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + Builder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty(string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty(Type propertyType, string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 1f2c5ae82d2..5e4061ab41a 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -152,6 +152,97 @@ public virtual PropertyBuilder Property(Expression + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty(string propertyName, Action buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + Type propertyType, string propertyName, Action buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the complex property. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + collection: false, + ConfigurationSource.Explicit)! + .Metadata); + + /// + /// Configures a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. @@ -1397,7 +1488,4 @@ public virtual DiscriminatorBuilder HasDiscriminatorThe same builder instance so that multiple configuration calls can be chained. public new virtual EntityTypeBuilder HasNoDiscriminator() => (EntityTypeBuilder)base.HasNoDiscriminator(); - - private InternalEntityTypeBuilder Builder - => (InternalEntityTypeBuilder)this.GetInfrastructure(); } diff --git a/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs new file mode 100644 index 00000000000..f389efee52c --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexPropertyBuilder : IConventionPropertyBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionComplexProperty Metadata { get; } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the requiredness was configured, + /// otherwise. + /// + IConventionComplexPropertyBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether this property requiredness can be configured + /// from the current configuration source. + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the property requiredness can be configured. + bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs new file mode 100644 index 00000000000..d2196e445f7 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs @@ -0,0 +1,339 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexTypeBuilder : IConventionTypeBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionComplexType Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionComplexTypeBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionComplexTypeBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionComplexTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexTypePropertyBuilder? Property( + Type propertyType, + string propertyName, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the property with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The or of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexTypePropertyBuilder? Property(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given property can be added to this complex type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveProperty( + Type? propertyType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given property can be added to this complex type. + /// + /// The or of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the indexer property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexTypePropertyBuilder? IndexerProperty( + Type complexType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given indexer property can be added to this complex type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveIndexerProperty( + Type complexType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Removes a property from this complex type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the property was removed, + /// otherwise. + /// + IConventionComplexTypeBuilder? HasNoProperty(IConventionComplexTypeProperty property, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be removed from this complex type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be removed from this complex type. + bool CanRemoveProperty(IConventionComplexTypeProperty property, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The target complex type. + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The target complex type. + /// The or of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this complex type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + Type? propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this complex type. + /// + /// The or of the property. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex indexer property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the complex type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex indexer property can be added to this complex type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Removes a complex property from this complex type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the complex property was removed, + /// otherwise. + /// + IConventionComplexTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the complex property can be removed from this complex type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the complex property can be removed from this complex type. + bool CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Excludes the given property from the complex type and prevents conventions from adding a matching property + /// or navigation to the type. + /// + /// The name of the member to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. + /// + new IConventionComplexTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + + /// + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. + /// + /// + /// The change tracking strategy to be used. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + IConventionComplexTypeBuilder? HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given change tracking strategy can be set from the current configuration source. + /// + /// + /// The change tracking strategy to be used. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the given change tracking strategy can be set. + bool CanSetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); + + /// + /// Sets the to use for all properties of this complex type. + /// + /// + /// The to use for properties of this complex type. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + IConventionComplexTypeBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given can be set from the current configuration source. + /// + /// + /// The to use for properties of this model. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the given can be set. + bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypePropertyBuilder.cs new file mode 100644 index 00000000000..cfd3acadf5b --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypePropertyBuilder.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexTypePropertyBuilder : IConventionPrimitivePropertyBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionComplexTypeProperty Metadata { get; } +} diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index da8fa4ea64a..3eed4a1fa2d 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -15,13 +15,52 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionEntityTypeBuilder : IConventionAnnotatableBuilder +public interface IConventionEntityTypeBuilder : IConventionTypeBaseBuilder { /// /// Gets the entity type being configured. /// new IConventionEntityType Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionEntityTypeBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionEntityTypeBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionEntityTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the base type of this entity type in an inheritance hierarchy. /// @@ -159,6 +198,136 @@ bool CanHaveIndexerProperty( /// The properties to remove. IConventionEntityTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); + /// + /// Removes a property from this entity type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the property was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be removed from this entity type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be removed from this entity type. + bool CanRemoveProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the entity type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The or of the property. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the entity type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this entity type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + Type? propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this entity type. + /// + /// The or of the property. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex indexer property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the entity type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex indexer property can be added to this entity type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType, + bool fromDataAnnotation = false); + + /// + /// Removes a complex property from this entity type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the complex property was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the complex property can be removed from this entity type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the complex property can be removed from this entity type. + bool CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + /// /// Returns an object that can be used to configure the service property with the given member info. /// If no matching property exists, then a new property will be added. @@ -198,41 +367,35 @@ bool CanHaveIndexerProperty( bool CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); /// - /// Indicates whether the given member name is ignored for the given configuration source. + /// Removes a service property from this entity type. /// - /// The name of the member that might be ignored. + /// The service property to be removed. /// Indicates whether the configuration was specified using a data annotation. /// - /// if the entity type contains a member with the given name, - /// the given member name hasn't been ignored or it was ignored using a lower configuration source; - /// otherwise. + /// The same builder instance if the service property was removed, + /// otherwise. /// - bool IsIgnored(string memberName, bool fromDataAnnotation = false); + IConventionEntityTypeBuilder? HasNoServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation = false); /// - /// Excludes the given property from the entity type and prevents conventions from adding a matching property - /// or navigation to the type. + /// Returns a value indicating whether the service property can be removed from this entity type. /// - /// The name of the member to be removed. + /// The service property to be removed. /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same instance so that additional configuration calls can be chained - /// if the given member was ignored, otherwise. - /// - IConventionEntityTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + /// if the service property can be removed from this entity type. + bool CanRemoveServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given member name can be ignored from the given configuration source. + /// Excludes the given property from the entity type and prevents conventions from adding a matching property + /// or navigation to the type. /// - /// The member name to be removed from the entity type. + /// The name of the member to be removed. /// Indicates whether the configuration was specified using a data annotation. - /// if the given member name can be ignored. /// - /// if the entity type contains a member with the given name - /// that was configured using a higher configuration source; - /// otherwise. + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. /// - bool CanIgnore(string memberName, bool fromDataAnnotation = false); + new IConventionEntityTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); /// /// Sets the properties that make up the primary key for this entity type. @@ -743,25 +906,6 @@ bool CanHaveIndexerProperty( /// if the foreign key can be removed from this entity type. bool CanRemoveRelationship(IConventionForeignKey foreignKey, bool fromDataAnnotation = false); - /// - /// Removes a skip navigation from this entity type. - /// - /// The skip navigation to be removed. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the skip navigation was removed, - /// otherwise. - /// - IConventionEntityTypeBuilder? HasNoSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the skip navigation can be removed from this entity type. - /// - /// The skip navigation to be removed. - /// Indicates whether the configuration was specified using a data annotation. - /// if the skip navigation can be removed from this entity type. - bool CanRemoveSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); - /// /// Returns a value indicating whether the given navigation can be added to this entity type. /// @@ -780,6 +924,25 @@ bool CanHaveIndexerProperty( bool CanHaveNavigation(MemberInfo navigation, bool fromDataAnnotation = false) => CanHaveNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation); + /// + /// Removes a navigation from this entity type. + /// + /// The navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the navigation was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoNavigation(IConventionNavigation navigation, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the navigation can be removed from this entity type. + /// + /// The navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the navigation can be removed from this entity type. + bool CanRemoveNavigation(IConventionNavigation navigation, bool fromDataAnnotation = false); + /// /// Returns a value indicating whether the given skip navigation can be added to this entity type. /// @@ -850,6 +1013,7 @@ bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = fals /// /// The navigation property name. /// The entity type that the navigation targets. + /// The navigation type. /// Whether the navigation property is a collection property. /// /// Whether the navigation property is defined on the dependent side of the underlying foreign key. @@ -862,10 +1026,30 @@ bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = fals IConventionSkipNavigationBuilder? HasSkipNavigation( string navigationName, IConventionEntityType targetEntityType, + Type? navigationType = null, bool? collection = null, bool? onDependent = null, bool fromDataAnnotation = false); + /// + /// Removes a skip navigation from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the skip navigation was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the skip navigation can be removed from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the skip navigation can be removed from this entity type. + bool CanRemoveSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); + /// /// Configures a database trigger when targeting a relational database. /// diff --git a/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs index 0e5778ba2ed..b3c2a2747b8 100644 --- a/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs @@ -22,6 +22,45 @@ public interface IConventionForeignKeyBuilder : IConventionAnnotatableBuilder /// new IConventionForeignKey Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionForeignKeyBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionForeignKeyBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionForeignKeyBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures which entity types participate in this relationship. /// By calling this method the principal and dependent types can be switched or the relationship could diff --git a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs index 93d7dafa6f8..142d6d69774 100644 --- a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs @@ -22,6 +22,45 @@ public interface IConventionIndexBuilder : IConventionAnnotatableBuilder /// new IConventionIndex Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionIndexBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionIndexBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionIndexBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures whether this index is unique (i.e. each set of values must be unique). /// diff --git a/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs index eef66af6481..104d3997d90 100644 --- a/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs @@ -21,4 +21,43 @@ public interface IConventionKeyBuilder : IConventionAnnotatableBuilder /// Gets the key being configured. /// new IConventionKey Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionKeyBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionKeyBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionKeyBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs index a8fcea58cb0..e0076e164fb 100644 --- a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs @@ -24,6 +24,45 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder /// new IConventionModel Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionModelBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionModelBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionModelBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Returns an object that can be used to configure a given entity type in the model. /// If an entity type with the provided name is not already part of the model, @@ -182,6 +221,38 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder /// IConventionModelBuilder? Ignore(string typeName, bool fromDataAnnotation = false); + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The name of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveEntity( + string name, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The type of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveEntity( + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The name of the entity type. + /// The type of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveSharedTypeEntity( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, + bool fromDataAnnotation = false); + /// /// Removes the given entity type from the model. /// @@ -193,7 +264,15 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder IConventionModelBuilder? HasNoEntityType(IConventionEntityType entityType, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given entity type can be ignored from the current configuration source + /// Returns a value indicating whether the entity type can be removed from the model. + /// + /// The entity type to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the navigation can be removed from this entity type. + bool CanRemoveEntity(IConventionEntityType entityType, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be ignored from the current configuration source. /// /// The entity type to be removed from the model. /// Indicates whether the configuration was specified using a data annotation. diff --git a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs index cdcb4fe0fad..82c073afebc 100644 --- a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder +public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation being configured. @@ -23,37 +23,15 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder new IConventionNavigation Metadata { get; } /// - /// Sets the backing field to use for this navigation. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionNavigationBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this navigation. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionNavigationBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this navigation. + /// Configures this navigation to be automatically included in a query. /// - /// The to use for this navigation. + /// A value indicating whether the navigation should be automatically included. /// Indicates whether the configuration was specified using a data annotation. /// /// The same builder instance if the configuration was applied, /// otherwise. /// - new IConventionNavigationBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + IConventionNavigationBuilder? AutoInclude(bool? autoInclude, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation can be configured to be automatically included in a query @@ -65,15 +43,15 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder bool CanSetAutoInclude(bool? autoInclude, bool fromDataAnnotation = false); /// - /// Configures this navigation to be automatically included in a query. + /// Configures this navigation to be enabled for lazy-loading. /// - /// A value indicating whether the navigation should be automatically included. + /// A value indicating whether the navigation should be enabled for lazy-loading. /// Indicates whether the configuration was specified using a data annotation. /// /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionNavigationBuilder? AutoInclude(bool? autoInclude, bool fromDataAnnotation = false); + IConventionNavigationBuilder? EnableLazyLoading(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation can be configured to enable lazy-loading @@ -85,15 +63,17 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder bool CanSetLazyLoadingEnabled(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); /// - /// Configures this navigation to be enabled for lazy-loading. + /// Configures whether this navigation is required. /// - /// A value indicating whether the navigation should be enabled for lazy-loading. + /// + /// A value indicating whether this is a required navigation. + /// to reset to default. + /// /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the configuration was applied, - /// otherwise. + /// The same builder instance if the requiredness was configured, otherwise. /// - IConventionNavigationBuilder? EnableLazyLoading(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); + IConventionNavigationBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation requiredness can be configured @@ -103,17 +83,4 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder /// Indicates whether the configuration was specified using a data annotation. /// if requiredness can be set for this navigation. bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); - - /// - /// Configures whether this navigation is required. - /// - /// - /// A value indicating whether this is a required navigation. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the requiredness was configured, otherwise. - /// - IConventionNavigationBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionPrimitivePropertyBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPrimitivePropertyBaseBuilder.cs new file mode 100644 index 00000000000..d9a1626f48b --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionPrimitivePropertyBaseBuilder.cs @@ -0,0 +1,542 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionPrimitivePropertyBaseBuilder : IConventionPropertyBaseBuilder + where TBuilder : IConventionPrimitivePropertyBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionPrimitivePropertyBase Metadata { get; } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the requiredness was configured, + /// otherwise. + /// + TBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether this property requiredness can be configured + /// from the current configuration source. + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the property requiredness can be configured. + bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Sets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See and + /// for more information and examples. + /// + /// + /// A value indicating when a value for this property will be generated by the database. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the requiredness was configured, + /// otherwise. + /// + TBuilder? ValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property value generation can be configured + /// from the current configuration source. + /// + /// + /// A value indicating when a value for this property will be generated by the database. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the property value generation can be configured. + bool CanSetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); + + /// + /// Configures whether this property should be used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// A value indicating whether this property is a concurrency token. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? IsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be configured as a concurrency token + /// from the current configuration source. + /// + /// A value indicating whether this property is a concurrency token. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be configured as a concurrency token. + bool CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + TBuilder? HasSentinel(object? sentinel, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the sentinel can be set for this property from the current configuration source. + /// + /// The sentinel value. + /// Indicates whether the configuration was specified using a data annotation. + /// if the sentinel can be set for this property. + bool CanSetSentinel(object? sentinel, bool fromDataAnnotation = false); + + /// + /// Configures the maximum length of data that can be stored in this property. + /// + /// + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the maximum length of data allowed can be set for this property + /// from the current configuration source. + /// + /// The maximum length of data allowed in the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the maximum length of data allowed can be set for this property. + bool CanSetMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Configures whether the property as capable of persisting unicode characters. + /// + /// A value indicating whether the property can contain unicode characters. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? IsUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be configured as capable of persisting unicode characters + /// from the current configuration source. + /// + /// A value indicating whether the property can contain unicode characters. + /// Indicates whether the configuration was specified using a data annotation. + /// if the capability of persisting unicode characters can be configured for this property. + bool CanSetIsUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the precision of data allowed can be set for this property + /// from the current configuration source. + /// + /// The precision of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the precision of data allowed can be set for this property. + bool CanSetPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Configures the scale of the property. + /// + /// The scale of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the scale of data allowed can be set for this property + /// from the current configuration source. + /// + /// The scale of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the scale of data allowed can be set for this property. + bool CanSetScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Configures whether this property can be modified before the entity is saved to the database. + /// + /// + /// A value indicating whether this property can be modified before the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? BeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the ability to be modified before the entity is saved to the database + /// can be configured for this property from the current configuration source. + /// + /// + /// A value indicating whether this property can be modified before the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the ability to be modified before the entity is saved to the database can be configured for this property. + /// + bool CanSetBeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); + + /// + /// Configures whether this property can be modified after the entity is saved to the database. + /// + /// + /// Sets a value indicating whether this property can be modified after the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? AfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the ability to be modified after the entity is saved to the database + /// can be configured for this property from the current configuration source. + /// + /// + /// A value indicating whether this property can be modified after the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the ability to be modified after the entity is saved to the database can be configured for this property. + /// + bool CanSetAfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); + + /// + /// Configures the that will generate values for this property. + /// + /// A type that inherits from . + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, + bool fromDataAnnotation = false); + + /// + /// Configures the that will generate values for this property. + /// + /// A delegate that will be used to create value generator instances. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasValueGenerator( + Func? factory, + bool fromDataAnnotation = false); + + /// + /// Configures the for creating a that will + /// generate values for this property. + /// + /// A type that inherits from . + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for this property + /// from the current configuration source. + /// + /// A delegate that will be used to create value generator instances. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + /// + /// if the can be configured for this property. + /// + bool CanSetValueGenerator( + Func? factory, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for this property + /// from the current configuration source. + /// + /// A type that inherits from . + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + /// + /// if the can be configured for this property. + /// + bool CanSetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + bool fromDataAnnotation = false); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasConversion(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for this property + /// from the current configuration source. + /// + /// The converter to use. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the can be configured for this property. + /// + bool CanSetConversion(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Configures the property so that the property value is converted to the given type before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasConversion(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given type to convert values to and from + /// can be configured for this property from the current configuration source. + /// + /// The type to convert to and from. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given type to convert values to and from can be configured for this property. + /// + bool CanSetConversion(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// + /// A type that derives from , + /// or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for this property + /// from the current configuration source. + /// + /// + /// A type that derives from , + /// or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the can be configured for this property. + /// + bool CanSetConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, bool fromDataAnnotation = false); + + /// + /// Configures the for this property. + /// + /// The type mapping, or to remove any previously set type mapping. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + TBuilder? HasTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured for this property from the current configuration source. + /// + /// The type mapping. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured for this property. + /// + bool CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); + + /// + /// Configures the for this property. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + TBuilder? HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured for this property from the current configuration source. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured for this property. + /// + bool CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Configures the for this property. + /// + /// + /// A type that derives from , + /// or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + TBuilder? HasValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured for this property from the current configuration source. + /// + /// + /// A type that derives from , + /// or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured for this property. + /// + bool CanSetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Configures the to use for the provider values for this property. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + TBuilder? HasProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured for this property from the current configuration source. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured for this property. + /// + bool CanSetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Configures the to use for the provider values for this property. + /// + /// + /// A type that derives from , + /// or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + TBuilder? HasProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured for this property from the current configuration source. + /// + /// + /// A type that derives from , + /// or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured for this property. + /// + bool CanSetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs index ebff66d4ea3..2bdd732ab94 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs @@ -15,13 +15,53 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder +public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder + where TBuilder : IConventionPropertyBaseBuilder { /// /// Gets the property-like object being configured. /// new IConventionPropertyBase Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new TBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new TBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new TBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the backing field to use for this property-like object. /// @@ -31,7 +71,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); + TBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this property-like object. @@ -42,7 +82,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); + TBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); /// /// Returns a value indicating whether the backing field can be set for this property-like object @@ -71,7 +111,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + TBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Returns a value indicating whether the can be set for this property-like object diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 8868fe52f08..0e39f3cbbb5 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -1,8 +1,6 @@ // 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; - namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// @@ -17,558 +15,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder +public interface IConventionPropertyBuilder : IConventionPrimitivePropertyBaseBuilder { /// /// Gets the property being configured. /// new IConventionProperty Metadata { get; } - - /// - /// Configures whether this property must have a value assigned or is a valid value. - /// A property can only be configured as non-required if it is based on a CLR type that can be - /// assigned . - /// - /// - /// A value indicating whether the property is required. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the requiredness was configured, - /// otherwise. - /// - IConventionPropertyBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether this property requiredness can be configured - /// from the current configuration source. - /// - /// - /// A value indicating whether the property is required. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the property requiredness can be configured. - bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); - - /// - /// Sets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See and - /// for more information and examples. - /// - /// - /// A value indicating when a value for this property will be generated by the database. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the requiredness was configured, - /// otherwise. - /// - IConventionPropertyBuilder? ValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the property value generation can be configured - /// from the current configuration source. - /// - /// - /// A value indicating when a value for this property will be generated by the database. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the property value generation can be configured. - bool CanSetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); - - /// - /// Configures whether this property should be used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - /// A value indicating whether this property is a concurrency token. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? IsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the property can be configured as a concurrency token - /// from the current configuration source. - /// - /// A value indicating whether this property is a concurrency token. - /// Indicates whether the configuration was specified using a data annotation. - /// if the property can be configured as a concurrency token. - bool CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this property. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this property. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this property. - /// - /// The to use for this property. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); - - /// - /// Configures the maximum length of data that can be stored in this property. - /// - /// - /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasMaxLength(int? maxLength, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the maximum length of data allowed can be set for this property - /// from the current configuration source. - /// - /// The maximum length of data allowed in the property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the maximum length of data allowed can be set for this property. - bool CanSetMaxLength(int? maxLength, bool fromDataAnnotation = false); - - /// - /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the - /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of - /// the property. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// The same builder instance if the configuration was applied, otherwise. - IConventionPropertyBuilder? HasSentinel(object? sentinel, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the sentinel can be set for this property from the current configuration source. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// if the sentinel can be set for this property. - bool CanSetSentinel(object? sentinel, bool fromDataAnnotation = false); - - /// - /// Configures whether the property as capable of persisting unicode characters. - /// - /// A value indicating whether the property can contain unicode characters. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? IsUnicode(bool? unicode, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the property can be configured as capable of persisting unicode characters - /// from the current configuration source. - /// - /// A value indicating whether the property can contain unicode characters. - /// Indicates whether the configuration was specified using a data annotation. - /// if the capability of persisting unicode characters can be configured for this property. - bool CanSetIsUnicode(bool? unicode, bool fromDataAnnotation = false); - - /// - /// Configures the precision of the property. - /// - /// The precision of the property. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasPrecision(int? precision, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the precision of data allowed can be set for this property - /// from the current configuration source. - /// - /// The precision of the property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the precision of data allowed can be set for this property. - bool CanSetPrecision(int? precision, bool fromDataAnnotation = false); - - /// - /// Configures the scale of the property. - /// - /// The scale of the property. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasScale(int? scale, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the scale of data allowed can be set for this property - /// from the current configuration source. - /// - /// The scale of the property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the scale of data allowed can be set for this property. - bool CanSetScale(int? scale, bool fromDataAnnotation = false); - - /// - /// Configures whether this property can be modified before the entity is saved to the database. - /// - /// - /// A value indicating whether this property can be modified before the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? BeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the ability to be modified before the entity is saved to the database - /// can be configured for this property from the current configuration source. - /// - /// - /// A value indicating whether this property can be modified before the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the ability to be modified before the entity is saved to the database can be configured for this property. - /// - bool CanSetBeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); - - /// - /// Configures whether this property can be modified after the entity is saved to the database. - /// - /// - /// Sets a value indicating whether this property can be modified after the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? AfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the ability to be modified after the entity is saved to the database - /// can be configured for this property from the current configuration source. - /// - /// - /// A value indicating whether this property can be modified after the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the ability to be modified after the entity is saved to the database can be configured for this property. - /// - bool CanSetAfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation = false); - - /// - /// Configures the that will generate values for this property. - /// - /// A type that inherits from . - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, - bool fromDataAnnotation = false); - - /// - /// Configures the that will generate values for this property. - /// - /// A delegate that will be used to create value generator instances. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasValueGenerator( - Func? factory, - bool fromDataAnnotation = false); - - /// - /// Configures the for creating a that will - /// generate values for this property. - /// - /// A type that inherits from . - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, - bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be configured for this property - /// from the current configuration source. - /// - /// A delegate that will be used to create value generator instances. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - /// - /// if the can be configured for this property. - /// - bool CanSetValueGenerator( - Func? factory, - bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be configured for this property - /// from the current configuration source. - /// - /// A type that inherits from . - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - /// - /// if the can be configured for this property. - /// - bool CanSetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, - bool fromDataAnnotation = false); - - /// - /// Configures the property so that the property value is converted to and from the database - /// using the given . - /// - /// The converter to use. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasConversion(ValueConverter? converter, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be configured for this property - /// from the current configuration source. - /// - /// The converter to use. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the can be configured for this property. - /// - bool CanSetConversion(ValueConverter? converter, bool fromDataAnnotation = false); - - /// - /// Configures the property so that the property value is converted to the given type before - /// writing to the database and converted back when reading from the database. - /// - /// The type to convert to and from. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasConversion(Type? providerClrType, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given type to convert values to and from - /// can be configured for this property from the current configuration source. - /// - /// The type to convert to and from. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given type to convert values to and from can be configured for this property. - /// - bool CanSetConversion(Type? providerClrType, bool fromDataAnnotation = false); - - /// - /// Configures the property so that the property value is converted to and from the database - /// using the given . - /// - /// - /// A type that derives from , - /// or to remove any previously set converter. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, - bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the can be configured for this property - /// from the current configuration source. - /// - /// - /// A type that derives from , - /// or to remove any previously set converter. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the can be configured for this property. - /// - bool CanSetConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, bool fromDataAnnotation = false); - - /// - /// Configures the for this property. - /// - /// The type mapping, or to remove any previously set type mapping. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - IConventionPropertyBuilder? HasTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given - /// can be configured for this property from the current configuration source. - /// - /// The type mapping. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given can be configured for this property. - /// - bool CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); - - /// - /// Configures the for this property. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - IConventionPropertyBuilder? HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given - /// can be configured for this property from the current configuration source. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given can be configured for this property. - /// - bool CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Configures the for this property. - /// - /// - /// A type that derives from , - /// or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - IConventionPropertyBuilder? HasValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given - /// can be configured for this property from the current configuration source. - /// - /// - /// A type that derives from , - /// or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given can be configured for this property. - /// - bool CanSetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation = false); - - /// - /// Configures the to use for the provider values for this property. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - IConventionPropertyBuilder? HasProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given - /// can be configured for this property from the current configuration source. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given can be configured for this property. - /// - bool CanSetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Configures the to use for the provider values for this property. - /// - /// - /// A type that derives from , - /// or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - IConventionPropertyBuilder? HasProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given - /// can be configured for this property from the current configuration source. - /// - /// - /// A type that derives from , - /// or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// if the given can be configured for this property. - /// - bool CanSetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs index 957949626d1..2fbf8a4284d 100644 --- a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs @@ -15,48 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionServicePropertyBuilder : IConventionPropertyBaseBuilder +public interface IConventionServicePropertyBuilder : IConventionPropertyBaseBuilder { /// /// Gets the service property being configured. /// new IConventionServiceProperty Metadata { get; } - /// - /// Sets the backing field to use for this property. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this property. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this property. - /// - /// The to use for this property. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation = false); - /// /// Sets the for this property. /// diff --git a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs index b1a1f8d818b..1c189bc815f 100644 --- a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs @@ -15,48 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionSkipNavigationBuilder : IConventionPropertyBaseBuilder +public interface IConventionSkipNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation property being configured. /// new IConventionSkipNavigation Metadata { get; } - /// - /// Sets the backing field to use for this navigation. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this navigation. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this navigation. - /// - /// The to use for this navigation. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation = false); - /// /// Sets the foreign key. /// diff --git a/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs index 49d79bf56f6..aeea9cbfe1d 100644 --- a/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs @@ -15,4 +15,43 @@ public interface IConventionTriggerBuilder : IConventionAnnotatableBuilder /// The trigger being configured. /// new IConventionTrigger Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTriggerBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionTriggerBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTriggerBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs new file mode 100644 index 00000000000..abf8b8bdb13 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionTypeBaseBuilder : IConventionAnnotatableBuilder +{ + /// + /// Gets the type-like object being configured. + /// + new IConventionTypeBase Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTypeBaseBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionTypeBaseBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTypeBaseBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + + /// + /// Indicates whether the given member name is ignored for the given configuration source. + /// + /// The name of the member that might be ignored. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the complex type contains a member with the given name, + /// the given member name hasn't been ignored or it was ignored using a lower configuration source; + /// otherwise. + /// + bool IsIgnored(string memberName, bool fromDataAnnotation = false); + + /// + /// Excludes the given property from the complex type and prevents conventions from adding a matching property + /// or navigation to the type. + /// + /// The name of the member to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. + /// + IConventionTypeBaseBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given member name can be ignored from the given configuration source. + /// + /// The member name to be removed from the complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given member name can be ignored. + /// + /// if the complex type contains a member with the given name + /// that was configured using a higher configuration source; + /// otherwise. + /// + bool CanIgnore(string memberName, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 4f9155f3c9d..1bb55229557 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -439,7 +439,7 @@ public virtual PropertyBuilder HasField(string fieldName) /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. - /// Properties are used for all other accesses. Calling this method will change that behavior + /// Properties are used for all other accesses. Calling this method will change that behavior /// for this property as described in the enum. /// /// diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs index a4264488fc0..e855e4520d2 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs @@ -338,7 +338,7 @@ public PropertyBuilder(IMutableProperty property) /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. - /// Properties are used for all other accesses. Calling this method will change that behavior + /// Properties are used for all other accesses. Calling this method will change that behavior /// for this property as described in the enum. /// /// diff --git a/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs b/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs index 3a8f4f3adb3..741c664d2a1 100644 --- a/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs @@ -10,7 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class BackingFieldAttributeConvention : PropertyAttributeConventionBase +public class BackingFieldAttributeConvention : + PropertyAttributeConventionBase, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -21,17 +24,27 @@ public BackingFieldAttributeConvention(ProviderConventionSetBuilderDependencies { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, BackingFieldAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + BackingFieldAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + BackingFieldAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs index 3042da396a8..dc23a1605a5 100644 --- a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs +++ b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs @@ -27,6 +27,7 @@ public class BackingFieldConvention : IPropertyAddedConvention, INavigationAddedConvention, ISkipNavigationAddedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -43,11 +44,7 @@ public BackingFieldConvention(ProviderConventionSetBuilderDependencies dependenc /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) @@ -65,6 +62,12 @@ public virtual void ProcessSkipNavigationAdded( IConventionContext context) => DiscoverField(skipNavigationBuilder); + /// + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + => DiscoverField(propertyBuilder); + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, @@ -88,7 +91,8 @@ public virtual void ProcessModelFinalizing( } } - private static void DiscoverField(IConventionPropertyBaseBuilder conventionPropertyBaseBuilder) + private static void DiscoverField(IConventionPropertyBaseBuilder conventionPropertyBaseBuilder) + where TBuilder : IConventionPropertyBaseBuilder { if (ConfigurationSource.Convention.Overrides(conventionPropertyBaseBuilder.Metadata.GetFieldInfoConfigurationSource())) { diff --git a/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs index a8e7e0bd148..3bce36be726 100644 --- a/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs @@ -22,17 +22,19 @@ public ConcurrencyCheckAttributeConvention(ProviderConventionSetBuilderDependenc { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, ConcurrencyCheckAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.IsConcurrencyToken(true, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + ConcurrencyCheckAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.IsConcurrencyToken(true, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs index cc7d88810b2..2b053ce46ce 100644 --- a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs +++ b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs @@ -49,6 +49,28 @@ public virtual void ProcessModelFinalizing( entityType.Builder.HasConstructorBinding(constructorBinding, ConfigurationSource.Convention); entityType.Builder.HasServiceOnlyConstructorBinding(serviceOnlyBinding, ConfigurationSource.Convention); } + + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + Process(complexProperty.ComplexType); + } + } + } + + private void Process(ComplexType complexType) + { + if (!complexType.ClrType.IsAbstract + && ConfigurationSource.Convention.Overrides(complexType.GetConstructorBindingConfigurationSource())) + { + Dependencies.ConstructorBindingFactory.GetBindings( + complexType, out var constructorBinding, out var serviceOnlyBinding); + complexType.Builder.HasConstructorBinding(constructorBinding, ConfigurationSource.Convention); + complexType.Builder.HasServiceOnlyConstructorBinding(serviceOnlyBinding, ConfigurationSource.Convention); + } + + foreach (var complexProperty in complexType.GetDeclaredComplexProperties()) + { + Process(complexProperty.ComplexType); } } } diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs index 3c4c0c5e96c..465103da76b 100644 --- a/src/EFCore/Metadata/Conventions/ConventionSet.cs +++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs @@ -32,14 +32,14 @@ public class ConventionSet public virtual List ModelAnnotationChangedConventions { get; } = new(); /// - /// Conventions to run when an entity type is added to the model. + /// Conventions to run when a type is ignored. /// - public virtual List EntityTypeAddedConventions { get; } = new(); + public virtual List TypeIgnoredConventions { get; } = new(); /// - /// Conventions to run when an entity type is ignored. + /// Conventions to run when an entity type is added to the model. /// - public virtual List EntityTypeIgnoredConventions { get; } = new(); + public virtual List EntityTypeAddedConventions { get; } = new(); /// /// Conventions to run when an entity type is removed. @@ -66,6 +66,41 @@ public class ConventionSet /// public virtual List EntityTypeAnnotationChangedConventions { get; } = new(); + /// + /// Conventions to run when a property is ignored. + /// + public virtual List ComplexTypeMemberIgnoredConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is set or removed on a complex type. + /// + public virtual List ComplexTypeAnnotationChangedConventions { get; } = new(); + + /// + /// Conventions to run when an entity type is added to the model. + /// + public virtual List ComplexPropertyAddedConventions { get; } = new(); + + /// + /// Conventions to run when an entity type is removed. + /// + public virtual List ComplexPropertyRemovedConventions { get; } = new(); + + /// + /// Conventions to run when the nullability of a property is changed. + /// + public virtual List ComplexPropertyNullabilityChangedConventions { get; } = new(); + + /// + /// Conventions to run when the field of a property is changed. + /// + public virtual List ComplexPropertyFieldChangedConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is set or removed on a complex property. + /// + public virtual List ComplexPropertyAnnotationChangedConventions { get; } = new(); + /// /// Conventions to run when a foreign key is added. /// @@ -232,6 +267,31 @@ public class ConventionSet /// public virtual List PropertyRemovedConventions { get; } = new(); + /// + /// Conventions to run when a property is added to a complex type. + /// + public virtual List ComplexTypePropertyAddedConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is changed on a property. + /// + public virtual List ComplexTypePropertyAnnotationChangedConventions { get; } = new(); + + /// + /// Conventions to run when the nullability of a property is changed. + /// + public virtual List ComplexTypePropertyNullabilityChangedConventions { get; } = new(); + + /// + /// Conventions to run when the field of a property is changed. + /// + public virtual List ComplexTypePropertyFieldChangedConventions { get; } = new(); + + /// + /// Conventions to run when a property is removed from a complex type. + /// + public virtual List ComplexTypePropertyRemovedConventions { get; } = new(); + /// /// Replaces an existing convention with a derived convention. Also registers the new convention for any /// convention types not implemented by the existing convention. @@ -266,16 +326,16 @@ public virtual void Replace(TImplementation newConvention) ModelAnnotationChangedConventions.Add(modelAnnotationChangedConvention); } - if (newConvention is IEntityTypeAddedConvention entityTypeAddedConvention - && !Replace(EntityTypeAddedConventions, entityTypeAddedConvention, oldConvetionType)) + if (newConvention is ITypeIgnoredConvention typeIgnoredConvention + && !Replace(TypeIgnoredConventions, typeIgnoredConvention, oldConvetionType)) { - EntityTypeAddedConventions.Add(entityTypeAddedConvention); + TypeIgnoredConventions.Add(typeIgnoredConvention); } - if (newConvention is IEntityTypeIgnoredConvention entityTypeIgnoredConvention - && !Replace(EntityTypeIgnoredConventions, entityTypeIgnoredConvention, oldConvetionType)) + if (newConvention is IEntityTypeAddedConvention entityTypeAddedConvention + && !Replace(EntityTypeAddedConventions, entityTypeAddedConvention, oldConvetionType)) { - EntityTypeIgnoredConventions.Add(entityTypeIgnoredConvention); + EntityTypeAddedConventions.Add(entityTypeAddedConvention); } if (newConvention is IEntityTypeRemovedConvention entityTypeRemovedConvention @@ -308,6 +368,42 @@ public virtual void Replace(TImplementation newConvention) EntityTypeAnnotationChangedConventions.Add(entityTypeAnnotationChangedConvention); } + if (newConvention is IComplexPropertyAddedConvention complexPropertyAddedConvention + && !Replace(ComplexPropertyAddedConventions, complexPropertyAddedConvention, oldConvetionType)) + { + ComplexPropertyAddedConventions.Add(complexPropertyAddedConvention); + } + + if (newConvention is IComplexPropertyRemovedConvention complexPropertyRemovedConvention + && !Replace(ComplexPropertyRemovedConventions, complexPropertyRemovedConvention, oldConvetionType)) + { + ComplexPropertyRemovedConventions.Add(complexPropertyRemovedConvention); + } + + if (newConvention is IComplexTypeMemberIgnoredConvention complexPropertyMemberIgnoredConvention + && !Replace(ComplexTypeMemberIgnoredConventions, complexPropertyMemberIgnoredConvention, oldConvetionType)) + { + ComplexTypeMemberIgnoredConventions.Add(complexPropertyMemberIgnoredConvention); + } + + if (newConvention is IComplexPropertyNullabilityChangedConvention complexPropertyNullabilityChangedConvention + && !Replace(ComplexPropertyNullabilityChangedConventions, complexPropertyNullabilityChangedConvention, oldConvetionType)) + { + ComplexPropertyNullabilityChangedConventions.Add(complexPropertyNullabilityChangedConvention); + } + + if (newConvention is IComplexPropertyFieldChangedConvention complexPropertyFieldChangedConvention + && !Replace(ComplexPropertyFieldChangedConventions, complexPropertyFieldChangedConvention, oldConvetionType)) + { + ComplexPropertyFieldChangedConventions.Add(complexPropertyFieldChangedConvention); + } + + if (newConvention is IComplexPropertyAnnotationChangedConvention complexPropertyAnnotationChangedConvention + && !Replace(ComplexPropertyAnnotationChangedConventions, complexPropertyAnnotationChangedConvention, oldConvetionType)) + { + ComplexPropertyAnnotationChangedConventions.Add(complexPropertyAnnotationChangedConvention); + } + if (newConvention is IForeignKeyAddedConvention foreignKeyAddedConvention && !Replace(ForeignKeyAddedConventions, foreignKeyAddedConvention, oldConvetionType)) { @@ -429,6 +525,18 @@ public virtual void Replace(TImplementation newConvention) KeyRemovedConventions.Add(keyRemovedConvention); } + if (newConvention is ITriggerAddedConvention triggerAddedConvention + && !Replace(TriggerAddedConventions, triggerAddedConvention, oldConvetionType)) + { + TriggerAddedConventions.Add(triggerAddedConvention); + } + + if (newConvention is ITriggerRemovedConvention triggerRemovedConvention + && !Replace(TriggerRemovedConventions, triggerRemovedConvention, oldConvetionType)) + { + TriggerRemovedConventions.Add(triggerRemovedConvention); + } + if (newConvention is IKeyAnnotationChangedConvention keyAnnotationChangedConvention && !Replace(KeyAnnotationChangedConventions, keyAnnotationChangedConvention, oldConvetionType)) { @@ -494,6 +602,36 @@ public virtual void Replace(TImplementation newConvention) { PropertyRemovedConventions.Add(propertyRemovedConvention); } + + if (newConvention is IComplexTypePropertyAddedConvention complexTypePropertyAddedConvention + && !Replace(ComplexTypePropertyAddedConventions, complexTypePropertyAddedConvention, oldConvetionType)) + { + ComplexTypePropertyAddedConventions.Add(complexTypePropertyAddedConvention); + } + + if (newConvention is IComplexTypePropertyNullabilityChangedConvention complexTypePropertyNullabilityChangedConvention + && !Replace(ComplexTypePropertyNullabilityChangedConventions, complexTypePropertyNullabilityChangedConvention, oldConvetionType)) + { + ComplexTypePropertyNullabilityChangedConventions.Add(complexTypePropertyNullabilityChangedConvention); + } + + if (newConvention is IComplexTypePropertyFieldChangedConvention complexTypePropertyFieldChangedConvention + && !Replace(ComplexTypePropertyFieldChangedConventions, complexTypePropertyFieldChangedConvention, oldConvetionType)) + { + ComplexTypePropertyFieldChangedConventions.Add(complexTypePropertyFieldChangedConvention); + } + + if (newConvention is IComplexTypePropertyAnnotationChangedConvention complexTypePropertyAnnotationChangedConvention + && !Replace(ComplexTypePropertyAnnotationChangedConventions, complexTypePropertyAnnotationChangedConvention, oldConvetionType)) + { + ComplexTypePropertyAnnotationChangedConventions.Add(complexTypePropertyAnnotationChangedConvention); + } + + if (newConvention is IComplexTypePropertyRemovedConvention complexTypePropertyRemovedConvention + && !Replace(ComplexTypePropertyRemovedConventions, complexTypePropertyRemovedConvention, oldConvetionType)) + { + ComplexTypePropertyRemovedConventions.Add(complexTypePropertyRemovedConvention); + } } /// @@ -562,14 +700,14 @@ public virtual void Add(IConvention convention) ModelAnnotationChangedConventions.Add(modelAnnotationChangedConvention); } - if (convention is IEntityTypeAddedConvention entityTypeAddedConvention) + if (convention is ITypeIgnoredConvention typeIgnoredConvention) { - EntityTypeAddedConventions.Add(entityTypeAddedConvention); + TypeIgnoredConventions.Add(typeIgnoredConvention); } - if (convention is IEntityTypeIgnoredConvention entityTypeIgnoredConvention) + if (convention is IEntityTypeAddedConvention entityTypeAddedConvention) { - EntityTypeIgnoredConventions.Add(entityTypeIgnoredConvention); + EntityTypeAddedConventions.Add(entityTypeAddedConvention); } if (convention is IEntityTypeRemovedConvention entityTypeRemovedConvention) @@ -597,6 +735,36 @@ public virtual void Add(IConvention convention) EntityTypeAnnotationChangedConventions.Add(entityTypeAnnotationChangedConvention); } + if (convention is IComplexPropertyAddedConvention complexPropertyAddedConvention) + { + ComplexPropertyAddedConventions.Add(complexPropertyAddedConvention); + } + + if (convention is IComplexPropertyRemovedConvention complexPropertyRemovedConvention) + { + ComplexPropertyRemovedConventions.Add(complexPropertyRemovedConvention); + } + + if (convention is IComplexTypeMemberIgnoredConvention complexPropertyMemberIgnoredConvention) + { + ComplexTypeMemberIgnoredConventions.Add(complexPropertyMemberIgnoredConvention); + } + + if (convention is IComplexPropertyNullabilityChangedConvention complexPropertyNullabilityChangedConvention) + { + ComplexPropertyNullabilityChangedConventions.Add(complexPropertyNullabilityChangedConvention); + } + + if (convention is IComplexPropertyFieldChangedConvention complexPropertyFieldChangedConvention) + { + ComplexPropertyFieldChangedConventions.Add(complexPropertyFieldChangedConvention); + } + + if (convention is IComplexPropertyAnnotationChangedConvention complexPropertyAnnotationChangedConvention) + { + ComplexPropertyAnnotationChangedConventions.Add(complexPropertyAnnotationChangedConvention); + } + if (convention is IForeignKeyAddedConvention foreignKeyAddedConvention) { ForeignKeyAddedConventions.Add(foreignKeyAddedConvention); @@ -761,6 +929,31 @@ public virtual void Add(IConvention convention) { PropertyRemovedConventions.Add(propertyRemovedConvention); } + + if (convention is IComplexTypePropertyAddedConvention complexTypePropertyAddedConvention) + { + ComplexTypePropertyAddedConventions.Add(complexTypePropertyAddedConvention); + } + + if (convention is IComplexTypePropertyNullabilityChangedConvention complexTypePropertyNullabilityChangedConvention) + { + ComplexTypePropertyNullabilityChangedConventions.Add(complexTypePropertyNullabilityChangedConvention); + } + + if (convention is IComplexTypePropertyFieldChangedConvention complexTypePropertyFieldChangedConvention) + { + ComplexTypePropertyFieldChangedConventions.Add(complexTypePropertyFieldChangedConvention); + } + + if (convention is IComplexTypePropertyAnnotationChangedConvention complexTypePropertyAnnotationChangedConvention) + { + ComplexTypePropertyAnnotationChangedConventions.Add(complexTypePropertyAnnotationChangedConvention); + } + + if (convention is IComplexTypePropertyRemovedConvention complexTypePropertyRemovedConvention) + { + ComplexTypePropertyRemovedConventions.Add(complexTypePropertyRemovedConvention); + } } /// @@ -845,14 +1038,14 @@ public virtual void Remove(Type conventionType) Remove(ModelAnnotationChangedConventions, conventionType); } - if (typeof(IEntityTypeAddedConvention).IsAssignableFrom(conventionType)) + if (typeof(ITypeIgnoredConvention).IsAssignableFrom(conventionType)) { - Remove(EntityTypeAddedConventions, conventionType); + Remove(TypeIgnoredConventions, conventionType); } - if (typeof(IEntityTypeIgnoredConvention).IsAssignableFrom(conventionType)) + if (typeof(IEntityTypeAddedConvention).IsAssignableFrom(conventionType)) { - Remove(EntityTypeIgnoredConventions, conventionType); + Remove(EntityTypeAddedConventions, conventionType); } if (typeof(IEntityTypeRemovedConvention).IsAssignableFrom(conventionType)) @@ -880,6 +1073,36 @@ public virtual void Remove(Type conventionType) Remove(EntityTypeAnnotationChangedConventions, conventionType); } + if (typeof(IComplexPropertyAddedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyAddedConventions, conventionType); + } + + if (typeof(IComplexPropertyRemovedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyRemovedConventions, conventionType); + } + + if (typeof(IComplexTypeMemberIgnoredConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypeMemberIgnoredConventions, conventionType); + } + + if (typeof(IComplexPropertyNullabilityChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyNullabilityChangedConventions, conventionType); + } + + if (typeof(IComplexPropertyFieldChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyFieldChangedConventions, conventionType); + } + + if (typeof(IComplexPropertyAnnotationChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyAnnotationChangedConventions, conventionType); + } + if (typeof(IForeignKeyAddedConvention).IsAssignableFrom(conventionType)) { Remove(ForeignKeyAddedConventions, conventionType); @@ -1044,6 +1267,31 @@ public virtual void Remove(Type conventionType) { Remove(PropertyRemovedConventions, conventionType); } + + if (typeof(IComplexTypePropertyAddedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypePropertyAddedConventions, conventionType); + } + + if (typeof(IComplexTypePropertyNullabilityChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypePropertyNullabilityChangedConventions, conventionType); + } + + if (typeof(IComplexTypePropertyFieldChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypePropertyFieldChangedConventions, conventionType); + } + + if (typeof(IComplexTypePropertyAnnotationChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypePropertyAnnotationChangedConventions, conventionType); + } + + if (typeof(IComplexTypePropertyRemovedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypePropertyRemovedConventions, conventionType); + } } /// diff --git a/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs b/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs index e0925d1d76b..ba8b1a13275 100644 --- a/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs @@ -25,13 +25,7 @@ public DatabaseGeneratedAttributeConvention(ProviderConventionSetBuilderDependen { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, DatabaseGeneratedAttribute attribute, @@ -47,4 +41,21 @@ protected override void ProcessPropertyAdded( propertyBuilder.ValueGenerated(valueGenerated, fromDataAnnotation: true); } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + DatabaseGeneratedAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var valueGenerated = + attribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity + ? ValueGenerated.OnAdd + : attribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Computed + ? ValueGenerated.OnAddOrUpdate + : ValueGenerated.Never; + + propertyBuilder.ValueGenerated(valueGenerated, fromDataAnnotation: true); + } } diff --git a/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs b/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs index b8605be4a5b..4a8c594faec 100644 --- a/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs @@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; public class DeleteBehaviorAttributeConvention : PropertyAttributeConventionBase, INavigationAddedConvention, IForeignKeyPrincipalEndChangedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -23,11 +24,7 @@ public DeleteBehaviorAttributeConvention(ProviderConventionSetBuilderDependencie { } - /// - /// Called after a navigation is added to the entity type. - /// - /// The builder for the navigation. - /// Additional information associated with convention execution. + /// public virtual void ProcessNavigationAdded( IConventionNavigationBuilder navigationBuilder, IConventionContext context) @@ -47,11 +44,7 @@ public virtual void ProcessNavigationAdded( foreignKey.Builder.OnDelete(navAttribute.Behavior, fromDataAnnotation: true); } - /// - /// Called after the principal end of a foreign key is changed. - /// - /// The builder for the foreign key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyPrincipalEndChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) @@ -71,42 +64,61 @@ public virtual void ProcessForeignKeyPrincipalEndChanged( relationshipBuilder.OnDelete(navAttribute.Behavior, fromDataAnnotation: true); } - /// - /// Called when a model is being finalized. - /// - /// The builder for the model. - /// Additional information associated with convention execution. + /// public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { - foreach (var navigation in entityType.GetNavigations()) + foreach (var navigation in entityType.GetDeclaredNavigations()) { - var navAttribute = navigation.PropertyInfo?.GetCustomAttribute(); - if (navAttribute == null) + if (navigation.IsOnDependent) { return; } - if (!navigation.IsOnDependent) + var navAttribute = navigation.PropertyInfo?.GetCustomAttribute(); + if (navAttribute != null) { - throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty); + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + navigation.DeclaringEntityType.DisplayName(), navigation.Name)); } } } } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, DeleteBehaviorAttribute attribute, MemberInfo clrMember, IConventionContext context) - => throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty); + { + var property = propertyBuilder.Metadata; + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + property.DeclaringEntityType.DisplayName(), property.Name)); + } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + DeleteBehaviorAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + property.DeclaringComplexType.DisplayName(), property.Name)); + } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + DeleteBehaviorAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + property.DeclaringType.DisplayName(), property.Name)); + } } diff --git a/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs deleted file mode 100644 index 3de9280bfaa..00000000000 --- a/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; - -namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; - -/// -/// A base type for conventions that perform configuration based on an attribute specified on an entity type. -/// -/// -/// See Model building conventions for more information and examples. -/// -/// The attribute type to look for. -public abstract class EntityTypeAttributeConventionBase : IEntityTypeAddedConvention - where TAttribute : Attribute -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - protected EntityTypeAttributeConventionBase(ProviderConventionSetBuilderDependencies dependencies) - { - Dependencies = dependencies; - } - - /// - /// Dependencies for this service. - /// - protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionContext context) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - - var type = entityTypeBuilder.Metadata.ClrType; - if (!Attribute.IsDefined(type, typeof(TAttribute), inherit: true)) - { - return; - } - - var attributes = type.GetCustomAttributes(true); - - foreach (var attribute in attributes) - { - ProcessEntityTypeAdded(entityTypeBuilder, attribute, context); - if (((IReadableConventionContext)context).ShouldStopProcessing()) - { - return; - } - } - } - - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. - protected abstract void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - TAttribute attribute, - IConventionContext context); -} diff --git a/src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs similarity index 72% rename from src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs index 37a8334f093..2fc6432627c 100644 --- a/src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs @@ -9,26 +9,22 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class EntityTypeConfigurationEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class EntityTypeConfigurationAttributeConvention : TypeAttributeConventionBase, + IComplexPropertyAddedConvention { private static readonly MethodInfo ConfigureMethod - = typeof(EntityTypeConfigurationEntityTypeAttributeConvention).GetTypeInfo().GetDeclaredMethod(nameof(Configure))!; + = typeof(EntityTypeConfigurationAttributeConvention).GetTypeInfo().GetDeclaredMethod(nameof(Configure))!; /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public EntityTypeConfigurationEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public EntityTypeConfigurationAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, EntityTypeConfigurationAttribute attribute, @@ -37,8 +33,7 @@ protected override void ProcessEntityTypeAdded( var entityTypeConfigurationType = attribute.EntityTypeConfigurationType; if (!entityTypeConfigurationType.GetInterfaces().Any( - x => - x.IsGenericType + x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>) && x.GenericTypeArguments[0] == entityTypeBuilder.Metadata.ClrType)) { @@ -51,6 +46,18 @@ protected override void ProcessEntityTypeAdded( .Invoke(null, new object[] { entityTypeBuilder.Metadata, entityTypeConfigurationType }); } + /// + protected override void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + EntityTypeConfigurationAttribute attribute, + IConventionContext context) + { + if (ReplaceWithEntityType(complexTypeBuilder) != null) + { + context.StopProcessing(); + } + } + private static void Configure(IConventionEntityType entityType, Type entityTypeConfigurationType) where TEntity : class { diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs index 357d13266dd..b8730f468ad 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs @@ -24,6 +24,8 @@ public class ForeignKeyAttributeConvention : IForeignKeyAddedConvention, INavigationAddedConvention, ISkipNavigationForeignKeyChangedConvention, + IComplexTypePropertyAddedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -71,7 +73,7 @@ public virtual void ProcessEntityTypeAdded( continue; } - if (GetAttribute(navigation) == null) + if (!Attribute.IsDefined(navigation, typeof(ForeignKeyAttribute), inherit: true)) { if (FindForeignKeyAttributeOnProperty(entityType, navigation) == null) { @@ -384,15 +386,7 @@ var fkPropertiesOnDependentToPrincipal private static TAttribute? GetAttribute(MemberInfo? memberInfo) where TAttribute : Attribute - { - if (memberInfo == null - || !Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) - { - return null; - } - - return memberInfo.GetCustomAttribute(inherit: true); - } + => memberInfo == null ? null : memberInfo.GetCustomAttribute(inherit: true); [ContractAnnotation("navigation:null => null")] private MemberInfo? FindForeignKeyAttributeOnProperty(IConventionEntityType entityType, MemberInfo? navigation) @@ -535,6 +529,36 @@ public virtual void ProcessSkipNavigationForeignKeyChanged( return properties; } + /// + public virtual void ProcessComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "ForeignKey", property.DeclaringComplexType.DisplayName(), property.Name)); + } + } + + /// + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "ForeignKey", property.DeclaringType.DisplayName(), property.Name)); + } + } + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs new file mode 100644 index 00000000000..6a9cb1d53be --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex property is added to an type-like object. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyAddedConvention : IConvention +{ + /// + /// Called after a complex property is added to a type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs new file mode 100644 index 00000000000..3aa1469d52b --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on a complex property. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a complex property. + /// + /// The builder for the complex property. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs new file mode 100644 index 00000000000..6ee35b101e6 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the backing field for a complex property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyFieldChangedConvention : IConvention +{ + /// + /// Called after the backing field for a complex property is changed. + /// + /// The builder for the property. + /// The new field. + /// The old field. + /// Additional information associated with convention execution. + void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs new file mode 100644 index 00000000000..72e20ef3ab6 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the nullability for a complex property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyNullabilityChangedConvention : IConvention +{ + /// + /// Called after the nullability for a complex property is changed. + /// + /// The builder for the property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs new file mode 100644 index 00000000000..28d603f8c0f --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex property is removed from a type-like object. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyRemovedConvention : IConvention +{ + /// + /// Called after a complex property is removed from a type-like object. + /// + /// The builder for the type-like object. + /// The removed complex property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs new file mode 100644 index 00000000000..9d1b68e107b --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on a complex type. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypeAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a complex type. + /// + /// The builder for the complex type. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs new file mode 100644 index 00000000000..928a09fdac1 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex type member is ignored. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypeMemberIgnoredConvention : IConvention +{ + /// + /// Called after a complex type member is ignored. + /// + /// The builder for the complex type. + /// The name of the ignored member. + /// Additional information associated with convention execution. + void ProcessComplexTypeMemberIgnored( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypePropertyAddedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypePropertyAddedConvention.cs new file mode 100644 index 00000000000..46a440927af --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypePropertyAddedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a property is added to the entity type. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypePropertyAddedConvention : IConvention +{ + /// + /// Called after a property is added to the entity type. + /// + /// The builder for the property. + /// Additional information associated with convention execution. + void ProcessComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypePropertyAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypePropertyAnnotationChangedConvention.cs new file mode 100644 index 00000000000..a3a85deadb4 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypePropertyAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on a property. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypePropertyAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a property. + /// + /// The builder for the property. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypePropertyFieldChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypePropertyFieldChangedConvention.cs new file mode 100644 index 00000000000..2bdd6c94be1 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypePropertyFieldChangedConvention.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the backing field for a property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypePropertyFieldChangedConvention : IConvention +{ + /// + /// Called after the backing field for a property is changed. + /// + /// The builder for the property. + /// The new field. + /// The old field. + /// Additional information associated with convention execution. + void ProcessComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypePropertyNullabilityChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypePropertyNullabilityChangedConvention.cs new file mode 100644 index 00000000000..76285331754 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypePropertyNullabilityChangedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the nullability for a property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypePropertyNullabilityChangedConvention : IConvention +{ + /// + /// Called after the nullability for a property is changed. + /// + /// The builder for the property. + /// Additional information associated with convention execution. + void ProcessComplexTypePropertyNullabilityChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypePropertyRemovedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypePropertyRemovedConvention.cs new file mode 100644 index 00000000000..c6ce0e11b6c --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypePropertyRemovedConvention.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a property is removed from the complex type. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypePropertyRemovedConvention : IConvention +{ + /// + /// Called after a property is removed from the complex type. + /// + /// The builder for the complex type that contained the property. + /// The removed property. + /// Additional information associated with convention execution. + void ProcessPropertyRemoved( + IConventionComplexTypeBuilder complexTypeBuilder, + IConventionComplexTypeProperty property, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs index f973a5680a0..fb11c4139c0 100644 --- a/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs +++ b/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs @@ -9,6 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// +[Obsolete("Use ITypeIgnoredConvention instead")] public interface IEntityTypeIgnoredConvention : IConvention { /// diff --git a/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs new file mode 100644 index 00000000000..86a7b8a5804 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a type is ignored. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface ITypeIgnoredConvention : IConvention +{ + /// + /// Called after an entity type is ignored. + /// + /// The builder for the model. + /// The name of the ignored type. + /// The ignored type. + /// Additional information associated with convention execution. + void ProcessTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index dc89fc504d8..7f383cd0067 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -55,10 +55,10 @@ public virtual ConventionSet CreateConventionSet() var conventionSet = new ConventionSet(); conventionSet.Add(new ModelCleanupConvention(Dependencies)); - conventionSet.Add(new NotMappedEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new OwnedEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new KeylessEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new EntityTypeConfigurationEntityTypeAttributeConvention(Dependencies)); + conventionSet.Add(new NotMappedTypeAttributeConvention(Dependencies)); + conventionSet.Add(new OwnedAttributeConvention(Dependencies)); + conventionSet.Add(new KeylessAttributeConvention(Dependencies)); + conventionSet.Add(new EntityTypeConfigurationAttributeConvention(Dependencies)); conventionSet.Add(new NotMappedMemberAttributeConvention(Dependencies)); conventionSet.Add(new BackingFieldAttributeConvention(Dependencies)); conventionSet.Add(new ConcurrencyCheckAttributeConvention(Dependencies)); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index 853b40898cb..e731f771d55 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -47,6 +47,11 @@ public int GetLeafCount() return leafCount; } + public abstract string? OnTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type); + public abstract IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder); public abstract IConventionAnnotation? OnEntityTypeAnnotationChanged( @@ -60,11 +65,6 @@ public int GetLeafCount() IConventionEntityType? newBaseType, IConventionEntityType? previousBaseType); - public abstract string? OnEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - string name, - Type? type); - public abstract string? OnEntityTypeMemberIgnored( IConventionEntityTypeBuilder entityTypeBuilder, string name); @@ -78,6 +78,37 @@ public int GetLeafCount() IConventionModelBuilder modelBuilder, IConventionEntityType entityType); + public abstract string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name); + + public abstract IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + + public abstract IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder); + + public abstract IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property); + + public abstract FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo); + + public abstract bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder); + + public abstract IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + public abstract IConventionForeignKeyBuilder? OnForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder); public abstract IConventionAnnotation? OnForeignKeyAnnotationChanged( @@ -185,7 +216,8 @@ public int GetLeafCount() IConventionEntityTypeBuilder entityTypeBuilder, IConventionSkipNavigation navigation); - public abstract IConventionPropertyBuilder? OnPropertyAdded(IConventionPropertyBuilder propertyBuilder); + public abstract IConventionPropertyBuilder? OnPropertyAdded( + IConventionPropertyBuilder propertyBuilder); public abstract IConventionAnnotation? OnPropertyAnnotationChanged( IConventionPropertyBuilder propertyBuilder, @@ -198,12 +230,34 @@ public int GetLeafCount() FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo); - public abstract bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder); + public abstract bool? OnPropertyNullabilityChanged( + IConventionPropertyBuilder propertyBuilder); public abstract IConventionProperty? OnPropertyRemoved( IConventionEntityTypeBuilder entityTypeBuilder, IConventionProperty property); + public abstract IConventionComplexTypePropertyBuilder? OnComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder); + + public abstract FieldInfo? OnComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo); + + public abstract bool? OnComplexTypePropertyNullabilityChanged( + IConventionComplexTypePropertyBuilder propertyBuilder); + + public abstract IConventionComplexTypeProperty? OnComplexTypePropertyRemoved( + IConventionComplexTypeBuilder complexPropertyBuilder, + IConventionComplexTypeProperty property); + + public abstract IConventionAnnotation? OnComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + public abstract IConventionTriggerBuilder? OnTriggerAdded(IConventionTriggerBuilder triggerBuilder); public abstract IConventionTrigger? OnTriggerRemoved(IConventionEntityTypeBuilder entityTypeBuilder, IConventionTrigger trigger); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index cadd461ed35..15c4baafb77 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -43,18 +43,28 @@ public override void Run(ConventionDispatcher dispatcher) } } - public override IConventionEntityTypeBuilder OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + public override IConventionAnnotation? OnModelAnnotationChanged( + IConventionModelBuilder modelBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) { - Add(new OnEntityTypeAddedNode(entityTypeBuilder)); - return entityTypeBuilder; + Add(new OnModelAnnotationChangedNode(modelBuilder, name, annotation, oldAnnotation)); + return annotation; } - public override string OnEntityTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) + public override string OnTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) { - Add(new OnEntityTypeIgnoredNode(modelBuilder, name, type)); + Add(new OnTypeIgnoredNode(modelBuilder, name, type)); return name; } + public override IConventionEntityTypeBuilder OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + { + Add(new OnEntityTypeAddedNode(entityTypeBuilder)); + return entityTypeBuilder; + } + public override IConventionEntityType OnEntityTypeRemoved( IConventionModelBuilder modelBuilder, IConventionEntityType entityType) @@ -88,13 +98,58 @@ public override string OnEntityTypeMemberIgnored(IConventionEntityTypeBuilder en return annotation; } - public override IConventionAnnotation? OnModelAnnotationChanged( - IConventionModelBuilder modelBuilder, + public override string OnComplexTypeMemberIgnored(IConventionComplexTypeBuilder complexTypeBuilder, string name) + { + Add(new OnComplexTypeMemberIgnoredNode(complexTypeBuilder, name)); + return name; + } + + public override IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, string name, IConventionAnnotation? annotation, IConventionAnnotation? oldAnnotation) { - Add(new OnModelAnnotationChangedNode(modelBuilder, name, annotation, oldAnnotation)); + Add(new OnComplexTypeAnnotationChangedNode(complexTypeBuilder, name, annotation, oldAnnotation)); + return annotation; + } + + public override IConventionComplexPropertyBuilder OnComplexPropertyAdded(IConventionComplexPropertyBuilder propertyBuilder) + { + Add(new OnComplexPropertyAddedNode(propertyBuilder)); + return propertyBuilder; + } + + public override IConventionComplexProperty OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + { + Add(new OnComplexPropertyRemovedNode(typeBaseBuilder, property)); + return property; + } + + public override bool? OnComplexPropertyNullabilityChanged(IConventionComplexPropertyBuilder propertyBuilder) + { + Add(new OnComplexPropertyNullabilityChangedNode(propertyBuilder)); + return propertyBuilder.Metadata.IsNullable; + } + + public override FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + Add(new OnComplexPropertyFieldChangedNode(propertyBuilder, newFieldInfo, oldFieldInfo)); + return newFieldInfo; + } + + public override IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + Add(new OnComplexPropertyAnnotationChangedNode(propertyBuilder, name, annotation, oldAnnotation)); return annotation; } @@ -336,7 +391,7 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu public override bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) { - Add(new OnPropertyNullableChangedNode(propertyBuilder)); + Add(new OnPropertyNullabilityChangedNode(propertyBuilder)); return propertyBuilder.Metadata.IsNullable; } @@ -366,6 +421,46 @@ public override IConventionProperty OnPropertyRemoved( Add(new OnPropertyRemovedNode(entityTypeBuilder, property)); return property; } + + public override IConventionComplexTypePropertyBuilder OnComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder) + { + Add(new OnComplexTypePropertyAddedNode(propertyBuilder)); + return propertyBuilder; + } + + public override bool? OnComplexTypePropertyNullabilityChanged(IConventionComplexTypePropertyBuilder propertyBuilder) + { + Add(new OnComplexTypePropertyNullabilityChangedNode(propertyBuilder)); + return propertyBuilder.Metadata.IsNullable; + } + + public override FieldInfo? OnComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + Add(new OnComplexTypePropertyFieldChangedNode(propertyBuilder, newFieldInfo, oldFieldInfo)); + return newFieldInfo; + } + + public override IConventionAnnotation? OnComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + Add(new OnComplexTypePropertyAnnotationChangedNode(propertyBuilder, name, annotation, oldAnnotation)); + return annotation; + } + + public override IConventionComplexTypeProperty OnComplexTypePropertyRemoved( + IConventionComplexTypeBuilder complexTypeBuilder, + IConventionComplexTypeProperty property) + { + Add(new OnComplexTypePropertyRemovedNode(complexTypeBuilder, property)); + return property; + } } private sealed class OnModelAnnotationChangedNode : ConventionNode @@ -392,34 +487,34 @@ public override void Run(ConventionDispatcher dispatcher) ModelBuilder, Name, Annotation, OldAnnotation); } - private sealed class OnEntityTypeAddedNode : ConventionNode + private sealed class OnTypeIgnoredNode : ConventionNode { - public OnEntityTypeAddedNode(IConventionEntityTypeBuilder entityTypeBuilder) + public OnTypeIgnoredNode(IConventionModelBuilder modelBuilder, string name, Type? type) { - EntityTypeBuilder = entityTypeBuilder; + ModelBuilder = modelBuilder; + Name = name; + Type = type; } - public IConventionEntityTypeBuilder EntityTypeBuilder { get; } + public IConventionModelBuilder ModelBuilder { get; } + public string Name { get; } + public Type? Type { get; } public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnEntityTypeAdded(EntityTypeBuilder); + => dispatcher._immediateConventionScope.OnTypeIgnored(ModelBuilder, Name, Type); } - private sealed class OnEntityTypeIgnoredNode : ConventionNode + private sealed class OnEntityTypeAddedNode : ConventionNode { - public OnEntityTypeIgnoredNode(IConventionModelBuilder modelBuilder, string name, Type? type) + public OnEntityTypeAddedNode(IConventionEntityTypeBuilder entityTypeBuilder) { - ModelBuilder = modelBuilder; - Name = name; - Type = type; + EntityTypeBuilder = entityTypeBuilder; } - public IConventionModelBuilder ModelBuilder { get; } - public string Name { get; } - public Type? Type { get; } + public IConventionEntityTypeBuilder EntityTypeBuilder { get; } public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnEntityTypeIgnored(ModelBuilder, Name, Type); + => dispatcher._immediateConventionScope.OnEntityTypeAdded(EntityTypeBuilder); } private sealed class OnEntityTypeRemovedNode : ConventionNode @@ -497,6 +592,128 @@ public override void Run(ConventionDispatcher dispatcher) EntityTypeBuilder, Name, Annotation, OldAnnotation); } + private sealed class OnComplexTypeMemberIgnoredNode : ConventionNode + { + public OnComplexTypeMemberIgnoredNode(IConventionComplexTypeBuilder complexTypeBuilder, string name) + { + ComplexTypeBuilder = complexTypeBuilder; + Name = name; + } + + public IConventionComplexTypeBuilder ComplexTypeBuilder { get; } + public string Name { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypeMemberIgnored(ComplexTypeBuilder, Name); + } + + private sealed class OnComplexTypeAnnotationChangedNode : ConventionNode + { + public OnComplexTypeAnnotationChangedNode( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + ComplexTypeBuilder = propertyBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionComplexTypeBuilder ComplexTypeBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypeAnnotationChanged( + ComplexTypeBuilder, Name, Annotation, OldAnnotation); + } + + private sealed class OnComplexPropertyAddedNode : ConventionNode + { + public OnComplexPropertyAddedNode(IConventionComplexPropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyAdded(PropertyBuilder); + } + + private sealed class OnComplexPropertyRemovedNode : ConventionNode + { + public OnComplexPropertyRemovedNode(IConventionTypeBaseBuilder modelBuilder, IConventionComplexProperty entityType) + { + TypeBaseBuilder = modelBuilder; + ComplexProperty = entityType; + } + + public IConventionTypeBaseBuilder TypeBaseBuilder { get; } + public IConventionComplexProperty ComplexProperty { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyRemoved(TypeBaseBuilder, ComplexProperty); + } + + private sealed class OnComplexPropertyNullabilityChangedNode : ConventionNode + { + public OnComplexPropertyNullabilityChangedNode(IConventionComplexPropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyNullabilityChanged(PropertyBuilder); + } + + private sealed class OnComplexPropertyFieldChangedNode : ConventionNode + { + public OnComplexPropertyFieldChangedNode( + IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + { + PropertyBuilder = propertyBuilder; + NewFieldInfo = newFieldInfo; + OldFieldInfo = oldFieldInfo; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + public FieldInfo? NewFieldInfo { get; } + public FieldInfo? OldFieldInfo { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyFieldChanged(PropertyBuilder, NewFieldInfo, OldFieldInfo); + } + + private sealed class OnComplexPropertyAnnotationChangedNode : ConventionNode + { + public OnComplexPropertyAnnotationChangedNode( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + PropertyBuilder = propertyBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyAnnotationChanged( + PropertyBuilder, Name, Annotation, OldAnnotation); + } + private sealed class OnForeignKeyAddedNode : ConventionNode { public OnForeignKeyAddedNode(IConventionForeignKeyBuilder relationshipBuilder) @@ -989,6 +1206,91 @@ public override void Run(ConventionDispatcher dispatcher) IndexBuilder, Name, Annotation, OldAnnotation); } + private sealed class OnComplexTypePropertyAddedNode : ConventionNode + { + public OnComplexTypePropertyAddedNode(IConventionComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexTypePropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypePropertyAdded(PropertyBuilder); + } + + private sealed class OnComplexTypePropertyNullabilityChangedNode : ConventionNode + { + public OnComplexTypePropertyNullabilityChangedNode(IConventionComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexTypePropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypePropertyNullabilityChanged(PropertyBuilder); + } + + private sealed class OnComplexTypePropertyFieldChangedNode : ConventionNode + { + public OnComplexTypePropertyFieldChangedNode( + IConventionComplexTypePropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + { + PropertyBuilder = propertyBuilder; + NewFieldInfo = newFieldInfo; + OldFieldInfo = oldFieldInfo; + } + + public IConventionComplexTypePropertyBuilder PropertyBuilder { get; } + public FieldInfo? NewFieldInfo { get; } + public FieldInfo? OldFieldInfo { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypePropertyFieldChanged(PropertyBuilder, NewFieldInfo, OldFieldInfo); + } + + private sealed class OnComplexTypePropertyAnnotationChangedNode : ConventionNode + { + public OnComplexTypePropertyAnnotationChangedNode( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + PropertyBuilder = propertyBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionComplexTypePropertyBuilder PropertyBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypePropertyAnnotationChanged( + PropertyBuilder, Name, Annotation, OldAnnotation); + } + + private sealed class OnComplexTypePropertyRemovedNode : ConventionNode + { + public OnComplexTypePropertyRemovedNode( + IConventionComplexTypeBuilder complexTypeBuilder, + IConventionComplexTypeProperty property) + { + ComplexTypeBuilder = complexTypeBuilder; + Property = property; + } + + public IConventionComplexTypeBuilder ComplexTypeBuilder { get; } + public IConventionComplexTypeProperty Property { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypePropertyRemoved(ComplexTypeBuilder, Property); + } + private sealed class OnPropertyAddedNode : ConventionNode { public OnPropertyAddedNode(IConventionPropertyBuilder propertyBuilder) @@ -1002,9 +1304,9 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnPropertyAdded(PropertyBuilder); } - private sealed class OnPropertyNullableChangedNode : ConventionNode + private sealed class OnPropertyNullabilityChangedNode : ConventionNode { - public OnPropertyNullableChangedNode(IConventionPropertyBuilder propertyBuilder) + public OnPropertyNullabilityChangedNode(IConventionPropertyBuilder propertyBuilder) { PropertyBuilder = propertyBuilder; } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index 59e76f1ddd7..68f19516ceb 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -11,6 +11,8 @@ private sealed class ImmediateConventionScope : ConventionScope private readonly ConventionDispatcher _dispatcher; private readonly ConventionContext _entityTypeBuilderConventionContext; private readonly ConventionContext _entityTypeConventionContext; + private readonly ConventionContext _complexPropertyBuilderConventionContext; + private readonly ConventionContext _complexPropertyConventionContext; private readonly ConventionContext _relationshipBuilderConventionContext; private readonly ConventionContext _foreignKeyConventionContext; private readonly ConventionContext _skipNavigationBuilderConventionContext; @@ -23,6 +25,8 @@ private sealed class ImmediateConventionScope : ConventionScope private readonly ConventionContext _keyConventionContext; private readonly ConventionContext _propertyBuilderConventionContext; private readonly ConventionContext _propertyConventionContext; + private readonly ConventionContext _complexTypePropertyBuilderConventionContext; + private readonly ConventionContext _complexTypePropertyConventionContext; private readonly ConventionContext _modelBuilderConventionContext; private readonly ConventionContext _triggerBuilderConventionContext; private readonly ConventionContext _triggerConventionContext; @@ -39,6 +43,8 @@ public ImmediateConventionScope(ConventionSet conventionSet, ConventionDispatche _dispatcher = dispatcher; _entityTypeBuilderConventionContext = new ConventionContext(dispatcher); _entityTypeConventionContext = new ConventionContext(dispatcher); + _complexPropertyBuilderConventionContext = new ConventionContext(dispatcher); + _complexPropertyConventionContext = new ConventionContext(dispatcher); _relationshipBuilderConventionContext = new ConventionContext(dispatcher); _foreignKeyConventionContext = new ConventionContext(dispatcher); _skipNavigationBuilderConventionContext = new ConventionContext(dispatcher); @@ -51,6 +57,8 @@ public ImmediateConventionScope(ConventionSet conventionSet, ConventionDispatche _keyConventionContext = new ConventionContext(dispatcher); _propertyBuilderConventionContext = new ConventionContext(dispatcher); _propertyConventionContext = new ConventionContext(dispatcher); + _complexTypePropertyBuilderConventionContext = new ConventionContext(dispatcher); + _complexTypePropertyConventionContext = new ConventionContext(dispatcher); _modelBuilderConventionContext = new ConventionContext(dispatcher); _triggerBuilderConventionContext = new ConventionContext(dispatcher); _triggerConventionContext = new ConventionContext(dispatcher); @@ -134,59 +142,59 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return annotation; } - public override IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + public override string? OnTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) { using (_dispatcher.DelayConventions()) { - _entityTypeBuilderConventionContext.ResetState(entityTypeBuilder); + _stringConventionContext.ResetState(name); #if DEBUG - var initialValue = entityTypeBuilder.Metadata.IsInModel; + var initialValue = modelBuilder.Metadata.IsIgnored(name); #endif - foreach (var entityTypeConvention in _conventionSet.EntityTypeAddedConventions) + foreach (var entityTypeConvention in _conventionSet.TypeIgnoredConventions) { - if (!entityTypeBuilder.Metadata.IsInModel) - { - return null; - } - - entityTypeConvention.ProcessEntityTypeAdded(entityTypeBuilder, _entityTypeBuilderConventionContext); - if (_entityTypeBuilderConventionContext.ShouldStopProcessing()) + entityTypeConvention.ProcessTypeIgnored(modelBuilder, name, type, _stringConventionContext); + if (_stringConventionContext.ShouldStopProcessing()) { - return _entityTypeBuilderConventionContext.Result; + return _stringConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsInModel, + Check.DebugAssert(initialValue == modelBuilder.Metadata.IsIgnored(name), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return !entityTypeBuilder.Metadata.IsInModel ? null : entityTypeBuilder; + return !modelBuilder.Metadata.IsIgnored(name) ? null : name; } - public override string? OnEntityTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) + public override IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) { using (_dispatcher.DelayConventions()) { - _stringConventionContext.ResetState(name); + _entityTypeBuilderConventionContext.ResetState(entityTypeBuilder); #if DEBUG - var initialValue = modelBuilder.Metadata.IsIgnored(name); + var initialValue = entityTypeBuilder.Metadata.IsInModel; #endif - foreach (var entityTypeConvention in _conventionSet.EntityTypeIgnoredConventions) + foreach (var entityTypeConvention in _conventionSet.EntityTypeAddedConventions) { - entityTypeConvention.ProcessEntityTypeIgnored(modelBuilder, name, type, _stringConventionContext); - if (_stringConventionContext.ShouldStopProcessing()) + if (!entityTypeBuilder.Metadata.IsInModel) { - return _stringConventionContext.Result; + return null; + } + + entityTypeConvention.ProcessEntityTypeAdded(entityTypeBuilder, _entityTypeBuilderConventionContext); + if (_entityTypeBuilderConventionContext.ShouldStopProcessing()) + { + return _entityTypeBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == modelBuilder.Metadata.IsIgnored(name), + Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsInModel, $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return !modelBuilder.Metadata.IsIgnored(name) ? null : name; + return !entityTypeBuilder.Metadata.IsInModel ? null : entityTypeBuilder; } public override IConventionEntityType? OnEntityTypeRemoved( @@ -353,6 +361,232 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !entityTypeBuilder.Metadata.IsInModel ? null : annotation; } + public override string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsIgnored(name); +#endif + using (_dispatcher.DelayConventions()) + { + _stringConventionContext.ResetState(name); + foreach (var entityTypeConvention in _conventionSet.ComplexTypeMemberIgnoredConventions) + { + if (!propertyBuilder.Metadata.IsIgnored(name)) + { + return null; + } + + entityTypeConvention.ProcessComplexTypeMemberIgnored(propertyBuilder, name, _stringConventionContext); + if (_stringConventionContext.ShouldStopProcessing()) + { + return _stringConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsIgnored(name), + $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsIgnored(name) ? null : name; + } + + public override IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!complexTypeBuilder.Metadata.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = complexTypeBuilder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var complexTypeConvention in _conventionSet.ComplexTypeAnnotationChangedConventions) + { + + complexTypeConvention.ProcessComplexTypeAnnotationChanged( + complexTypeBuilder, name, annotation, oldAnnotation, _annotationConventionContext); + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(complexTypeBuilder.Metadata.IsInModel + && initialValue == complexTypeBuilder.Metadata[name], + $"Convention {complexTypeConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !complexTypeBuilder.Metadata.IsInModel ? null : annotation; + } + + public override IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder) + { + using (_dispatcher.DelayConventions()) + { + _complexPropertyBuilderConventionContext.ResetState(propertyBuilder); +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsInModel; +#endif + foreach (var complexPropertyConvention in _conventionSet.ComplexPropertyAddedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + complexPropertyConvention.ProcessComplexPropertyAdded(propertyBuilder, _complexPropertyBuilderConventionContext); + if (_complexPropertyBuilderConventionContext.ShouldStopProcessing()) + { + return _complexPropertyBuilderConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsInModel, + $"Convention {complexPropertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : propertyBuilder; + } + + public override IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + { + using (_dispatcher.DelayConventions()) + { + _complexPropertyConventionContext.ResetState(property); + foreach (var complexPropertyConvention in _conventionSet.ComplexPropertyRemovedConventions) + { + complexPropertyConvention.ProcessComplexPropertyRemoved(typeBaseBuilder, property, _complexPropertyConventionContext); + if (_complexPropertyConventionContext.ShouldStopProcessing()) + { + return _complexPropertyConventionContext.Result; + } + } + } + + return property; + } + + public override bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder) + { + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsNullable; +#endif + using (_dispatcher.DelayConventions()) + { + _boolConventionContext.ResetState(propertyBuilder.Metadata.IsNullable); + foreach (var propertyConvention in _conventionSet.ComplexPropertyNullabilityChangedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + propertyConvention.ProcessComplexPropertyNullabilityChanged(propertyBuilder, _boolConventionContext); + if (_boolConventionContext.ShouldStopProcessing()) + { + return _boolConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsNullable, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result; + } + + public override FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + if (!propertyBuilder.Metadata.IsInModel + || !propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.FieldInfo; +#endif + _fieldInfoConventionContext.ResetState(newFieldInfo); + foreach (var propertyConvention in _conventionSet.ComplexPropertyFieldChangedConventions) + { + propertyConvention.ProcessComplexPropertyFieldChanged( + propertyBuilder, newFieldInfo, oldFieldInfo, _fieldInfoConventionContext); + if (_fieldInfoConventionContext.ShouldStopProcessing()) + { + return _fieldInfoConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.FieldInfo, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + + return _fieldInfoConventionContext.Result; + } + + public override IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var propertyConvention in _conventionSet.ComplexPropertyAnnotationChangedConventions) + { + + propertyConvention.ProcessComplexPropertyAnnotationChanged( + propertyBuilder, name, annotation, oldAnnotation, _annotationConventionContext); + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(propertyBuilder.Metadata.IsInModel + && initialValue == propertyBuilder.Metadata[name], + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : annotation; + } + public override IConventionForeignKeyBuilder? OnForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder) { if (!relationshipBuilder.Metadata.DeclaringEntityType.IsInModel @@ -1342,7 +1576,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB } } - return annotation; + return !propertyBuilder.Metadata.IsInModel ? null : annotation; } public override IConventionProperty? OnPropertyRemoved( @@ -1364,5 +1598,162 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return property; } + + public override IConventionComplexTypePropertyBuilder? OnComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder) + { + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } + + using (_dispatcher.DelayConventions()) + { + _complexTypePropertyBuilderConventionContext.ResetState(propertyBuilder); + foreach (var propertyConvention in _conventionSet.ComplexTypePropertyAddedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + propertyConvention.ProcessComplexTypePropertyAdded(propertyBuilder, _complexTypePropertyBuilderConventionContext); + if (_complexTypePropertyBuilderConventionContext.ShouldStopProcessing()) + { + return _complexTypePropertyBuilderConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(propertyBuilder.Metadata.IsInModel, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : propertyBuilder; + } + + public override bool? OnComplexTypePropertyNullabilityChanged( + IConventionComplexTypePropertyBuilder propertyBuilder) + { + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsNullable; +#endif + using (_dispatcher.DelayConventions()) + { + _boolConventionContext.ResetState(propertyBuilder.Metadata.IsNullable); + foreach (var propertyConvention in _conventionSet.ComplexTypePropertyNullabilityChangedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + propertyConvention.ProcessComplexTypePropertyNullabilityChanged(propertyBuilder, _boolConventionContext); + if (_boolConventionContext.ShouldStopProcessing()) + { + return _boolConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsNullable, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result; + } + + public override FieldInfo? OnComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + if (!propertyBuilder.Metadata.IsInModel + || !propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.FieldInfo; +#endif + _fieldInfoConventionContext.ResetState(newFieldInfo); + foreach (var propertyConvention in _conventionSet.ComplexTypePropertyFieldChangedConventions) + { + propertyConvention.ProcessComplexTypePropertyFieldChanged( + propertyBuilder, newFieldInfo, oldFieldInfo, _fieldInfoConventionContext); + if (_fieldInfoConventionContext.ShouldStopProcessing()) + { + return _fieldInfoConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.FieldInfo, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + + return _fieldInfoConventionContext.Result; + } + + public override IConventionAnnotation? OnComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!propertyBuilder.Metadata.IsInModel + || !propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var propertyConvention in _conventionSet.ComplexTypePropertyAnnotationChangedConventions) + { + + propertyConvention.ProcessComplexTypePropertyAnnotationChanged( + propertyBuilder, name, annotation, oldAnnotation, _annotationConventionContext); + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(propertyBuilder.Metadata.IsInModel + && propertyBuilder.Metadata.DeclaringType.IsInModel + && initialValue == propertyBuilder.Metadata[name], + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : annotation; + } + + public override IConventionComplexTypeProperty? OnComplexTypePropertyRemoved( + IConventionComplexTypeBuilder complexTypeBuilder, + IConventionComplexTypeProperty property) + { + using (_dispatcher.DelayConventions()) + { + _complexTypePropertyConventionContext.ResetState(property); + foreach (var propertyConvention in _conventionSet.ComplexTypePropertyRemovedConventions) + { + propertyConvention.ProcessPropertyRemoved(complexTypeBuilder, property, _complexTypePropertyConventionContext); + if (_complexTypePropertyConventionContext.ShouldStopProcessing()) + { + return _complexTypePropertyConventionContext.Result; + } + } + } + + return property; + } } } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 8eef98c96b8..860a654ee72 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -86,8 +86,11 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// 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 IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) - => _scope.OnEntityTypeAdded(entityTypeBuilder); + public virtual string? OnTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type) + => _scope.OnTypeIgnored(modelBuilder, name, type); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -95,11 +98,8 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// 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 string? OnEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - string name, - Type? type) - => _scope.OnEntityTypeIgnored(modelBuilder, name, type); + public virtual IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + => _scope.OnEntityTypeAdded(entityTypeBuilder); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -159,6 +159,108 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder oldAnnotation); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name) + => _scope.OnComplexTypeMemberIgnored(propertyBuilder, name); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder) + => _scope.OnComplexPropertyAdded(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (CoreAnnotationNames.AllNames.Contains(name)) + { + return annotation; + } + + return _scope.OnComplexTypeAnnotationChanged( + complexTypeBuilder, + name, + annotation, + oldAnnotation); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + => _scope.OnComplexPropertyRemoved(typeBaseBuilder, property); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + => _scope.OnComplexPropertyFieldChanged(propertyBuilder, newFieldInfo, oldFieldInfo); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder) + => _scope.OnComplexPropertyNullabilityChanged(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (CoreAnnotationNames.AllNames.Contains(name)) + { + return annotation; + } + + return _scope.OnComplexPropertyAnnotationChanged( + propertyBuilder, + name, + annotation, + oldAnnotation); + } + /// /// 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 @@ -563,7 +665,7 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// 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? OnPropertyNullableChanged(IConventionPropertyBuilder propertyBuilder) + public virtual bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) => _scope.OnPropertyNullabilityChanged(propertyBuilder); /// @@ -602,6 +704,73 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder oldAnnotation); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionComplexTypePropertyBuilder? OnComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder) + => _scope.OnComplexTypePropertyAdded(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual FieldInfo? OnComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + => _scope.OnComplexTypePropertyFieldChanged(propertyBuilder, newFieldInfo, oldFieldInfo); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? OnComplexTypePropertyNullabilityChanged( + IConventionComplexTypePropertyBuilder propertyBuilder) + => _scope.OnComplexTypePropertyNullabilityChanged(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionComplexTypeProperty? OnComplexTypePropertyRemoved( + IConventionComplexTypeBuilder complexPropertyBuilder, + IConventionComplexTypeProperty property) + => _scope.OnComplexTypePropertyRemoved(complexPropertyBuilder, property); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (CoreAnnotationNames.AllNames.Contains(name)) + { + return annotation; + } + + return _scope.OnComplexTypePropertyAnnotationChanged( + propertyBuilder, + name, + annotation, + oldAnnotation); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs index ced69fe8985..352c744e9b0 100644 --- a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -17,7 +18,8 @@ public class KeyAttributeConvention : PropertyAttributeConventionBase, IModelFinalizingConvention, IEntityTypeAddedConvention, - IEntityTypeBaseTypeChangedConvention + IEntityTypeBaseTypeChangedConvention, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -116,6 +118,40 @@ private bool CheckAttributesAndEnsurePrimaryKey( return primaryKeyAttributeExists; } + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + KeyAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Key", property.DeclaringComplexType.DisplayName(), property.Name)); + } + } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + KeyAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Key", property.DeclaringType.DisplayName(), property.Name)); + } + } + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, diff --git a/src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs similarity index 81% rename from src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs index 082481129e2..aa9ac94f40b 100644 --- a/src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs @@ -9,13 +9,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class KeylessEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class KeylessAttributeConvention : TypeAttributeConventionBase { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public KeylessEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public KeylessAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } diff --git a/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs b/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs index ae1e289157f..6e2c3266395 100644 --- a/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations.Schema; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -11,7 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class MaxLengthAttributeConvention : PropertyAttributeConventionBase +public class MaxLengthAttributeConvention : PropertyAttributeConventionBase, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -22,13 +25,7 @@ public MaxLengthAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, MaxLengthAttribute attribute, @@ -40,4 +37,34 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasMaxLength(attribute.Length, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + MaxLengthAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + if (attribute.Length > 0) + { + propertyBuilder.HasMaxLength(attribute.Length, fromDataAnnotation: true); + } + } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + MaxLengthAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "MaxLength", property.DeclaringType.DisplayName(), property.Name)); + } + } } diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index a0cdb322fbe..a083c858f16 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -30,36 +30,6 @@ protected NavigationAttributeConventionBase(ProviderConventionSetBuilderDependen /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionContext context) - { - var navigations = GetNavigationsWithAttribute(entityTypeBuilder.Metadata); - if (navigations == null) - { - return; - } - - foreach (var navigationTuple in navigations) - { - var (navigationPropertyInfo, targetClrType) = navigationTuple; - var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); - foreach (var attribute in attributes) - { - ProcessEntityTypeAdded(entityTypeBuilder, navigationPropertyInfo, targetClrType, attribute, context); - if (((ConventionContext)context).ShouldStopProcessing()) - { - return; - } - } - } - } - /// /// Called after an entity type is ignored. /// @@ -67,7 +37,7 @@ public virtual void ProcessEntityTypeAdded( /// The name of the ignored entity type. /// The ignored entity type. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeIgnored( + public virtual void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type? type, @@ -103,7 +73,7 @@ public virtual void ProcessEntityTypeIgnored( var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); foreach (var attribute in attributes) { - ProcessEntityTypeIgnored(modelBuilder, type, navigationPropertyInfo, targetClrType, attribute, context); + ProcessTypeIgnored(modelBuilder, type, navigationPropertyInfo, targetClrType, attribute, context); if (((ConventionContext)context).ShouldStopProcessing()) { return; @@ -112,6 +82,36 @@ public virtual void ProcessEntityTypeIgnored( } } + /// + /// Called after an entity type is added to the model. + /// + /// The builder for the entity type. + /// Additional information associated with convention execution. + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + { + var navigations = GetNavigationsWithAttribute(entityTypeBuilder.Metadata); + if (navigations == null) + { + return; + } + + foreach (var navigationTuple in navigations) + { + var (navigationPropertyInfo, targetClrType) = navigationTuple; + var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); + foreach (var attribute in attributes) + { + ProcessEntityTypeAdded(entityTypeBuilder, navigationPropertyInfo, targetClrType, attribute, context); + if (((ConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + } + /// /// Called after an entity type is removed from the model. /// @@ -364,37 +364,37 @@ private static IEnumerable GetAttributes(Mem } /// - /// Called for every navigation property that has an attribute after an entity type is added to the model. + /// Called for every navigation property that has an attribute after an entity type is ignored. /// - /// The builder for the entity type. + /// The builder for the model. + /// The ignored entity type. /// The navigation member info. - /// The CLR type of the target entity type + /// The CLR type of the target entity type. /// The attribute. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, + public virtual void ProcessTypeIgnored( + IConventionModelBuilder modelBuilder, + Type type, MemberInfo navigationMemberInfo, Type targetClrType, TAttribute attribute, - IConventionContext context) + IConventionContext context) => throw new NotSupportedException(); /// - /// Called for every navigation property that has an attribute after an entity type is ignored. + /// Called for every navigation property that has an attribute after an entity type is added to the model. /// - /// The builder for the model. - /// The ignored entity type. + /// The builder for the entity type. /// The navigation member info. - /// The CLR type of the target entity type. + /// The CLR type of the target entity type /// The attribute. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - Type type, + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, MemberInfo navigationMemberInfo, Type targetClrType, TAttribute attribute, - IConventionContext context) + IConventionContext context) => throw new NotSupportedException(); /// diff --git a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs index 77e7d51b36c..bed2b4c26fb 100644 --- a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs +++ b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs @@ -13,7 +13,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// public class NonNullableReferencePropertyConvention : NonNullableConventionBase, IPropertyAddedConvention, - IPropertyFieldChangedConvention + IPropertyFieldChangedConvention, + IComplexTypePropertyAddedConvention, + IComplexTypePropertyFieldChangedConvention, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -33,27 +37,78 @@ private void Process(IConventionPropertyBuilder propertyBuilder) } } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + private void Process(IConventionComplexTypePropertyBuilder propertyBuilder) + { + if (propertyBuilder.Metadata.GetIdentifyingMemberInfo() is MemberInfo memberInfo + && IsNonNullableReferenceType(propertyBuilder.ModelBuilder, memberInfo)) + { + propertyBuilder.IsRequired(true); + } + } + + private void Process(IConventionComplexPropertyBuilder propertyBuilder) + { + if (propertyBuilder.Metadata.GetIdentifyingMemberInfo() is MemberInfo memberInfo + && IsNonNullableReferenceType(propertyBuilder.ModelBuilder, memberInfo)) + { + propertyBuilder.IsRequired(true); + } + } + + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) => Process(propertyBuilder); - /// - /// Called after the backing field for a property is changed. - /// - /// The builder for the property. - /// The new field. - /// The old field. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyFieldChanged( IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo, IConventionContext context) + { + if (propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder); + } + } + + /// + public void ProcessComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context) => Process(propertyBuilder); + + /// + public void ProcessComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder); + } + } + + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + => Process(propertyBuilder); + + /// + public void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder); + } + } } diff --git a/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs b/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs index 60d97b098f7..98ae30b9dcd 100644 --- a/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -12,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class NotMappedMemberAttributeConvention : IEntityTypeAddedConvention +public class NotMappedMemberAttributeConvention : IEntityTypeAddedConvention, IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -28,17 +29,11 @@ public NotMappedMemberAttributeConvention(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - var entityType = entityTypeBuilder.Metadata; var members = entityType.GetRuntimeProperties().Values.Cast() .Concat(entityType.GetRuntimeFields().Values); @@ -53,6 +48,25 @@ public virtual void ProcessEntityTypeAdded( } } + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var complexType = propertyBuilder.Metadata.ComplexType; + var members = complexType.GetRuntimeProperties().Values.Cast() + .Concat(complexType.GetRuntimeFields().Values); + + foreach (var member in members) + { + if (Attribute.IsDefined(member, typeof(NotMappedAttribute), inherit: true) + && ShouldIgnore(member)) + { + complexType.Builder.Ignore(member.GetSimpleMemberName(), fromDataAnnotation: true); + } + } + } + /// /// Returns a value indicating whether the given CLR member should be ignored. /// diff --git a/src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs similarity index 82% rename from src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs index 9b62ba7e3ce..1557ee96143 100644 --- a/src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs @@ -11,13 +11,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class NotMappedEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class NotMappedTypeAttributeConvention : TypeAttributeConventionBase { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public NotMappedEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public NotMappedTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } diff --git a/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs b/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs new file mode 100644 index 00000000000..e3629a8a8e2 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A convention that configures the entity types that have the as owned. +/// +/// +/// See Model building conventions for more information and examples. +/// +public class OwnedAttributeConvention : TypeAttributeConventionBase, + IComplexPropertyAddedConvention +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public OwnedAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + protected override void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + OwnedAttribute attribute, + IConventionContext context) + { + entityTypeBuilder.ModelBuilder.Owned(entityTypeBuilder.Metadata.ClrType, fromDataAnnotation: true); + if (!entityTypeBuilder.Metadata.IsInModel) + { + context.StopProcessing(); + } + } + + /// + protected override void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + OwnedAttribute attribute, + IConventionContext context) + { + var complexProperty = complexTypeBuilder.Metadata.ComplexProperty; + var entityTypeBuilder = ReplaceWithEntityType(complexTypeBuilder, shouldBeOwned: true); + if (entityTypeBuilder == null) + { + return; + } + + context.StopProcessing(); + + var memberInfo = complexProperty.GetIdentifyingMemberInfo(); + if (memberInfo != null + && complexProperty.Builder is IConventionEntityTypeBuilder conventionEntityTypeBuilder) + { + conventionEntityTypeBuilder.HasOwnership( + entityTypeBuilder.Metadata, memberInfo, fromDataAnnotation: true); + } + } +} diff --git a/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs deleted file mode 100644 index 086405ce2aa..00000000000 --- a/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; - -/// -/// A convention that configures the entity types that have the as owned. -/// -/// -/// See Model building conventions for more information and examples. -/// -public class OwnedEntityTypeAttributeConvention : EntityTypeAttributeConventionBase -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - public OwnedEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) - : base(dependencies) - { - } - - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. - protected override void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - OwnedAttribute attribute, - IConventionContext context) - { - entityTypeBuilder.ModelBuilder.Owned(entityTypeBuilder.Metadata.ClrType, fromDataAnnotation: true); - if (!entityTypeBuilder.Metadata.IsInModel) - { - context.StopProcessing(); - } - } -} diff --git a/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs index 55dbc3e5720..4b7be070be8 100644 --- a/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// @@ -20,13 +24,7 @@ public PrecisionAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, PrecisionAttribute attribute, @@ -40,4 +38,37 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasScale(attribute.Scale, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + PrecisionAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + propertyBuilder.HasPrecision(attribute.Precision, fromDataAnnotation: true); + + if (attribute.Scale.HasValue) + { + propertyBuilder.HasScale(attribute.Scale, fromDataAnnotation: true); + } + } + + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + PrecisionAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Precision", property.DeclaringType.DisplayName(), property.Name)); + } + } } diff --git a/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs index cf6a46fd7a0..2a1df454a04 100644 --- a/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs @@ -10,10 +10,20 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// A base type for conventions that perform configuration based on an attribute applied to a property. /// /// -/// See Model building conventions for more information and examples. +/// +/// The deriving class must implement and +/// to also handle complex properties. +/// +/// +/// See Model building conventions for more information and examples. +/// /// /// The attribute type to look for. -public abstract class PropertyAttributeConventionBase : IPropertyAddedConvention, IPropertyFieldChangedConvention +public abstract class PropertyAttributeConventionBase : + IPropertyAddedConvention, + IPropertyFieldChangedConvention, + IComplexTypePropertyAddedConvention, + IComplexTypePropertyFieldChangedConvention where TAttribute : Attribute { /// @@ -30,11 +40,7 @@ protected PropertyAttributeConventionBase(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) @@ -50,15 +56,117 @@ public virtual void ProcessPropertyAdded( Process(propertyBuilder, memberInfo, (IReadableConventionContext)context); } + /// + public virtual void ProcessPropertyFieldChanged( + IConventionPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (newFieldInfo != null + && propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder, newFieldInfo, (IReadableConventionContext)context); + } + } + + private void Process(IConventionPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) + { + if (!Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) + { + return; + } + + var attributes = memberInfo.GetCustomAttributes(inherit: true); + + foreach (var attribute in attributes) + { + ProcessPropertyAdded(propertyBuilder, attribute, memberInfo, context); + if (context.ShouldStopProcessing()) + { + break; + } + } + } + + /// + public virtual void ProcessComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context) + { + Check.NotNull(propertyBuilder, nameof(propertyBuilder)); + + var memberInfo = propertyBuilder.Metadata.GetIdentifyingMemberInfo(); + if (memberInfo == null) + { + return; + } + + Process(propertyBuilder, memberInfo, (IReadableConventionContext)context); + } + + /// + public virtual void ProcessComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (newFieldInfo != null + && propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder, newFieldInfo, (IReadableConventionContext)context); + } + } + + private void Process(IConventionComplexTypePropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) + { + if (!Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) + { + return; + } + + var attributes = memberInfo.GetCustomAttributes(inherit: true); + + foreach (var attribute in attributes) + { + ProcessPropertyAdded(propertyBuilder, attribute, memberInfo, context); + if (context.ShouldStopProcessing()) + { + break; + } + } + } + /// - /// Called after the backing field for a property is changed. + /// Called after a complex property is added to an type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Check.NotNull(propertyBuilder, nameof(propertyBuilder)); + + var memberInfo = propertyBuilder.Metadata.GetIdentifyingMemberInfo(); + if (memberInfo == null) + { + return; + } + + Process(propertyBuilder, memberInfo, (IReadableConventionContext)context); + } + + /// + /// Called after the backing field for a complex property is changed. /// /// The builder for the property. /// The new field. /// The old field. /// Additional information associated with convention execution. - public virtual void ProcessPropertyFieldChanged( - IConventionPropertyBuilder propertyBuilder, + public virtual void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo, IConventionContext context) @@ -70,7 +178,7 @@ public virtual void ProcessPropertyFieldChanged( } } - private void Process(IConventionPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) + private void Process(IConventionComplexPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) { if (!Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) { @@ -101,4 +209,31 @@ protected abstract void ProcessPropertyAdded( TAttribute attribute, MemberInfo clrMember, IConventionContext context); + + /// + /// Called after a property is added to the complex type with an attribute on the associated CLR property or field. + /// + /// The builder for the property. + /// The attribute. + /// The member that has the attribute. + /// Additional information associated with convention execution. + protected abstract void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + TAttribute attribute, + MemberInfo clrMember, + IConventionContext context); + + /// + /// Called after a complex property is added to a type with an attribute on the associated CLR property or field. + /// + /// The builder for the property. + /// The attribute. + /// The member that has the attribute. + /// Additional information associated with convention execution. + protected virtual void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + TAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => throw new NotSupportedException(); } diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index b855a79c91d..4a73cb154ca 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.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 Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -13,7 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// public class PropertyDiscoveryConvention : IEntityTypeAddedConvention, - IEntityTypeBaseTypeChangedConvention + IEntityTypeBaseTypeChangedConvention, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -29,23 +31,31 @@ public PropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies depe /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) => Process(entityTypeBuilder); - /// - /// Called after the base type of an entity type changes. - /// - /// The builder for the entity type. - /// The new base entity type. - /// The old base entity type. - /// Additional information associated with convention execution. + /// + 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)) + { + continue; + } + + complexType.Builder.Property(propertyInfo); + } + } + + /// public virtual void ProcessEntityTypeBaseTypeChanged( IConventionEntityTypeBuilder entityTypeBuilder, IConventionEntityType? newBaseType, diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index 0fc7dd606d9..b0c28d5cacc 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -15,8 +15,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// See Model building conventions for more information and examples. /// public class RelationshipDiscoveryConvention : + ITypeIgnoredConvention, IEntityTypeAddedConvention, - IEntityTypeIgnoredConvention, IEntityTypeBaseTypeChangedConvention, IEntityTypeMemberIgnoredConvention, INavigationRemovedConvention, @@ -1134,12 +1134,13 @@ private static bool IsCandidateNavigationProperty( => sourceEntityType.Builder.IsIgnored(navigationName) == false && sourceEntityType.FindProperty(navigationName) == null && sourceEntityType.FindServiceProperty(navigationName) == null + && sourceEntityType.FindComplexProperty(navigationName) == null && (memberInfo is not PropertyInfo propertyInfo || propertyInfo.GetIndexParameters().Length == 0) && (!sourceEntityType.IsKeyless || (memberInfo as PropertyInfo)?.PropertyType.TryGetSequenceType() == null); /// - public virtual void ProcessEntityTypeIgnored( + public virtual void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type? type, diff --git a/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs index fb395f87983..12e4d9d4aa2 100644 --- a/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs @@ -11,7 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RequiredPropertyAttributeConvention : PropertyAttributeConventionBase +public class RequiredPropertyAttributeConvention : PropertyAttributeConventionBase, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -22,17 +24,27 @@ public RequiredPropertyAttributeConvention(ProviderConventionSetBuilderDependenc { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, RequiredAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.IsRequired(true, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + RequiredAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.IsRequired(true, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + RequiredAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.IsRequired(true, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 2f0a65583ed..2f3428af91f 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -73,6 +73,14 @@ protected virtual RuntimeModel Create(IModel model) (ServiceParameterBinding)Create(serviceProperty.ParameterBinding, runtimeEntityType); } + foreach (var property in entityType.GetDeclaredComplexProperties()) + { + var runtimeProperty = Create(property, runtimeEntityType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + foreach (var key in entityType.GetDeclaredKeys()) { var runtimeKey = Create(key, runtimeEntityType); @@ -384,6 +392,57 @@ protected virtual void ProcessPropertyAnnotations( } } + private static RuntimeComplexTypeProperty Create(IComplexTypeProperty property, RuntimeComplexType runtimeComplexType) + => runtimeComplexType.AddProperty( + property.Name, + property.ClrType, + property.Sentinel, + property.PropertyInfo, + property.FieldInfo, + property.GetPropertyAccessMode(), + property.IsNullable, + property.IsConcurrencyToken, + property.ValueGenerated, + property.GetBeforeSaveBehavior(), + property.GetAfterSaveBehavior(), + property.GetMaxLength(), + property.IsUnicode(), + property.GetPrecision(), + property.GetScale(), + property.GetProviderClrType(), + property.GetValueGeneratorFactory(), + property.GetValueConverter(), + property.GetValueComparer(), + property.GetKeyValueComparer(), + property.GetProviderValueComparer(), + property.GetJsonValueReaderWriter(), + property.GetTypeMapping()); + + /// + /// Updates the property annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source property. + /// The target property that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessPropertyAnnotations( + Dictionary annotations, + IComplexTypeProperty property, + RuntimeComplexTypeProperty runtimeProperty, + bool runtime) + { + if (!runtime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key)) + { + annotations.Remove(key); + } + } + } + } + private static RuntimeServiceProperty Create(IServiceProperty property, RuntimeEntityType runtimeEntityType) => runtimeEntityType.AddServiceProperty( property.Name, @@ -417,6 +476,107 @@ protected virtual void ProcessServicePropertyAnnotations( } } + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeEntityType runtimeEntityType) + { + var runtimeComplexProperty = runtimeEntityType.AddComplexProperty( + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); + + var complexType = complexProperty.ComplexType; + var runtimeComplexType = runtimeComplexProperty.ComplexType; + + foreach (var property in complexType.GetProperties()) + { + var runtimeProperty = Create(property, runtimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessPropertyAnnotations(annotations, source, target, runtime)); + } + + foreach (var property in complexType.GetComplexProperties()) + { + var runtimeProperty = Create(property, runtimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + + return runtimeComplexProperty; + } + + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeComplexType runtimeComplexType) + { + var runtimeComplexProperty = runtimeComplexType.AddComplexProperty( + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); + + var complexType = complexProperty.ComplexType; + var newRuntimeComplexType = runtimeComplexProperty.ComplexType; + + foreach (var property in complexType.GetProperties()) + { + var runtimeProperty = Create(property, newRuntimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessPropertyAnnotations(annotations, source, target, runtime)); + } + + foreach (var property in complexType.GetComplexProperties()) + { + var runtimeProperty = Create(property, newRuntimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + + return runtimeComplexProperty; + } + + /// + /// Updates the property annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source property. + /// The target property that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessComplexPropertyAnnotations( + Dictionary annotations, + IComplexProperty property, + RuntimeComplexProperty runtimeProperty, + 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/StringLengthAttributeConvention.cs b/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs index 26b92c066a0..4b79df6043f 100644 --- a/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs @@ -22,13 +22,7 @@ public StringLengthAttributeConvention(ProviderConventionSetBuilderDependencies { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, StringLengthAttribute attribute, @@ -40,4 +34,17 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasMaxLength(attribute.MaximumLength, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + StringLengthAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + if (attribute.MaximumLength > 0) + { + propertyBuilder.HasMaxLength(attribute.MaximumLength, fromDataAnnotation: true); + } + } } diff --git a/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs b/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs index 3ef8cac9f5a..51996a8a014 100644 --- a/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs @@ -22,13 +22,7 @@ public TimestampAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, TimestampAttribute attribute, @@ -38,4 +32,15 @@ protected override void ProcessPropertyAdded( propertyBuilder.ValueGenerated(ValueGenerated.OnAddOrUpdate, fromDataAnnotation: true); propertyBuilder.IsConcurrencyToken(true, fromDataAnnotation: true); } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + TimestampAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + propertyBuilder.ValueGenerated(ValueGenerated.OnAddOrUpdate, fromDataAnnotation: true); + propertyBuilder.IsConcurrencyToken(true, fromDataAnnotation: true); + } } diff --git a/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs new file mode 100644 index 00000000000..4bf7904f143 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A base type for conventions that perform configuration based on an attribute specified on an entity type. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// The attribute type to look for. +public abstract class TypeAttributeConventionBase : IEntityTypeAddedConvention + where TAttribute : Attribute +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + protected TypeAttributeConventionBase(ProviderConventionSetBuilderDependencies dependencies) + { + Dependencies = dependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + + /// + /// Called after an entity type is added to the model. + /// + /// The builder for the entity type. + /// Additional information associated with convention execution. + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + { + var type = entityTypeBuilder.Metadata.ClrType; + + var attributes = type.GetCustomAttributes(true); + foreach (var attribute in attributes) + { + ProcessEntityTypeAdded(entityTypeBuilder, attribute, context); + if (((IReadableConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + + /// + /// Called after a complex property is added to a type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var complexType = propertyBuilder.Metadata.ComplexType; + var type = complexType.ClrType; + var attributes = type.GetCustomAttributes(true); + foreach (var attribute in attributes) + { + ProcessComplexTypeAdded(complexType.Builder, attribute, context); + if (((IReadableConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + + /// + /// Called after an entity type is added to the model if it has an attribute. + /// + /// The builder for the entity type. + /// The attribute. + /// Additional information associated with convention execution. + protected abstract void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + TAttribute attribute, + IConventionContext context); + + /// + /// Called after an complex type is added to the model if it has an attribute. + /// + /// The builder for the complex type. + /// The attribute. + /// Additional information associated with convention execution. + protected virtual void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + TAttribute attribute, + IConventionContext context) + => throw new NotSupportedException(); + + /// + /// Tries to replace the complex type with an entity type. + /// + /// The complex type builder. + /// A value indicating whether the new entity type should be owned. + /// The builder for the new entity type. + protected virtual IConventionEntityTypeBuilder? ReplaceWithEntityType( + IConventionComplexTypeBuilder complexTypeBuilder, + bool? shouldBeOwned = null) + { + var modelBuilder = complexTypeBuilder.ModelBuilder; + if (!modelBuilder.CanHaveEntity(complexTypeBuilder.Metadata.ClrType, fromDataAnnotation: true)) + { + return null; + } + + var complexProperty = complexTypeBuilder.Metadata.ComplexProperty; + switch (complexProperty.DeclaringType.Builder) + { + case IConventionEntityTypeBuilder conventionEntityTypeBuilder: + if (conventionEntityTypeBuilder.HasNoComplexProperty(complexProperty, fromDataAnnotation: true) == null) + { + return null; + } + break; + case IConventionComplexTypeBuilder conventionComplexTypeBuilder: + if (conventionComplexTypeBuilder.HasNoComplexProperty(complexProperty, fromDataAnnotation: true) == null) + { + return null; + } + break; + } + + return complexTypeBuilder.ModelBuilder.Entity(complexTypeBuilder.Metadata.ClrType, shouldBeOwned, fromDataAnnotation: true); + } +} diff --git a/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs index 96ca3f21f2d..a84a999a6b6 100644 --- a/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs @@ -20,17 +20,19 @@ public UnicodeAttributeConvention(ProviderConventionSetBuilderDependencies depen { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, UnicodeAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.IsUnicode(attribute.IsUnicode, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + UnicodeAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.IsUnicode(attribute.IsUnicode, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/EntityTypeFullNameComparer.cs b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs index 2edf328b749..ab42efeca2b 100644 --- a/src/EFCore/Metadata/EntityTypeFullNameComparer.cs +++ b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// /// An implementation of and to compare -/// instances by name including the defining entity type when present. +/// instances by the full unique name. /// /// /// This type is typically used by database providers (and other extensions). It is generally diff --git a/src/EFCore/Metadata/ForeignKeyComparer.cs b/src/EFCore/Metadata/ForeignKeyComparer.cs index ba5c2a2e163..a3a61479829 100644 --- a/src/EFCore/Metadata/ForeignKeyComparer.cs +++ b/src/EFCore/Metadata/ForeignKeyComparer.cs @@ -50,8 +50,8 @@ public int Compare(IReadOnlyForeignKey? x, IReadOnlyForeignKey? y) return result; } - result = EntityTypeFullNameComparer.Instance.Compare(x?.PrincipalEntityType, y?.PrincipalEntityType); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + result = TypeBaseNameComparer.Instance.Compare(x?.PrincipalEntityType, y?.PrincipalEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -73,8 +73,8 @@ public int GetHashCode(IReadOnlyForeignKey obj) var hashCode = new HashCode(); hashCode.Add(obj.PrincipalKey.Properties, PropertyListComparer.Instance); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.PrincipalEntityType, EntityTypeFullNameComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.PrincipalEntityType, TypeBaseNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/IComplexProperty.cs b/src/EFCore/Metadata/IComplexProperty.cs new file mode 100644 index 00000000000..a3c8888c63c --- /dev/null +++ b/src/EFCore/Metadata/IComplexProperty.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IComplexProperty : IReadOnlyComplexProperty, IPropertyBase +{ + /// + /// Gets the associated complex type. + /// + new IComplexType ComplexType { get; } +} diff --git a/src/EFCore/Metadata/IComplexType.cs b/src/EFCore/Metadata/IComplexType.cs new file mode 100644 index 00000000000..95c60401aff --- /dev/null +++ b/src/EFCore/Metadata/IComplexType.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IComplexType : IReadOnlyComplexType, ITypeBase +{ + /// + /// Gets the associated property. + /// + new IComplexProperty ComplexProperty { get; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + new IEntityType FundametalEntityType + => (IEntityType)((IReadOnlyComplexType)this).FundametalEntityType; + + /// + /// Gets a property on the given complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IComplexTypeProperty? FindProperty(MemberInfo memberInfo) + => (IComplexTypeProperty?)((IReadOnlyComplexType)this).FindProperty(memberInfo); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IComplexTypeProperty? FindProperty(string name); + + /// + /// Finds matching properties on this complex type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) + => (IReadOnlyList?)((IReadOnlyComplexType)this).FindProperties(propertyNames); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetProperties(); + + /// + /// Gets a complex property on the given complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(string name); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetComplexProperties(); +} diff --git a/src/EFCore/Metadata/IComplexTypeProperty.cs b/src/EFCore/Metadata/IComplexTypeProperty.cs new file mode 100644 index 00000000000..7cbbbeffadf --- /dev/null +++ b/src/EFCore/Metadata/IComplexTypeProperty.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of an entity type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IComplexTypeProperty : IReadOnlyComplexTypeProperty, IPrimitivePropertyBase +{ + /// + /// Gets the type that this property belongs to. + /// + new IComplexType DeclaringComplexType { get; } +} diff --git a/src/EFCore/Metadata/IConstructorBindingFactory.cs b/src/EFCore/Metadata/IConstructorBindingFactory.cs index 1eb5a643ac0..0e474667080 100644 --- a/src/EFCore/Metadata/IConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/IConstructorBindingFactory.cs @@ -58,6 +58,18 @@ void GetBindings( out InstantiationBinding constructorBinding, out InstantiationBinding? serviceOnlyBinding); + /// + /// Create a for the constructor with most parameters and + /// the constructor with only service property parameters. + /// + /// The complex type. + /// The binding for the constructor with most parameters. + /// The binding for the constructor with only service property parameters. + void GetBindings( + IReadOnlyComplexType complexType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding); + /// /// Attempts to create a for the given entity type and /// diff --git a/src/EFCore/Metadata/IConventionComplexProperty.cs b/src/EFCore/Metadata/IConventionComplexProperty.cs new file mode 100644 index 00000000000..484d1c54613 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexProperty.cs @@ -0,0 +1,49 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionComplexProperty : IReadOnlyComplexProperty, IConventionPropertyBase +{ + /// + /// Gets the builder that can be used to configure this property. + /// + /// If the property has been removed from the model. + new IConventionComplexPropertyBuilder Builder { get; } + + /// + /// Gets the associated complex type. + /// + new IConventionComplexType ComplexType { get; } + + /// + /// Sets a value indicating whether this property can contain . + /// + /// + /// A value indicating whether this property can contain . + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsNullable(bool? nullable, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsNullableConfigurationSource(); +} diff --git a/src/EFCore/Metadata/IConventionComplexType.cs b/src/EFCore/Metadata/IConventionComplexType.cs new file mode 100644 index 00000000000..853cc694113 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexType.cs @@ -0,0 +1,304 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionComplexType : IReadOnlyComplexType, IConventionTypeBase +{ + /// + /// Gets the builder that can be used to configure this property. + /// + /// If the property has been removed from the model. + new IConventionComplexTypeBuilder Builder { get; } + + /// + /// Gets the associated property. + /// + new IConventionComplexProperty ComplexProperty { get; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + new IConventionEntityType FundametalEntityType { get; } + + /// + /// Sets the change tracking strategy to use for this entity type. This strategy indicates how the + /// context detects changes to properties for an instance of the entity type. + /// + /// The strategy to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ChangeTrackingStrategy? SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetChangeTrackingStrategyConfigurationSource(); + + /// + /// Adds a property to this complex type. + /// + /// The corresponding member on the complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IConventionComplexTypeProperty? AddProperty(MemberInfo memberInfo, bool fromDataAnnotation = false) + => AddProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, setTypeConfigurationSource: true, fromDataAnnotation); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexTypeProperty? AddProperty(string name, bool fromDataAnnotation = false); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexTypeProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexTypeProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Adds a property backed by and indexer to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexTypeProperty? AddIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddProperty(name, propertyType, indexerPropertyInfo, setTypeConfigurationSource, fromDataAnnotation); + } + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IConventionComplexTypeProperty? FindProperty(string name); + + /// + /// Gets a property with the given member info. Returns if no property is found. + /// + /// The member on the complex type. + /// The property, or if none is found. + new IConventionComplexTypeProperty? FindProperty(MemberInfo memberInfo) + => (IConventionComplexTypeProperty?)((IReadOnlyComplexType)this).FindProperty(memberInfo); + + /// + /// Finds matching properties on the given complex type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties(IReadOnlyList propertyNames); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetProperties(); + + /// + /// Removes a property from this complex type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionComplexTypeProperty? RemoveProperty(string name); + + /// + /// Removes a property from this complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionComplexTypeProperty? RemoveProperty(IReadOnlyComplexTypeProperty property); + + /// + /// Adds a property to this complex type. + /// + /// The corresponding member on the complex type. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, bool collection = false, bool fromDataAnnotation = false) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, memberInfo.GetMemberType(), collection, fromDataAnnotation); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty(string name, bool collection = false, bool fromDataAnnotation = false); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member or for a shadow property. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property backed by and indexer to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, collection, fromDataAnnotation); + } + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IConventionComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex properties defined on this complex type. + /// + /// The complex properties defined on this complex type. + new IEnumerable GetComplexProperties(); + + /// + /// Removes a property from this complex type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionComplexProperty? RemoveComplexProperty(IConventionComplexProperty property); +} diff --git a/src/EFCore/Metadata/IConventionComplexTypeProperty.cs b/src/EFCore/Metadata/IConventionComplexTypeProperty.cs new file mode 100644 index 00000000000..ccefb8d6b32 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexTypeProperty.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a complex type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionComplexTypeProperty : IReadOnlyComplexTypeProperty, IConventionPrimitivePropertyBase +{ + /// + /// Gets the builder that can be used to configure this property. + /// + /// If the property has been removed from the model. + new IConventionComplexTypePropertyBuilder Builder { get; } + + /// + /// Gets the complex type that this property belongs to. + /// + new IConventionComplexType DeclaringComplexType { get; } +} diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index bdef687dd02..9098fe0b476 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -26,11 +26,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The configuration source. ConfigurationSource GetConfigurationSource(); - /// - /// Gets the model this entity belongs to. - /// - new IConventionModel Model { get; } - /// /// Gets the builder that can be used to configure this entity type. /// @@ -586,6 +581,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// Adds a new skip navigation property to this entity type. /// /// The name of the skip navigation property to add. + /// The navigation type. /// /// /// The corresponding CLR type member or for a shadow navigation. @@ -603,6 +599,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The newly created skip navigation property. IConventionSkipNavigation? AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IConventionEntityType targetEntityType, bool collection, @@ -820,7 +817,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The type of value the property will hold. /// /// - /// The corresponding CLR type member or for a shadow property. + /// The corresponding CLR type member. /// /// /// An indexer with a parameter and return type can be used. @@ -832,7 +829,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas IConventionProperty? AddProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo, + MemberInfo memberInfo, bool setTypeConfigurationSource = true, bool fromDataAnnotation = false); @@ -871,16 +868,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The property, or if none is found. new IConventionProperty? FindProperty(string name); - /// - /// Gets the properties defined on this entity type. - /// - /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation properties. - /// - /// The properties defined on this entity type. - new IEnumerable GetProperties(); - /// /// Gets a property on the given entity type. Returns if no property is found. /// @@ -926,7 +913,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas => (IConventionProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets all scalar properties declared on this entity type. /// /// /// This method does not return properties declared on base types. @@ -934,11 +921,10 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// Use to also return properties declared on base types. /// /// Declared non-navigation properties. - new IEnumerable GetDeclaredProperties() - => ((IReadOnlyEntityType)this).GetDeclaredProperties().Cast(); + new IEnumerable GetDeclaredProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets all scalar properties declared on the types derived from this entity type. /// /// /// This method does not return properties declared on the given entity type itself. @@ -949,6 +935,16 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas new IEnumerable GetDerivedProperties() => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); + /// + /// Gets all scalar properties defined on this entity type. + /// + /// + /// This API only returns scalar properties and does not return navigation properties. Use + /// to get navigation properties. + /// + /// The properties defined on this entity type. + new IEnumerable GetProperties(); + /// /// Removes a property from this entity type. /// @@ -963,6 +959,157 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The removed property, or if the property was not found. IConventionProperty? RemoveProperty(IReadOnlyProperty property); + /// + /// Adds a property to this entity type. + /// + /// The corresponding member on the entity type. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, bool collection = false, bool fromDataAnnotation = false) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, memberInfo.GetMemberType(), collection, fromDataAnnotation); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty(string name, bool collection = false, bool fromDataAnnotation = false); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property backed by and indexer to this entity type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, fromDataAnnotation); + } + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IConventionComplexProperty? FindDeclaredComplexProperty(string name) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets the complex properties defined on this entity type. + /// + /// The complex properties defined on this entity type. + new IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this entity type. + /// + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this entity type. + /// + /// + /// This method does not return complex properties declared on the given entity type itself. + /// Use to return complex properties declared on this + /// and base entity typed types. + /// + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); + + /// + /// Removes a property from this entity type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this entity type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionComplexProperty? RemoveComplexProperty(IConventionComplexProperty property); + /// /// Adds a service property to this entity type. /// diff --git a/src/EFCore/Metadata/IConventionPrimitivePropertyBase.cs b/src/EFCore/Metadata/IConventionPrimitivePropertyBase.cs new file mode 100644 index 00000000000..5b73460ea99 --- /dev/null +++ b/src/EFCore/Metadata/IConventionPrimitivePropertyBase.cs @@ -0,0 +1,445 @@ +// 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.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionPrimitivePropertyBase : IReadOnlyPrimitivePropertyBase, IConventionPropertyBase +{ + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetTypeConfigurationSource(); + + /// + /// Sets a value indicating whether this property can contain . + /// + /// + /// A value indicating whether this property can contain . + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsNullable(bool? nullable, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsNullableConfigurationSource(); + + /// + /// Sets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See and + /// for more information and examples. + /// + /// + /// A value indicating when a value for this property will be generated by the database. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueGenerated? SetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueGeneratedConfigurationSource(); + + /// + /// Sets a value indicating whether this property is used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// + /// Sets a value indicating whether this property is used as a concurrency token. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsConcurrencyTokenConfigurationSource(); + + /// + /// Returns a value indicating whether the property was created implicitly and isn't based on the CLR model. + /// + /// A value indicating whether the property was created implicitly and isn't based on the CLR model. + bool IsImplicitlyCreated() + => (IsShadowProperty() || (DeclaringType.IsPropertyBag && IsIndexerProperty())) + && GetConfigurationSource() == ConfigurationSource.Convention; + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + new IConventionPrimitivePropertyBase? FindFirstPrincipal() + => (IConventionPrimitivePropertyBase?)((IReadOnlyProperty)this).FindFirstPrincipal(); + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + => GetPrincipals(); + + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// + /// The foreign keys that use this property. + /// + new IEnumerable GetContainingForeignKeys(); + + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// + /// The indexes that use this property. + /// + new IEnumerable GetContainingIndexes(); + + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// + /// The primary that use this property, or if it is not part of the primary key. + /// + new IConventionKey? FindContainingPrimaryKey() + => (IConventionKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); + + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// + /// The primary and alternate keys that use this property. + /// + new IEnumerable GetContainingKeys(); + + /// + /// Sets the for the given property + /// + /// The for this property. + /// Indicates whether the configuration was specified using a data annotation. + CoreTypeMapping? SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); + + /// + /// Gets the for of the property. + /// + /// The for of the property. + ConfigurationSource? GetTypeMappingConfigurationSource(); + + /// + /// Sets the maximum length of data that is allowed in this property. For example, if the property is a ' + /// then this is the maximum number of characters. + /// + /// The maximum length of data that is allowed in this property. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured property. + int? SetMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetMaxLengthConfigurationSource(); + + /// + /// Sets the precision of data that is allowed in this property. + /// For example, if the property is a + /// then this is the maximum number of digits. + /// + /// The maximum number of digits that is allowed in this property. + /// Indicates whether the configuration was specified using a data annotation. + int? SetPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetPrecisionConfigurationSource(); + + /// + /// Sets the scale of data that is allowed in this property. + /// For example, if the property is a + /// then this is the maximum number of decimal places. + /// + /// The maximum number of decimal places that is allowed in this property. + /// Indicates whether the configuration was specified using a data annotation. + int? SetScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetScaleConfigurationSource(); + + /// + /// Sets a value indicating whether this property can persist Unicode characters. + /// + /// + /// if the property accepts Unicode characters, if it does not, + /// to clear the setting. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsUnicodeConfigurationSource(); + + /// + /// Sets a value indicating whether this property can be modified before the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + /// + /// A value indicating whether this property can be modified before the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + PropertySaveBehavior? SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetBeforeSaveBehaviorConfigurationSource(); + + /// + /// Sets a value indicating whether this property can be modified after the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + /// + /// Sets a value indicating whether this property can be modified after the entity is + /// saved to the database. to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + PropertySaveBehavior? SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetAfterSaveBehaviorConfigurationSource(); + + /// + /// Sets the sentinel value that indicates that this property is not set. + /// + /// The sentinel value. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + object? SetSentinel(object? sentinel, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetSentinelConfigurationSource(); + + /// + /// Sets the factory to use for generating values for this property, or to clear any previously set factory. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A factory that will be used to create the value generator, or to + /// clear any previously set factory. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Func? SetValueGeneratorFactory( + Func? valueGeneratorFactory, + bool fromDataAnnotation = false); + + /// + /// Sets the factory to use for generating values for this property, or to clear any previously set factory. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A factory that will be used to create the value generator, or to + /// clear any previously set factory. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueGeneratorFactoryConfigurationSource(); + + /// + /// Sets the custom for this property. + /// + /// The converter, or to remove any previously set converter. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueConverter? SetValueConverter(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Sets the custom for this property. + /// + /// + /// A type that inherits from , or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueConverterConfigurationSource(); + + /// + /// Sets the type that the property value will be converted to before being sent to the database provider. + /// + /// The type to use, or to remove any previously set type. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetProviderClrType(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetProviderClrTypeConfigurationSource(); + + /// + /// Sets the custom for this property. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueComparer? SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Sets the custom for this property. + /// + /// + /// A type that inherits from , or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueComparerConfigurationSource(); + + /// + /// Sets the custom to use for the provider values for this property. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueComparer? SetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Sets the custom to use for the provider values for this property. + /// + /// + /// A type that inherits from , or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? SetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetProviderValueComparerConfigurationSource(); + + /// + /// Sets the type of to use for this property. + /// + /// + /// A type that inherits from , or to use the reader/writer + /// from the type mapping. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetJsonValueReaderWriterType(Type? readerWriterType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource(); +} diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index 9cd79384d95..0f15b3a279a 100644 --- a/src/EFCore/Metadata/IConventionProperty.cs +++ b/src/EFCore/Metadata/IConventionProperty.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// See Model building conventions for more information and examples. /// /// -public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBase +public interface IConventionProperty : IReadOnlyProperty, IConventionPrimitivePropertyBase { /// /// Gets the builder that can be used to configure this property. @@ -31,93 +31,6 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas /// new IConventionEntityType DeclaringEntityType { get; } - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetTypeConfigurationSource(); - - /// - /// Sets a value indicating whether this property can contain . - /// - /// - /// A value indicating whether this property can contain . - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - bool? SetIsNullable(bool? nullable, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetIsNullableConfigurationSource(); - - /// - /// Sets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See and - /// for more information and examples. - /// - /// - /// A value indicating when a value for this property will be generated by the database. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ValueGenerated? SetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetValueGeneratedConfigurationSource(); - - /// - /// Sets a value indicating whether this property is used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - /// - /// Sets a value indicating whether this property is used as a concurrency token. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - bool? SetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetIsConcurrencyTokenConfigurationSource(); - - /// - /// Sets the sentinel value that indicates that this property is not set. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - object? SetSentinel(object? sentinel, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetSentinelConfigurationSource(); - - /// - /// Returns a value indicating whether the property was created implicitly and isn't based on the CLR model. - /// - /// A value indicating whether the property was created implicitly and isn't based on the CLR model. - bool IsImplicitlyCreated() - => (IsShadowProperty() || (DeclaringEntityType.IsPropertyBag && IsIndexerProperty())) - && GetConfigurationSource() == ConfigurationSource.Convention; - /// /// Finds the first principal property that the given property is constrained by /// if the given property is part of a foreign key. @@ -132,329 +45,5 @@ bool IsImplicitlyCreated() /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// - /// The foreign keys that use this property. - /// - new IEnumerable GetContainingForeignKeys(); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// - /// The indexes that use this property. - /// - new IEnumerable GetContainingIndexes(); - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// - /// The primary that use this property, or if it is not part of the primary key. - /// - new IConventionKey? FindContainingPrimaryKey() - => (IConventionKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); - - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// - /// The primary and alternate keys that use this property. - /// - new IEnumerable GetContainingKeys(); - - /// - /// Sets the for the given property - /// - /// The for this property. - /// Indicates whether the configuration was specified using a data annotation. - CoreTypeMapping? SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); - - /// - /// Gets the for of the property. - /// - /// The for of the property. - ConfigurationSource? GetTypeMappingConfigurationSource(); - - /// - /// Sets the maximum length of data that is allowed in this property. For example, if the property is a ' - /// then this is the maximum number of characters. - /// - /// The maximum length of data that is allowed in this property. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured property. - int? SetMaxLength(int? maxLength, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetMaxLengthConfigurationSource(); - - /// - /// Sets the precision of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of digits. - /// - /// The maximum number of digits that is allowed in this property. - /// Indicates whether the configuration was specified using a data annotation. - int? SetPrecision(int? precision, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetPrecisionConfigurationSource(); - - /// - /// Sets the scale of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of decimal places. - /// - /// The maximum number of decimal places that is allowed in this property. - /// Indicates whether the configuration was specified using a data annotation. - int? SetScale(int? scale, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetScaleConfigurationSource(); - - /// - /// Sets a value indicating whether this property can persist Unicode characters. - /// - /// - /// if the property accepts Unicode characters, if it does not, - /// to clear the setting. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - bool? SetIsUnicode(bool? unicode, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetIsUnicodeConfigurationSource(); - - /// - /// Sets a value indicating whether this property can be modified before the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a value is assigned to this property when it is in - /// the state. - /// - /// - /// If , then any value - /// set will be ignored when it is in the state. - /// - /// - /// - /// A value indicating whether this property can be modified before the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - PropertySaveBehavior? SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetBeforeSaveBehaviorConfigurationSource(); - - /// - /// Sets a value indicating whether this property can be modified after the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a new value is assigned to this property after the entity exists in the database. - /// - /// - /// If , then any modification to the - /// property value of an entity that already exists in the database will be ignored. - /// - /// - /// - /// Sets a value indicating whether this property can be modified after the entity is - /// saved to the database. to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - PropertySaveBehavior? SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetAfterSaveBehaviorConfigurationSource(); - - /// - /// Sets the factory to use for generating values for this property, or to clear any previously set factory. - /// - /// - /// Setting does not disable value generation for this property, it just clears any generator explicitly - /// configured for this property. The database provider may still have a value generator for the property type. - /// - /// - /// A factory that will be used to create the value generator, or to - /// clear any previously set factory. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - Func? SetValueGeneratorFactory( - Func? valueGeneratorFactory, - bool fromDataAnnotation = false); - - /// - /// Sets the factory to use for generating values for this property, or to clear any previously set factory. - /// - /// - /// Setting does not disable value generation for this property, it just clears any generator explicitly - /// configured for this property. The database provider may still have a value generator for the property type. - /// - /// - /// A factory that will be used to create the value generator, or to - /// clear any previously set factory. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - Type? SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory, - bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetValueGeneratorFactoryConfigurationSource(); - - /// - /// Sets the custom for this property. - /// - /// The converter, or to remove any previously set converter. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ValueConverter? SetValueConverter(ValueConverter? converter, bool fromDataAnnotation = false); - - /// - /// Sets the custom for this property. - /// - /// - /// A type that inherits from , or to remove any previously set converter. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - Type? SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, - bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetValueConverterConfigurationSource(); - - /// - /// Sets the type that the property value will be converted to before being sent to the database provider. - /// - /// The type to use, or to remove any previously set type. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - Type? SetProviderClrType(Type? providerClrType, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetProviderClrTypeConfigurationSource(); - - /// - /// Sets the custom for this property. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ValueComparer? SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Sets the custom for this property. - /// - /// - /// A type that inherits from , or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetValueComparerConfigurationSource(); - - /// - /// Sets the custom to use for the provider values for this property. - /// - /// The comparer, or to remove any previously set comparer. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ValueComparer? SetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); - - /// - /// Sets the custom to use for the provider values for this property. - /// - /// - /// A type that inherits from , or to remove any previously set comparer. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetProviderValueComparerConfigurationSource(); - - /// - /// Sets the type of to use for this property. - /// - /// - /// A type that inherits from , or to use the reader/writer - /// from the type mapping. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - Type? SetJsonValueReaderWriterType(Type? readerWriterType, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource(); + => GetPrincipals(); } diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 06f982987ea..67f73d598ac 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -24,6 +24,12 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable /// new IConventionModel Model { get; } + /// + /// Gets the builder that can be used to configure this type. + /// + /// If the type has been removed from the model. + new IConventionTypeBaseBuilder Builder { get; } + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 31ec35acb13..33cffa7b69b 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -154,7 +154,7 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// This method does not return keys declared on base types. /// It is useful when iterating over all entity types to avoid processing the same key more than once. - /// Use to also return keys declared on base types. + /// Use to also return keys declared on base types. /// /// Declared keys. new IEnumerable GetDeclaredKeys(); @@ -476,7 +476,8 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// The property name. /// The property, or if none is found. - new IProperty? FindDeclaredProperty(string name); + new IProperty? FindDeclaredProperty(string name) + => (IProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); /// /// Gets all non-navigation properties declared on the given . @@ -524,6 +525,54 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// The properties that need a value to be generated on add. IEnumerable GetValueGeneratingProperties(); + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IComplexProperty? FindDeclaredComplexProperty(string name) + => (IComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets the complex properties defined on this entity type. + /// + /// The complex properties defined on this entity type. + new IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this entity type. + /// + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this entity type. + /// + /// + /// This method does not return complex properties declared on the given entity type itself. + /// Use to return complex properties declared on this + /// and base entity typed types. + /// + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); + /// /// Gets the service property with a given name. /// Returns if no property with the given name is defined. diff --git a/src/EFCore/Metadata/IMutableComplexProperty.cs b/src/EFCore/Metadata/IMutableComplexProperty.cs new file mode 100644 index 00000000000..46822c630dc --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexProperty.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableComplexProperty : IReadOnlyComplexProperty, IMutablePropertyBase +{ + /// + /// Gets the associated complex type. + /// + new IMutableComplexType ComplexType { get; } + + /// + /// Gets or sets a value indicating whether this property can contain . + /// + new bool IsNullable { get; set; } + + /// + bool IReadOnlyComplexProperty.IsNullable => + IsNullable; +} diff --git a/src/EFCore/Metadata/IMutableComplexType.cs b/src/EFCore/Metadata/IMutableComplexType.cs new file mode 100644 index 00000000000..1635c201622 --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexType.cs @@ -0,0 +1,264 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableComplexType : IReadOnlyComplexType, IMutableTypeBase +{ + /// + /// Gets the associated property. + /// + new IMutableComplexProperty ComplexProperty { get; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + new IMutableEntityType FundametalEntityType { get; } + + /// + /// Sets the change tracking strategy to use for this entity type. This strategy indicates how the + /// context detects changes to properties for an instance of the entity type. + /// + /// The strategy to use. + void SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy); + + /// + /// Adds a property to this complex type. + /// + /// The corresponding member on the entity class. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IMutableComplexTypeProperty AddProperty(MemberInfo memberInfo) + => AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The newly created property. + IMutableComplexTypeProperty AddProperty(string name); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableComplexTypeProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The newly created property. + IMutableComplexTypeProperty AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo); + + /// + /// Adds a property backed up by an indexer to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableComplexTypeProperty AddIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddProperty(name, propertyType, indexerPropertyInfo); + } + + /// + /// Gets a property on the given complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IMutableComplexTypeProperty? FindProperty(MemberInfo memberInfo) + => (IMutableComplexTypeProperty?)((IReadOnlyComplexType)this).FindProperty(memberInfo); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IMutableComplexTypeProperty? FindProperty(string name); + + /// + /// Finds matching properties on this complex type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) + => (IReadOnlyList?)((IReadOnlyComplexType)this).FindProperties(propertyNames); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetProperties(); + + /// + /// Removes a property from this complex type. + /// + /// The name of the property to remove. + /// The removed property, or if the property was not found. + IMutableComplexTypeProperty? RemoveProperty(string name); + + /// + /// Removes a property from this complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableComplexTypeProperty? RemoveProperty(IReadOnlyComplexTypeProperty property); + + /// + /// Adds a complex property to this complex type. + /// + /// The corresponding member on the complex type. + /// Indicates whether the property represents a collection. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, bool collection = false) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo, + collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), collection); + + /// + /// Adds a complex property to this complex type. + /// + /// The name of the property to add. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty(string name, bool collection = false); + + /// + /// Adds a complex property to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); + + /// + /// Adds a complex property to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); + + /// + /// Adds a complex property backed up by an indexer to this complex type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, collection); + } + + /// + /// Gets a complex property on the given complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IMutableComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(string name); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetComplexProperties(); + + /// + /// Removes a property from this complex type. + /// + /// The name of the property to remove. + /// The removed property, or if the property was not found. + IMutableComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableComplexProperty? RemoveComplexProperty(IReadOnlyProperty property); +} diff --git a/src/EFCore/Metadata/IMutableComplexTypeProperty.cs b/src/EFCore/Metadata/IMutableComplexTypeProperty.cs new file mode 100644 index 00000000000..fb988660c45 --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexTypeProperty.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a complex type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableComplexTypeProperty : IReadOnlyComplexTypeProperty, IMutablePrimitivePropertyBase +{ + /// + /// Gets the complex type that this property belongs to. + /// + new IMutableComplexType DeclaringComplexType { get; } +} diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index a1823a5b51f..5e9edcc38b5 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -18,11 +18,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase { - /// - /// Gets the model this entity belongs to. - /// - new IMutableModel Model { get; } - /// /// Gets or sets the base type of this entity type. Returns if this is not a derived type in an inheritance /// hierarchy. @@ -505,6 +500,40 @@ IMutableSkipNavigation AddSkipNavigation( MemberInfo? memberInfo, IMutableEntityType targetEntityType, bool collection, + bool onDependent) + => AddSkipNavigation( + name, + navigationType: null, + memberInfo, + targetEntityType, + collection, + onDependent); + + /// + /// Adds a new skip navigation property to this entity type. + /// + /// The name of the skip navigation property to add. + /// The navigation type. + /// + /// + /// The corresponding CLR type member or for a shadow navigation. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The entity type that the skip navigation property will hold an instance(s) of. + /// Whether the navigation property is a collection property. + /// + /// Whether the navigation property is defined on the dependent side of the underlying foreign key. + /// + /// The newly created skip navigation property. + IMutableSkipNavigation AddSkipNavigation( + string name, + Type? navigationType, + MemberInfo? memberInfo, + IMutableEntityType targetEntityType, + bool collection, bool onDependent); /// @@ -664,6 +693,30 @@ IMutableIndex AddIndex(IMutableProperty property, string name) /// The removed index, or if the index was not found. IMutableIndex? RemoveIndex(IReadOnlyIndex index); + /// + /// Adds a property to this entity type. + /// + /// The corresponding member on the entity class. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IMutableProperty AddProperty(MemberInfo memberInfo) + => AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The newly created property. + IMutableProperty AddProperty(string name); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); + /// /// Adds a property to this entity type. /// @@ -671,7 +724,7 @@ IMutableIndex AddIndex(IMutableProperty property, string name) /// The type of value the property will hold. /// /// - /// The corresponding CLR type member or for a shadow property. + /// The corresponding CLR type member. /// /// /// An indexer with a parameter and return type can be used. @@ -681,7 +734,27 @@ IMutableIndex AddIndex(IMutableProperty property, string name) IMutableProperty AddProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo); + MemberInfo memberInfo); + + /// + /// Adds a property backed up by an indexer to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableProperty AddIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddProperty(name, propertyType, indexerPropertyInfo); + } /// /// Gets a property on the given entity type. Returns if no property is found. @@ -708,7 +781,7 @@ IMutableProperty AddProperty( new IMutableProperty? FindProperty(string name); /// - /// Finds matching properties on the given entity type. Returns if any property is not found. + /// Finds matching properties on this entity type. Returns if any property is not found. /// /// /// This API only finds scalar properties and does not find navigation or service properties. @@ -740,38 +813,122 @@ IMutableProperty AddProperty( => (IMutableProperty)((IReadOnlyEntityType)this).GetProperty(name); /// - /// Adds a property to this entity type. + /// Gets all scalar properties declared on this entity type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared scalar properties. + new IEnumerable GetDeclaredProperties(); + + /// + /// Gets all scalar properties declared on the types derived from this entity type. + /// + /// + /// This method does not return properties declared on the given entity type itself. + /// Use to return properties declared on this + /// and base entity typed types. + /// + /// Derived scalar properties. + new IEnumerable GetDerivedProperties() + => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); + + /// + /// Gets all scalar properties defined on this entity type. + /// + /// + /// This API only returns scalar properties and does not return navigation properties. Use + /// to get navigation + /// properties. + /// + /// The properties defined on this entity type. + new IEnumerable GetProperties(); + + /// + /// Removes a property from this entity type. + /// + /// The name of the property to remove. + /// The removed property, or if the property was not found. + IMutableProperty? RemoveProperty(string name); + + /// + /// Removes a property from this entity type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableProperty? RemoveProperty(IReadOnlyProperty property); + + /// + /// Adds a complex property to this entity type. /// /// The corresponding member on the entity class. + /// Indicates whether the property represents a collection. /// The newly created property. [RequiresUnreferencedCode("Currently used only in tests")] - IMutableProperty AddProperty(MemberInfo memberInfo) - => AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); + IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, bool collection = false) + => AddComplexProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), collection); /// - /// Adds a property to this entity type. + /// Adds a complex property to this entity type. /// /// The name of the property to add. + /// Indicates whether the property represents a collection. /// The newly created property. - IMutableProperty AddProperty(string name); + IMutableComplexProperty AddComplexProperty(string name, bool collection = false); /// - /// Adds a property to this entity type. + /// Adds a complex property to this entity type. /// /// The name of the property to add. - /// The type of value the property will hold. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. /// The newly created property. - IMutableProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); + IMutableComplexProperty AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); /// - /// Adds a property backed up by an indexer to this entity type. + /// Adds a complex property to this entity type. /// /// The name of the property to add. - /// The type of value the property will hold. + /// The property type. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. /// The newly created property. - IMutableProperty AddIndexerProperty( + IMutableComplexProperty AddComplexProperty( string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); + + /// + /// Adds a complex property backed up by an indexer to this entity type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false) { var indexerPropertyInfo = FindIndexerPropertyInfo(); if (indexerPropertyInfo == null) @@ -780,32 +937,64 @@ IMutableProperty AddIndexerProperty( CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); } - return AddProperty(name, propertyType, indexerPropertyInfo); + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, collection); } /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets a complex property on the given entity type. Returns if no property is found. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. Use + /// to find a navigation property. + /// + /// The property on the entity class. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. Use + /// to find + /// a navigation property. + /// + /// The name of the property. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(string name); + + /// + /// Finds a complex property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IMutableComplexProperty? FindDeclaredComplexProperty(string name) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets all complex properties declared on this entity type. /// /// /// This method does not return properties declared on base types. /// It is useful when iterating over all entity types to avoid processing the same property more than once. /// Use to also return properties declared on base types. /// - /// Declared non-navigation properties. - new IEnumerable GetDeclaredProperties() - => ((IReadOnlyEntityType)this).GetDeclaredProperties().Cast(); + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets all complex properties declared on the types derived from this entity type. /// /// /// This method does not return properties declared on the given entity type itself. /// Use to return properties declared on this /// and base entity typed types. /// - /// Derived non-navigation properties. - new IEnumerable GetDerivedProperties() - => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); /// /// Gets the properties defined on this entity type. @@ -816,21 +1005,21 @@ IMutableProperty AddIndexerProperty( /// properties. /// /// The properties defined on this entity type. - new IEnumerable GetProperties(); + new IEnumerable GetComplexProperties(); /// /// Removes a property from this entity type. /// /// The name of the property to remove. /// The removed property, or if the property was not found. - IMutableProperty? RemoveProperty(string name); + IMutableComplexProperty? RemoveComplexProperty(string name); /// /// Removes a property from this entity type. /// /// The property to remove. /// The removed property, or if the property was not found. - IMutableProperty? RemoveProperty(IReadOnlyProperty property); + IMutableComplexProperty? RemoveComplexProperty(IReadOnlyProperty property); /// /// Adds a service property to this entity type. diff --git a/src/EFCore/Metadata/IMutablePrimitivePropertyBase.cs b/src/EFCore/Metadata/IMutablePrimitivePropertyBase.cs new file mode 100644 index 00000000000..b300d2e8b05 --- /dev/null +++ b/src/EFCore/Metadata/IMutablePrimitivePropertyBase.cs @@ -0,0 +1,278 @@ +// 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.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutablePrimitivePropertyBase : IReadOnlyPrimitivePropertyBase, IMutablePropertyBase +{ + /// + /// Gets or sets a value indicating whether this property can contain . + /// + new bool IsNullable { get; set; } + + /// + /// Gets or sets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See + /// and for more information and examples. + /// + new ValueGenerated ValueGenerated { get; set; } + + /// + /// Gets or sets a value indicating whether this property is used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + new bool IsConcurrencyToken { get; set; } + + /// + /// Gets or sets the sentinel value that indicates that this property is not set. + /// + new object? Sentinel { get; set; } + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + new IMutablePrimitivePropertyBase? FindFirstPrincipal() + => (IMutablePrimitivePropertyBase?)((IReadOnlyProperty)this).FindFirstPrincipal(); + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + => GetPrincipals(); + + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// + /// The foreign keys that use this property. + /// + new IEnumerable GetContainingForeignKeys(); + + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// + /// The indexes that use this property. + /// + new IEnumerable GetContainingIndexes(); + + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// + /// The primary that use this property, or if it is not part of the primary key. + /// + new IMutableKey? FindContainingPrimaryKey() + => (IMutableKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); + + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// + /// The primary and alternate keys that use this property. + /// + new IEnumerable GetContainingKeys(); + + /// + /// Sets the maximum length of data that is allowed in this property. For example, if the property is a + /// then this is the maximum number of characters. + /// + /// The maximum length of data that is allowed in this property. + void SetMaxLength(int? maxLength); + + /// + /// Sets the precision of data that is allowed in this property. + /// For example, if the property is a + /// then this is the maximum number of digits. + /// + /// The maximum number of digits that is allowed in this property. + void SetPrecision(int? precision); + + /// + /// Sets the scale of data that is allowed in this property. + /// For example, if the property is a + /// then this is the maximum number of decimal places. + /// + /// The maximum number of decimal places that is allowed in this property. + void SetScale(int? scale); + + /// + /// Sets a value indicating whether this property can persist Unicode characters. + /// + /// + /// if the property accepts Unicode characters, if it does not, + /// to clear the setting. + /// + void SetIsUnicode(bool? unicode); + + /// + /// Gets or sets a value indicating whether this property can be modified before the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + /// + /// A value indicating whether this property can be modified before the entity is saved to the database. + /// + void SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior); + + /// + /// Gets or sets a value indicating whether this property can be modified after the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + /// + /// A value indicating whether this property can be modified after the entity is saved to the database. + /// + void SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior); + + /// + /// Sets the factory to use for generating values for this property, or to clear any previously set factory. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A factory that will be used to create the value generator, or to + /// clear any previously set factory. + /// + void SetValueGeneratorFactory(Func? valueGeneratorFactory); + + /// + /// Sets the factory to use for generating values for this property, or to clear any previously set factory. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A factory that will be used to create the value generator, or to + /// clear any previously set factory. + /// + void SetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory); + + /// + /// Sets the custom for this property. + /// + /// The converter, or to remove any previously set converter. + void SetValueConverter(ValueConverter? converter); + + /// + /// Sets the custom for this property. + /// + /// + /// A type that derives from , or to remove any previously set converter. + /// + void SetValueConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType); + + /// + /// Sets the type that the property value will be converted to before being sent to the database provider. + /// + /// The type to use, or to remove any previously set type. + void SetProviderClrType(Type? providerClrType); + + /// + /// Sets the for the given property + /// + /// The for this property. + void SetTypeMapping(CoreTypeMapping typeMapping); + + /// + /// Sets the custom for this property. + /// + /// The comparer, or to remove any previously set comparer. + void SetValueComparer(ValueComparer? comparer); + + /// + /// Sets the custom for this property. + /// + /// + /// A type that derives from , or to remove any previously set comparer. + /// + void SetValueComparer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); + + /// + /// Sets the custom to use for the provider values for this property. + /// + /// The comparer, or to remove any previously set comparer. + void SetProviderValueComparer(ValueComparer? comparer); + + /// + /// Sets the custom to use for the provider values for this property. + /// + /// + /// A type that derives from , or to remove any previously set comparer. + /// + void SetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); + + /// + /// Sets the type of to use for this property for this property. + /// + /// + /// A type that derives from , or to use the reader/writer + /// from the type mapping. + /// + void SetJsonValueReaderWriterType(Type? readerWriterType); + + /// + bool IReadOnlyPrimitivePropertyBase.IsNullable => + IsNullable; + + /// + ValueGenerated IReadOnlyPrimitivePropertyBase.ValueGenerated => + ValueGenerated; + + /// + bool IReadOnlyPrimitivePropertyBase.IsConcurrencyToken => + IsConcurrencyToken; +} diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index 936c2616a91..6f1b264e222 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -19,41 +19,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// examples. /// /// -public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase +public interface IMutableProperty : IReadOnlyProperty, IMutablePrimitivePropertyBase { /// /// Gets the type that this property belongs to. /// new IMutableEntityType DeclaringEntityType { get; } - /// - /// Gets or sets a value indicating whether this property can contain . - /// - new bool IsNullable { get; set; } - - /// - /// Gets or sets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See - /// and for more information and examples. - /// - new ValueGenerated ValueGenerated { get; set; } - - /// - /// Gets or sets a value indicating whether this property is used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - new bool IsConcurrencyToken { get; set; } - - /// - /// Gets or sets the sentinel value that indicates that this property is not set. - /// - new object? Sentinel { get; set; } - /// /// Finds the first principal property that the given property is constrained by /// if the given property is part of a foreign key. @@ -68,205 +40,5 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// - /// The foreign keys that use this property. - /// - new IEnumerable GetContainingForeignKeys(); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// - /// The indexes that use this property. - /// - new IEnumerable GetContainingIndexes(); - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// - /// The primary that use this property, or if it is not part of the primary key. - /// - new IMutableKey? FindContainingPrimaryKey() - => (IMutableKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); - - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// - /// The primary and alternate keys that use this property. - /// - new IEnumerable GetContainingKeys(); - - /// - /// Sets the maximum length of data that is allowed in this property. For example, if the property is a - /// then this is the maximum number of characters. - /// - /// The maximum length of data that is allowed in this property. - void SetMaxLength(int? maxLength); - - /// - /// Sets the precision of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of digits. - /// - /// The maximum number of digits that is allowed in this property. - void SetPrecision(int? precision); - - /// - /// Sets the scale of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of decimal places. - /// - /// The maximum number of decimal places that is allowed in this property. - void SetScale(int? scale); - - /// - /// Sets a value indicating whether this property can persist Unicode characters. - /// - /// - /// if the property accepts Unicode characters, if it does not, - /// to clear the setting. - /// - void SetIsUnicode(bool? unicode); - - /// - /// Gets or sets a value indicating whether this property can be modified before the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a value is assigned to this property when it is in - /// the state. - /// - /// - /// If , then any value - /// set will be ignored when it is in the state. - /// - /// - /// - /// A value indicating whether this property can be modified before the entity is saved to the database. - /// - void SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior); - - /// - /// Gets or sets a value indicating whether this property can be modified after the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a new value is assigned to this property after the entity exists in the database. - /// - /// - /// If , then any modification to the - /// property value of an entity that already exists in the database will be ignored. - /// - /// - /// - /// A value indicating whether this property can be modified after the entity is saved to the database. - /// - void SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior); - - /// - /// Sets the factory to use for generating values for this property, or to clear any previously set factory. - /// - /// - /// Setting does not disable value generation for this property, it just clears any generator explicitly - /// configured for this property. The database provider may still have a value generator for the property type. - /// - /// - /// A factory that will be used to create the value generator, or to - /// clear any previously set factory. - /// - void SetValueGeneratorFactory(Func? valueGeneratorFactory); - - /// - /// Sets the factory to use for generating values for this property, or to clear any previously set factory. - /// - /// - /// Setting does not disable value generation for this property, it just clears any generator explicitly - /// configured for this property. The database provider may still have a value generator for the property type. - /// - /// - /// A factory that will be used to create the value generator, or to - /// clear any previously set factory. - /// - void SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory); - - /// - /// Sets the custom for this property. - /// - /// The converter, or to remove any previously set converter. - void SetValueConverter(ValueConverter? converter); - - /// - /// Sets the custom for this property. - /// - /// - /// A type that derives from , or to remove any previously set converter. - /// - void SetValueConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType); - - /// - /// Sets the type that the property value will be converted to before being sent to the database provider. - /// - /// The type to use, or to remove any previously set type. - void SetProviderClrType(Type? providerClrType); - - /// - /// Sets the for the given property - /// - /// The for this property. - void SetTypeMapping(CoreTypeMapping typeMapping); - - /// - /// Sets the custom for this property. - /// - /// The comparer, or to remove any previously set comparer. - void SetValueComparer(ValueComparer? comparer); - - /// - /// Sets the custom for this property. - /// - /// - /// A type that derives from , or to remove any previously set comparer. - /// - void SetValueComparer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); - - /// - /// Sets the custom to use for the provider values for this property. - /// - /// The comparer, or to remove any previously set comparer. - void SetProviderValueComparer(ValueComparer? comparer); - - /// - /// Sets the custom to use for the provider values for this property. - /// - /// - /// A type that derives from , or to remove any previously set comparer. - /// - void SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); - - /// - /// Sets the type of to use for this property for this property. - /// - /// - /// A type that derives from , or to use the reader/writer - /// from the type mapping. - /// - void SetJsonValueReaderWriterType(Type? readerWriterType); + => GetPrincipals(); } diff --git a/src/EFCore/Metadata/IPrimitivePropertyBase.cs b/src/EFCore/Metadata/IPrimitivePropertyBase.cs new file mode 100644 index 00000000000..61f73dc54e1 --- /dev/null +++ b/src/EFCore/Metadata/IPrimitivePropertyBase.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IPrimitivePropertyBase : IReadOnlyPrimitivePropertyBase, IPropertyBase +{ + /// + /// Creates an for values of the given property type. + /// + /// The property type. + /// A new equality comparer. + IEqualityComparer CreateKeyEqualityComparer() + => NullableComparerAdapter.Wrap(GetKeyValueComparer()); + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + new IPrimitivePropertyBase? FindFirstPrincipal() + => (IPrimitivePropertyBase?)((IReadOnlyProperty)this).FindFirstPrincipal(); + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + => GetPrincipals(); + + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// + /// The foreign keys that use this property. + /// + new IEnumerable GetContainingForeignKeys(); + + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// + /// The indexes that use this property. + /// + new IEnumerable GetContainingIndexes(); + + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// + /// The primary that use this property, or if it is not part of the primary key. + /// + new IKey? FindContainingPrimaryKey() + => (IKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); + + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// + /// The primary and alternate keys that use this property. + /// + new IEnumerable GetContainingKeys(); + + /// + /// Gets the for this property. + /// + /// The comparer. + new ValueComparer GetValueComparer(); + + /// + /// Gets the to use with keys for this property. + /// + /// The comparer. + new ValueComparer GetKeyValueComparer(); + + /// + /// Gets the to use for the provider values for this property. + /// + /// The comparer. + new ValueComparer GetProviderValueComparer(); +} diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 40ee5b4c195..f2925cde6cb 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -1,9 +1,6 @@ // 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.Internal; - namespace Microsoft.EntityFrameworkCore.Metadata; /// @@ -12,21 +9,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public interface IProperty : IReadOnlyProperty, IPropertyBase +public interface IProperty : IReadOnlyProperty, IPrimitivePropertyBase { /// /// Gets the type that this property belongs to. /// new IEntityType DeclaringEntityType { get; } - /// - /// Creates an for values of the given property type. - /// - /// The property type. - /// A new equality comparer. - IEqualityComparer CreateKeyEqualityComparer() - => NullableComparerAdapter.Wrap(GetKeyValueComparer()); - /// /// Finds the first principal property that the given property is constrained by /// if the given property is part of a foreign key. @@ -41,64 +30,9 @@ IEqualityComparer CreateKeyEqualityComparer() /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// - /// The foreign keys that use this property. - /// - new IEnumerable GetContainingForeignKeys(); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// - /// The indexes that use this property. - /// - new IEnumerable GetContainingIndexes(); - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// - /// The primary that use this property, or if it is not part of the primary key. - /// - new IKey? FindContainingPrimaryKey() - => (IKey?)((IReadOnlyProperty)this).FindContainingPrimaryKey(); - - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// - /// The primary and alternate keys that use this property. - /// - new IEnumerable GetContainingKeys(); - - /// - /// Gets the for this property. - /// - /// The comparer. - new ValueComparer GetValueComparer(); - - /// - /// Gets the to use with keys for this property. - /// - /// The comparer. - new ValueComparer GetKeyValueComparer(); - - /// - /// Gets the to use for the provider values for this property. - /// - /// The comparer. - new ValueComparer GetProviderValueComparer(); + => GetPrincipals(); - internal const DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = + internal const System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties diff --git a/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs b/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs index d97fd1c3ad8..b00eccc4cd8 100644 --- a/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs +++ b/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs @@ -31,4 +31,16 @@ public interface IPropertyParameterBindingFactory IEntityType entityType, Type parameterType, string parameterName); + + /// + /// Finds a specifically for an in the model. + /// + /// The complex type on which the is defined. + /// The parameter name. + /// The parameter type. + /// The parameter binding, or if none was found. + ParameterBinding? FindParameter( + IComplexType complexType, + Type parameterType, + string parameterName); } diff --git a/src/EFCore/Metadata/IReadOnlyComplexProperty.cs b/src/EFCore/Metadata/IReadOnlyComplexProperty.cs new file mode 100644 index 00000000000..63684be49bb --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexProperty.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.Text; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyComplexProperty : IReadOnlyPropertyBase +{ + /// + /// Gets the associated complex type. + /// + IReadOnlyComplexType ComplexType { get; } + + /// + /// Gets a value indicating whether this property can contain . + /// + bool IsNullable { get; } + + /// + /// Gets a value indicating whether this property represents a collection. + /// + bool IsCollection { get; } + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"ComplexProperty: {DeclaringType.DisplayName()}."); + } + + builder.Append(Name).Append(" ("); + + var field = GetFieldName(); + if (field == null) + { + builder.Append("no field, "); + } + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + builder.Append(field).Append(", "); + } + + builder.Append(ClrType.ShortDisplayName()).Append(')'); + + if (IsShadowProperty()) + { + builder.Append(" Shadow"); + } + + if (IsIndexerProperty()) + { + builder.Append(" Indexer"); + } + + if (!IsNullable) + { + builder.Append(" Required"); + } + + if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) + { + builder.Append(" Sentinel:").Append(Sentinel); + } + + if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) + { + builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); + } + + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 + && ((AnnotatableBase)this).IsReadOnly) + { + var indexes = ((IProperty)this).GetPropertyIndexes(); + builder.Append(' ').Append(indexes.Index); + builder.Append(' ').Append(indexes.OriginalValueIndex); + builder.Append(' ').Append(indexes.RelationshipIndex); + builder.Append(' ').Append(indexes.ShadowIndex); + builder.Append(' ').Append(indexes.StoreGenerationIndex); + } + + if (!singleLine) + { + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + + builder.AppendLine().Append(indentString).Append(ComplexType.ToDebugString(options, indent + 1)); + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore/Metadata/IReadOnlyComplexType.cs b/src/EFCore/Metadata/IReadOnlyComplexType.cs new file mode 100644 index 00000000000..ed280e76b22 --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexType.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.AccessControl; +using System.Text; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyComplexType : IReadOnlyTypeBase +{ + /// + /// Gets the associated property. + /// + IReadOnlyComplexProperty ComplexProperty { get; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + IReadOnlyEntityType FundametalEntityType { get; } + + /// + /// Gets the change tracking strategy being used for this entity type. This strategy indicates how the + /// context detects changes to properties for an instance of the entity type. + /// + /// The change tracking strategy. + ChangeTrackingStrategy GetChangeTrackingStrategy(); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + IReadOnlyComplexTypeProperty? FindProperty(string name); + + /// + /// Gets a property with the given member info. Returns if no property is found. + /// + /// The member on the complex class. + /// The property, or if none is found. + IReadOnlyComplexTypeProperty? FindProperty(MemberInfo memberInfo) + => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true + ? null + : FindProperty(memberInfo.GetSimpleMemberName()); + + /// + /// Finds matching properties on the given complex type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. + /// + /// The property names. + /// The properties, or if any property is not found. + IReadOnlyList? FindProperties(IReadOnlyList propertyNames); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + IEnumerable GetProperties(); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the complex class. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true + ? null + : FindComplexProperty(memberInfo.GetSimpleMemberName()); + + /// + /// Gets the complex properties defined on this complex type. + /// + /// The complex properties defined on this complex type. + IEnumerable GetComplexProperties(); + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder + .Append(indentString) + .Append("ComplexType: ") + .Append(DisplayName()); + + if (HasSharedClrType) + { + builder.Append(" CLR Type: ").Append(ClrType.ShortDisplayName()); + } + + if (IsAbstract()) + { + builder.Append(" Abstract"); + } + + if (this is EntityType + && GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot) + { + builder.Append(" ChangeTrackingStrategy.").Append(GetChangeTrackingStrategy()); + } + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + var properties = GetProperties().ToList(); + if (properties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Properties: "); + foreach (var property in properties) + { + builder.AppendLine().Append(property.ToDebugString(options, indent + 4)); + } + } + + var complexProperties = GetComplexProperties().ToList(); + if (complexProperties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Complex properties: "); + foreach (var complexProperty in complexProperties) + { + builder.AppendLine().Append(complexProperty.ToDebugString(options, indent + 4)); + } + } + + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent: indent + 2)); + } + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore/Metadata/IReadOnlyComplexTypeProperty.cs b/src/EFCore/Metadata/IReadOnlyComplexTypeProperty.cs new file mode 100644 index 00000000000..5bfc0fc8ea9 --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexTypeProperty.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a complex type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyComplexTypeProperty : IReadOnlyPrimitivePropertyBase +{ + /// + /// Gets the complex type that this property belongs to. + /// + IReadOnlyComplexType DeclaringComplexType { get; } +} diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index aa1f891fd64..9bbb4b73e6b 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -677,29 +677,29 @@ IReadOnlyProperty GetProperty(string name) } /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets all scalar properties declared on this entity type. /// /// /// This method does not return properties declared on base types. /// It is useful when iterating over all entity types to avoid processing the same property more than once. /// Use to also return properties declared on base types. /// - /// Declared non-navigation properties. + /// Declared scalar properties. IEnumerable GetDeclaredProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets all scalar properties declared on the types derived from this entity type. /// /// /// This method does not return properties declared on the given entity type itself. /// Use to return properties declared on this /// and base entity typed types. /// - /// Derived non-navigation properties. + /// Derived scalar properties. IEnumerable GetDerivedProperties(); /// - /// Gets the properties defined on this entity type. + /// Gets all scalar properties defined on this entity type. /// /// /// This API only returns scalar properties and does not return navigation properties. Use @@ -708,6 +708,54 @@ IReadOnlyProperty GetProperty(string name) /// The properties defined on this entity type. IEnumerable GetProperties(); + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the entity class. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true + ? null + : FindComplexProperty(memberInfo.GetSimpleMemberName()); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindDeclaredComplexProperty(string name); + + /// + /// Gets the complex properties defined on this entity type. + /// + /// The complex properties defined on this entity type. + IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this entity type. + /// + /// Declared complex properties. + IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this entity type. + /// + /// + /// This method does not return complex properties declared on the given entity type itself. + /// Use to return complex properties declared on this + /// and base entity typed types. + /// + /// Derived complex properties. + IEnumerable GetDerivedComplexProperties(); + /// /// Gets the service property with a given name. /// Returns if no property with the given name is defined. @@ -855,6 +903,16 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt } } + var complexProperties = GetDeclaredComplexProperties().ToList(); + if (complexProperties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Complex properties: "); + foreach (var complexProperty in complexProperties) + { + builder.AppendLine().Append(complexProperty.ToDebugString(options, indent + 4)); + } + } + var serviceProperties = GetDeclaredServiceProperties().ToList(); if (serviceProperties.Count != 0) { diff --git a/src/EFCore/Metadata/IReadOnlyNavigation.cs b/src/EFCore/Metadata/IReadOnlyNavigation.cs index 3553402dc5e..fb37e1af464 100644 --- a/src/EFCore/Metadata/IReadOnlyNavigation.cs +++ b/src/EFCore/Metadata/IReadOnlyNavigation.cs @@ -41,15 +41,6 @@ public interface IReadOnlyNavigation : IReadOnlyNavigationBase get => IsOnDependent ? ForeignKey.PrincipalToDependent : ForeignKey.DependentToPrincipal; } - /// - /// Gets a value indicating whether the navigation property is a collection property. - /// - new bool IsCollection - { - [DebuggerStepThrough] - get => !IsOnDependent && !ForeignKey.IsUnique; - } - /// /// Gets the foreign key that defines the relationship this navigation property will navigate. /// @@ -97,7 +88,7 @@ IReadOnlyEntityType IReadOnlyNavigationBase.TargetEntityType bool IReadOnlyNavigationBase.IsCollection { [DebuggerStepThrough] - get => IsCollection; + get => !IsOnDependent && !ForeignKey.IsUnique; } /// diff --git a/src/EFCore/Metadata/IReadOnlyPrimitivePropertyBase.cs b/src/EFCore/Metadata/IReadOnlyPrimitivePropertyBase.cs new file mode 100644 index 00000000000..1d93350056a --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyPrimitivePropertyBase.cs @@ -0,0 +1,426 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; +using System.Text; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents a scalar property of a structural type. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + public interface IReadOnlyPrimitivePropertyBase : IReadOnlyPropertyBase + { + /// + /// Gets a value indicating whether this property can contain . + /// + bool IsNullable { get; } + + /// + /// Gets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See + /// and for more information and examples. + /// + ValueGenerated ValueGenerated { get; } + + /// + /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + bool IsConcurrencyToken { get; } + + /// + /// Returns the for the given property from a finalized model. + /// + /// The type mapping. + CoreTypeMapping GetTypeMapping() + { + var mapping = FindTypeMapping(); + if (mapping == null) + { + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); + } + + return mapping; + } + + /// + /// Returns the type mapping for this property. + /// + /// The type mapping, or if none was found. + CoreTypeMapping? FindTypeMapping(); + + /// + /// Gets the maximum length of data that is allowed in this property. For example, if the property is a + /// then this is the maximum number of characters. + /// + /// + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. + /// + int? GetMaxLength(); + + /// + /// Gets the precision of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of digits. + /// + /// The precision, or if none is defined. + int? GetPrecision(); + + /// + /// Gets the scale of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of decimal places. + /// + /// The scale, or if none is defined. + int? GetScale(); + + /// + /// Gets a value indicating whether or not the property can persist Unicode characters. + /// + /// The Unicode setting, or if none is defined. + bool? IsUnicode(); + + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + /// The before save behavior for this property. + PropertySaveBehavior GetBeforeSaveBehavior(); + + /// + /// Gets a value indicating whether or not this property can be modified after the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + /// The after save behavior for this property. + PropertySaveBehavior GetAfterSaveBehavior(); + + /// + /// Gets the factory that has been set to generate values for this property, if any. + /// + /// The factory, or if no factory has been set. + Func? GetValueGeneratorFactory(); + + /// + /// Gets the custom set for this property. + /// + /// The converter, or if none has been set. + ValueConverter? GetValueConverter(); + + /// + /// Gets the type that the property value will be converted to before being sent to the database provider. + /// + /// The provider type, or if none has been set. + Type? GetProviderClrType(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetValueComparer(); + + /// + /// Gets the to use with keys for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetKeyValueComparer(); + + /// + /// Gets the to use for the provider values for this property. + /// + /// The comparer, or if none has been set. + ValueComparer? GetProviderValueComparer(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The reader/writer, or if none has been set. + JsonValueReaderWriter? GetJsonValueReaderWriter(); + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + IReadOnlyPrimitivePropertyBase? FindFirstPrincipal() + { + foreach (var foreignKey in GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (this == foreignKey.Properties[propertyIndex]) + { + return foreignKey.PrincipalKey.Properties[propertyIndex]; + } + } + } + + return null; + } + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + where T : IReadOnlyPrimitivePropertyBase + { + var principals = new List { (T)this }; + AddPrincipals((T)this, principals); + return principals; + } + + private static void AddPrincipals(T property, List visited) + where T : IReadOnlyPrimitivePropertyBase + { + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (ReferenceEquals(property, foreignKey.Properties[propertyIndex])) + { + var principal = (T)foreignKey.PrincipalKey.Properties[propertyIndex]; + if (!visited.Contains(principal)) + { + visited.Add(principal); + + AddPrincipals(principal, visited); + } + } + } + } + } + + /// + /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). + /// + /// if the property is used as a foreign key, otherwise . + bool IsForeignKey(); + + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// The foreign keys that use this property. + IEnumerable GetContainingForeignKeys(); + + /// + /// Gets a value indicating whether this property is used as an index (or part of a composite index). + /// + /// if the property is used as an index, otherwise . + bool IsIndex(); + + /// + /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). + /// + /// if the property is used as an unique index, otherwise . + bool IsUniqueIndex() + => GetContainingIndexes().Any(e => e.IsUnique); + + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// The indexes that use this property. + IEnumerable GetContainingIndexes(); + + /// + /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). + /// + /// if the property is used as the primary key, otherwise . + bool IsPrimaryKey() + => FindContainingPrimaryKey() != null; + + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// The primary that use this property, or if it is not part of the primary key. + IReadOnlyKey? FindContainingPrimaryKey(); + + /// + /// Gets a value indicating whether this property is used as the primary key or alternate key + /// (or part of a composite primary or alternate key). + /// + /// if the property is used as a key, otherwise . + bool IsKey(); + + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// The primary and alternate keys that use this property. + IEnumerable GetContainingKeys(); + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"Property: {DeclaringType.DisplayName()}."); + } + + builder.Append(Name).Append(" ("); + + var field = GetFieldName(); + if (field == null) + { + builder.Append("no field, "); + } + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + builder.Append(field).Append(", "); + } + + builder.Append(ClrType.ShortDisplayName()).Append(')'); + + if (IsShadowProperty()) + { + builder.Append(" Shadow"); + } + + if (IsIndexerProperty()) + { + builder.Append(" Indexer"); + } + + if (!IsNullable) + { + builder.Append(" Required"); + } + + if (IsPrimaryKey()) + { + builder.Append(" PK"); + } + + if (IsForeignKey()) + { + builder.Append(" FK"); + } + + if (IsKey() + && !IsPrimaryKey()) + { + builder.Append(" AlternateKey"); + } + + if (IsIndex()) + { + builder.Append(" Index"); + } + + if (IsConcurrencyToken) + { + builder.Append(" Concurrency"); + } + + if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) + { + builder.Append(" Sentinel:").Append(Sentinel); + } + + if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); + } + + if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); + } + + if (ValueGenerated != ValueGenerated.Never) + { + builder.Append(" ValueGenerated.").Append(ValueGenerated); + } + + if (GetMaxLength() != null) + { + builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); + } + + if (IsUnicode() == false) + { + builder.Append(" ANSI"); + } + + if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) + { + builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); + } + + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 + && ((AnnotatableBase)this).IsReadOnly) + { + var indexes = ((IPrimitivePropertyBase)this).GetPropertyIndexes(); + builder.Append(' ').Append(indexes.Index); + builder.Append(' ').Append(indexes.OriginalValueIndex); + builder.Append(' ').Append(indexes.RelationshipIndex); + builder.Append(' ').Append(indexes.ShadowIndex); + builder.Append(' ').Append(indexes.StoreGenerationIndex); + } + + if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs index 08bbfe85bf8..1a7017eda76 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -1,10 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage.Json; - namespace Microsoft.EntityFrameworkCore.Metadata; /// @@ -13,184 +9,20 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public interface IReadOnlyProperty : IReadOnlyPropertyBase +public interface IReadOnlyProperty : IReadOnlyPrimitivePropertyBase { /// /// Gets the entity type that this property belongs to. /// IReadOnlyEntityType DeclaringEntityType { get; } - /// - /// Gets a value indicating whether this property can contain . - /// - bool IsNullable { get; } - - /// - /// Gets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See - /// and for more information and examples. - /// - ValueGenerated ValueGenerated { get; } - - /// - /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - bool IsConcurrencyToken { get; } - - /// - /// Returns the for the given property from a finalized model. - /// - /// The type mapping. - CoreTypeMapping GetTypeMapping() - { - var mapping = FindTypeMapping(); - if (mapping == null) - { - throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); - } - - return mapping; - } - - /// - /// Returns the type mapping for this property. - /// - /// The type mapping, or if none was found. - CoreTypeMapping? FindTypeMapping(); - - /// - /// Gets the maximum length of data that is allowed in this property. For example, if the property is a - /// then this is the maximum number of characters. - /// - /// - /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been - /// set. - /// - int? GetMaxLength(); - - /// - /// Gets the precision of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of digits. - /// - /// The precision, or if none is defined. - int? GetPrecision(); - - /// - /// Gets the scale of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of decimal places. - /// - /// The scale, or if none is defined. - int? GetScale(); - - /// - /// Gets a value indicating whether or not the property can persist Unicode characters. - /// - /// The Unicode setting, or if none is defined. - bool? IsUnicode(); - - /// - /// Gets a value indicating whether or not this property can be modified before the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a value is assigned to this property when it is in - /// the state. - /// - /// - /// If , then any value - /// set will be ignored when it is in the state. - /// - /// - /// The before save behavior for this property. - PropertySaveBehavior GetBeforeSaveBehavior(); - - /// - /// Gets a value indicating whether or not this property can be modified after the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a new value is assigned to this property after the entity exists in the database. - /// - /// - /// If , then any modification to the - /// property value of an entity that already exists in the database will be ignored. - /// - /// - /// The after save behavior for this property. - PropertySaveBehavior GetAfterSaveBehavior(); - - /// - /// Gets the factory that has been set to generate values for this property, if any. - /// - /// The factory, or if no factory has been set. - Func? GetValueGeneratorFactory(); - - /// - /// Gets the custom set for this property. - /// - /// The converter, or if none has been set. - ValueConverter? GetValueConverter(); - - /// - /// Gets the type that the property value will be converted to before being sent to the database provider. - /// - /// The provider type, or if none has been set. - Type? GetProviderClrType(); - - /// - /// Gets the for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetValueComparer(); - - /// - /// Gets the to use with keys for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetKeyValueComparer(); - - /// - /// Gets the to use for the provider values for this property. - /// - /// The comparer, or if none has been set. - ValueComparer? GetProviderValueComparer(); - - /// - /// Gets the for this property, or if none is set. - /// - /// The reader/writer, or if none has been set. - JsonValueReaderWriter? GetJsonValueReaderWriter(); - /// /// Finds the first principal property that the given property is constrained by /// if the given property is part of a foreign key. /// /// The first associated principal property, or if none exists. - IReadOnlyProperty? FindFirstPrincipal() - { - foreach (var foreignKey in GetContainingForeignKeys()) - { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) - { - if (this == foreignKey.Properties[propertyIndex]) - { - return foreignKey.PrincipalKey.Properties[propertyIndex]; - } - } - } - - return null; - } + new IReadOnlyProperty? FindFirstPrincipal() + => (IReadOnlyProperty?)((IReadOnlyPrimitivePropertyBase)this).FindFirstPrincipal(); /// /// Finds the list of principal properties including the given property that the given property is constrained by @@ -198,231 +30,5 @@ CoreTypeMapping GetTypeMapping() /// /// The list of all associated principal properties including the given property. IReadOnlyList GetPrincipals() - { - var principals = new List { this }; - AddPrincipals(this, principals); - return principals; - } - - private static void AddPrincipals(IReadOnlyProperty property, List visited) - { - foreach (var foreignKey in property.GetContainingForeignKeys()) - { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) - { - if (property == foreignKey.Properties[propertyIndex]) - { - var principal = foreignKey.PrincipalKey.Properties[propertyIndex]; - if (!visited.Contains(principal)) - { - visited.Add(principal); - - AddPrincipals(principal, visited); - } - } - } - } - } - - /// - /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). - /// - /// if the property is used as a foreign key, otherwise . - bool IsForeignKey(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// The foreign keys that use this property. - IEnumerable GetContainingForeignKeys(); - - /// - /// Gets a value indicating whether this property is used as an index (or part of a composite index). - /// - /// if the property is used as an index, otherwise . - bool IsIndex(); - - /// - /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). - /// - /// if the property is used as an unique index, otherwise . - bool IsUniqueIndex() - => GetContainingIndexes().Any(e => e.IsUnique); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// The indexes that use this property. - IEnumerable GetContainingIndexes(); - - /// - /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). - /// - /// if the property is used as the primary key, otherwise . - bool IsPrimaryKey() - => FindContainingPrimaryKey() != null; - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// The primary that use this property, or if it is not part of the primary key. - IReadOnlyKey? FindContainingPrimaryKey(); - - /// - /// Gets a value indicating whether this property is used as the primary key or alternate key - /// (or part of a composite primary or alternate key). - /// - /// if the property is used as a key, otherwise . - bool IsKey(); - - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// The primary and alternate keys that use this property. - IEnumerable GetContainingKeys(); - - /// - /// - /// Creates a human-readable representation of the given metadata. - /// - /// - /// Warning: Do not rely on the format of the returned string. - /// It is designed for debugging only and may change arbitrarily between releases. - /// - /// - /// Options for generating the string. - /// The number of indent spaces to use before each new line. - /// A human-readable representation. - string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) - { - var builder = new StringBuilder(); - var indentString = new string(' ', indent); - - try - { - builder.Append(indentString); - - var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; - if (singleLine) - { - builder.Append($"Property: {DeclaringEntityType.DisplayName()}."); - } - - builder.Append(Name).Append(" ("); - - var field = GetFieldName(); - if (field == null) - { - builder.Append("no field, "); - } - else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) - { - builder.Append(field).Append(", "); - } - - builder.Append(ClrType.ShortDisplayName()).Append(')'); - - if (IsShadowProperty()) - { - builder.Append(" Shadow"); - } - - if (IsIndexerProperty()) - { - builder.Append(" Indexer"); - } - - if (!IsNullable) - { - builder.Append(" Required"); - } - - if (IsPrimaryKey()) - { - builder.Append(" PK"); - } - - if (IsForeignKey()) - { - builder.Append(" FK"); - } - - if (IsKey() - && !IsPrimaryKey()) - { - builder.Append(" AlternateKey"); - } - - if (IsIndex()) - { - builder.Append(" Index"); - } - - if (IsConcurrencyToken) - { - builder.Append(" Concurrency"); - } - - if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); - } - - if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); - } - - if (ValueGenerated != ValueGenerated.Never) - { - builder.Append(" ValueGenerated.").Append(ValueGenerated); - } - - if (GetMaxLength() != null) - { - builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); - } - - if (IsUnicode() == false) - { - builder.Append(" Ansi"); - } - - if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) - { - builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); - } - - if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) - { - builder.Append(" Sentinel:").Append(Sentinel); - } - - if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 - && ((AnnotatableBase)this).IsReadOnly) - { - var indexes = ((IProperty)this).GetPropertyIndexes(); - builder.Append(' ').Append(indexes.Index); - builder.Append(' ').Append(indexes.OriginalValueIndex); - builder.Append(' ').Append(indexes.RelationshipIndex); - builder.Append(' ').Append(indexes.ShadowIndex); - builder.Append(' ').Append(indexes.StoreGenerationIndex); - } - - if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) - { - builder.Append(AnnotationsToDebugString(indent + 2)); - } - } - catch (Exception exception) - { - builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); - } - - return builder.ToString(); - } + => GetPrincipals(); } diff --git a/src/EFCore/Metadata/IReadOnlySkipNavigation.cs b/src/EFCore/Metadata/IReadOnlySkipNavigation.cs index 3ce55b9e5f5..d7017c74085 100644 --- a/src/EFCore/Metadata/IReadOnlySkipNavigation.cs +++ b/src/EFCore/Metadata/IReadOnlySkipNavigation.cs @@ -26,15 +26,6 @@ public interface IReadOnlySkipNavigation : IReadOnlyNavigationBase /// new IReadOnlySkipNavigation Inverse { get; } - /// - /// Gets the inverse navigation. - /// - IReadOnlyNavigationBase IReadOnlyNavigationBase.Inverse - { - [DebuggerStepThrough] - get => Inverse; - } - /// /// Gets the foreign key to the join type. /// @@ -130,4 +121,13 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt return builder.ToString(); } + + /// + /// Gets the inverse navigation. + /// + IReadOnlyNavigationBase IReadOnlyNavigationBase.Inverse + { + [DebuggerStepThrough] + get => Inverse; + } } diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index 9aa78564c5f..03743949470 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a type in the model. +/// Represents a structural type in the model. /// /// /// See Modeling entity types and relationships for more information and examples. @@ -41,13 +41,13 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable bool HasSharedClrType { get; } /// - /// Gets a value indicating whether this entity type has an indexer which is able to contain arbitrary properties + /// Gets a value indicating whether this structural type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// bool IsPropertyBag { get; } /// - /// Gets a value indicating whether this entity type represents an abstract type. + /// Gets a value indicating whether this structural type represents an abstract type. /// /// if the type is abstract, otherwise. [DebuggerStepThrough] @@ -55,7 +55,7 @@ bool IsAbstract() => ClrType.IsAbstract; /// - /// Gets the friendly display name for the given . + /// Gets the friendly display name for this structural type. /// /// The display name. [DebuggerStepThrough] diff --git a/src/EFCore/Metadata/IndexComparer.cs b/src/EFCore/Metadata/IndexComparer.cs index 3ee62dfde53..d1d9eb15ae1 100644 --- a/src/EFCore/Metadata/IndexComparer.cs +++ b/src/EFCore/Metadata/IndexComparer.cs @@ -39,7 +39,7 @@ private IndexComparer() public int Compare(IReadOnlyIndex? x, IReadOnlyIndex? y) { var result = PropertyListComparer.Instance.Compare(x?.Properties, y?.Properties); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -60,7 +60,7 @@ public int GetHashCode(IReadOnlyIndex obj) { var hashCode = new HashCode(); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/Internal/AdHocMapper.cs b/src/EFCore/Metadata/Internal/AdHocMapper.cs index 5f47aa16d6c..539f7f9078f 100644 --- a/src/EFCore/Metadata/Internal/AdHocMapper.cs +++ b/src/EFCore/Metadata/Internal/AdHocMapper.cs @@ -47,15 +47,15 @@ private ConventionSet ConventionSet _conventionSet.Remove(typeof(ForeignKeyPropertyDiscoveryConvention)); _conventionSet.Remove(typeof(IndexAttributeConvention)); _conventionSet.Remove(typeof(KeyAttributeConvention)); - _conventionSet.Remove(typeof(KeylessEntityTypeAttributeConvention)); + _conventionSet.Remove(typeof(KeylessAttributeConvention)); _conventionSet.Remove(typeof(ManyToManyJoinEntityTypeConvention)); _conventionSet.Remove(typeof(RequiredNavigationAttributeConvention)); _conventionSet.Remove(typeof(NavigationBackingFieldAttributeConvention)); _conventionSet.Remove(typeof(InversePropertyAttributeConvention)); _conventionSet.Remove(typeof(NavigationEagerLoadingConvention)); _conventionSet.Remove(typeof(NonNullableNavigationConvention)); - _conventionSet.Remove(typeof(NotMappedEntityTypeAttributeConvention)); - _conventionSet.Remove(typeof(OwnedEntityTypeAttributeConvention)); + _conventionSet.Remove(typeof(NotMappedTypeAttributeConvention)); + _conventionSet.Remove(typeof(OwnedAttributeConvention)); _conventionSet.Remove(typeof(QueryFilterRewritingConvention)); _conventionSet.Remove(typeof(ServicePropertyDiscoveryConvention)); _conventionSet.Remove(typeof(ValueGenerationConvention)); diff --git a/src/EFCore/Metadata/Internal/ComplexProperty.cs b/src/EFCore/Metadata/Internal/ComplexProperty.cs new file mode 100644 index 00000000000..c36953cde41 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexProperty.cs @@ -0,0 +1,349 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexProperty : PropertyBase, IMutableComplexProperty, IConventionComplexProperty, IComplexProperty +{ + private InternalComplexPropertyBuilder? _builder; + private bool? _isNullable; + + private ConfigurationSource? _isNullableConfigurationSource; + + /// + /// 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 ComplexProperty( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + TypeBase declaringType, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + : base(name, propertyInfo, fieldInfo, configurationSource) + { + ClrType = type; + DeclaringType = declaringType; + IsCollection = collection; + ComplexType = new ComplexType( + declaringType.GetOwnedName(targetType.ShortDisplayName(), name), + targetType, this, configurationSource); + _builder = new InternalComplexPropertyBuilder(this, declaringType.Model.Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType ComplexType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsCollection { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsInModel + => _builder is not null + && DeclaringType.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + { + _builder = null; + ComplexType.SetRemovedFromModel(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsNullable + { + get => _isNullable ?? DefaultIsNullable; + set => SetIsNullable(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) + { + EnsureMutable(); + + var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; + if (nullable == null) + { + _isNullable = null; + _isNullableConfigurationSource = null; + if (isChanging) + { + OnPropertyNullableChanged(); + } + + return nullable; + } + + if (nullable.Value) + { + if (!ClrType.IsNullableType()) + { + throw new InvalidOperationException( + CoreStrings.CannotBeNullable(Name, DeclaringType.DisplayName(), ClrType.ShortDisplayName())); + } + } + + _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); + + _isNullable = nullable; + + return isChanging + ? OnPropertyNullableChanged() + : nullable; + } + + private bool DefaultIsNullable + => ClrType.IsNullableType(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsNullableConfigurationSource() + => _isNullableConfigurationSource; + + /// + /// 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. + /// + protected virtual bool? OnPropertyNullableChanged() + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyNullabilityChanged(Builder); + + /// + /// 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. + /// + protected override FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); + + /// + /// 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 override TypeBase DeclaringType { get; } + + /// + /// 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. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + public override Type ClrType { get; } + + /// + /// 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 static bool IsCompatible( + string propertyName, + MemberInfo memberInfo, + TypeBase sourceType, + Type targetType, + bool shouldBeCollection, + bool shouldThrow) + { + var memberClrType = memberInfo.GetMemberType().TryGetSequenceType(); + if (shouldBeCollection + && memberClrType?.IsAssignableFrom(targetType) != true) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.NavigationCollectionWrongClrType( + propertyName, + sourceType.DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + targetType.ShortDisplayName())); + } + + return false; + } + + if (!shouldBeCollection + && !memberInfo.GetMemberType().IsAssignableFrom(targetType)) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.NavigationSingleWrongClrType( + propertyName, + sourceType.DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + targetType.ShortDisplayName())); + } + + return false; + } + + return true; + } + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyEntityType)this).ToDebugString(), + () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// 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 override string ToString() + => ((IReadOnlyComplexProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + IConventionComplexPropertyBuilder IConventionComplexProperty.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IReadOnlyComplexType IReadOnlyComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IMutableComplexType IMutableComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IConventionComplexType IConventionComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IComplexType IComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool? IConventionComplexProperty.SetIsNullable(bool? nullable, bool fromDataAnnotation) + => SetIsNullable( + nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs b/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs new file mode 100644 index 00000000000..3912cc0e635 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexPropertyConfiguration : AnnotatableBase +{ + /// + /// 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 ComplexPropertyConfiguration(Type clrType) + { + ClrType = clrType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Apply(IMutableComplexProperty property) + { + foreach (var annotation in GetAnnotations()) + { + if (!CoreAnnotationNames.AllNames.Contains(annotation.Name)) + { + property.SetAnnotation(annotation.Name, annotation.Value); + } + } + } +} diff --git a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs new file mode 100644 index 00000000000..a2e76df4fca --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexPropertySnapshot +{ + /// + /// 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 ComplexPropertySnapshot( + InternalComplexPropertyBuilder complexPropertyBuilder, + ComplexTypePropertiesSnapshot? properties, + List? indexes, + List<(InternalKeyBuilder, ConfigurationSource?)>? keys, + List? relationships) + { + ComplexPropertyBuilder = complexPropertyBuilder; + ComplexTypeBuilder = ComplexProperty.ComplexType.Builder; + Properties = properties ?? new ComplexTypePropertiesSnapshot(null, null, null, null); + if (indexes != null) + { + Properties.Add(indexes); + } + + if (keys != null) + { + Properties.Add(keys); + } + + if (relationships != null) + { + Properties.Add(relationships); + } + } + + private InternalComplexPropertyBuilder ComplexPropertyBuilder { [DebuggerStepThrough] get; } + private InternalComplexTypeBuilder ComplexTypeBuilder { [DebuggerStepThrough] get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty ComplexProperty => ComplexPropertyBuilder.Metadata; + private ComplexType ComplexType => ComplexTypeBuilder.Metadata; + private ComplexTypePropertiesSnapshot Properties { [DebuggerStepThrough] get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? Attach(InternalEntityTypeBuilder entityTypeBuilder) + { + var newProperty = entityTypeBuilder.Metadata.FindComplexProperty(ComplexProperty.Name); + if (newProperty == ComplexProperty) + { + return newProperty.Builder; + } + + InternalComplexPropertyBuilder? complexPropertyBuilder; + var configurationSource = ComplexProperty.GetConfigurationSource(); + if (newProperty != null + && (newProperty.GetConfigurationSource().Overrides(configurationSource) + || (ComplexProperty.ClrType == newProperty.ClrType + && ComplexProperty.Name == newProperty.Name + && ComplexProperty.GetIdentifyingMemberInfo() == newProperty.GetIdentifyingMemberInfo()))) + { + complexPropertyBuilder = newProperty.Builder; + newProperty.UpdateConfigurationSource(configurationSource); + } + else + { + complexPropertyBuilder = ComplexProperty.IsIndexerProperty() + ? entityTypeBuilder.ComplexIndexerProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource) + : entityTypeBuilder.ComplexProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexProperty.GetIdentifyingMemberInfo(), + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource); + + if (complexPropertyBuilder is null) + { + return null; + } + } + + return MergeConfiguration(complexPropertyBuilder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? Attach(InternalComplexTypeBuilder complexTypeBuilder) + { + var newProperty = complexTypeBuilder.Metadata.FindComplexProperty(ComplexProperty.Name); + if (newProperty == ComplexProperty) + { + return newProperty.Builder; + } + + InternalComplexPropertyBuilder? complexPropertyBuilder; + var configurationSource = ComplexProperty.GetConfigurationSource(); + if (newProperty != null + && (newProperty.GetConfigurationSource().Overrides(configurationSource) + || (ComplexProperty.ClrType == newProperty.ClrType + && ComplexProperty.Name == newProperty.Name + && ComplexProperty.GetIdentifyingMemberInfo() == newProperty.GetIdentifyingMemberInfo()))) + { + complexPropertyBuilder = newProperty.Builder; + newProperty.UpdateConfigurationSource(configurationSource); + } + else + { + complexPropertyBuilder = ComplexProperty.IsIndexerProperty() + ? complexTypeBuilder.ComplexIndexerProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource) + : complexTypeBuilder.ComplexProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexProperty.GetIdentifyingMemberInfo(), + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource); + + if (complexPropertyBuilder is null) + { + return null; + } + } + + return MergeConfiguration(complexPropertyBuilder); + } + + private InternalComplexPropertyBuilder MergeConfiguration(InternalComplexPropertyBuilder complexPropertyBuilder) + { + complexPropertyBuilder.MergeAnnotationsFrom(ComplexProperty); + + var oldIsNullableConfigurationSource = ComplexProperty.GetIsNullableConfigurationSource(); + if (oldIsNullableConfigurationSource.HasValue) + { + complexPropertyBuilder.IsRequired(!ComplexProperty.IsNullable, oldIsNullableConfigurationSource.Value); + } + + var oldPropertyAccessModeConfigurationSource = ComplexProperty.GetPropertyAccessModeConfigurationSource(); + if (oldPropertyAccessModeConfigurationSource.HasValue) + { + complexPropertyBuilder.UsePropertyAccessMode( + ((IReadOnlyProperty)ComplexProperty).GetPropertyAccessMode(), oldPropertyAccessModeConfigurationSource.Value); + } + + var oldFieldInfoConfigurationSource = ComplexProperty.GetFieldInfoConfigurationSource(); + if (oldFieldInfoConfigurationSource.HasValue + && complexPropertyBuilder.CanSetField(ComplexProperty.FieldInfo, oldFieldInfoConfigurationSource)) + { + complexPropertyBuilder.HasField(ComplexProperty.FieldInfo, oldFieldInfoConfigurationSource.Value); + } + + complexPropertyBuilder.MergeAnnotationsFrom(ComplexProperty); + if (ComplexProperty.GetIsNullableConfigurationSource() != null) + { + complexPropertyBuilder.IsRequired(!ComplexProperty.IsNullable, ComplexProperty.GetIsNullableConfigurationSource()!.Value); + } + + var complexTypeBuilder = complexPropertyBuilder.Metadata.ComplexType.Builder; + complexTypeBuilder.MergeAnnotationsFrom(ComplexType); + + foreach (var ignoredMember in ComplexType.GetIgnoredMembers()) + { + complexTypeBuilder.Ignore(ignoredMember, ComplexType.FindDeclaredIgnoredConfigurationSource(ignoredMember)!.Value); + } + + if (ComplexType.GetChangeTrackingStrategyConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetChangeTrackingStrategy( + ComplexType.GetChangeTrackingStrategy(), ComplexType.GetChangeTrackingStrategyConfigurationSource()!.Value); + } + + Properties.Attach(complexTypeBuilder); + + if (ComplexType.GetConstructorBindingConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetConstructorBinding( + Create(ComplexType.ConstructorBinding, complexTypeBuilder.Metadata), + ComplexType.GetConstructorBindingConfigurationSource()!.Value); + } + + if (ComplexType.GetServiceOnlyConstructorBindingConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetServiceOnlyConstructorBinding( + Create(ComplexType.ServiceOnlyConstructorBinding, complexTypeBuilder.Metadata), + ComplexType.GetServiceOnlyConstructorBindingConfigurationSource()!.Value); + } + + return complexPropertyBuilder; + } + + private static InstantiationBinding? Create(InstantiationBinding? instantiationBinding, ComplexType complexType) + => instantiationBinding?.With( + instantiationBinding.ParameterBindings.Select(binding => Create(binding, complexType)).ToList()); + + private static ParameterBinding Create(ParameterBinding parameterBinding, ComplexType complexType) + => parameterBinding.With( + parameterBinding.ConsumedProperties.Select( + property => + (IPropertyBase?)complexType.FindProperty(property.Name) + ?? complexType.FindComplexProperty(property.Name)!).ToArray()); +} diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs new file mode 100644 index 00000000000..0b994cefda4 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -0,0 +1,2091 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexType : TypeBase, IMutableComplexType, IConventionComplexType, IRuntimeComplexType +{ + private readonly SortedDictionary _properties = + new SortedDictionary(StringComparer.Ordinal); + + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + + private InternalComplexTypeBuilder? _builder; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseTypeComparer.Instance); + private ComplexType? _baseType; + private ChangeTrackingStrategy? _changeTrackingStrategy; + + private ConfigurationSource? _baseTypeConfigurationSource; + private ConfigurationSource? _changeTrackingStrategyConfigurationSource; + private ConfigurationSource? _constructorBindingConfigurationSource; + private ConfigurationSource? _serviceOnlyConstructorBindingConfigurationSource; + + // Warning: Never access these fields directly as access needs to be thread-safe + private PropertyCounts? _counts; + + // _serviceOnlyConstructorBinding needs to be set as well whenever _constructorBinding is set + private InstantiationBinding? _constructorBinding; + private InstantiationBinding? _serviceOnlyConstructorBinding; + + private Func? _originalValuesFactory; + private Func? _temporaryValuesFactory; + private Func? _storeGeneratedValuesFactory; + private Func? _shadowValuesFactory; + private Func? _emptyShadowValuesFactory; + private IComplexTypeProperty[]? _foreignKeyProperties; + private IComplexTypeProperty[]? _valueGeneratingProperties; + + /// + /// 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 ComplexType( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + ComplexProperty property, + ConfigurationSource configurationSource) + : base(name, type, property.DeclaringType.Model, configurationSource) + { + if (!type.IsValidComplexType()) + { + throw new ArgumentException(CoreStrings.InvalidComplexType(type)); + } + + if (EntityType.DynamicProxyGenAssemblyName.Equals( + type.Assembly.GetName().Name, StringComparison.Ordinal)) + { + throw new ArgumentException( + CoreStrings.AddingProxyTypeAsEntityType(type.FullName)); + } + + ComplexProperty = property; + _builder = new InternalComplexTypeBuilder(this, property.DeclaringType.Model.Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// 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. + /// + protected override IConventionTypeBaseBuilder BaseBuilder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty ComplexProperty { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual EntityType FundamentalEntityType + => ComplexProperty.DeclaringType switch + { + EntityType entityType => entityType, + ComplexType declaringComplexType => declaringComplexType.FundamentalEntityType, + _ => throw new NotImplementedException() + }; + + /// + /// 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 override bool IsInModel + => _builder is not null + && ComplexProperty.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + => _builder = null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType? BaseType + => _baseType; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType? SetBaseType(ComplexType? newBaseType, ConfigurationSource configurationSource) + { + EnsureMutable(); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + + if (_baseType == newBaseType) + { + UpdateBaseTypeConfigurationSource(configurationSource); + newBaseType?.UpdateConfigurationSource(configurationSource); + return newBaseType; + } + else if (_baseType == null) + { + throw new NotImplementedException(); + } + + var originalBaseType = _baseType; + + _baseType?._directlyDerivedTypes.Remove(this); + _baseType = null; + + if (newBaseType != null) + { + if (!newBaseType.ClrType.IsAssignableFrom(ClrType)) + { + throw new InvalidOperationException( + CoreStrings.NotAssignableClrBaseType( + DisplayName(), newBaseType.DisplayName(), ClrType.ShortDisplayName(), + newBaseType.ClrType.ShortDisplayName())); + } + + if (newBaseType.InheritsFrom(this)) + { + throw new InvalidOperationException(CoreStrings.CircularInheritance(DisplayName(), newBaseType.DisplayName())); + } + + var conflictingMember = newBaseType.GetMembers() + .Select(p => p.Name) + .SelectMany(FindMembersInHierarchy) + .FirstOrDefault(); + + if (conflictingMember != null) + { + var baseProperty = newBaseType.FindMembersInHierarchy(conflictingMember.Name).Single(); + throw new InvalidOperationException( + CoreStrings.DuplicatePropertiesOnBase( + DisplayName(), + newBaseType.DisplayName(), + conflictingMember.DeclaringType.DisplayName(), + conflictingMember.Name, + baseProperty.DeclaringType.DisplayName(), + baseProperty.Name)); + } + + _baseType = newBaseType; + _baseType._directlyDerivedTypes.Add(this); + } + + UpdateBaseTypeConfigurationSource(configurationSource); + newBaseType?.UpdateConfigurationSource(configurationSource); + + return newBaseType; + //return (ComplexType?)Model.ConventionDispatcher.OnComplexTypeBaseTypeChanged(Builder, newBaseType, originalBaseType); + } + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + => _baseTypeConfigurationSource; + + [DebuggerStepThrough] + private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) + => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual IReadOnlySet GetDirectlyDerivedTypes() + => _directlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedTypes() + { + if (_directlyDerivedTypes.Count == 0) + { + return Enumerable.Empty(); + } + + var derivedTypes = new List(); + var type = this; + var currentTypeIndex = 0; + while (type != null) + { + derivedTypes.AddRange(type.GetDirectlyDerivedTypes()); + type = derivedTypes.Count > currentTypeIndex + ? derivedTypes[currentTypeIndex] + : null; + currentTypeIndex++; + } + + return derivedTypes; + } + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual IEnumerable GetDerivedTypesInclusive() + => _directlyDerivedTypes.Count == 0 + ? new[] { this } + : new[] { this }.Concat(GetDerivedTypes()); + + private bool InheritsFrom(ComplexType type) + { + var currentType = this; + + do + { + if (type == currentType) + { + return true; + } + } + while ((currentType = currentType._baseType) != null); + + return false; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsAssignableFrom(ComplexType derivedType) + { + Check.NotNull(derivedType, nameof(derivedType)); + + if (derivedType == this) + { + return true; + } + + if (!GetDirectlyDerivedTypes().Any()) + { + return false; + } + + var baseType = derivedType.BaseType; + while (baseType != null) + { + if (baseType == this) + { + return true; + } + + baseType = baseType.BaseType; + } + + return false; + } + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual ComplexType GetRootType() + => BaseType?.GetRootType() ?? this; + + /// + /// 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. + /// + private string DisplayName() + => ((IReadOnlyComplexType)this).DisplayName(); + + /// + /// 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 override void OnTypeRemoved() + { + _baseType?._directlyDerivedTypes.Remove(this); + } + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => Model.ConventionDispatcher.OnComplexTypeAnnotationChanged(Builder, name, annotation, oldAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetMembers() + => GetProperties().Cast() + .Concat(GetComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredMembers() + => GetDeclaredProperties().Cast() + .Concat(GetDeclaredComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindMembersInHierarchy(string name) + => FindPropertiesInHierarchy(name).Cast() + .Concat(FindComplexPropertiesInHierarchy(name)); + + #region Properties + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + + return AddProperty( + name, + propertyType, + null, + typeConfigurationSource, + configurationSource); + } + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexTypeProperty? AddProperty( + MemberInfo memberInfo, + ConfigurationSource configurationSource) + => AddProperty( + memberInfo.GetSimpleMemberName(), + memberInfo.GetMemberType(), + memberInfo, + configurationSource, + configurationSource); + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexTypeProperty? AddProperty( + string name, + ConfigurationSource configurationSource) + { + MemberInfo? clrMember; + if (IsPropertyBag) + { + clrMember = FindIndexerPropertyInfo()!; + } + else + { + clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); + } + } + + return AddProperty(clrMember, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); + if (conflictingMember != null) + { + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + name, DisplayName(), + conflictingMember.DeclaringType.DisplayName())); + } + + if (memberInfo != null) + { + propertyType = ValidateClrMember(name, memberInfo, typeConfigurationSource != null) + ?? propertyType; + + if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + } + else if (IsPropertyBag) + { + memberInfo = FindIndexerPropertyInfo(); + } + else + { + memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + } + + if (memberInfo != null + && propertyType != memberInfo.GetMemberType() + && memberInfo != FindIndexerPropertyInfo()) + { + if (typeConfigurationSource != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongClrType( + name, + DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + propertyType.ShortDisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + var property = new ComplexTypeProperty( + name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, + configurationSource, typeConfigurationSource); + + _properties.Add(property.Name, property); + + if (Model.Configuration != null) + { + using (Model.ConventionDispatcher.DelayConventions()) + { + Model.ConventionDispatcher.OnComplexTypePropertyAdded(property.Builder); + Model.Configuration.ConfigureProperty(property); + return property; + } + } + + return (ComplexTypeProperty?)Model.ConventionDispatcher.OnComplexTypePropertyAdded(property.Builder)?.Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? FindProperty(string name) + => FindDeclaredProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindProperty(name); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? FindDeclaredProperty(string name) + => _properties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) + ? property + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredProperties() + => _properties.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedProperties(string propertyName) + { + Check.NotNull(propertyName, nameof(propertyName)); + + return _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredProperty(propertyName)).Where(p => p != null); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedPropertiesInclusive(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindDeclaredProperty(propertyName)) + : ToEnumerable(FindDeclaredProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindPropertiesInHierarchy(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindProperty(propertyName)) + : ToEnumerable(FindProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? FindProperties(IReadOnlyList propertyNames) + { + Check.NotNull(propertyNames, nameof(propertyNames)); + + var properties = new List(propertyNames.Count); + foreach (var propertyName in propertyNames) + { + var property = FindProperty(propertyName); + if (property == null) + { + return null; + } + + properties.Add(property); + } + + return properties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? RemoveProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindDeclaredProperty(name); + return property == null + ? null + : RemoveProperty(property); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeProperty? RemoveProperty(ComplexTypeProperty property) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (property.DeclaringComplexType != this) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongType( + property.Name, + DisplayName(), + property.DeclaringComplexType.DisplayName())); + } + + CheckPropertyNotInUse(property); + + var removed = _properties.Remove(property.Name); + Check.DebugAssert(removed, "removed is false"); + + property.SetRemovedFromModel(); + + return (ComplexTypeProperty?)Model.ConventionDispatcher.OnComplexTypePropertyRemoved(Builder, property); + } + + private void CheckPropertyNotInUse(ComplexTypeProperty property) + { + var containingKey = property.Keys?.FirstOrDefault(); + if (containingKey != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseKey(property.Name, DisplayName(), containingKey.Properties.Format())); + } + + var containingForeignKey = property.ForeignKeys?.FirstOrDefault(); + if (containingForeignKey != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseForeignKey( + property.Name, DisplayName(), + containingForeignKey.Properties.Format(), containingForeignKey.DeclaringEntityType.DisplayName())); + } + + var containingIndex = property.Indexes?.FirstOrDefault(); + if (containingIndex != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseIndex( + property.Name, DisplayName(), + containingIndex.DisplayName(), containingIndex.DeclaringEntityType.DisplayName())); + } + } + + private Type? ValidateClrMember(string name, MemberInfo memberInfo, bool throwOnNameMismatch = true) + { + if (name != memberInfo.GetSimpleMemberName()) + { + if (memberInfo != FindIndexerPropertyInfo()) + { + if (throwOnNameMismatch) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongName( + name, + DisplayName(), + memberInfo.GetSimpleMemberName())); + } + + return memberInfo.GetMemberType(); + } + + var clashingMemberInfo = IsPropertyBag + ? null + : ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clashingMemberInfo != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyClashingNonIndexer( + name, + DisplayName())); + } + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetProperties() + => _baseType != null + ? _baseType.GetProperties().Concat(_properties.Values) + : _properties.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized( + ref _counts, this, static complexType => + { + complexType.EnsureReadOnly(); + return complexType.CalculateCounts(); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func OriginalValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _originalValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new OriginalValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func StoreGeneratedValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _storeGeneratedValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new StoreGeneratedValuesFactoryFactory().CreateEmpty(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func TemporaryValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _temporaryValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new TemporaryValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func ShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _shadowValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new ShadowValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func EmptyShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _emptyShadowValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new EmptyShadowValuesFactoryFactory().CreateEmpty(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ForeignKeyProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _foreignKeyProperties, this, + static entityType => + { + entityType.EnsureReadOnly(); + + return entityType.GetProperties().Where(p => p.IsForeignKey()).ToArray(); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ValueGeneratingProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _valueGeneratingProperties, this, + static complexType => + { + complexType.EnsureReadOnly(); + + return complexType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray(); + }); + + #endregion + + #region Complex properties + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); + + return AddComplexProperty( + name, + propertyType, + memberInfo: null, + targetType, + collection, + configurationSource); + } + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + MemberInfo memberInfo, + bool collection, + ConfigurationSource configurationSource) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), + memberInfo.GetMemberType(), + memberInfo, + memberInfo.GetMemberType(), + collection, + configurationSource); + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + string name, + bool collection, + ConfigurationSource configurationSource) + { + MemberInfo? clrMember; + if (IsPropertyBag) + { + clrMember = FindIndexerPropertyInfo()!; + } + else + { + clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); + } + } + + return AddComplexProperty(clrMember, collection, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); + if (conflictingMember != null) + { + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + name, DisplayName(), + conflictingMember.DeclaringType.DisplayName())); + } + + if (memberInfo != null) + { + propertyType = ValidateClrMember(name, memberInfo) + ?? propertyType; + + if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + } + else if (IsPropertyBag) + { + memberInfo = FindIndexerPropertyInfo(); + } + else + { + memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + } + + if (memberInfo != null) + { + if (propertyType != memberInfo.GetMemberType() + && memberInfo != FindIndexerPropertyInfo()) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongClrType( + name, + DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + propertyType.ShortDisplayName())); + } + + ComplexProperty.IsCompatible( + name, + memberInfo, + this, + targetType, + collection, + shouldThrow: true); + } + + var property = new ComplexProperty( + name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, + targetType, collection, configurationSource); + + _complexProperties.Add(property.Name, property); + + if (Model.Configuration != null) + { + using (Model.ConventionDispatcher.DelayConventions()) + { + property = (ComplexProperty)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)!.Metadata; + Model.Configuration.ConfigureComplexProperty(property); + return property; + } + } + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)?.Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindComplexProperty(name); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) + ? property + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedComplexProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedComplexProperties(string propertyName) + { + Check.NotNull(propertyName, nameof(propertyName)); + + return _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedComplexPropertiesInclusive(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindDeclaredComplexProperty(propertyName)) + : ToEnumerable(FindDeclaredComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindComplexProperty(propertyName)) + : ToEnumerable(FindComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindDeclaredComplexProperty(name); + return property == null + ? null + : RemoveComplexProperty(property); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(ComplexProperty property) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongType( + property.Name, + DisplayName(), + property.DeclaringType.DisplayName())); + } + + CheckPropertyNotInUse(property); + var removed = _complexProperties.Remove(property.Name); + Check.DebugAssert(removed, "removed is false"); + + property.SetRemovedFromModel(); + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyRemoved(Builder, property); + } + + private void CheckPropertyNotInUse(ComplexProperty property) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetComplexProperties() + => _baseType != null + ? _baseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + #endregion + + #region Ignore + + /// + /// 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 override ConfigurationSource? FindIgnoredConfigurationSource(string name) + { + var ignoredSource = FindDeclaredIgnoredConfigurationSource(name); + + return BaseType == null ? ignoredSource : BaseType.FindIgnoredConfigurationSource(name).Max(ignoredSource); + } + + /// + /// 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 override string? OnTypeMemberIgnored(string name) + => Model.ConventionDispatcher.OnComplexTypeMemberIgnored(Builder, name); + + #endregion + + #region Other + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() + => _changeTrackingStrategy ?? Model.GetChangeTrackingStrategy(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ChangeTrackingStrategy? SetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + if (changeTrackingStrategy != null) + { + var requireFullNotifications = + (bool?)Model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true; + var errorMessage = CheckChangeTrackingStrategy(this, changeTrackingStrategy.Value, requireFullNotifications); + if (errorMessage != null) + { + throw new InvalidOperationException(errorMessage); + } + } + + _changeTrackingStrategy = changeTrackingStrategy; + + _changeTrackingStrategyConfigurationSource = _changeTrackingStrategy == null + ? null + : configurationSource.Max(_changeTrackingStrategyConfigurationSource); + + return changeTrackingStrategy; + } + + /// + /// 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 static string? CheckChangeTrackingStrategy( + IReadOnlyComplexType complexType, + ChangeTrackingStrategy value, + bool requireFullNotifications) + { + if (requireFullNotifications) + { + if (value != ChangeTrackingStrategy.ChangingAndChangedNotifications + && value != ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) + { + return CoreStrings.FullChangeTrackingRequired( + complexType.DisplayName(), value, nameof(ChangeTrackingStrategy.ChangingAndChangedNotifications), + nameof(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues)); + } + } + else + { + if (value != ChangeTrackingStrategy.Snapshot + && !typeof(INotifyPropertyChanged).IsAssignableFrom(complexType.ClrType)) + { + return CoreStrings.ChangeTrackingInterfaceMissing(complexType.DisplayName(), value, nameof(INotifyPropertyChanged)); + } + + if ((value == ChangeTrackingStrategy.ChangingAndChangedNotifications + || value == ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) + && !typeof(INotifyPropertyChanging).IsAssignableFrom(complexType.ClrType)) + { + return CoreStrings.ChangeTrackingInterfaceMissing(complexType.DisplayName(), value, nameof(INotifyPropertyChanging)); + } + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetChangeTrackingStrategyConfigurationSource() + => _changeTrackingStrategyConfigurationSource; + + /// + /// 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. + /// + [DebuggerStepThrough] + public virtual ConfigurationSource? GetDiscriminatorPropertyConfigurationSource() + => FindAnnotation(CoreAnnotationNames.DiscriminatorProperty)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsImplicitlyCreatedJoinEntityType + => GetConfigurationSource() == ConfigurationSource.Convention + && ClrType == Model.DefaultPropertyBagType; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? ConstructorBinding + { + get => IsReadOnly && !ClrType.IsAbstract + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _constructorBinding, this, static complexType => + { + ((IModel)complexType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( + (IReadOnlyEntityType)complexType, + out complexType._constructorBinding, + out complexType._serviceOnlyConstructorBinding); + }) + : _constructorBinding; + + set => SetConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? SetConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _constructorBinding = constructorBinding; + + if (_constructorBinding == null) + { + _constructorBindingConfigurationSource = null; + } + else + { + UpdateConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetConstructorBindingConfigurationSource() + => _constructorBindingConfigurationSource; + + private void UpdateConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _constructorBindingConfigurationSource = configurationSource.Max(_constructorBindingConfigurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? ServiceOnlyConstructorBinding + { + get => _serviceOnlyConstructorBinding; + set => SetServiceOnlyConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? SetServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _serviceOnlyConstructorBinding = constructorBinding; + + if (_serviceOnlyConstructorBinding == null) + { + _serviceOnlyConstructorBindingConfigurationSource = null; + } + else + { + UpdateServiceOnlyConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource() + => _serviceOnlyConstructorBindingConfigurationSource; + + private void UpdateServiceOnlyConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _serviceOnlyConstructorBindingConfigurationSource = + configurationSource.Max(_serviceOnlyConstructorBindingConfigurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyEntityType)this).ToDebugString(), + () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// 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 override string ToString() + => ((IReadOnlyComplexProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + #endregion + + #region Explicit interface implementations + + /// + /// 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. + /// + IConventionComplexTypeBuilder IConventionComplexType.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IConventionAnnotatableBuilder IConventionAnnotatable.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + /// 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. + /// + IMutableModel IMutableTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + /// 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. + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + /// 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. + /// + IComplexProperty IComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IReadOnlyComplexProperty IReadOnlyComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IConventionComplexProperty IConventionComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IMutableComplexProperty IMutableComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IReadOnlyEntityType IReadOnlyComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IMutableEntityType IMutableComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IConventionEntityType IConventionComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IEntityType IComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + [DebuggerStepThrough] + void IMutableComplexType.SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy) + => SetChangeTrackingStrategy(changeTrackingStrategy, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + ChangeTrackingStrategy? IConventionComplexType.SetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => SetChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableComplexType.AddComplexProperty(string name, bool collection) + => AddComplexProperty(name, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.AddComplexProperty( + string name, bool collection, bool fromDataAnnotation) + => AddComplexProperty( + name, + collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableComplexType.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableComplexType.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutableComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IComplexProperty? IComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableComplexType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableComplexType.RemoveComplexProperty(IReadOnlyProperty property) + => RemoveComplexProperty((ComplexProperty)property); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionComplexType.RemoveComplexProperty(IConventionComplexProperty property) + => RemoveComplexProperty((ComplexProperty)property); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty IMutableComplexType.AddProperty(string name) + => AddProperty(name, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.AddProperty(string name, bool fromDataAnnotation) + => AddProperty( + name, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty IMutableComplexType.AddProperty( + string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + => AddProperty( + name, + propertyType, + ConfigurationSource.Explicit, + ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.AddProperty( + string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, bool setTypeConfigurationSource, bool fromDataAnnotation) + => AddProperty( + name, + propertyType, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty IMutableComplexType.AddProperty( + string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo? memberInfo) + => AddProperty( + name, propertyType, memberInfo, + ConfigurationSource.Explicit, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.AddProperty( + string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo? memberInfo, bool setTypeConfigurationSource, bool fromDataAnnotation) + => AddProperty( + name, + propertyType, + memberInfo, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyList? IReadOnlyComplexType.FindProperties(IReadOnlyList propertyNames) + => FindProperties(propertyNames); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyList? IConventionComplexType.FindProperties(IReadOnlyList propertyNames) + => FindProperties(propertyNames); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyComplexTypeProperty? IReadOnlyComplexType.FindProperty(string name) + => FindProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty? IMutableComplexType.FindProperty(string name) + => FindProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.FindProperty(string name) + => FindProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IComplexTypeProperty? IComplexType.FindProperty(string name) + => FindProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetProperties() + => GetProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutableComplexType.GetProperties() + => GetProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionComplexType.GetProperties() + => GetProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetProperties() + => GetProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty? IMutableComplexType.RemoveProperty(string name) + => RemoveProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.RemoveProperty(string name) + => RemoveProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexTypeProperty? IMutableComplexType.RemoveProperty(IReadOnlyComplexTypeProperty property) + => RemoveProperty((ComplexTypeProperty)property); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexTypeProperty? IConventionComplexType.RemoveProperty(IReadOnlyComplexTypeProperty property) + => RemoveProperty((ComplexTypeProperty)property); + + #endregion + + private static IEnumerable ToEnumerable(T? element) + where T : class + => element == null + ? Enumerable.Empty() + : new[] { element }; +} diff --git a/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs b/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs new file mode 100644 index 00000000000..54d85423508 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable ArgumentsStyleOther +// ReSharper disable ArgumentsStyleNamedExpression +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 static class ComplexTypeExtensions +{ + /// + /// 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 static bool UseEagerSnapshots(this IReadOnlyComplexType complexType) + { + var changeTrackingStrategy = complexType.GetChangeTrackingStrategy(); + + return changeTrackingStrategy == ChangeTrackingStrategy.Snapshot + || changeTrackingStrategy == ChangeTrackingStrategy.ChangedNotifications; + } + + /// + /// 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 static PropertyCounts CalculateCounts(this IRuntimeComplexType complexType) + { + var propertyIndex = 0; + var complexPropertyIndex = 0; + var originalValueIndex = 0; + var shadowIndex = 0; + var storeGenerationIndex = 0; + var relationshipIndex = ((IRuntimeTypeBase)complexType.ComplexProperty.DeclaringType).Counts.RelationshipCount; + + var baseCounts = (complexType as ComplexType)?.BaseType?.Counts; + if (baseCounts != null) + { + propertyIndex = baseCounts.PropertyCount; + originalValueIndex = baseCounts.OriginalValueCount; + shadowIndex = baseCounts.ShadowCount; + storeGenerationIndex = baseCounts.StoreGeneratedCount; + } + + foreach (var property in complexType.GetProperties()) + { + var indexes = new PropertyIndexes( + index: propertyIndex++, + originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, + shadowIndex: property.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: property.IsKey() || property.IsForeignKey() ? relationshipIndex++ : -1, + storeGenerationIndex: property.MayBeStoreGenerated() ? storeGenerationIndex++ : -1); + + ((IRuntimePropertyBase)property).PropertyIndexes = indexes; + } + + foreach (var complexProperty in complexType.GetComplexProperties()) + { + var indexes = new PropertyIndexes( + index: complexPropertyIndex++, + originalValueIndex: -1, + shadowIndex: complexProperty.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: -1, + storeGenerationIndex: -1); + + ((IRuntimePropertyBase)complexProperty).PropertyIndexes = indexes; + } + + return new PropertyCounts( + propertyIndex, + navigationCount: 0, + complexPropertyIndex, + originalValueIndex, + shadowIndex, + relationshipCount: 0, + storeGenerationIndex); + } +} diff --git a/src/EFCore/Metadata/Internal/ComplexTypePropertiesSnapshot.cs b/src/EFCore/Metadata/Internal/ComplexTypePropertiesSnapshot.cs new file mode 100644 index 00000000000..e68a77e24bf --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexTypePropertiesSnapshot.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexTypePropertiesSnapshot +{ + /// + /// 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 ComplexTypePropertiesSnapshot( + List? properties, + List? indexes, + List<(InternalKeyBuilder, ConfigurationSource?)>? keys, + List? relationships) + { + Properties = properties; + Indexes = indexes; + Keys = keys; + Relationships = relationships; + } + + private List? Properties { [DebuggerStepThrough] get; } + private List? Relationships { [DebuggerStepThrough] get; set; } + private List? Indexes { [DebuggerStepThrough] get; set; } + private List<(InternalKeyBuilder, ConfigurationSource?)>? Keys { [DebuggerStepThrough] get; set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Add(List relationships) + { + if (Relationships == null) + { + Relationships = relationships; + } + else + { + Relationships.AddRange(relationships); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Add(List indexes) + { + if (Indexes == null) + { + Indexes = indexes; + } + else + { + Indexes.AddRange(indexes); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Add(List<(InternalKeyBuilder, ConfigurationSource?)> keys) + { + if (Keys == null) + { + Keys = keys; + } + else + { + Keys.AddRange(keys); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Attach(InternalComplexTypeBuilder complexTypeBuilder) + { + if (Properties != null) + { + foreach (var propertyBuilder in Properties) + { + propertyBuilder.Attach(complexTypeBuilder); + } + } + + var fundamentalTypeBuilder = complexTypeBuilder.Metadata.FundamentalEntityType.Builder; + if (Keys != null) + { + foreach (var (internalKeyBuilder, configurationSource) in Keys) + { + internalKeyBuilder.Attach(fundamentalTypeBuilder, configurationSource); + } + } + + if (Indexes != null) + { + foreach (var indexBuilder in Indexes) + { + indexBuilder.Attach(fundamentalTypeBuilder); + } + } + + if (Relationships != null) + { + foreach (var detachedRelationshipTuple in Relationships) + { + detachedRelationshipTuple.Attach(fundamentalTypeBuilder); + } + } + } +} diff --git a/src/EFCore/Metadata/Internal/ComplexTypeProperty.cs b/src/EFCore/Metadata/Internal/ComplexTypeProperty.cs new file mode 100644 index 00000000000..eb1ad71bc8c --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexTypeProperty.cs @@ -0,0 +1,199 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexTypeProperty : + PrimitivePropertyBase, IMutableComplexTypeProperty, IConventionComplexTypeProperty, IComplexTypeProperty +{ + private InternalComplexTypePropertyBuilder? _builder; + + /// + /// 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 ComplexTypeProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type clrType, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + ComplexType declaringComplexType, + ConfigurationSource configurationSource, + ConfigurationSource? typeConfigurationSource) + : base(name, clrType, propertyInfo, fieldInfo, declaringComplexType, configurationSource, typeConfigurationSource) + { + _builder = new InternalComplexTypePropertyBuilder(this, declaringComplexType.Model.Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType DeclaringComplexType => (ComplexType)DeclaringType; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsInModel + => _builder is not null + && DeclaringComplexType.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + => _builder = null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override bool? OnPropertyNullableChanged() + => DeclaringComplexType.Model.ConventionDispatcher.OnComplexTypePropertyNullabilityChanged(Builder); + + /// + /// 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. + /// + protected override FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => DeclaringComplexType.Model.ConventionDispatcher.OnComplexTypePropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => DeclaringType.Model.ConventionDispatcher.OnComplexTypePropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); + + /// + /// 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 override string ToString() + => ((IReadOnlyComplexTypeProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyComplexTypeProperty)this).ToDebugString(), + () => ((IReadOnlyComplexTypeProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// 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. + /// + IConventionComplexTypePropertyBuilder IConventionComplexTypeProperty.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IConventionAnnotatableBuilder IConventionAnnotatable.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IReadOnlyComplexType IReadOnlyComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } + + /// + /// 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. + /// + IMutableComplexType IMutableComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } + + /// + /// 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. + /// + IConventionComplexType IConventionComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } + + /// + /// 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. + /// + IComplexType IComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } +} diff --git a/src/EFCore/Metadata/Internal/ComplexTypePropertyExtensions.cs b/src/EFCore/Metadata/Internal/ComplexTypePropertyExtensions.cs new file mode 100644 index 00000000000..55dc3085a3f --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexTypePropertyExtensions.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 static class ComplexTypePropertyExtensions +{ + /// + /// 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 static bool RequiresOriginalValue(this IReadOnlyComplexTypeProperty property) + => property.DeclaringComplexType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.ChangingAndChangedNotifications + || property.IsConcurrencyToken + || property.IsKey() + || property.IsForeignKey() + || property.IsUniqueIndex(); +} diff --git a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs index 45fe64271ab..b40b5816385 100644 --- a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs @@ -42,7 +42,8 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, - static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); @@ -58,7 +59,8 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, - static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); @@ -74,15 +76,35 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); - private void GetBindings( - IReadOnlyEntityType entityType, - Func bind, + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void GetBindings( + IReadOnlyComplexType complexType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + => GetBindings( + complexType, + static (f, e, p, n) => f.FindParameter((IComplexType)e, p, n), + static (f, e, p, n) => null, + out constructorBinding, + out serviceOnlyBinding); + + private void GetBindings( + T entityType, + Func bindToProperty, + Func bind, out InstantiationBinding constructorBinding, out InstantiationBinding? serviceOnlyBinding) + where T : IReadOnlyTypeBase { var maxServiceParams = 0; var maxServiceOnlyParams = 0; @@ -98,7 +120,7 @@ private void GetBindings( // Trying to find the constructor with the most service properties // followed by the least scalar property parameters if (TryBindConstructor( - entityType, constructor, bind, out var binding, out var failures)) + entityType, constructor, bindToProperty, bind, out var binding, out var failures)) { var serviceParamCount = binding.ParameterBindings.OfType().Count(); var propertyParamCount = binding.ParameterBindings.Count - serviceParamCount; @@ -192,7 +214,8 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out binding, out unboundParameters); @@ -210,40 +233,63 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out binding, out unboundParameters); - private bool TryBindConstructor( - IReadOnlyEntityType entityType, + private bool TryBindConstructor( + T entityType, ConstructorInfo constructor, - Func bind, + Func bindToProperty, + Func bind, [NotNullWhen(true)] out InstantiationBinding? binding, [NotNullWhen(false)] out IEnumerable? unboundParameters) + where T : IReadOnlyTypeBase { - IEnumerable<(ParameterInfo Parameter, ParameterBinding? Binding)> bindings - = constructor.GetParameters().Select( - p => (p, string.IsNullOrEmpty(p.Name) - ? null - : _propertyFactory.FindParameter((IEntityType)entityType, p.ParameterType, p.Name) - ?? bind(_factories.FindFactory(p.ParameterType, p.Name), entityType, p.ParameterType, p.Name))) - .ToList(); - - if (bindings.Any(b => b.Binding == null)) + var bindings = new List(); + List? unboundParametersList = null; + foreach (var parameter in constructor.GetParameters()) + { + var parameterBinding = BindParameter(entityType, bindToProperty, bind, parameter); + if (parameterBinding == null) + { + unboundParametersList ??= new List(); + unboundParametersList.Add(parameter); + } + else + { + bindings.Add(parameterBinding); + } + } + + if (unboundParametersList != null) { - unboundParameters = bindings.Where(b => b.Binding == null).Select(b => b.Parameter); + unboundParameters = unboundParametersList; binding = null; return false; } unboundParameters = null; - binding = new ConstructorBinding(constructor, bindings.Select(b => b.Binding).ToList()!); + binding = new ConstructorBinding(constructor, bindings); return true; } - private static string FormatConstructorString(IReadOnlyEntityType entityType, InstantiationBinding binding) + private ParameterBinding? BindParameter( + T entityType, + Func bindToProperty, + Func bind, + ParameterInfo p) + where T : IReadOnlyTypeBase + => string.IsNullOrEmpty(p.Name) + ? null + : bindToProperty(_propertyFactory, entityType, p.ParameterType, p.Name) + ?? bind(_factories.FindFactory(p.ParameterType, p.Name), entityType, p.ParameterType, p.Name); + + private static string FormatConstructorString(T entityType, InstantiationBinding binding) + where T : IReadOnlyTypeBase => entityType.ClrType.ShortDisplayName() + "(" + string.Join(", ", binding.ParameterBindings.Select(b => b.ParameterType.ShortDisplayName())) diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 2bcaf6f5025..747a753e470 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class EntityType : TypeBase, IMutableEntityType, IConventionEntityType, IRuntimeEntityType { - private const string DynamicProxyGenAssemblyName = "DynamicProxyGenAssembly2"; + internal const string DynamicProxyGenAssemblyName = "DynamicProxyGenAssembly2"; private readonly SortedSet _foreignKeys = new(ForeignKeyComparer.Instance); @@ -32,6 +32,9 @@ private readonly SortedDictionary _serviceProperties private readonly SortedDictionary _properties; + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + private readonly SortedDictionary, Index> _unnamedIndexes = new(PropertyListComparer.Instance); @@ -49,6 +52,7 @@ private readonly SortedDictionary _triggers private bool? _isKeyless; private bool _isOwned; private EntityType? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); private ChangeTrackingStrategy? _changeTrackingStrategy; private InternalEntityTypeBuilder? _builder; @@ -168,7 +172,19 @@ public virtual InternalEntityTypeBuilder Builder /// 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 IsInModel + protected override IConventionTypeBaseBuilder BaseBuilder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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 override bool IsInModel => _builder is not null; /// @@ -197,7 +213,7 @@ public virtual EntityType? BaseType /// public virtual bool IsKeyless { - get => RootType()._isKeyless ?? false; + get => GetRootType()._isKeyless ?? false; set => SetIsKeyless(value, ConfigurationSource.Explicit); } @@ -258,7 +274,7 @@ private string DisplayName() if (_baseType != null) { throw new InvalidOperationException( - CoreStrings.DerivedEntityTypeHasNoKey(DisplayName(), RootType().DisplayName())); + CoreStrings.DerivedEntityTypeHasNoKey(DisplayName(), GetRootType().DisplayName())); } if (_keys.Count != 0) @@ -365,9 +381,9 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio CoreStrings.DuplicatePropertiesOnBase( DisplayName(), newBaseType.DisplayName(), - ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(), + conflictingMember.DeclaringType.DisplayName(), conflictingMember.Name, - ((IReadOnlyTypeBase)baseProperty.DeclaringType).DisplayName(), + baseProperty.DeclaringType.DisplayName(), baseProperty.Name)); } @@ -381,42 +397,6 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio return (EntityType?)Model.ConventionDispatcher.OnEntityTypeBaseTypeChanged(Builder, newBaseType, originalBaseType); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void OnTypeRemoved() - { - if (_foreignKeys.Count > 0) - { - foreach (var foreignKey in GetDeclaredForeignKeys().ToList()) - { - if (foreignKey.PrincipalEntityType != this) - { - RemoveForeignKey(foreignKey); - } - } - } - - if (_skipNavigations.Count > 0) - { - foreach (var skipNavigation in GetDeclaredSkipNavigations().ToList()) - { - if (skipNavigation.TargetEntityType != this) - { - RemoveSkipNavigation(skipNavigation); - } - } - } - - _builder = null; - _baseType?._directlyDerivedTypes.Remove(this); - - Model.ConventionDispatcher.OnEntityTypeRemoved(Model.Builder, this); - } - /// /// 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 @@ -431,8 +411,6 @@ public virtual void OnTypeRemoved() private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); - private readonly SortedSet _directlyDerivedTypes = new(EntityTypeFullNameComparer.Instance); - /// /// 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 @@ -518,7 +496,7 @@ private bool InheritsFrom(EntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual EntityType RootType() + public virtual EntityType GetRootType() => (EntityType)((IReadOnlyEntityType)this).GetRootType(); /// @@ -530,6 +508,42 @@ public virtual EntityType RootType() public override string ToString() => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + /// 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 override void OnTypeRemoved() + { + if (_foreignKeys.Count > 0) + { + foreach (var foreignKey in GetDeclaredForeignKeys().ToList()) + { + if (foreignKey.PrincipalEntityType != this) + { + RemoveForeignKey(foreignKey); + } + } + } + + if (_skipNavigations.Count > 0) + { + foreach (var skipNavigation in GetDeclaredSkipNavigations().ToList()) + { + if (skipNavigation.TargetEntityType != this) + { + RemoveSkipNavigation(skipNavigation); + } + } + } + + _builder = null; + _baseType?._directlyDerivedTypes.Remove(this); + + Model.ConventionDispatcher.OnEntityTypeRemoved(Model.Builder, this); + } + /// /// Runs the conventions when an annotation was set or removed. /// @@ -549,8 +563,9 @@ public override string ToString() /// 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 IEnumerable GetMembers() - => GetProperties().Cast() + public virtual IEnumerable GetMembers() + => GetProperties().Cast() + .Concat(GetComplexProperties()) .Concat(GetServiceProperties()) .Concat(GetNavigations()) .Concat(GetSkipNavigations()); @@ -561,8 +576,9 @@ public virtual IEnumerable GetMembers() /// 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 IEnumerable GetDeclaredMembers() - => GetDeclaredProperties().Cast() + public virtual IEnumerable GetDeclaredMembers() + => GetDeclaredProperties().Cast() + .Concat(GetDeclaredComplexProperties()) .Concat(GetDeclaredServiceProperties()) .Concat(GetDeclaredNavigations()) .Concat(GetDeclaredSkipNavigations()); @@ -573,8 +589,9 @@ public virtual IEnumerable GetDeclaredMembers() /// 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 IEnumerable FindMembersInHierarchy(string name) - => FindPropertiesInHierarchy(name).Cast() + public virtual IEnumerable FindMembersInHierarchy(string name) + => FindPropertiesInHierarchy(name).Cast() + .Concat(FindComplexPropertiesInHierarchy(name)) .Concat(FindServicePropertiesInHierarchy(name)) .Concat(FindNavigationsInHierarchy(name)) .Concat(FindSkipNavigationsInHierarchy(name)); @@ -608,7 +625,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) if (_baseType != null) { - throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), RootType().DisplayName())); + throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), GetRootType().DisplayName())); } var oldPrimaryKey = _primaryKey; @@ -1656,6 +1673,7 @@ public virtual IEnumerable GetNavigations() /// public virtual SkipNavigation? AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, EntityType targetEntityType, bool collection, @@ -1671,7 +1689,7 @@ public virtual IEnumerable GetNavigations() { throw new InvalidOperationException( CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), ((IReadOnlyTypeBase)duplicateProperty.DeclaringType).DisplayName())); + name, DisplayName(), duplicateProperty.DeclaringType.DisplayName())); } if (memberInfo != null) @@ -1700,6 +1718,7 @@ public virtual IEnumerable GetNavigations() var skipNavigation = new SkipNavigation( name, + navigationType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, @@ -2983,7 +3002,7 @@ public virtual IEnumerable GetDerivedServiceProperties() #endregion - #region Triggers + #region Complex properties /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2991,26 +3010,24 @@ public virtual IEnumerable GetDerivedServiceProperties() /// 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 Trigger? AddTrigger( - string modelName, + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, ConfigurationSource configurationSource) { - Check.NotEmpty(modelName, nameof(modelName)); - Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureMutable(); - - if (_triggers.ContainsKey(modelName)) - { - throw new InvalidOperationException( - CoreStrings.DuplicateTrigger( - modelName, DisplayName(), DisplayName())); - } - - var trigger = new Trigger(modelName, this, configurationSource); - - _triggers.Add(modelName, trigger); + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); - return (Trigger?)Model.ConventionDispatcher.OnTriggerAdded(trigger.Builder)?.Metadata; + return AddComplexProperty( + name, + propertyType, + memberInfo: null, + targetType, + collection, + configurationSource); } /// @@ -3019,14 +3036,18 @@ public virtual IEnumerable GetDerivedServiceProperties() /// 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 Trigger? FindDeclaredTrigger(string modelName) - { - Check.NotEmpty(modelName, nameof(modelName)); - - return _triggers.TryGetValue(modelName, out var trigger) - ? trigger - : null; - } + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + MemberInfo memberInfo, + bool collection, + ConfigurationSource configurationSource) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), + memberInfo.GetMemberType(), + memberInfo, + memberInfo.GetMemberType(), + collection, + configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3034,8 +3055,28 @@ public virtual IEnumerable GetDerivedServiceProperties() /// 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 IEnumerable GetDeclaredTriggers() - => _triggers.Values; + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + string name, + bool collection, + ConfigurationSource configurationSource) + { + MemberInfo? clrMember; + if (IsPropertyBag) + { + clrMember = FindIndexerPropertyInfo()!; + } + else + { + clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); + } + } + + return AddComplexProperty(clrMember, collection, configurationSource); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3043,27 +3084,90 @@ public virtual IEnumerable GetDeclaredTriggers() /// 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 Trigger? RemoveTrigger(string modelName) + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) { - Check.NotEmpty(modelName, nameof(modelName)); + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); EnsureMutable(); - if (!_triggers.TryGetValue(modelName, out var trigger)) + var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); + if (conflictingMember != null) { - return null; + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + name, DisplayName(), + conflictingMember.DeclaringType.DisplayName())); } - _triggers.Remove(modelName); + if (memberInfo != null) + { + propertyType = ValidateClrMember(name, memberInfo) + ?? propertyType; - trigger.SetRemovedFromModel(); + if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + } + else if (IsPropertyBag) + { + memberInfo = FindIndexerPropertyInfo(); + } + else + { + memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + } - return (Trigger?)Model.ConventionDispatcher.OnTriggerRemoved(Builder, trigger); - } + if (memberInfo != null) + { + if (propertyType != memberInfo.GetMemberType() + && memberInfo != FindIndexerPropertyInfo()) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongClrType( + name, + DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + propertyType.ShortDisplayName())); + } - #endregion + ComplexProperty.IsCompatible( + name, + memberInfo, + this, + targetType, + collection, + shouldThrow: true); + } - #region Ignore + var property = new ComplexProperty( + name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, + targetType, collection, configurationSource); + + _complexProperties.Add(property.Name, property); + + if (Model.Configuration != null) + { + using (Model.ConventionDispatcher.DelayConventions()) + { + property = (ComplexProperty)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)!.Metadata; + Model.Configuration.ConfigureComplexProperty(property); + return property; + } + } + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)?.Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3071,12 +3175,8 @@ public virtual IEnumerable GetDeclaredTriggers() /// 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 override ConfigurationSource? FindIgnoredConfigurationSource(string name) - { - var ignoredSource = FindDeclaredIgnoredConfigurationSource(name); - - return BaseType == null ? ignoredSource : BaseType.FindIgnoredConfigurationSource(name).Max(ignoredSource); - } + public virtual ComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3084,12 +3184,30 @@ public virtual IEnumerable GetDeclaredTriggers() /// 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 override string? OnTypeMemberIgnored(string name) - => Model.ConventionDispatcher.OnEntityTypeMemberIgnored(Builder, name); + public virtual ComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) + ? property + : null; - #endregion + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; - #region Data + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedComplexProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3097,19 +3215,228 @@ public virtual IEnumerable GetDeclaredTriggers() /// 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 IEnumerable> GetSeedData(bool providerValues = false) + public virtual IEnumerable FindDerivedComplexProperties(string propertyName) { - if (_data == null - || _data.Count == 0) - { - return Enumerable.Empty>(); - } + Check.NotNull(propertyName, nameof(propertyName)); - List? propertiesList = null; - Dictionary? propertiesMap = null; - var data = new List>(); - var valueConverters = new Dictionary(StringComparer.Ordinal); - foreach (var rawSeed in _data) + return _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : (IEnumerable)GetDerivedTypes() + .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedComplexPropertiesInclusive(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindDeclaredComplexProperty(propertyName)) + : ToEnumerable(FindDeclaredComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindComplexProperty(propertyName)) + : ToEnumerable(FindComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindDeclaredComplexProperty(name); + return property == null + ? null + : RemoveComplexProperty(property); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(ComplexProperty property) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongType( + property.Name, + DisplayName(), + property.DeclaringType.DisplayName())); + } + + CheckPropertyNotInUse(property); + var removed = _complexProperties.Remove(property.Name); + Check.DebugAssert(removed, "removed is false"); + + property.SetRemovedFromModel(); + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyRemoved(Builder, property); + } + + private void CheckPropertyNotInUse(ComplexProperty property) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetComplexProperties() + => _baseType != null + ? _baseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + #endregion + + #region Triggers + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Trigger? AddTrigger( + string modelName, + ConfigurationSource configurationSource) + { + Check.NotEmpty(modelName, nameof(modelName)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (_triggers.ContainsKey(modelName)) + { + throw new InvalidOperationException( + CoreStrings.DuplicateTrigger( + modelName, DisplayName(), DisplayName())); + } + + var trigger = new Trigger(modelName, this, configurationSource); + + _triggers.Add(modelName, trigger); + + return (Trigger?)Model.ConventionDispatcher.OnTriggerAdded(trigger.Builder)?.Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Trigger? FindDeclaredTrigger(string modelName) + { + Check.NotEmpty(modelName, nameof(modelName)); + + return _triggers.TryGetValue(modelName, out var trigger) + ? trigger + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredTriggers() + => _triggers.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Trigger? RemoveTrigger(string modelName) + { + Check.NotEmpty(modelName, nameof(modelName)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (!_triggers.TryGetValue(modelName, out var trigger)) + { + return null; + } + + _triggers.Remove(modelName); + + trigger.SetRemovedFromModel(); + + return (Trigger?)Model.ConventionDispatcher.OnTriggerRemoved(Builder, trigger); + } + + #endregion + + #region Ignore + + /// + /// 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 override ConfigurationSource? FindIgnoredConfigurationSource(string name) + { + var ignoredSource = FindDeclaredIgnoredConfigurationSource(name); + + return BaseType == null ? ignoredSource : BaseType.FindIgnoredConfigurationSource(name).Max(ignoredSource); + } + + /// + /// 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 override string? OnTypeMemberIgnored(string name) + => Model.ConventionDispatcher.OnEntityTypeMemberIgnored(Builder, name); + + #endregion + + #region Data + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable> GetSeedData(bool providerValues = false) + { + if (_data == null + || _data.Count == 0) + { + return Enumerable.Empty>(); + } + + List? propertiesList = null; + Dictionary? propertiesMap = null; + var data = new List>(); + var valueConverters = new Dictionary(StringComparer.Ordinal); + foreach (var rawSeed in _data) { var seed = new Dictionary(StringComparer.Ordinal); data.Add(seed); @@ -3637,30 +3964,6 @@ IMutableModel IMutableTypeBase.Model get => Model; } - /// - /// 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. - /// - IMutableModel IMutableEntityType.Model - { - [DebuggerStepThrough] - get => Model; - } - - /// - /// 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. - /// - IConventionModel IConventionEntityType.Model - { - [DebuggerStepThrough] - get => Model; - } - /// /// 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 @@ -4439,12 +4742,13 @@ IEnumerable IEntityType.GetNavigations() [DebuggerStepThrough] IMutableSkipNavigation IMutableEntityType.AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IMutableEntityType targetEntityType, bool collection, bool onDependent) => AddSkipNavigation( - name, memberInfo, (EntityType)targetEntityType, collection, onDependent, + name, navigationType, memberInfo, (EntityType)targetEntityType, collection, onDependent, ConfigurationSource.Explicit)!; /// @@ -4456,13 +4760,14 @@ IMutableSkipNavigation IMutableEntityType.AddSkipNavigation( [DebuggerStepThrough] IConventionSkipNavigation? IConventionEntityType.AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IConventionEntityType targetEntityType, bool collection, bool onDependent, bool fromDataAnnotation) => AddSkipNavigation( - name, memberInfo, (EntityType)targetEntityType, collection, onDependent, + name, navigationType, memberInfo, (EntityType)targetEntityType, collection, onDependent, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -4921,7 +5226,7 @@ IMutableProperty IMutableEntityType.AddProperty( IMutableProperty IMutableEntityType.AddProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo) + MemberInfo memberInfo) => AddProperty( name, propertyType, memberInfo, ConfigurationSource.Explicit, ConfigurationSource.Explicit)!; @@ -5028,6 +5333,26 @@ IMutableProperty IMutableEntityType.AddProperty( IEnumerable IReadOnlyEntityType.GetDeclaredProperties() => GetDeclaredProperties(); + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutableEntityType.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionEntityType.GetDeclaredProperties() + => GetDeclaredProperties(); + /// /// 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 @@ -5331,6 +5656,255 @@ IEnumerable IEntityType.GetServiceProperties() IConventionServiceProperty? IConventionEntityType.RemoveServiceProperty(string name) => RemoveServiceProperty(name); + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableEntityType.AddComplexProperty(string name, bool collection) + => AddComplexProperty(name, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.AddComplexProperty(string name, bool collection, bool fromDataAnnotation) + => AddComplexProperty( + name, + collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableEntityType.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableEntityType.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IComplexProperty? IEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyEntityType.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutableEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutableEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetDerivedComplexProperties() + => GetDerivedComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableEntityType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableEntityType.RemoveComplexProperty(IReadOnlyProperty property) + => RemoveComplexProperty((ComplexProperty)property); + + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionEntityType.RemoveComplexProperty(IConventionComplexProperty property) + => RemoveComplexProperty((ComplexProperty)property); + /// /// 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 @@ -5599,6 +6173,7 @@ private static ParameterBinding Create(ParameterBinding parameterBinding, Entity property => (entityType.FindProperty(property.Name) ?? entityType.FindServiceProperty(property.Name) + ?? entityType.FindComplexProperty(property.Name) ?? entityType.FindNavigation(property.Name) ?? (IPropertyBase?)entityType.FindSkipNavigation(property.Name))!).ToArray()); } diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index d6697ef782d..e96b5f2f262 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; - // ReSharper disable ArgumentsStyleOther // ReSharper disable ArgumentsStyleNamedExpression namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -15,24 +13,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public static class EntityTypeExtensions { - /// - /// 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 static string DisplayName(this TypeBase entityType) - => ((IReadOnlyTypeBase)entityType).DisplayName(); - - /// - /// 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 static string ShortName(this TypeBase entityType) - => ((IReadOnlyTypeBase)entityType).ShortName(); - /// /// 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 @@ -158,16 +138,6 @@ public static bool IsInOwnershipPath(this IReadOnlyEntityType entityType, IReadO } } - /// - /// 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. - /// - [DebuggerStepThrough] - public static string GetOwnedName(this IReadOnlyTypeBase type, string simpleName, string ownershipNavigation) - => type.Name + "." + ownershipNavigation + "#" + simpleName; - /// /// 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 @@ -182,69 +152,6 @@ public static bool UseEagerSnapshots(this IReadOnlyEntityType entityType) || changeTrackingStrategy == ChangeTrackingStrategy.ChangedNotifications; } - /// - /// 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 static int StoreGeneratedCount(this IEntityType entityType) - => GetCounts(entityType).StoreGeneratedCount; - - /// - /// 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 static int RelationshipPropertyCount(this IEntityType entityType) - => GetCounts(entityType).RelationshipCount; - - /// - /// 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 static int OriginalValueCount(this IEntityType entityType) - => GetCounts(entityType).OriginalValueCount; - - /// - /// 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 static int ShadowPropertyCount(this IEntityType entityType) - => GetCounts(entityType).ShadowCount; - - /// - /// 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 static int NavigationCount(this IEntityType entityType) - => GetCounts(entityType).NavigationCount; - - /// - /// 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 static int PropertyCount(this IEntityType entityType) - => GetCounts(entityType).PropertyCount; - - /// - /// 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 static PropertyCounts GetCounts(this IEntityType entityType) - => ((IRuntimeEntityType)entityType).Counts; - /// /// 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 @@ -253,17 +160,18 @@ public static PropertyCounts GetCounts(this IEntityType entityType) /// public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) { - var index = 0; + var propertyIndex = 0; var navigationIndex = 0; + var complexPropertyIndex = 0; var originalValueIndex = 0; var shadowIndex = 0; var relationshipIndex = 0; var storeGenerationIndex = 0; - var baseCounts = entityType.BaseType?.GetCounts(); + var baseCounts = entityType.BaseType?.Counts; if (baseCounts != null) { - index = baseCounts.PropertyCount; + propertyIndex = baseCounts.PropertyCount; navigationIndex = baseCounts.NavigationCount; originalValueIndex = baseCounts.OriginalValueCount; shadowIndex = baseCounts.ShadowCount; @@ -274,7 +182,7 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) foreach (var property in entityType.GetDeclaredProperties()) { var indexes = new PropertyIndexes( - index: index++, + index: propertyIndex++, originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, shadowIndex: property.IsShadowProperty() ? shadowIndex++ : -1, relationshipIndex: property.IsKey() || property.IsForeignKey() ? relationshipIndex++ : -1, @@ -283,6 +191,18 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) ((IRuntimePropertyBase)property).PropertyIndexes = indexes; } + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + var indexes = new PropertyIndexes( + index: complexPropertyIndex++, + originalValueIndex: -1, + shadowIndex: complexProperty.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: -1, + storeGenerationIndex: -1); + + ((IRuntimePropertyBase)complexProperty).PropertyIndexes = indexes; + } + var isNotifying = entityType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot; foreach (var navigation in entityType.GetDeclaredNavigations() @@ -311,23 +231,15 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) } return new PropertyCounts( - index, + propertyIndex, navigationIndex, + complexPropertyIndex, originalValueIndex, shadowIndex, relationshipIndex, storeGenerationIndex); } - /// - /// 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 static Func GetEmptyShadowValuesFactory(this IEntityType entityType) - => ((IRuntimeEntityType)entityType).EmptyShadowValuesFactory; - /// /// 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 @@ -385,16 +297,6 @@ public static IEnumerable FindDerivedNavigations( => entityType.GetDerivedTypes().Select(t => t.FindDeclaredNavigation(navigationName)!) .Where(n => n != null); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static IEnumerable GetPropertiesAndNavigations( - this IEntityType entityType) - => entityType.GetProperties().Concat(entityType.GetNavigations()); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/IRuntimeComplexType.cs b/src/EFCore/Metadata/Internal/IRuntimeComplexType.cs new file mode 100644 index 00000000000..272627b3619 --- /dev/null +++ b/src/EFCore/Metadata/Internal/IRuntimeComplexType.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 interface IRuntimeComplexType : IComplexType, IRuntimeTypeBase +{ + /// + /// 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. + /// + IEnumerable IRuntimeTypeBase.GetSnapshottableMembers() + => GetProperties(); +} diff --git a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs index 1eb92d77e9d..1bfb9b0d5e8 100644 --- a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs @@ -11,15 +11,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public interface IRuntimeEntityType : IEntityType +public interface IRuntimeEntityType : IEntityType, IRuntimeTypeBase { /// - /// 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. + /// Gets the base type of this entity type. Returns if this is not a derived type in an inheritance + /// hierarchy. /// - PropertyCounts Counts { get; } + new IRuntimeEntityType? BaseType + => (IRuntimeEntityType?)((IEntityType)this).BaseType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,53 +34,6 @@ public interface IRuntimeEntityType : IEntityType /// 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. /// - Func OriginalValuesFactory { get; } - - /// - /// 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. - /// - Func StoreGeneratedValuesFactory { get; } - - /// - /// 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. - /// - Func TemporaryValuesFactory { get; } - - /// - /// 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. - /// - Func ShadowValuesFactory { get; } - - /// - /// 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. - /// - Func EmptyShadowValuesFactory { get; } - - /// - /// 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. - /// - ConfigurationSource? GetConstructorBindingConfigurationSource(); - - /// - /// 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. - /// - ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource(); + IEnumerable IRuntimeTypeBase.GetSnapshottableMembers() + => GetProperties().Concat(GetNavigations()); } diff --git a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs new file mode 100644 index 00000000000..6bfa4bbd60f --- /dev/null +++ b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 interface IRuntimeTypeBase : ITypeBase +{ + /// + /// 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. + /// + Func OriginalValuesFactory { get; } + + /// + /// 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. + /// + Func StoreGeneratedValuesFactory { get; } + + /// + /// 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. + /// + Func TemporaryValuesFactory { get; } + + /// + /// 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. + /// + Func ShadowValuesFactory { get; } + + /// + /// 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. + /// + Func EmptyShadowValuesFactory { get; } + + /// + /// 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. + /// + PropertyCounts Counts { get; } + + /// + /// 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. + /// + int OriginalValueCount + => Counts.OriginalValueCount; + + /// + /// 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. + /// + int PropertyCount + => Counts.PropertyCount; + + /// + /// 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. + /// + int ShadowPropertyCount + => Counts.ShadowCount; + + /// + /// 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. + /// + int StoreGeneratedCount + => Counts.StoreGeneratedCount; + + /// + /// 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. + /// + int RelationshipPropertyCount + => Counts.RelationshipCount; + + /// + /// 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. + /// + int NavigationCount + => Counts.NavigationCount; + + /// + /// 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. + /// + ConfigurationSource? GetConstructorBindingConfigurationSource(); + + /// + /// 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. + /// + ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource(); + + /// + /// 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. + /// + IEnumerable GetSnapshottableMembers() + => throw new NotImplementedException(); +} diff --git a/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs new file mode 100644 index 00000000000..f0f3bdacb73 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs @@ -0,0 +1,318 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class InternalComplexPropertyBuilder + : InternalPropertyBaseBuilder, IConventionComplexPropertyBuilder +{ + /// + /// 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 InternalComplexPropertyBuilder(ComplexProperty metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// 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. + /// + protected override InternalComplexPropertyBuilder This + => this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder ComplexTypeBuilder => Metadata.ComplexType.Builder; + + /// + /// 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 static ComplexPropertySnapshot? Detach(ComplexProperty complexProperty) + { + var complexType = complexProperty.ComplexType; + if (!complexProperty.IsInModel + || !complexType.IsInModel) + { + return null; + } + + var property = ((EntityType)complexProperty.DeclaringType).FindDeclaredComplexProperty(complexProperty.Name); + if (property == null) + { + return null; + } + + var propertyBuilder = property.Builder; + // Reset convention configuration + propertyBuilder.IsRequired(null, ConfigurationSource.Convention); + + List? detachedRelationships = null; + foreach (var relationshipToBeDetached in complexType.FundamentalEntityType.GetDeclaredForeignKeys().ToList()) + { + if (!relationshipToBeDetached.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + detachedRelationships ??= new List(); + + var detachedRelationship = InternalEntityTypeBuilder.DetachRelationship(relationshipToBeDetached, false); + if (detachedRelationship.Relationship.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation) + || relationshipToBeDetached.IsOwnership) + { + detachedRelationships.Add(detachedRelationship); + } + } + + List<(InternalKeyBuilder, ConfigurationSource?)>? detachedKeys = null; + foreach (var keyToDetach in complexType.FundamentalEntityType.GetDeclaredKeys().ToList()) + { + if (!keyToDetach.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + foreach (var relationshipToBeDetached in keyToDetach.GetReferencingForeignKeys().ToList()) + { + if (!relationshipToBeDetached.IsInModel + || !relationshipToBeDetached.DeclaringEntityType.IsInModel) + { + // Referencing type might have been removed while removing other foreign keys + continue; + } + + detachedRelationships ??= new List(); + + var detachedRelationship = InternalEntityTypeBuilder.DetachRelationship(relationshipToBeDetached, true); + if (detachedRelationship.Relationship.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation) + || relationshipToBeDetached.IsOwnership) + { + detachedRelationships.Add(detachedRelationship); + } + } + + if (!keyToDetach.IsInModel) + { + continue; + } + + detachedKeys ??= new List<(InternalKeyBuilder, ConfigurationSource?)>(); + + var detachedKey = InternalEntityTypeBuilder.DetachKey(keyToDetach); + if (detachedKey.Item1.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) + { + detachedKeys.Add(detachedKey); + } + } + + List? detachedIndexes = null; + foreach (var indexToBeDetached in complexType.FundamentalEntityType.GetDeclaredIndexes().ToList()) + { + if (!indexToBeDetached.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + detachedIndexes ??= new List(); + + var detachedIndex = InternalEntityTypeBuilder.DetachIndex(indexToBeDetached); + if (detachedIndex.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) + { + detachedIndexes.Add(detachedIndex); + } + } + + var detachedProperties = InternalComplexTypeBuilder.DetachProperties(complexType.GetDeclaredProperties().ToList()); + + return new ComplexPropertySnapshot( + complexProperty.Builder, + detachedProperties, + detachedIndexes, + detachedKeys, + detachedRelationships); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? IsRequired(bool? required, ConfigurationSource configurationSource) + { + if (configurationSource != ConfigurationSource.Explicit + && !CanSetIsRequired(required, configurationSource)) + { + return null; + } + + Metadata.SetIsNullable(!required, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource) + => (configurationSource.HasValue + && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource())) + || (Metadata.IsNullable == !required); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + IConventionComplexProperty IConventionComplexPropertyBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionComplexPropertyBuilder.IsRequired(bool? required, bool fromDataAnnotation) + => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionComplexPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAnnotation) + => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs new file mode 100644 index 00000000000..90aa5ad85ef --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs @@ -0,0 +1,1722 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class InternalComplexTypeBuilder : InternalTypeBaseBuilder, IConventionComplexTypeBuilder +{ + /// + /// 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 InternalComplexTypeBuilder(ComplexType metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? Property( + Type? propertyType, + string propertyName, + ConfigurationSource? configurationSource) + => Property(propertyType, propertyName, typeConfigurationSource: configurationSource, configurationSource: configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? Property( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) + => Property( + propertyType, propertyName, memberInfo: null, + typeConfigurationSource, + configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? Property(string propertyName, ConfigurationSource? configurationSource) + => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? Property(MemberInfo memberInfo, ConfigurationSource? configurationSource) + => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? IndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + ConfigurationSource? configurationSource) + { + var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); + } + + return Property(propertyType, propertyName, indexerPropertyInfo, configurationSource, configurationSource); + } + + private InternalComplexTypePropertyBuilder? Property( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) + { + var complexType = Metadata; + List? propertiesToDetach = null; + var existingProperty = complexType.FindProperty(propertyName); + if (existingProperty != null) + { + if (existingProperty.DeclaringComplexType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + complexType = existingProperty.DeclaringComplexType; + } + + if (InternalEntityTypeBuilder.IsCompatible(memberInfo, existingProperty) + && (propertyType == null || propertyType == existingProperty.ClrType)) + { + if (configurationSource.HasValue) + { + existingProperty.UpdateConfigurationSource(configurationSource.Value); + } + + if (propertyType != null + && typeConfigurationSource.HasValue) + { + existingProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); + } + + return existingProperty.Builder; + } + + if (memberInfo == null + || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) + { + if (existingProperty.GetTypeConfigurationSource() is ConfigurationSource existingTypeConfigurationSource + && !typeConfigurationSource.Overrides(existingTypeConfigurationSource)) + { + return null; + } + + memberInfo ??= existingProperty.PropertyInfo ?? (MemberInfo?)existingProperty.FieldInfo; + } + else if (!configurationSource.Overrides(existingProperty.GetConfigurationSource())) + { + return null; + } + + propertyType ??= existingProperty.ClrType; + + propertiesToDetach = new List { existingProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value))) + { + return null; + } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + typeConfigurationSource = ConfigurationSource.Explicit; + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredProperty(propertyName); + if (derivedProperty != null) + { + propertiesToDetach ??= new List(); + + propertiesToDetach.Add(derivedProperty); + } + } + } + + Check.DebugAssert(configurationSource is not null, "configurationSource is null"); + + InternalComplexTypePropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + + if (existingProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + foreach (var conflictingComplexProperty in Metadata.FindComplexPropertiesInHierarchy(propertyName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + } + + builder = complexType.AddProperty( + propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; + + detachedProperties?.Attach(this); + } + + return builder.Metadata.IsInModel + ? builder + : Metadata.FindProperty(propertyName)?.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanHaveProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource, + bool checkClrProperty = false) + { + var existingProperty = Metadata.FindProperty(propertyName); + return existingProperty != null + ? (InternalEntityTypeBuilder.IsCompatible(memberInfo, existingProperty) + && (propertyType == null || propertyType == existingProperty.ClrType)) + || ((memberInfo == null + || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) + && (existingProperty.GetTypeConfigurationSource() is not ConfigurationSource existingTypeConfigurationSource + || typeConfigurationSource.Overrides(existingTypeConfigurationSource))) + || configurationSource.Overrides(existingProperty.GetConfigurationSource()) + : configurationSource.HasValue + && CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value, checkClrProperty); + } + + private bool CanAddProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (propertyType == null + || Metadata.Model.Builder.CanBeConfigured(propertyType, TypeConfigurationType.Property, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindComplexPropertiesInHierarchy(propertyName) + .All( + m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + + private bool CanRemoveProperty( + ComplexTypeProperty property, + ConfigurationSource configurationSource, + bool canOverrideSameSource = true) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(property.DeclaringComplexType == Metadata, "property.DeclaringComplexType != ComplexType"); + + var currentConfigurationSource = property.GetConfigurationSource(); + return configurationSource.Overrides(currentConfigurationSource) + && (canOverrideSameSource || (configurationSource != currentConfigurationSource)); + } + + private ConfigurationSource? RemoveProperty( + ComplexTypeProperty property, + ConfigurationSource configurationSource, + bool canOverrideSameSource = true) + { + var currentConfigurationSource = property.GetConfigurationSource(); + if (!configurationSource.Overrides(currentConfigurationSource) + || !(canOverrideSameSource || (configurationSource != currentConfigurationSource))) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + var detachedRelationships = property.GetContainingForeignKeys().ToList() + .Select(InternalEntityTypeBuilder.DetachRelationship).ToList(); + + foreach (var key in property.GetContainingKeys().ToList()) + { + detachedRelationships.AddRange( + key.GetReferencingForeignKeys().ToList() + .Select(InternalEntityTypeBuilder.DetachRelationship)); + var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + + foreach (var index in property.GetContainingIndexes().ToList()) + { + var removed = index.DeclaringEntityType.Builder.HasNoIndex(index, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + + if (property.IsInModel) + { + var removedProperty = Metadata.RemoveProperty(property.Name); + Check.DebugAssert(removedProperty == property, "removedProperty != property"); + } + + foreach (var relationshipSnapshot in detachedRelationships) + { + relationshipSnapshot.Attach(); + } + } + + return currentConfigurationSource; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexIndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); + } + + return ComplexProperty(propertyType, propertyName, indexerPropertyInfo, complexType, collection, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + memberInfo.GetMemberType(), memberInfo.Name, memberInfo, targetComplexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + propertyType, propertyName, memberInfo: null, targetComplexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? targetComplexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var complexType = Metadata; + List? propertiesToDetach = null; + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + if (existingComplexProperty != null) + { + if (existingComplexProperty.DeclaringType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + complexType = (ComplexType)existingComplexProperty.DeclaringType; + } + + var existingComplexType = existingComplexProperty.ComplexType; + if (InternalEntityTypeBuilder.IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (targetComplexType == null + || existingComplexType.ClrType == targetComplexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + { + if (configurationSource.HasValue) + { + existingComplexProperty.UpdateConfigurationSource(configurationSource.Value); + } + + return existingComplexProperty.Builder; + } + + if (!configurationSource.Overrides(existingComplexProperty.GetConfigurationSource())) + { + return null; + } + + Debug.Assert(configurationSource.HasValue); + + memberInfo ??= existingComplexProperty.PropertyInfo ?? (MemberInfo?)existingComplexProperty.FieldInfo; + propertyType ??= existingComplexProperty.ClrType; + collection ??= existingComplexProperty.IsCollection; + targetComplexType ??= existingComplexType.ClrType; + + propertiesToDetach = new List { existingComplexProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddComplexProperty( + propertyName, propertyType ?? memberInfo?.GetMemberType(), targetComplexType, collection, configurationSource.Value))) + { + return null; + } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + if (collection == false) + { + targetComplexType = propertyType; + } + + if (collection == null + || targetComplexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + targetComplexType ??= collection.Value ? elementType : propertyType; + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredComplexProperty(propertyName); + if (derivedProperty != null) + { + propertiesToDetach ??= new List(); + + propertiesToDetach.Add(derivedProperty); + } + } + } + + InternalComplexPropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + if (existingComplexProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName)) + { + if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingProperty.DeclaringComplexType.RemoveProperty(conflictingProperty); + } + } + } + + builder = complexType.AddComplexProperty( + propertyName, propertyType, memberInfo, targetComplexType!, collection.Value, configurationSource.Value)!.Builder; + + if (detachedProperties != null) + { + foreach (var detachedProperty in detachedProperties) + { + detachedProperty.Attach(this); + } + } + } + + return builder.Metadata.IsInModel + ? builder + : Metadata.FindComplexProperty(propertyName)?.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanHaveComplexProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + propertyType ??= memberInfo?.GetMemberType(); + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + var existingComplexType = existingComplexProperty?.ComplexType; + return existingComplexProperty != null + ? (InternalEntityTypeBuilder.IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (complexType == null + || existingComplexType!.ClrType == complexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + || configurationSource.Overrides(existingComplexProperty.GetConfigurationSource()) + : configurationSource.HasValue + && CanAddComplexProperty(propertyName, propertyType, complexType, collection, configurationSource.Value); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanAddComplexProperty( + string propertyName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? targetType, + bool? collection, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (targetType == null || !ModelBuilder.IsIgnored(targetType, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindPropertiesInHierarchy(propertyName) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasNoComplexProperty( + ComplexProperty complexProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveComplexProperty(complexProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveComplexProperty(complexProperty); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveComplexProperty(ComplexProperty complexProperty, ConfigurationSource configurationSource) + => configurationSource.Overrides(complexProperty.GetConfigurationSource()); + + /// + /// 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 static List? DetachProperties(IReadOnlyList propertiesToDetach) + { + if (propertiesToDetach.Count == 0) + { + return null; + } + + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var snapshot = InternalComplexPropertyBuilder.Detach(propertyToDetach); + if (snapshot == null) + { + continue; + } + + detachedProperties.Add(snapshot); + + var removedProperty = ((EntityType)propertyToDetach.DeclaringType).RemoveComplexProperty(propertyToDetach); + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + + return detachedProperties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? Ignore(string name, ConfigurationSource configurationSource) + { + var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); + if (ignoredConfigurationSource.HasValue) + { + if (ignoredConfigurationSource.Value.Overrides(configurationSource)) + { + return this; + } + } + else if (!CanIgnore(name, configurationSource, shouldThrow: true)) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + Metadata.AddIgnored(name, configurationSource); + + var property = Metadata.FindProperty(name); + if (property != null) + { + Check.DebugAssert(property.DeclaringComplexType == Metadata, "property.DeclaringComplexType != ComplexType"); + + if (property.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedPropertyIgnoredWarning(property); + } + + var removedProperty = RemoveProperty(property, configurationSource); + + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + else + { + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) + { + Check.DebugAssert(complexProperty.DeclaringType == Metadata, "property.DeclaringType != ComplexType"); + + if (complexProperty.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedComplexPropertyIgnoredWarning(complexProperty); + } + + var removedComplexProperty = Metadata.RemoveComplexProperty(complexProperty); + + Check.DebugAssert(removedComplexProperty != null, "removedProperty is null"); + } + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedIgnoredSource = derivedType.FindDeclaredIgnoredConfigurationSource(name); + if (derivedIgnoredSource.HasValue) + { + if (configurationSource.Overrides(derivedIgnoredSource)) + { + derivedType.RemoveIgnored(name); + } + + continue; + } + + var derivedProperty = derivedType.FindDeclaredProperty(name); + if (derivedProperty != null) + { + derivedType.Builder.RemoveProperty( + derivedProperty, configurationSource, + canOverrideSameSource: configurationSource != ConfigurationSource.Explicit); + } + else + { + var declaredComplexProperty = derivedType.FindDeclaredComplexProperty(name); + if (declaredComplexProperty != null) + { + if (configurationSource.Overrides(declaredComplexProperty.GetConfigurationSource()) + && declaredComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.RemoveComplexProperty(declaredComplexProperty); + } + } + } + } + } + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanIgnore(string name, ConfigurationSource configurationSource) + => CanIgnore(name, configurationSource, shouldThrow: false); + + private bool CanIgnore(string name, ConfigurationSource configurationSource, bool shouldThrow) + { + var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); + if (ignoredConfigurationSource.HasValue) + { + return true; + } + + var property = Metadata.FindProperty(name); + if (property != null) + { + if (property.DeclaringComplexType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), property.DeclaringComplexType.DisplayName())); + } + + return false; + } + + if (!property.DeclaringComplexType.Builder.CanRemoveProperty( + property, configurationSource, canOverrideSameSource: true)) + { + return false; + } + } + else + { + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) + { + if (complexProperty.DeclaringType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), complexProperty.DeclaringType.DisplayName())); + } + + return false; + } + + if (!configurationSource.Overrides(complexProperty.GetConfigurationSource())) + { + return false; + } + } + } + + return true; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasBaseType( + ComplexType? baseComplexType, + ConfigurationSource configurationSource) + { + if (Metadata.BaseType == baseComplexType) + { + Metadata.SetBaseType(baseComplexType, configurationSource); + return this; + } + + if (!CanSetBaseType(baseComplexType, configurationSource)) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + ComplexTypePropertiesSnapshot? detachedProperties = null; + List? detachedComplexProperties = null; + // We use at least DataAnnotation as ConfigurationSource while removing to allow us + // to remove metadata object which were defined in derived type + // while corresponding annotations were present on properties in base type. + var configurationSourceForRemoval = ConfigurationSource.DataAnnotation.Max(configurationSource); + if (baseComplexType != null) + { + var baseMemberNames = baseComplexType.GetMembers() + .ToDictionary(m => m.Name, m => (ConfigurationSource?)m.GetConfigurationSource()); + + var propertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredProperties()), + baseMemberNames, + p => baseComplexType.FindProperty(p.Name) != null, + p => p.DeclaringComplexType.Builder.RemoveProperty(p, ConfigurationSource.Explicit)); + + if (propertiesToDetach != null) + { + detachedProperties = DetachProperties(propertiesToDetach); + } + + var complexPropertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredComplexProperties()), + baseMemberNames, + p => baseComplexType.FindComplexProperty(p.Name) != null, + p => ((ComplexType)p.DeclaringType).RemoveComplexProperty(p)); + + if (complexPropertiesToDetach != null) + { + detachedComplexProperties = new List(); + foreach (var complexPropertyToDetach in complexPropertiesToDetach) + { + detachedComplexProperties.Add(InternalComplexPropertyBuilder.Detach(complexPropertyToDetach)!); + } + } + + foreach (var ignoredMember in Metadata.GetIgnoredMembers().ToList()) + { + if (baseComplexType.FindIgnoredConfigurationSource(ignoredMember) + .Overrides(Metadata.FindDeclaredIgnoredConfigurationSource(ignoredMember))) + { + Metadata.RemoveIgnored(ignoredMember); + } + } + + baseComplexType.UpdateConfigurationSource(configurationSource); + } + + Metadata.SetBaseType(baseComplexType, configurationSource); + + if (detachedComplexProperties != null) + { + foreach (var detachedComplexProperty in detachedComplexProperties) + { + detachedComplexProperty.Attach( + ((ComplexType)detachedComplexProperty.ComplexProperty.DeclaringType).Builder); + } + } + + detachedProperties?.Attach(this); + } + + return this; + + List? FindConflictingMembers( + IEnumerable derivedMembers, + Dictionary baseMemberNames, + Func compatibleWithBaseMember, + Action removeMember) + where T : PropertyBase + { + List? membersToBeDetached = null; + List? membersToBeRemoved = null; + foreach (var member in derivedMembers) + { + ConfigurationSource? baseConfigurationSource = null; + if ((!member.GetConfigurationSource().OverridesStrictly( + baseComplexType.FindIgnoredConfigurationSource(member.Name)) + && member.GetConfigurationSource() != ConfigurationSource.Explicit) + || (baseMemberNames.TryGetValue(member.Name, out baseConfigurationSource) + && baseConfigurationSource.Overrides(member.GetConfigurationSource()) + && !compatibleWithBaseMember(member))) + { + if (baseConfigurationSource == ConfigurationSource.Explicit + && configurationSource == ConfigurationSource.Explicit + && member.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.DuplicatePropertiesOnBase( + Metadata.DisplayName(), + baseComplexType.DisplayName(), + ((IReadOnlyTypeBase)member.DeclaringType).DisplayName(), + member.Name, + baseComplexType.DisplayName(), + member.Name)); + } + + membersToBeRemoved ??= new List(); + + membersToBeRemoved.Add(member); + continue; + } + + if (baseConfigurationSource != null) + { + membersToBeDetached ??= new List(); + + membersToBeDetached.Add(member); + } + } + + if (membersToBeRemoved != null) + { + foreach (var memberToBeRemoved in membersToBeRemoved) + { + removeMember(memberToBeRemoved); + } + } + + return membersToBeDetached; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetBaseType(ComplexType? baseComplexType, ConfigurationSource configurationSource) + { + if (Metadata.BaseType == baseComplexType + || configurationSource == ConfigurationSource.Explicit) + { + return true; + } + + if (!configurationSource.Overrides(Metadata.GetBaseTypeConfigurationSource())) + { + return false; + } + + if (baseComplexType == null) + { + return true; + } + + var baseMembers = baseComplexType.GetMembers() + .Where(m => m.GetConfigurationSource() == ConfigurationSource.Explicit) + .ToDictionary(m => m.Name); + + foreach (var derivedMember in Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredMembers())) + { + if (derivedMember.GetConfigurationSource() == ConfigurationSource.Explicit + && baseMembers.TryGetValue(derivedMember.Name, out var baseMember)) + { + switch (derivedMember) + { + case IReadOnlyProperty: + return baseMember is IReadOnlyComplexTypeProperty; + case IReadOnlyNavigation derivedNavigation: + return baseMember is IReadOnlyNavigation baseNavigation + && derivedNavigation.TargetEntityType == baseNavigation.TargetEntityType; + case IReadOnlyServiceProperty: + return baseMember is IReadOnlyServiceProperty; + case IReadOnlySkipNavigation derivedSkipNavigation: + return baseMember is IReadOnlySkipNavigation baseSkipNavigation + && derivedSkipNavigation.TargetEntityType == baseSkipNavigation.TargetEntityType; + } + } + } + + return true; + } + + /// + /// 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 static ComplexTypePropertiesSnapshot? DetachProperties(IReadOnlyList propertiesToDetach) + { + if (propertiesToDetach.Count == 0) + { + return null; + } + + List? detachedRelationships = null; + foreach (var propertyToDetach in propertiesToDetach) + { + foreach (var relationship in propertyToDetach.GetContainingForeignKeys().ToList()) + { + detachedRelationships ??= new List(); + + detachedRelationships.Add(InternalEntityTypeBuilder.DetachRelationship(relationship)); + } + } + + var detachedIndexes = InternalEntityTypeBuilder.DetachIndexes(propertiesToDetach.SelectMany(p => p.GetContainingIndexes()).Distinct()); + + var keysToDetach = propertiesToDetach.SelectMany(p => p.GetContainingKeys()).Distinct().ToList(); + foreach (var key in keysToDetach) + { + foreach (var referencingForeignKey in key.GetReferencingForeignKeys().ToList()) + { + detachedRelationships ??= new List(); + + detachedRelationships.Add(InternalEntityTypeBuilder.DetachRelationship(referencingForeignKey)); + } + } + + var detachedKeys = InternalEntityTypeBuilder.DetachKeys(keysToDetach); + + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var property = propertyToDetach.DeclaringComplexType.FindDeclaredProperty(propertyToDetach.Name); + if (property != null) + { + var propertyBuilder = property.Builder; + // Reset convention configuration + propertyBuilder.ValueGenerated(null, ConfigurationSource.Convention); + propertyBuilder.AfterSave(null, ConfigurationSource.Convention); + propertyBuilder.BeforeSave(null, ConfigurationSource.Convention); + ConfigurationSource? removedConfigurationSource; + if (property.DeclaringComplexType.IsInModel) + { + removedConfigurationSource = property.DeclaringComplexType.Builder + .RemoveProperty(property, property.GetConfigurationSource()); + } + else + { + removedConfigurationSource = property.GetConfigurationSource(); + property.DeclaringComplexType.RemoveProperty(property.Name); + } + + Check.DebugAssert(removedConfigurationSource.HasValue, "removedConfigurationSource.HasValue is false"); + detachedProperties.Add(propertyBuilder); + } + } + + return new ComplexTypePropertiesSnapshot(detachedProperties, detachedIndexes, detachedKeys, detachedRelationships); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetOrCreateProperties( + IReadOnlyList? propertyNames, + ConfigurationSource? configurationSource, + IReadOnlyList? referencedProperties = null, + bool required = false, + bool useDefaultType = false) + { + if (propertyNames == null) + { + return null; + } + + if (referencedProperties != null + && referencedProperties.Count != propertyNames.Count) + { + referencedProperties = null; + } + + var propertyList = new List(); + for (var i = 0; i < propertyNames.Count; i++) + { + var propertyName = propertyNames[i]; + var property = Metadata.FindProperty(propertyName); + if (property == null) + { + var type = referencedProperties == null + ? useDefaultType + ? typeof(int) + : null + : referencedProperties[i].ClrType; + + if (!configurationSource.HasValue) + { + return null; + } + + var propertyBuilder = Property( + required + ? type + : type?.MakeNullable(), + propertyName, + typeConfigurationSource: null, + configurationSource.Value); + + if (propertyBuilder == null) + { + return null; + } + + property = propertyBuilder.Metadata; + } + else if (configurationSource.HasValue) + { + if (ConfigurationSource.Convention.Overrides(property.GetTypeConfigurationSource()) + && (property.IsShadowProperty() || property.IsIndexerProperty()) + && (!property.IsNullable || (required && property.GetIsNullableConfigurationSource() == null)) + && property.ClrType.IsNullableType()) + { + property = property.DeclaringComplexType.Builder.Property( + property.ClrType.MakeNullable(false), + property.Name, + configurationSource.Value)! + .Metadata; + } + else + { + property = property.DeclaringComplexType.Builder.Property(property.Name, configurationSource.Value)! + .Metadata; + } + } + + propertyList.Add(property); + } + + return propertyList; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetOrCreateProperties( + IEnumerable? clrMembers, + ConfigurationSource? configurationSource) + { + if (clrMembers == null) + { + return null; + } + + var list = new List(); + foreach (var propertyInfo in clrMembers) + { + var propertyBuilder = Property(propertyInfo, configurationSource); + if (propertyBuilder == null) + { + return null; + } + + list.Add(propertyBuilder.Metadata); + } + + return list; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetActualProperties( + IReadOnlyList? properties, + ConfigurationSource? configurationSource) + { + if (properties == null) + { + return null; + } + + var actualProperties = new ComplexTypeProperty[properties.Count]; + for (var i = 0; i < actualProperties.Length; i++) + { + var property = properties[i]; + var typeConfigurationSource = property.GetTypeConfigurationSource(); + var builder = property.IsInModel && property.DeclaringComplexType.IsAssignableFrom(Metadata) + ? property.Builder + : Property( + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? property.ClrType : null, + property.Name, + property.GetIdentifyingMemberInfo(), + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, + configurationSource); + if (builder == null) + { + return null; + } + + actualProperties[i] = builder.Metadata; + } + + return actualProperties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + { + if (CanSetChangeTrackingStrategy(changeTrackingStrategy, configurationSource)) + { + Metadata.SetChangeTrackingStrategy(changeTrackingStrategy, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetChangeTrackingStrategyConfigurationSource()) + || Metadata.GetChangeTrackingStrategy() == changeTrackingStrategy; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + if (CanSetConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConstructorBinding(InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetConstructorBindingConfigurationSource()) + || Metadata.ConstructorBinding == constructorBinding; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + if (CanSetServiceOnlyConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetServiceOnlyConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetServiceOnlyConstructorBindingConfigurationSource()) + || Metadata.ServiceOnlyConstructorBinding == constructorBinding; + + IConventionTypeBase IConventionTypeBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + IConventionComplexType IConventionComplexTypeBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypePropertyBuilder? IConventionComplexTypeBuilder.Property( + Type propertyType, + string propertyName, + bool setTypeConfigurationSource, + bool fromDataAnnotation) + => Property( + propertyType, + propertyName, setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypePropertyBuilder? IConventionComplexTypeBuilder.Property(MemberInfo memberInfo, bool fromDataAnnotation) + => Property(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveProperty( + Type? propertyType, + string propertyName, + bool fromDataAnnotation) + => CanHaveProperty( + propertyType, + propertyName, + null, + propertyType != null + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation) + => CanHaveProperty( + memberInfo.GetMemberType(), + memberInfo.Name, + memberInfo, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypePropertyBuilder? IConventionComplexTypeBuilder.IndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation) + => Property( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveIndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation) + => CanHaveProperty( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoProperty(IConventionComplexTypeProperty property, bool fromDataAnnotation) + => RemoveProperty( + (ComplexTypeProperty)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) == null + ? null + : this; + + /// + /// 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. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanRemoveProperty(IConventionComplexTypeProperty property, bool fromDataAnnotation) + => CanRemoveProperty( + (ComplexTypeProperty)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionComplexTypeBuilder.ComplexProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType, + propertyName, + memberInfo: null, + targetComplexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionComplexTypeBuilder.ComplexProperty( + MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType: memberInfo.GetMemberType(), + propertyName: memberInfo.Name, + memberInfo: memberInfo, + targetComplexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveComplexProperty( + Type? propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + memberInfo: null, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveComplexProperty(MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + memberInfo.GetMemberType(), + memberInfo.Name, + memberInfo, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionComplexTypeBuilder.ComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexIndexerProperty( + propertyType, + propertyName, + complexType, + collection: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanHaveComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoComplexProperty( + IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => HasNoComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => CanRemoveComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.IsIgnored(string memberName, bool fromDataAnnotation) + => IsIgnored(memberName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.Ignore(string memberName, bool fromDataAnnotation) + => Ignore(memberName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.Ignore(string name, bool fromDataAnnotation) + => Ignore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanIgnore(string name, bool fromDataAnnotation) + => CanIgnore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => HasChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanSetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => CanSetChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionComplexTypeBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexTypePropertyBuilder.cs new file mode 100644 index 00000000000..9951818e645 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexTypePropertyBuilder.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class InternalComplexTypePropertyBuilder : + InternalPrimitivePropertyBaseBuilder, + IConventionComplexTypePropertyBuilder +{ + /// + /// 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 InternalComplexTypePropertyBuilder(ComplexTypeProperty property, InternalModelBuilder modelBuilder) + : base(property, modelBuilder) + { + } + + /// + /// 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. + /// + protected override InternalComplexTypePropertyBuilder This + => this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypePropertyBuilder? Attach(InternalComplexTypeBuilder complexTypeBuilder) + { + var newProperty = complexTypeBuilder.Metadata.FindProperty(Metadata.Name); + InternalComplexTypePropertyBuilder? newPropertyBuilder; + var configurationSource = Metadata.GetConfigurationSource(); + var typeConfigurationSource = Metadata.GetTypeConfigurationSource(); + if (newProperty != null + && (newProperty.GetConfigurationSource().Overrides(configurationSource) + || newProperty.GetTypeConfigurationSource().Overrides(typeConfigurationSource) + || (Metadata.ClrType == newProperty.ClrType + && Metadata.GetIdentifyingMemberInfo()?.Name == newProperty.GetIdentifyingMemberInfo()?.Name))) + { + newPropertyBuilder = newProperty.Builder; + newProperty.UpdateConfigurationSource(configurationSource); + if (typeConfigurationSource.HasValue) + { + newProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); + } + } + else + { + var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo(); + + newPropertyBuilder = Metadata.IsIndexerProperty() + ? complexTypeBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) + : identifyingMemberInfo == null + ? complexTypeBuilder.Property( + Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) + : complexTypeBuilder.Property(identifyingMemberInfo, configurationSource); + + if (newPropertyBuilder is null) + { + return null; + } + } + + if (newProperty == Metadata) + { + return newPropertyBuilder; + } + + newPropertyBuilder.MergeAnnotationsFrom(Metadata); + + var oldBeforeSaveBehaviorConfigurationSource = Metadata.GetBeforeSaveBehaviorConfigurationSource(); + if (oldBeforeSaveBehaviorConfigurationSource.HasValue) + { + newPropertyBuilder.BeforeSave( + Metadata.GetBeforeSaveBehavior(), + oldBeforeSaveBehaviorConfigurationSource.Value); + } + + var oldAfterSaveBehaviorConfigurationSource = Metadata.GetAfterSaveBehaviorConfigurationSource(); + if (oldAfterSaveBehaviorConfigurationSource.HasValue) + { + newPropertyBuilder.AfterSave( + Metadata.GetAfterSaveBehavior(), + oldAfterSaveBehaviorConfigurationSource.Value); + } + + var oldIsNullableConfigurationSource = Metadata.GetIsNullableConfigurationSource(); + if (oldIsNullableConfigurationSource.HasValue) + { + newPropertyBuilder.IsRequired(!Metadata.IsNullable, oldIsNullableConfigurationSource.Value); + } + + var oldIsConcurrencyTokenConfigurationSource = Metadata.GetIsConcurrencyTokenConfigurationSource(); + if (oldIsConcurrencyTokenConfigurationSource.HasValue) + { + newPropertyBuilder.IsConcurrencyToken( + Metadata.IsConcurrencyToken, + oldIsConcurrencyTokenConfigurationSource.Value); + } + + var oldValueGeneratedConfigurationSource = Metadata.GetValueGeneratedConfigurationSource(); + if (oldValueGeneratedConfigurationSource.HasValue) + { + newPropertyBuilder.ValueGenerated(Metadata.ValueGenerated, oldValueGeneratedConfigurationSource.Value); + } + + var oldPropertyAccessModeConfigurationSource = Metadata.GetPropertyAccessModeConfigurationSource(); + if (oldPropertyAccessModeConfigurationSource.HasValue) + { + newPropertyBuilder.UsePropertyAccessMode( + ((IReadOnlyProperty)Metadata).GetPropertyAccessMode(), oldPropertyAccessModeConfigurationSource.Value); + } + + var oldFieldInfoConfigurationSource = Metadata.GetFieldInfoConfigurationSource(); + if (oldFieldInfoConfigurationSource.HasValue + && newPropertyBuilder.CanSetField(Metadata.FieldInfo, oldFieldInfoConfigurationSource)) + { + newPropertyBuilder.HasField(Metadata.FieldInfo, oldFieldInfoConfigurationSource.Value); + } + + var oldTypeMappingConfigurationSource = Metadata.GetTypeMappingConfigurationSource(); + if (oldTypeMappingConfigurationSource.HasValue + && newPropertyBuilder.CanSetTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource)) + { + newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value); + } + + return newPropertyBuilder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + IConventionComplexTypeProperty IConventionComplexTypePropertyBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } +} diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 8a406133a34..cf823812156 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalEntityTypeBuilder : AnnotatableBuilder, IConventionEntityTypeBuilder +public class InternalEntityTypeBuilder : InternalTypeBaseBuilder, IConventionEntityTypeBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -26,20 +26,6 @@ public InternalEntityTypeBuilder(EntityType metadata, InternalModelBuilder model { } - /// - /// 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. - /// - [DebuggerStepThrough] - IConventionKeyBuilder? IConventionEntityTypeBuilder.PrimaryKey( - IReadOnlyList? propertyNames, - bool fromDataAnnotation) - => PrimaryKey( - propertyNames, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -396,7 +382,13 @@ public virtual bool CanRemoveKey(Key key, ConfigurationSource configurationSourc return detachedKeys; } - private static (InternalKeyBuilder, ConfigurationSource?) DetachKey(Key keyToDetach) + /// + /// 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 static (InternalKeyBuilder, ConfigurationSource?) DetachKey(Key keyToDetach) { var entityTypeBuilder = keyToDetach.DeclaringEntityType.Builder; var keyBuilder = keyToDetach.Builder; @@ -648,6 +640,14 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource) } } + foreach (var conflictingComplexProperty in Metadata.FindComplexPropertiesInHierarchy(propertyName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName)) { if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) @@ -740,13 +740,19 @@ private bool CanAddProperty( || propertyType != null || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) && Metadata.FindServicePropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(propertyName)) .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) - .All( - m => configurationSource.Overrides(m.GetConfigurationSource()) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) && m.GetConfigurationSource() != ConfigurationSource.Explicit); - private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingProperty) + /// + /// 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 static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existingProperty) { if (newMemberInfo == null) { @@ -764,25 +770,28 @@ private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingPro return true; } - var declaringType = (IMutableEntityType)existingProperty.DeclaringType; - if (!newMemberInfo.DeclaringType!.IsAssignableFrom(declaringType.ClrType)) + if (!newMemberInfo.DeclaringType!.IsAssignableFrom(existingProperty.DeclaringType.ClrType)) { return existingMemberInfo.IsOverriddenBy(newMemberInfo); } IMutableEntityType? existingMemberDeclaringEntityType = null; - foreach (var baseType in declaringType.GetAllBaseTypes()) + var declaringType = existingProperty.DeclaringType as IMutableEntityType; + if (declaringType != null) { - if (newMemberInfo.DeclaringType == baseType.ClrType) + foreach (var baseType in declaringType.GetAllBaseTypes()) { - return existingMemberDeclaringEntityType != null - && existingMemberInfo.IsOverriddenBy(newMemberInfo); - } + if (newMemberInfo.DeclaringType == baseType.ClrType) + { + return existingMemberDeclaringEntityType != null + && existingMemberInfo.IsOverriddenBy(newMemberInfo); + } - if (existingMemberDeclaringEntityType == null - && existingMemberInfo.DeclaringType == baseType.ClrType) - { - existingMemberDeclaringEntityType = baseType; + if (existingMemberDeclaringEntityType == null + && existingMemberInfo.DeclaringType == baseType.ClrType) + { + existingMemberDeclaringEntityType = baseType; + } } } @@ -970,6 +979,14 @@ public virtual IMutableNavigationBase Navigation(string navigationName) } } + foreach (var conflictingComplexProperty in Metadata.FindComplexPropertiesInHierarchy(propertyName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName).ToList()) { if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) @@ -1050,6 +1067,7 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource co && Metadata.Model.Builder.CanBeConfigured( memberInfo.GetMemberType(), TypeConfigurationType.ServiceProperty, configurationSource) && Metadata.FindPropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(propertyName)) .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) .All( @@ -1073,6 +1091,392 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource co return builder; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? HasNoServiceProperty( + ServiceProperty serviceProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveServiceProperty(serviceProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveServiceProperty(serviceProperty); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveServiceProperty(ServiceProperty serviceProperty, ConfigurationSource configurationSource) + => configurationSource.Overrides(serviceProperty.GetConfigurationSource()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexIndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); + } + + return ComplexProperty(propertyType, propertyName, indexerPropertyInfo, complexType, collection, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + memberInfo.GetMemberType(), memberInfo.Name, memberInfo, complexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + propertyType, propertyName, memberInfo: null, complexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var entityType = Metadata; + List? propertiesToDetach = null; + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + if (existingComplexProperty != null) + { + if (existingComplexProperty.DeclaringType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + entityType = (EntityType)existingComplexProperty.DeclaringType; + } + + var existingComplexType = existingComplexProperty.ComplexType; + if (IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (complexType == null + || existingComplexType.ClrType == complexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + { + if (configurationSource.HasValue) + { + existingComplexProperty.UpdateConfigurationSource(configurationSource.Value); + } + + return existingComplexProperty.Builder; + } + + if (!configurationSource.Overrides(existingComplexProperty.GetConfigurationSource())) + { + return null; + } + + Debug.Assert(configurationSource.HasValue); + + memberInfo ??= existingComplexProperty.PropertyInfo ?? (MemberInfo?)existingComplexProperty.FieldInfo; + propertyType ??= existingComplexProperty.ClrType; + collection ??= existingComplexProperty.IsCollection; + complexType ??= existingComplexType.ClrType; + + propertiesToDetach = new List { existingComplexProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddComplexProperty( + propertyName, propertyType ?? memberInfo?.GetMemberType(), complexType, collection, configurationSource.Value))) + { + return null; + } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + if (collection == false) + { + complexType = propertyType; + } + + if (collection == null + || complexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + complexType ??= collection.Value ? elementType : propertyType; + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredComplexProperty(propertyName); + if (derivedProperty != null) + { + propertiesToDetach ??= new List(); + + propertiesToDetach.Add(derivedProperty); + } + } + } + + InternalComplexPropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + if (existingComplexProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName)) + { + if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty); + } + } + + foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(propertyName)) + { + if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); + } + } + + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName)) + { + if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName())); + } + + var foreignKey = conflictingNavigation.ForeignKey; + if (foreignKey.GetConfigurationSource() == ConfigurationSource.Convention) + { + foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Convention); + } + else if (foreignKey.Builder.HasNavigation( + (string?)null, + conflictingNavigation.IsOnDependent, + configurationSource.Value) + == null) + { + return null; + } + } + + foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName)) + { + if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + { + continue; + } + + var inverse = conflictingSkipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); + } + + conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( + conflictingSkipNavigation, configurationSource.Value); + } + } + + builder = entityType.AddComplexProperty( + propertyName, propertyType, memberInfo, complexType!, collection.Value, configurationSource.Value)!.Builder; + + if (detachedProperties != null) + { + foreach (var detachedProperty in detachedProperties) + { + detachedProperty.Attach(this); + } + } + } + + return builder.Metadata.IsInModel + ? builder + : Metadata.FindComplexProperty(propertyName)?.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanHaveComplexProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + propertyType ??= memberInfo?.GetMemberType(); + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + var existingComplexType = existingComplexProperty?.ComplexType; + return existingComplexProperty != null + ? (IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (complexType == null + || existingComplexType!.ClrType == complexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + || configurationSource.Overrides(existingComplexProperty.GetConfigurationSource()) + : configurationSource.HasValue + && CanAddComplexProperty(propertyName, propertyType, complexType, collection, configurationSource.Value); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanAddComplexProperty( + string propertyName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? targetType, + bool? collection, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (targetType == null || !ModelBuilder.IsIgnored(targetType, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindPropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindServicePropertiesInHierarchy(propertyName)) + .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) + .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? HasNoComplexProperty( + ComplexProperty complexProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveComplexProperty(complexProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveComplexProperty(complexProperty); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveComplexProperty(ComplexProperty complexProperty, ConfigurationSource configurationSource) + => configurationSource.Overrides(complexProperty.GetConfigurationSource()); + + /// + /// 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 static List? DetachProperties(IReadOnlyList propertiesToDetach) + { + if (propertiesToDetach.Count == 0) + { + return null; + } + + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var snapshot = InternalComplexPropertyBuilder.Detach(propertyToDetach); + if (snapshot == null) + { + continue; + } + + detachedProperties.Add(snapshot); + + var removedProperty = ((EntityType)propertyToDetach.DeclaringType).RemoveComplexProperty(propertyToDetach); + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + + return detachedProperties; + } + /// /// 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 @@ -1107,6 +1511,7 @@ public virtual bool CanAddNavigation( && (type == null || CanBeNavigation(type, configurationSource)) && Metadata.FindPropertiesInHierarchy(navigationName).Cast() .Concat(Metadata.FindServicePropertiesInHierarchy(navigationName)) + .Concat(Metadata.FindComplexPropertiesInHierarchy(navigationName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(navigationName)) .All( m => configurationSource.Overrides(m.GetConfigurationSource()) @@ -1142,26 +1547,13 @@ private bool CanAddSkipNavigation( => !IsIgnored(skipNavigationName, configurationSource) && (type == null || CanBeNavigation(type, configurationSource)) && Metadata.FindPropertiesInHierarchy(skipNavigationName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(skipNavigationName)) .Concat(Metadata.FindServicePropertiesInHierarchy(skipNavigationName)) .Concat(Metadata.FindNavigationsInHierarchy(skipNavigationName)) .All( m => configurationSource.Overrides(m.GetConfigurationSource()) && m.GetConfigurationSource() != ConfigurationSource.Explicit); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsIgnored(string name, ConfigurationSource? configurationSource) - { - Check.NotEmpty(name, nameof(name)); - - return configurationSource != ConfigurationSource.Explicit - && !configurationSource.OverridesStrictly(Metadata.FindIgnoredConfigurationSource(name)); - } - /// /// 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 @@ -1239,35 +1631,52 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou } else { - var skipNavigation = Metadata.FindSkipNavigation(name); - if (skipNavigation != null) + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) { - var inverse = skipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } - - Check.DebugAssert( - skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); + Check.DebugAssert(complexProperty.DeclaringType == Metadata, "property.DeclaringType != Metadata"); - if (skipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + if (complexProperty.GetConfigurationSource() == ConfigurationSource.Explicit) { - ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedNavigationIgnoredWarning(skipNavigation); + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedComplexPropertyIgnoredWarning(complexProperty); } - Metadata.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + var removedComplexProperty = Metadata.RemoveComplexProperty(complexProperty); + + Check.DebugAssert(removedComplexProperty != null, "removedProperty is null"); } else { - var serviceProperty = Metadata.FindServiceProperty(name); - if (serviceProperty != null) + var skipNavigation = Metadata.FindSkipNavigation(name); + if (skipNavigation != null) { + var inverse = skipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); + } + Check.DebugAssert( - serviceProperty.DeclaringEntityType == Metadata, "serviceProperty.DeclaringEntityType != Metadata"); + skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); - Metadata.RemoveServiceProperty(serviceProperty); + if (skipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedNavigationIgnoredWarning(skipNavigation); + } + + Metadata.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + } + else + { + var serviceProperty = Metadata.FindServiceProperty(name); + if (serviceProperty != null) + { + Check.DebugAssert( + serviceProperty.DeclaringEntityType == Metadata, "serviceProperty.DeclaringEntityType != Metadata"); + + Metadata.RemoveServiceProperty(serviceProperty); + } } } } @@ -1322,29 +1731,41 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou } else { - var skipNavigation = derivedType.FindDeclaredSkipNavigation(name); - if (skipNavigation != null) + var declaredComplexProperty = derivedType.FindDeclaredComplexProperty(name); + if (declaredComplexProperty != null) { - var inverse = skipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + if (configurationSource.Overrides(declaredComplexProperty.GetConfigurationSource()) + && declaredComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } - - if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) - { - derivedType.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + derivedType.RemoveComplexProperty(declaredComplexProperty); } } else { - var derivedServiceProperty = derivedType.FindDeclaredServiceProperty(name); - if (derivedServiceProperty != null - && configurationSource.Overrides(derivedServiceProperty.GetConfigurationSource()) - && derivedServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + var skipNavigation = derivedType.FindDeclaredSkipNavigation(name); + if (skipNavigation != null) + { + var inverse = skipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); + } + + if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + } + } + else { - derivedType.RemoveServiceProperty(name); + var derivedServiceProperty = derivedType.FindDeclaredServiceProperty(name); + if (derivedServiceProperty != null + && configurationSource.Overrides(derivedServiceProperty.GetConfigurationSource()) + && derivedServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.RemoveServiceProperty(name); + } } } } @@ -1417,48 +1838,71 @@ private bool CanIgnore(string name, ConfigurationSource configurationSource, boo } else { - var skipNavigation = Metadata.FindSkipNavigation(name); - if (skipNavigation != null) + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) { - if (skipNavigation.DeclaringEntityType != Metadata) + if (complexProperty.DeclaringType != Metadata) { if (shouldThrow) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - name, Metadata.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName())); + name, Metadata.DisplayName(), complexProperty.DeclaringType.DisplayName())); } return false; } - if (!configurationSource.Overrides(skipNavigation.GetConfigurationSource())) + if (!configurationSource.Overrides(complexProperty.GetConfigurationSource())) { return false; } } else { - var serviceProperty = Metadata.FindServiceProperty(name); - if (serviceProperty != null) + var skipNavigation = Metadata.FindSkipNavigation(name); + if (skipNavigation != null) { - if (serviceProperty.DeclaringEntityType != Metadata) + if (skipNavigation.DeclaringEntityType != Metadata) { if (shouldThrow) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - name, Metadata.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName())); + name, Metadata.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName())); } return false; } - if (!configurationSource.Overrides(serviceProperty.GetConfigurationSource())) + if (!configurationSource.Overrides(skipNavigation.GetConfigurationSource())) { return false; } } + else + { + var serviceProperty = Metadata.FindServiceProperty(name); + if (serviceProperty != null) + { + if (serviceProperty.DeclaringEntityType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName())); + } + + return false; + } + + if (!configurationSource.Overrides(serviceProperty.GetConfigurationSource())) + { + return false; + } + } + } } } } @@ -1615,6 +2059,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo List? detachedRelationships = null; List? detachedSkipNavigations = null; PropertiesSnapshot? detachedProperties = null; + List? detachedComplexProperties = null; List? detachedServiceProperties = null; IReadOnlyList<(InternalKeyBuilder, ConfigurationSource?)>? detachedKeys = null; // We use at least DataAnnotation as ConfigurationSource while removing to allow us @@ -1726,6 +2171,22 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo detachedProperties = DetachProperties(propertiesToDetach); } + var complexPropertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredComplexProperties()), + baseMemberNames, + p => baseEntityType.FindComplexProperty(p.Name) != null, + p => ((EntityType)p.DeclaringType).RemoveComplexProperty(p)); + + if (complexPropertiesToDetach != null) + { + detachedComplexProperties = new List(); + foreach (var complexPropertyToDetach in complexPropertiesToDetach) + { + detachedComplexProperties.Add(InternalComplexPropertyBuilder.Detach(complexPropertyToDetach)!); + } + } + var servicePropertiesToDetach = FindConflictingMembers( Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredServiceProperties()), @@ -1872,13 +2333,22 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo } } + if (detachedComplexProperties != null) + { + foreach (var detachedComplexProperty in detachedComplexProperties) + { + detachedComplexProperty.Attach( + ((EntityType)detachedComplexProperty.ComplexProperty.DeclaringType).Builder); + } + } + detachedProperties?.Attach(this); if (detachedKeys != null) { foreach (var (internalKeyBuilder, value) in detachedKeys) { - var newKeyBuilder = internalKeyBuilder.Attach(Metadata.RootType().Builder, value); + var newKeyBuilder = internalKeyBuilder.Attach(Metadata.GetRootType().Builder, value); if (newKeyBuilder == null && internalKeyBuilder.Metadata.GetConfigurationSource() == ConfigurationSource.Explicit) { @@ -2295,11 +2765,11 @@ public static RelationshipSnapshot DetachRelationship(ForeignKey foreignKey, boo } List? detachedIndexes = null; - foreach (var index in entityType.GetDeclaredIndexes().ToList()) + foreach (var indexToBeDetached in entityType.GetDeclaredIndexes().ToList()) { detachedIndexes ??= new List(); - var detachedIndex = DetachIndex(index); + var detachedIndex = DetachIndex(indexToBeDetached); if (detachedIndex.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) { detachedIndexes.Add(detachedIndex); @@ -2611,7 +3081,13 @@ public virtual bool CanRemoveIndex(Index index, ConfigurationSource configuratio return detachedIndexes; } - private static InternalIndexBuilder DetachIndex(Index indexToDetach) + /// + /// 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 static InternalIndexBuilder DetachIndex(Index indexToDetach) { var entityTypeBuilder = indexToDetach.DeclaringEntityType.Builder; var indexBuilder = indexToDetach.Builder; @@ -3576,6 +4052,35 @@ private static InternalIndexBuilder DetachIndex(Index indexToDetach) return relationship; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? HasNoNavigation( + Navigation navigation, + ConfigurationSource configurationSource) + { + if (!CanRemoveNavigation(navigation, configurationSource)) + { + return null; + } + + navigation.ForeignKey.Builder.HasNavigation((string?)null, navigation.IsOnDependent, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveNavigation(Navigation navigation, ConfigurationSource configurationSource) + => configurationSource.Overrides(navigation.GetConfigurationSource()); + /// /// 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 @@ -4064,7 +4569,7 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK ConfigurationSource? configurationSource) { var principalType = principalEntityTypeBuilder.Metadata; - var principalBaseEntityTypeBuilder = principalType.RootType().Builder; + var principalBaseEntityTypeBuilder = principalType.GetRootType().Builder; if (principalKey == null) { if (principalType.IsKeyless @@ -4211,20 +4716,22 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK public virtual InternalSkipNavigationBuilder? HasSkipNavigation( MemberIdentity navigation, EntityType targetEntityType, + Type? navigationType, MemberIdentity inverseNavigation, + Type? inverseNavigationType, ConfigurationSource configurationSource, bool? collections = null, bool? onDependent = null) { var skipNavigationBuilder = HasSkipNavigation( - navigation, targetEntityType, configurationSource, collections, onDependent); + navigation, targetEntityType, navigationType, configurationSource, collections, onDependent); if (skipNavigationBuilder == null) { return null; } var inverseSkipNavigationBuilder = targetEntityType.Builder.HasSkipNavigation( - inverseNavigation, Metadata, configurationSource, collections, onDependent); + inverseNavigation, Metadata, inverseNavigationType, configurationSource, collections, onDependent); if (inverseSkipNavigationBuilder == null) { HasNoSkipNavigation(skipNavigationBuilder.Metadata, configurationSource); @@ -4241,8 +4748,49 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalSkipNavigationBuilder? HasSkipNavigation( - MemberIdentity navigationProperty, + MemberInfo navigation, + EntityType targetEntityType, + ConfigurationSource? configurationSource, + bool? collection = null, + bool? onDependent = null) + => HasSkipNavigation( + MemberIdentity.Create(navigation), + targetEntityType, + navigation.GetMemberType(), + configurationSource, + collection, + onDependent); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalSkipNavigationBuilder? HasSkipNavigation( + MemberIdentity navigation, + EntityType targetEntityType, + ConfigurationSource? configurationSource, + bool? collection = null, + bool? onDependent = null) + => HasSkipNavigation( + navigation, + targetEntityType, + navigation.MemberInfo?.GetMemberType(), + configurationSource, + collection, + onDependent); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalSkipNavigationBuilder? HasSkipNavigation( + MemberIdentity navigation, EntityType targetEntityType, + Type? navigationType, ConfigurationSource? configurationSource, bool? collection = null, bool? onDependent = null) @@ -4251,10 +4799,11 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK List<(InternalSkipNavigationBuilder Navigation, InternalSkipNavigationBuilder Inverse)>? detachedNavigations = null; InternalSkipNavigationBuilder builder; - var navigationName = navigationProperty.Name; + var navigationName = navigation.Name; if (navigationName != null) { - var memberInfo = navigationProperty.MemberInfo; + var memberInfo = navigation.MemberInfo; + navigationType ??= memberInfo?.GetMemberType(); var existingNavigation = Metadata.FindSkipNavigation(navigationName); if (existingNavigation != null) { @@ -4307,9 +4856,8 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } if (collection == null - && memberInfo != null) + && navigationType != null) { - var navigationType = memberInfo.GetMemberType(); var navigationTargetClrType = navigationType.TryGetSequenceType(); collection = navigationTargetClrType != null && navigationType != targetEntityType.ClrType @@ -4336,6 +4884,14 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } } + foreach (var conflictingComplexProperty in Metadata.FindComplexPropertiesInHierarchy(navigationName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(navigationName)) { if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) @@ -4370,14 +4926,14 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } builder = Metadata.AddSkipNavigation( - navigationName, memberInfo, - targetEntityType, collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; + navigationName, navigationType, memberInfo, targetEntityType, + collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; if (detachedNavigations != null) { - foreach (var (navigation, inverse) in detachedNavigations) + foreach (var (detachedNavigation, inverse) in detachedNavigations) { - navigation.Attach(this, inverseBuilder: inverse); + detachedNavigation.Attach(this, inverseBuilder: inverse); } } } @@ -4393,8 +4949,8 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } builder = Metadata.AddSkipNavigation( - navigationName, null, - targetEntityType, collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; + navigationName, navigationType, null, targetEntityType, + collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; } return builder.Metadata.IsInModel @@ -4518,7 +5074,13 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) isRequired, baseName).Item2; - private (bool, IReadOnlyList?) TryCreateUniqueProperties( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties( int propertyCount, IReadOnlyList? currentProperties, IEnumerable principalPropertyTypes, @@ -4791,36 +5353,6 @@ public virtual bool CanSetChangeTrackingStrategy( => configurationSource.Overrides(Metadata.GetChangeTrackingStrategyConfigurationSource()) || Metadata.GetChangeTrackingStrategy() == changeTrackingStrategy; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - { - if (CanSetPropertyAccessMode(propertyAccessMode, configurationSource)) - { - Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) - => configurationSource.Overrides(((IConventionEntityType)Metadata).GetPropertyAccessModeConfigurationSource()) - || ((IConventionEntityType)Metadata).GetPropertyAccessMode() == propertyAccessMode; - /// /// 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 @@ -4937,7 +5469,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => CanSetDiscriminator( Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) ? DiscriminatorBuilder( - Metadata.RootType().Builder.Property( + Metadata.GetRootType().Builder.Property( memberInfo, configurationSource), configurationSource) : null; @@ -4955,7 +5487,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( discriminatorProperty = null; } - return Metadata.RootType().Builder.Property( + return (InternalPropertyBuilder?)Metadata.GetRootType().Builder.Property( type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, typeConfigurationSource: type != null ? configurationSource : null, @@ -4972,7 +5504,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( return null; } - var rootTypeBuilder = Metadata.RootType().Builder; + var rootTypeBuilder = Metadata.GetRootType().Builder; var discriminatorProperty = discriminatorPropertyBuilder.Metadata; // Make sure the property is on the root type discriminatorPropertyBuilder = rootTypeBuilder.Property( @@ -5095,7 +5627,7 @@ private bool CanSetDiscriminator( && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) || configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) && (discriminatorProperty != null - || Metadata.RootType().Builder.CanAddDiscriminatorProperty( + || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( discriminatorType ?? DefaultDiscriminatorType, name ?? DefaultDiscriminatorName, typeConfigurationSource: discriminatorType != null @@ -5141,6 +5673,78 @@ IConventionEntityType IConventionEntityTypeBuilder.Metadata get => Metadata; } + IConventionTypeBase IConventionTypeBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5300,6 +5904,157 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr IReadOnlyList properties) => RemoveUnusedImplicitProperties(properties); + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoProperty(IConventionProperty property, bool fromDataAnnotation) + => RemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) == null + ? null + : this; + + /// + /// 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. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveProperty(IConventionProperty property, bool fromDataAnnotation) + => CanRemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionEntityTypeBuilder.ComplexProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType, + propertyName, + memberInfo: null, + complexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionEntityTypeBuilder.ComplexProperty( + MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType: memberInfo.GetMemberType(), + propertyName: memberInfo.Name, + memberInfo: memberInfo, + complexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanHaveComplexProperty( + Type? propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + memberInfo: null, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanHaveComplexProperty(MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + memberInfo.GetMemberType(), + memberInfo.Name, + memberInfo, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionEntityTypeBuilder.ComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexIndexerProperty( + propertyType, + propertyName, + complexType, + collection: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanHaveComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoComplexProperty( + IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => HasNoComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => CanRemoveComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5337,9 +6092,44 @@ bool IConventionEntityTypeBuilder.CanHaveServiceProperty(MemberInfo memberInfo, /// 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 IConventionEntityTypeBuilder.IsIgnored(string name, bool fromDataAnnotation) + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoServiceProperty( + IConventionServiceProperty serviceProperty, bool fromDataAnnotation) + => HasNoServiceProperty( + (ServiceProperty)serviceProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation) + => CanRemoveServiceProperty( + (ServiceProperty)serviceProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionTypeBaseBuilder.IsIgnored(string name, bool fromDataAnnotation) => IsIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.Ignore(string memberName, bool fromDataAnnotation) + => Ignore(memberName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5357,9 +6147,23 @@ bool IConventionEntityTypeBuilder.IsIgnored(string name, bool fromDataAnnotation /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanIgnore(string name, bool fromDataAnnotation) + bool IConventionTypeBaseBuilder.CanIgnore(string name, bool fromDataAnnotation) => CanIgnore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionEntityTypeBuilder.PrimaryKey( + IReadOnlyList? propertyNames, + bool fromDataAnnotation) + => PrimaryKey( + propertyNames, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5728,7 +6532,9 @@ bool IConventionEntityTypeBuilder.CanRemoveIndex(IConventionIndex index, bool fr => HasSkipNavigation( MemberIdentity.Create(navigation), (EntityType)targetEntityType, + navigation.GetMemberType(), MemberIdentity.Create(inverseNavigation), + inverseNavigation.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collections, onDependent); @@ -5948,6 +6754,30 @@ bool IConventionEntityTypeBuilder.CanHaveNavigation( type, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoNavigation(IConventionNavigation navigation, bool fromDataAnnotation) + => HasNoNavigation( + (Navigation)navigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveNavigation(IConventionNavigation navigation, bool fromDataAnnotation) + => CanRemoveNavigation( + (Navigation)navigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5972,6 +6802,7 @@ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationNam => HasSkipNavigation( MemberIdentity.Create(navigation), (EntityType)targetEntityType, + navigation.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collection, onDependent); @@ -5981,12 +6812,14 @@ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationNam IConventionSkipNavigationBuilder? IConventionEntityTypeBuilder.HasSkipNavigation( string navigationName, IConventionEntityType targetEntityType, + Type? navigationType, bool? collection, bool? onDependent, bool fromDataAnnotation) => HasSkipNavigation( MemberIdentity.Create(navigationName), (EntityType)targetEntityType, + navigationType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collection, onDependent); @@ -6097,7 +6930,7 @@ bool IConventionEntityTypeBuilder.CanSetChangeTrackingStrategy( IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => UsePropertyAccessMode( + => (IConventionEntityTypeBuilder?)UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index b24fac41ada..704b1b3ed74 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -282,6 +282,14 @@ public InternalForeignKeyBuilder( } } + foreach (var conflictingComplexProperty in dependentEntityType.FindComplexPropertiesInHierarchy(navigationToPrincipalName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + foreach (var conflictingSkipNavigation in dependentEntityType.FindSkipNavigationsInHierarchy(navigationToPrincipalName)) { if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) @@ -319,6 +327,14 @@ public InternalForeignKeyBuilder( } } + foreach (var conflictingComplexProperty in principalEntityType.FindComplexPropertiesInHierarchy(navigationToDependentName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((EntityType)conflictingComplexProperty.DeclaringType).RemoveComplexProperty(conflictingComplexProperty); + } + } + foreach (var conflictingSkipNavigation in principalEntityType.FindSkipNavigationsInHierarchy(navigationToDependentName)) { if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) @@ -1597,7 +1613,7 @@ public virtual bool CanInvert( { foreach (var (internalKeyBuilder, configurationSource) in detachedKeys) { - internalKeyBuilder.Attach(Metadata.DeclaringEntityType.RootType().Builder, configurationSource); + internalKeyBuilder.Attach(Metadata.DeclaringEntityType.GetRootType().Builder, configurationSource); } } @@ -2445,7 +2461,7 @@ private bool CanSetPrincipalKey( if (principalProperties != null && principalProperties.Count != 0) { - principalKey = principalEntityTypeBuilder.Metadata.RootType().Builder + principalKey = principalEntityTypeBuilder.Metadata.GetRootType().Builder .HasKey(principalProperties, configurationSource)!.Metadata; } @@ -2926,7 +2942,7 @@ private static InternalForeignKeyBuilder MergeFacetsFrom(Navigation newNavigatio if (newRelationshipBuilder == null) { var principalKey = principalProperties != null - ? principalEntityType.RootType().Builder.HasKey(principalProperties, configurationSource)!.Metadata + ? principalEntityType.GetRootType().Builder.HasKey(principalProperties, configurationSource)!.Metadata : principalEntityType.FindPrimaryKey(); if (principalKey != null) { @@ -3999,6 +4015,24 @@ IConventionForeignKey IConventionForeignKeyBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasEntityTypes( diff --git a/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs index f55646fa897..089488a89eb 100644 --- a/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs @@ -112,7 +112,43 @@ public virtual bool CanSetIsDescending(IReadOnlyList? descending, Configur /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionIndex IConventionIndexBuilder.Metadata - => Metadata; + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs index 0caca66515b..a4fe1d10b96 100644 --- a/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs @@ -59,5 +59,26 @@ public InternalKeyBuilder(Key key, InternalModelBuilder modelBuilder) } IConventionKey IConventionKeyBuilder.Metadata - => Metadata; + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 831819651bf..31b16d2e0a8 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Security.AccessControl; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -51,12 +52,24 @@ public override InternalModelBuilder ModelBuilder /// 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 InternalEntityTypeBuilder? SharedTypeEntity( - string name, - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, + public virtual InternalEntityTypeBuilder? Entity( + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, ConfigurationSource configurationSource, - bool? shouldBeOwned = false) - => Entity(new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), configurationSource, shouldBeOwned); + bool? shouldBeOwned = null) + => Entity(new TypeIdentity(type, Metadata), configurationSource, shouldBeOwned); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? Entity( + string name, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => Entity(new TypeIdentity(name), definingNavigationName, definingEntityType, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -66,35 +79,43 @@ public override InternalModelBuilder ModelBuilder /// public virtual InternalEntityTypeBuilder? Entity( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => Entity(new TypeIdentity(type, Metadata), definingNavigationName, definingEntityType, configurationSource); + + private InternalEntityTypeBuilder? Entity( + in TypeIdentity type, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => SharedTypeEntity( + definingEntityType.GetOwnedName(type.Type?.ShortDisplayName() ?? type.Name, definingNavigationName), + type.Type, configurationSource, shouldBeOwned: true); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? SharedTypeEntity( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, ConfigurationSource configurationSource, - bool? shouldBeOwned = null) - => Entity(new TypeIdentity(type, Metadata), configurationSource, shouldBeOwned); + bool? shouldBeOwned = false) + => Entity(new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), configurationSource, shouldBeOwned); private InternalEntityTypeBuilder? Entity( in TypeIdentity type, ConfigurationSource configurationSource, bool? shouldBeOwned) { - if (IsIgnored(type, configurationSource)) + if (!CanHaveEntity(type, configurationSource, shouldBeOwned, shouldThrow: configurationSource == ConfigurationSource.Explicit)) { return null; } - if (type.Type != null - && shouldBeOwned != null) - { - var configurationType = shouldBeOwned.Value - ? TypeConfigurationType.OwnedEntityType - : type.IsNamed - ? TypeConfigurationType.SharedTypeEntityType - : TypeConfigurationType.EntityType; - - if (!CanBeConfigured(type.Type, configurationType, configurationSource)) - { - return null; - } - } - using var batch = Metadata.DelayConventions(); var clrType = type.Type; EntityType? entityType; @@ -106,19 +127,6 @@ public override InternalModelBuilder ModelBuilder entityType = Metadata.FindEntityType(clrType); if (entityType != null) { - Check.DebugAssert( - entityType.Name != type.Name || !entityType.HasSharedClrType, - "Shared type entity types shouldn't be named the same as non-shared"); - - if (!configurationSource.OverridesStrictly(entityType.GetConfigurationSource()) - && !entityType.IsOwned()) - { - return configurationSource == ConfigurationSource.Explicit - ? throw new InvalidOperationException( - CoreStrings.ClashingNonSharedType(type.Name, clrType.ShortDisplayName())) - : null; - } - entityTypeSnapshot = InternalEntityTypeBuilder.DetachAllMembers(entityType); // TODO: Use convention batch to track replaced entity type, see #15898 @@ -194,43 +202,41 @@ public override InternalModelBuilder ModelBuilder } } - if (type.Type != null) + if (shouldBeOwned == null) { - if (shouldBeOwned == null) + if (type.Type == null) + { + return null; + } + + var configurationType = Metadata.Configuration?.GetConfigurationType(type.Type); + switch (configurationType) { - var configurationType = Metadata.Configuration?.GetConfigurationType(type.Type); - switch (configurationType) + case null: + break; + case TypeConfigurationType.EntityType: + case TypeConfigurationType.SharedTypeEntityType: { - case null: - break; - case TypeConfigurationType.EntityType: - case TypeConfigurationType.SharedTypeEntityType: - { - shouldBeOwned ??= false; - break; - } - case TypeConfigurationType.OwnedEntityType: + shouldBeOwned ??= false; + break; + } + case TypeConfigurationType.OwnedEntityType: + { + shouldBeOwned ??= true; + break; + } + default: + { + if (configurationSource != ConfigurationSource.Explicit) { - shouldBeOwned ??= true; - break; + return null; } - default: - { - if (configurationSource != ConfigurationSource.Explicit) - { - return null; - } - break; - } + break; } - - shouldBeOwned ??= Metadata.FindIsOwnedConfigurationSource(type.Type) != null; } - } - else if (shouldBeOwned == null) - { - return null; + + shouldBeOwned ??= Metadata.FindIsOwnedConfigurationSource(type.Type) != null; } Metadata.RemoveIgnored(type.Name); @@ -255,34 +261,98 @@ public override InternalModelBuilder ModelBuilder /// 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 InternalEntityTypeBuilder? Entity( - string name, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => Entity(new TypeIdentity(name), definingNavigationName, definingEntityType, configurationSource); + public virtual bool CanHaveEntity( + in TypeIdentity type, + ConfigurationSource configurationSource, + bool? shouldBeOwned, + bool shouldThrow = false) + { + if (IsIgnored(type, configurationSource)) + { + return false; + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? Entity( - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => Entity(new TypeIdentity(type, Metadata), definingNavigationName, definingEntityType, configurationSource); + if (type.Type != null + && shouldBeOwned != null) + { + var configurationType = shouldBeOwned.Value + ? TypeConfigurationType.OwnedEntityType + : type.IsNamed + ? TypeConfigurationType.SharedTypeEntityType + : TypeConfigurationType.EntityType; - private InternalEntityTypeBuilder? Entity( - in TypeIdentity type, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => SharedTypeEntity( - definingEntityType.GetOwnedName(type.Type?.ShortDisplayName() ?? type.Name, definingNavigationName), - type.Type, configurationSource, shouldBeOwned: true); + if (!CanBeConfigured(type.Type, configurationType, configurationSource)) + { + return false; + } + } + + var clrType = type.Type; + EntityType? entityType; + if (type.IsNamed) + { + if (clrType != null) + { + entityType = Metadata.FindEntityType(clrType); + if (entityType != null) + { + Check.DebugAssert( + entityType.Name != type.Name || !entityType.HasSharedClrType, + "Shared type entity types shouldn't be named the same as non-shared"); + + if (!configurationSource.OverridesStrictly(entityType.GetConfigurationSource()) + && !entityType.IsOwned()) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingNonSharedType(type.Name, clrType.ShortDisplayName())) + : false; + } + } + } + + entityType = Metadata.FindEntityType(type.Name); + } + else + { + clrType = type.Type!; + var sharedConfigurationSource = Metadata.FindIsSharedConfigurationSource(clrType); + if (sharedConfigurationSource != null + && !configurationSource.OverridesStrictly(sharedConfigurationSource.Value)) + { + return shouldThrow + ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.ShortDisplayName())) + : false; + } + + entityType = Metadata.FindEntityType(clrType); + } + + if (shouldBeOwned == false + && clrType != null + && (!configurationSource.OverridesStrictly(Metadata.FindIsOwnedConfigurationSource(clrType)) + || (Metadata.Configuration?.GetConfigurationType(clrType) == TypeConfigurationType.OwnedEntityType + && configurationSource != ConfigurationSource.Explicit))) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingOwnedEntityType(clrType == null ? type.Name : clrType.ShortDisplayName())) + : false; + } + + if (entityType != null + && type.Type != null + && entityType.ClrType != type.Type + && !configurationSource.OverridesStrictly(entityType.GetConfigurationSource())) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingMismatchedSharedType(type.Name, entityType.ClrType.ShortDisplayName())) + : false; + } + + return true; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -600,6 +670,15 @@ private bool CanIgnore(in TypeIdentity type, ConfigurationSource configurationSo return this; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveEntityType(EntityType entityType, ConfigurationSource configurationSource) + => configurationSource.Overrides(entityType.GetConfigurationSource()); + /// /// 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 @@ -676,6 +755,39 @@ IConventionModel IConventionModelBuilder.Metadata get => Metadata; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -762,6 +874,56 @@ IConventionModel IConventionModelBuilder.Metadata bool fromDataAnnotation) => Owned(type, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveEntity(string name, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(name), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveEntity(Type type, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(type, Metadata), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveSharedTypeEntity(string name, Type? type, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanRemoveEntity(IConventionEntityType entityType, bool fromDataAnnotation) + => CanRemoveEntityType((EntityType)entityType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs index 766dbfe52c5..91429cf7972 100644 --- a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalNavigationBuilder : InternalPropertyBaseBuilder, IConventionNavigationBuilder +public class InternalNavigationBuilder : + InternalPropertyBaseBuilder, IConventionNavigationBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,6 +23,15 @@ public InternalNavigationBuilder(Navigation metadata, InternalModelBuilder model { } + /// + /// 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. + /// + protected override IConventionNavigationBuilder This + => this; + /// /// 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 @@ -193,7 +203,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource configu return null; } - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; @@ -205,23 +215,48 @@ IConventionNavigation IConventionNavigationBuilder.Metadata get => Metadata; } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => CanSetPropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.UsePropertyAccessMode( + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -229,43 +264,30 @@ bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) - => CanSetField( - fieldName, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) => HasField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) => HasField( - fieldName, + fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) => CanSetField( - fieldInfo, + fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( - fieldInfo, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalPrimitivePropertyBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalPrimitivePropertyBaseBuilder.cs new file mode 100644 index 00000000000..c73a8b92868 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalPrimitivePropertyBaseBuilder.cs @@ -0,0 +1,1262 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 abstract class InternalPrimitivePropertyBaseBuilder + : InternalPropertyBaseBuilder, IConventionPrimitivePropertyBaseBuilder + where TBuilder : class, IConventionPrimitivePropertyBaseBuilder + where TPropertyBase : PrimitivePropertyBase +{ + /// + /// 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 InternalPrimitivePropertyBaseBuilder(TPropertyBase property, InternalModelBuilder modelBuilder) + : base(property, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? IsRequired(bool? required, ConfigurationSource configurationSource) + { + if (configurationSource != ConfigurationSource.Explicit + && !CanSetIsRequired(required, configurationSource)) + { + return null; + } + + if (required == false) + { + using (Metadata.DeclaringType.Model.DelayConventions()) + { + foreach (var key in Metadata.GetContainingKeys().ToList()) + { + if (configurationSource == ConfigurationSource.Explicit + && key.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.KeyPropertyCannotBeNullable( + Metadata.Name, Metadata.DeclaringType.DisplayName(), key.Properties.Format())); + } + + var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + + Metadata.SetIsNullable(true, configurationSource); + } + } + else + { + Metadata.SetIsNullable(!required, configurationSource); + } + + return This; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource) + => ((configurationSource.HasValue + && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource())) + || (Metadata.IsNullable == !required)) + && (required != false + || (Metadata.ClrType.IsNullableType() + && Metadata.GetContainingKeys().All(k => configurationSource.Overrides(k.GetConfigurationSource())))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? ValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) + { + if (CanSetValueGenerated(valueGenerated, configurationSource)) + { + Metadata.SetValueGenerated(valueGenerated, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueGeneratedConfigurationSource()) + || Metadata.ValueGenerated == valueGenerated; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? IsConcurrencyToken(bool? concurrencyToken, ConfigurationSource configurationSource) + { + if (CanSetIsConcurrencyToken(concurrencyToken, configurationSource)) + { + Metadata.SetIsConcurrencyToken(concurrencyToken, configurationSource); + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetIsConcurrencyTokenConfigurationSource()) + || Metadata.IsConcurrencyToken == concurrencyToken; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasSentinel(object? sentinel, ConfigurationSource configurationSource) + { + if (CanSetSentinel(sentinel, configurationSource)) + { + Metadata.SetSentinel(sentinel, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetSentinelConfigurationSource()) + || Equals(Metadata.Sentinel, sentinel); + + /// + /// 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 new virtual TBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) + => base.HasField(fieldName, configurationSource) == null + ? null + : This; + + /// + /// 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 new virtual TBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) + => base.HasField(fieldInfo, configurationSource) == null + ? null + : This; + + /// + /// 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 new virtual TBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + ConfigurationSource configurationSource) + => base.UsePropertyAccessMode(propertyAccessMode, configurationSource) == null + ? null + : This; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasMaxLength(int? maxLength, ConfigurationSource configurationSource) + { + if (CanSetMaxLength(maxLength, configurationSource)) + { + Metadata.SetMaxLength(maxLength, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) + || Metadata.GetMaxLength() == maxLength; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasPrecision(int? precision, ConfigurationSource configurationSource) + { + if (CanSetPrecision(precision, configurationSource)) + { + Metadata.SetPrecision(precision, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetPrecisionConfigurationSource()) + || Metadata.GetPrecision() == precision; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasScale(int? scale, ConfigurationSource configurationSource) + { + if (CanSetScale(scale, configurationSource)) + { + Metadata.SetScale(scale, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetScaleConfigurationSource()) + || Metadata.GetScale() == scale; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? IsUnicode(bool? unicode, ConfigurationSource configurationSource) + { + if (CanSetIsUnicode(unicode, configurationSource)) + { + Metadata.SetIsUnicode(unicode, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetIsUnicodeConfigurationSource()) + || Metadata.IsUnicode() == unicode; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? BeforeSave(PropertySaveBehavior? behavior, ConfigurationSource configurationSource) + { + if (CanSetBeforeSave(behavior, configurationSource)) + { + Metadata.SetBeforeSaveBehavior(behavior, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetBeforeSaveBehaviorConfigurationSource()) + || Metadata.GetBeforeSaveBehavior() == behavior; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? AfterSave(PropertySaveBehavior? behavior, ConfigurationSource configurationSource) + { + if (CanSetAfterSave(behavior, configurationSource)) + { + Metadata.SetAfterSaveBehavior(behavior, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, ConfigurationSource? configurationSource) + => (configurationSource.Overrides(Metadata.GetAfterSaveBehaviorConfigurationSource()) + && (behavior == null + || Metadata.CheckAfterSaveBehavior(behavior.Value) == null)) + || Metadata.GetAfterSaveBehavior() == behavior; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, + ConfigurationSource configurationSource) + { + if (valueGeneratorType == null) + { + return HasValueGenerator((Func?)null, configurationSource); + } + + if (!typeof(ValueGenerator).IsAssignableFrom(valueGeneratorType)) + { + throw new ArgumentException( + CoreStrings.BadValueGeneratorType(valueGeneratorType.ShortDisplayName(), typeof(ValueGenerator).ShortDisplayName())); + } + + return HasValueGenerator( + (_, _) + => + { + try + { + return (ValueGenerator)Activator.CreateInstance(valueGeneratorType)!; + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueGenerator( + valueGeneratorType.ShortDisplayName(), nameof(PropertyBuilder.HasValueGenerator)), e); + } + }, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasValueGenerator( + Func? factory, + ConfigurationSource configurationSource) + { + if (CanSetValueGenerator(factory, configurationSource)) + { + Metadata.SetValueGeneratorFactory(factory, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, + ConfigurationSource configurationSource) + { + if (CanSetValueGeneratorFactory(factory, configurationSource)) + { + Metadata.SetValueGeneratorFactory(factory, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueGenerator( + Func? factory, + ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueGeneratorFactoryConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == null + && (Func?)Metadata[CoreAnnotationNames.ValueGeneratorFactory] == factory); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, + ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueGeneratorFactoryConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueGeneratorFactory] == null + && (Type?)Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == factory); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasConversion(ValueConverter? converter, ConfigurationSource configurationSource) + { + if (CanSetConversion(converter, configurationSource)) + { + Metadata.SetProviderClrType(null, configurationSource); + Metadata.SetValueConverter(converter, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConversion( + ValueConverter? converter, + ConfigurationSource? configurationSource) + => (configurationSource == ConfigurationSource.Explicit + || (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) + && Metadata.CheckValueConverter(converter) == null) + || (Metadata[CoreAnnotationNames.ValueConverterType] == null + && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter)) + && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasConversion(Type? providerClrType, ConfigurationSource configurationSource) + { + if (CanSetConversion(providerClrType, configurationSource)) + { + Metadata.SetValueConverter((ValueConverter?)null, configurationSource); + Metadata.SetProviderClrType(providerClrType, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? configurationSource) + => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()) + || Metadata.GetProviderClrType() == providerClrType) + && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + ConfigurationSource configurationSource) + { + if (CanSetConverter(converterType, configurationSource)) + { + Metadata.SetProviderClrType(null, configurationSource); + Metadata.SetValueConverter(converterType, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueConverter] == null + && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasTypeMapping( + CoreTypeMapping? typeMapping, + ConfigurationSource configurationSource) + { + if (CanSetTypeMapping(typeMapping, configurationSource)) + { + Metadata.SetTypeMapping(typeMapping, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetTypeMappingConfigurationSource()) + || Metadata.TypeMapping == typeMapping; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasValueComparer( + ValueComparer? comparer, + ConfigurationSource configurationSource) + { + if (CanSetValueComparer(comparer, configurationSource)) + { + Metadata.SetValueComparer(comparer, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource) + { + if (configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource())) + { + var errorString = Metadata.CheckValueComparer(comparer); + if (errorString != null) + { + if (configurationSource == ConfigurationSource.Explicit) + { + throw new InvalidOperationException(errorString); + } + + return false; + } + + return true; + } + + return Metadata[CoreAnnotationNames.ValueComparerType] == null + && Metadata[CoreAnnotationNames.ValueComparer] == comparer; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + ConfigurationSource configurationSource) + { + if (CanSetValueComparer(comparerType, configurationSource)) + { + Metadata.SetValueComparer(comparerType, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueComparer] == null + && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasProviderValueComparer( + ValueComparer? comparer, + ConfigurationSource configurationSource) + { + if (CanSetProviderValueComparer(comparer, configurationSource)) + { + Metadata.SetProviderValueComparer(comparer, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource) + { + if (configurationSource.Overrides(Metadata.GetProviderValueComparerConfigurationSource())) + { + return true; + } + + return Metadata[CoreAnnotationNames.ProviderValueComparerType] == null + && Metadata[CoreAnnotationNames.ProviderValueComparer] == comparer; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + ConfigurationSource configurationSource) + { + if (CanSetProviderValueComparer(comparerType, configurationSource)) + { + Metadata.SetProviderValueComparer(comparerType, configurationSource); + + return This; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetProviderValueComparerConfigurationSource()) + || (Metadata[CoreAnnotationNames.ProviderValueComparer] == null + && (Type?)Metadata[CoreAnnotationNames.ProviderValueComparerType] == comparerType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + IConventionPrimitivePropertyBase IConventionPrimitivePropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + [DebuggerStepThrough] + TBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : This; + + /// + /// 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. + /// + [DebuggerStepThrough] + TBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : This; + + /// + /// 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. + /// + [DebuggerStepThrough] + TBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : This; + + /// + /// 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. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.IsRequired(bool? required, bool fromDataAnnotation) + => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetIsRequired(bool? required, bool fromDataAnnotation) + => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.ValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) + => ValueGenerated(valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) + => CanSetValueGenerated( + valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.IsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) + => IsConcurrencyToken( + concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) + => CanSetIsConcurrencyToken( + concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasSentinel(object? sentinel, bool fromDataAnnotation) + => HasSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetSentinel(object? sentinel, bool fromDataAnnotation) + => CanSetSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasMaxLength(int? maxLength, bool fromDataAnnotation) + => HasMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnnotation) + => CanSetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.IsUnicode(bool? unicode, bool fromDataAnnotation) + => IsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnnotation) + => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasPrecision(int? precision, bool fromDataAnnotation) + => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetPrecision(int? precision, bool fromDataAnnotation) + => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasScale(int? scale, bool fromDataAnnotation) + => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetScale(int? scale, bool fromDataAnnotation) + => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.BeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) + => BeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) + => CanSetBeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.AfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) + => AfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) + => CanSetAfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, + bool fromDataAnnotation) + => HasValueGenerator( + valueGeneratorType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasValueGenerator( + Func? factory, + bool fromDataAnnotation) + => HasValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetValueGenerator(Func? factory, bool fromDataAnnotation) + => CanSetValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + bool fromDataAnnotation) + => HasValueGeneratorFactory( + valueGeneratorFactoryType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + bool fromDataAnnotation) + => CanSetValueGeneratorFactory( + valueGeneratorFactoryType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasConversion(ValueConverter? converter, bool fromDataAnnotation) + => HasConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetConversion(ValueConverter? converter, bool fromDataAnnotation) + => CanSetConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation) + => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation) + => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasConversion(Type? providerClrType, bool fromDataAnnotation) + => HasConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetConversion(Type? providerClrType, bool fromDataAnnotation) + => CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation) + => HasTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) + => CanSetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => HasValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => CanSetValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => HasValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => HasProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => CanSetProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TBuilder? IConventionPrimitivePropertyBaseBuilder.HasProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => HasProviderValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPrimitivePropertyBaseBuilder.CanSetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => CanSetProviderValueComparer( + comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs b/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs similarity index 87% rename from src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs rename to src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs index f8fa5b3d1ea..050d0cafce5 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs @@ -9,7 +9,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalPropertyBaseBuilder : AnnotatableBuilder +public abstract class InternalPropertyBaseBuilder + : AnnotatableBuilder + where TBuilder : class, IConventionPropertyBaseBuilder where TPropertyBase : PropertyBase { /// @@ -29,7 +31,15 @@ public InternalPropertyBaseBuilder(TPropertyBase metadata, InternalModelBuilder /// 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 InternalPropertyBaseBuilder? HasField( + protected abstract TBuilder This { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasField( string? fieldName, ConfigurationSource configurationSource) { @@ -37,7 +47,7 @@ public InternalPropertyBaseBuilder(TPropertyBase metadata, InternalModelBuilder { Metadata.SetField(fieldName, configurationSource); - return this; + return This; } return null; @@ -77,7 +87,7 @@ public virtual bool CanSetField(string? fieldName, ConfigurationSource? configur /// 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 InternalPropertyBaseBuilder? HasField( + public virtual TBuilder? HasField( FieldInfo? fieldInfo, ConfigurationSource configurationSource) { @@ -85,7 +95,7 @@ public virtual bool CanSetField(string? fieldName, ConfigurationSource? configur { Metadata.SetFieldInfo(fieldInfo, configurationSource); - return this; + return This; } return null; @@ -111,7 +121,7 @@ public virtual bool CanSetField(FieldInfo? fieldInfo, ConfigurationSource? confi /// 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 InternalPropertyBaseBuilder? UsePropertyAccessMode( + public virtual TBuilder? UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) { @@ -119,7 +129,7 @@ public virtual bool CanSetField(FieldInfo? fieldInfo, ConfigurationSource? confi { Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); - return this; + return This; } return null; diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index a6d43af00c8..dd28ea25228 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -1,8 +1,6 @@ // 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; - namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -11,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalPropertyBuilder : InternalPropertyBaseBuilder, IConventionPropertyBuilder +public class InternalPropertyBuilder + : InternalPrimitivePropertyBaseBuilder, IConventionPropertyBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,56 +29,8 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil /// 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 InternalPropertyBuilder? IsRequired(bool? required, ConfigurationSource configurationSource) - { - if (configurationSource != ConfigurationSource.Explicit - && !CanSetIsRequired(required, configurationSource)) - { - return null; - } - - if (required == false) - { - using (Metadata.DeclaringEntityType.Model.DelayConventions()) - { - foreach (var key in Metadata.GetContainingKeys().ToList()) - { - if (configurationSource == ConfigurationSource.Explicit - && key.GetConfigurationSource() == ConfigurationSource.Explicit) - { - throw new InvalidOperationException( - CoreStrings.KeyPropertyCannotBeNullable( - Metadata.Name, Metadata.DeclaringEntityType.DisplayName(), key.Properties.Format())); - } - - var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); - Check.DebugAssert(removed != null, "removed is null"); - } - - Metadata.SetIsNullable(true, configurationSource); - } - } - else - { - Metadata.SetIsNullable(!required, configurationSource); - } - - return this; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource) - => ((configurationSource.HasValue - && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource())) - || (Metadata.IsNullable == !required)) - && (required != false - || (Metadata.ClrType.IsNullableType() - && Metadata.GetContainingKeys().All(k => configurationSource.Overrides(k.GetConfigurationSource())))); + protected override InternalPropertyBuilder This + => this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -87,268 +38,108 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config /// 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 InternalPropertyBuilder? ValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) + public virtual InternalPropertyBuilder? Attach(InternalEntityTypeBuilder entityTypeBuilder) { - if (CanSetValueGenerated(valueGenerated, configurationSource)) + var newProperty = entityTypeBuilder.Metadata.FindProperty(Metadata.Name); + InternalPropertyBuilder? newPropertyBuilder; + var configurationSource = Metadata.GetConfigurationSource(); + var typeConfigurationSource = Metadata.GetTypeConfigurationSource(); + if (newProperty != null + && (newProperty.GetConfigurationSource().Overrides(configurationSource) + || newProperty.GetTypeConfigurationSource().Overrides(typeConfigurationSource) + || (Metadata.ClrType == newProperty.ClrType + && Metadata.Name == newProperty.Name + && Metadata.GetIdentifyingMemberInfo() == newProperty.GetIdentifyingMemberInfo()))) { - Metadata.SetValueGenerated(valueGenerated, configurationSource); - - return this; + newPropertyBuilder = newProperty.Builder; + newProperty.UpdateConfigurationSource(configurationSource); + if (typeConfigurationSource.HasValue) + { + newProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); + } } + else + { + var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo(); - return null; - } + newPropertyBuilder = Metadata.IsIndexerProperty() + ? entityTypeBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) + : identifyingMemberInfo == null + ? entityTypeBuilder.Property( + Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) + : entityTypeBuilder.Property(identifyingMemberInfo, configurationSource); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetValueGeneratedConfigurationSource()) - || Metadata.ValueGenerated == valueGenerated; + if (newPropertyBuilder is null) + { + return null; + } + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? IsConcurrencyToken(bool? concurrencyToken, ConfigurationSource configurationSource) - { - if (CanSetIsConcurrencyToken(concurrencyToken, configurationSource)) + if (newProperty == Metadata) { - Metadata.SetIsConcurrencyToken(concurrencyToken, configurationSource); - return this; + return newPropertyBuilder; } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetIsConcurrencyTokenConfigurationSource()) - || Metadata.IsConcurrencyToken == concurrencyToken; - - /// - /// 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 new virtual InternalPropertyBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.HasField(fieldName, configurationSource); - - /// - /// 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 new virtual InternalPropertyBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.HasField(fieldInfo, configurationSource); - - /// - /// 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 new virtual InternalPropertyBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.UsePropertyAccessMode(propertyAccessMode, configurationSource); + newPropertyBuilder.MergeAnnotationsFrom(Metadata); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasMaxLength(int? maxLength, ConfigurationSource configurationSource) - { - if (CanSetMaxLength(maxLength, configurationSource)) + var oldBeforeSaveBehaviorConfigurationSource = Metadata.GetBeforeSaveBehaviorConfigurationSource(); + if (oldBeforeSaveBehaviorConfigurationSource.HasValue) { - Metadata.SetMaxLength(maxLength, configurationSource); - - return this; + newPropertyBuilder.BeforeSave( + Metadata.GetBeforeSaveBehavior(), + oldBeforeSaveBehaviorConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) - || Metadata.GetMaxLength() == maxLength; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasSentinel(object? sentinel, ConfigurationSource configurationSource) - { - if (CanSetSentinel(sentinel, configurationSource)) + var oldAfterSaveBehaviorConfigurationSource = Metadata.GetAfterSaveBehaviorConfigurationSource(); + if (oldAfterSaveBehaviorConfigurationSource.HasValue) { - Metadata.SetSentinel(sentinel, configurationSource); - - return this; + newPropertyBuilder.AfterSave( + Metadata.GetAfterSaveBehavior(), + oldAfterSaveBehaviorConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetSentinelConfigurationSource()) - || Equals(Metadata.Sentinel, sentinel); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasPrecision(int? precision, ConfigurationSource configurationSource) - { - if (CanSetPrecision(precision, configurationSource)) + var oldIsNullableConfigurationSource = Metadata.GetIsNullableConfigurationSource(); + if (oldIsNullableConfigurationSource.HasValue) { - Metadata.SetPrecision(precision, configurationSource); - - return this; + newPropertyBuilder.IsRequired(!Metadata.IsNullable, oldIsNullableConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetPrecisionConfigurationSource()) - || Metadata.GetPrecision() == precision; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasScale(int? scale, ConfigurationSource configurationSource) - { - if (CanSetScale(scale, configurationSource)) + var oldIsConcurrencyTokenConfigurationSource = Metadata.GetIsConcurrencyTokenConfigurationSource(); + if (oldIsConcurrencyTokenConfigurationSource.HasValue) { - Metadata.SetScale(scale, configurationSource); - - return this; + newPropertyBuilder.IsConcurrencyToken( + Metadata.IsConcurrencyToken, + oldIsConcurrencyTokenConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetScaleConfigurationSource()) - || Metadata.GetScale() == scale; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? IsUnicode(bool? unicode, ConfigurationSource configurationSource) - { - if (CanSetIsUnicode(unicode, configurationSource)) + var oldValueGeneratedConfigurationSource = Metadata.GetValueGeneratedConfigurationSource(); + if (oldValueGeneratedConfigurationSource.HasValue) { - Metadata.SetIsUnicode(unicode, configurationSource); - - return this; + newPropertyBuilder.ValueGenerated(Metadata.ValueGenerated, oldValueGeneratedConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetIsUnicodeConfigurationSource()) - || Metadata.IsUnicode() == unicode; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? BeforeSave(PropertySaveBehavior? behavior, ConfigurationSource configurationSource) - { - if (CanSetBeforeSave(behavior, configurationSource)) + var oldPropertyAccessModeConfigurationSource = Metadata.GetPropertyAccessModeConfigurationSource(); + if (oldPropertyAccessModeConfigurationSource.HasValue) { - Metadata.SetBeforeSaveBehavior(behavior, configurationSource); - - return this; + newPropertyBuilder.UsePropertyAccessMode( + ((IReadOnlyProperty)Metadata).GetPropertyAccessMode(), oldPropertyAccessModeConfigurationSource.Value); } - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetBeforeSaveBehaviorConfigurationSource()) - || Metadata.GetBeforeSaveBehavior() == behavior; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? AfterSave(PropertySaveBehavior? behavior, ConfigurationSource configurationSource) - { - if (CanSetAfterSave(behavior, configurationSource)) + var oldFieldInfoConfigurationSource = Metadata.GetFieldInfoConfigurationSource(); + if (oldFieldInfoConfigurationSource.HasValue + && newPropertyBuilder.CanSetField(Metadata.FieldInfo, oldFieldInfoConfigurationSource)) { - Metadata.SetAfterSaveBehavior(behavior, configurationSource); + newPropertyBuilder.HasField(Metadata.FieldInfo, oldFieldInfoConfigurationSource.Value); + } - return this; + var oldTypeMappingConfigurationSource = Metadata.GetTypeMappingConfigurationSource(); + if (oldTypeMappingConfigurationSource.HasValue + && newPropertyBuilder.CanSetTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource)) + { + newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value); } - return null; + return newPropertyBuilder; } /// @@ -357,1000 +148,9 @@ public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, Configurati /// 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 CanSetAfterSave(PropertySaveBehavior? behavior, ConfigurationSource? configurationSource) - => (configurationSource.Overrides(Metadata.GetAfterSaveBehaviorConfigurationSource()) - && (behavior == null - || Metadata.CheckAfterSaveBehavior(behavior.Value) == null)) - || Metadata.GetAfterSaveBehavior() == behavior; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, - ConfigurationSource configurationSource) + IConventionProperty IConventionPropertyBuilder.Metadata { - if (valueGeneratorType == null) - { - return HasValueGenerator((Func?)null, configurationSource); - } - - if (!typeof(ValueGenerator).IsAssignableFrom(valueGeneratorType)) - { - throw new ArgumentException( - CoreStrings.BadValueGeneratorType(valueGeneratorType.ShortDisplayName(), typeof(ValueGenerator).ShortDisplayName())); - } - - return HasValueGenerator( - (_, _) - => - { - try - { - return (ValueGenerator)Activator.CreateInstance(valueGeneratorType)!; - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateValueGenerator( - valueGeneratorType.ShortDisplayName(), nameof(PropertyBuilder.HasValueGenerator)), e); - } - }, configurationSource); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasValueGenerator( - Func? factory, - ConfigurationSource configurationSource) - { - if (CanSetValueGenerator(factory, configurationSource)) - { - Metadata.SetValueGeneratorFactory(factory, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, - ConfigurationSource configurationSource) - { - if (CanSetValueGeneratorFactory(factory, configurationSource)) - { - Metadata.SetValueGeneratorFactory(factory, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetValueGenerator( - Func? factory, - ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetValueGeneratorFactoryConfigurationSource()) - || (Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == null - && (Func?)Metadata[CoreAnnotationNames.ValueGeneratorFactory] == factory); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, - ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetValueGeneratorFactoryConfigurationSource()) - || (Metadata[CoreAnnotationNames.ValueGeneratorFactory] == null - && (Type?)Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == factory); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasConversion(ValueConverter? converter, ConfigurationSource configurationSource) - { - if (CanSetConversion(converter, configurationSource)) - { - Metadata.SetProviderClrType(null, configurationSource); - Metadata.SetValueConverter(converter, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetConversion( - ValueConverter? converter, - ConfigurationSource? configurationSource) - => (configurationSource == ConfigurationSource.Explicit - || (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) - && Metadata.CheckValueConverter(converter) == null) - || (Metadata[CoreAnnotationNames.ValueConverterType] == null - && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter)) - && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasConversion(Type? providerClrType, ConfigurationSource configurationSource) - { - if (CanSetConversion(providerClrType, configurationSource)) - { - Metadata.SetValueConverter((ValueConverter?)null, configurationSource); - Metadata.SetProviderClrType(providerClrType, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? configurationSource) - => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()) - || Metadata.GetProviderClrType() == providerClrType) - && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, - ConfigurationSource configurationSource) - { - if (CanSetConverter(converterType, configurationSource)) - { - Metadata.SetProviderClrType(null, configurationSource); - Metadata.SetValueConverter(converterType, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, - ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) - || (Metadata[CoreAnnotationNames.ValueConverter] == null - && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasTypeMapping( - CoreTypeMapping? typeMapping, - ConfigurationSource configurationSource) - { - if (CanSetTypeMapping(typeMapping, configurationSource)) - { - Metadata.SetTypeMapping(typeMapping, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetTypeMappingConfigurationSource()) - || Metadata.TypeMapping == typeMapping; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasValueComparer( - ValueComparer? comparer, - ConfigurationSource configurationSource) - { - if (CanSetValueComparer(comparer, configurationSource)) - { - Metadata.SetValueComparer(comparer, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource) - { - if (configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource())) - { - var errorString = Metadata.CheckValueComparer(comparer); - if (errorString != null) - { - if (configurationSource == ConfigurationSource.Explicit) - { - throw new InvalidOperationException(errorString); - } - - return false; - } - - return true; - } - - return Metadata[CoreAnnotationNames.ValueComparerType] == null - && Metadata[CoreAnnotationNames.ValueComparer] == comparer; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - ConfigurationSource configurationSource) - { - if (CanSetValueComparer(comparerType, configurationSource)) - { - Metadata.SetValueComparer(comparerType, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource()) - || (Metadata[CoreAnnotationNames.ValueComparer] == null - && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasProviderValueComparer( - ValueComparer? comparer, - ConfigurationSource configurationSource) - { - if (CanSetProviderValueComparer(comparer, configurationSource)) - { - Metadata.SetProviderValueComparer(comparer, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource) - { - if (configurationSource.Overrides(Metadata.GetProviderValueComparerConfigurationSource())) - { - return true; - } - - return Metadata[CoreAnnotationNames.ProviderValueComparerType] == null - && Metadata[CoreAnnotationNames.ProviderValueComparer] == comparer; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - ConfigurationSource configurationSource) - { - if (CanSetProviderValueComparer(comparerType, configurationSource)) - { - Metadata.SetProviderValueComparer(comparerType, configurationSource); - - return this; - } - - return null; + [DebuggerStepThrough] + get => Metadata; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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 CanSetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetProviderValueComparerConfigurationSource()) - || (Metadata[CoreAnnotationNames.ProviderValueComparer] == null - && (Type?)Metadata[CoreAnnotationNames.ProviderValueComparerType] == comparerType); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? Attach(InternalEntityTypeBuilder entityTypeBuilder) - { - var newProperty = entityTypeBuilder.Metadata.FindProperty(Metadata.Name); - InternalPropertyBuilder? newPropertyBuilder; - var configurationSource = Metadata.GetConfigurationSource(); - var typeConfigurationSource = Metadata.GetTypeConfigurationSource(); - if (newProperty != null - && (newProperty.GetConfigurationSource().Overrides(configurationSource) - || newProperty.GetTypeConfigurationSource().Overrides(typeConfigurationSource) - || (Metadata.ClrType == newProperty.ClrType - && Metadata.GetIdentifyingMemberInfo()?.Name == newProperty.GetIdentifyingMemberInfo()?.Name))) - { - newPropertyBuilder = newProperty.Builder; - newProperty.UpdateConfigurationSource(configurationSource); - if (typeConfigurationSource.HasValue) - { - newProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); - } - } - else - { - var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo(); - - newPropertyBuilder = Metadata.IsIndexerProperty() - ? entityTypeBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) - : identifyingMemberInfo == null - ? entityTypeBuilder.Property( - Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) - : entityTypeBuilder.Property(identifyingMemberInfo, configurationSource); - - if (newPropertyBuilder is null) - { - return null; - } - } - - if (newProperty == Metadata) - { - return newPropertyBuilder; - } - - newPropertyBuilder.MergeAnnotationsFrom(Metadata); - - var oldBeforeSaveBehaviorConfigurationSource = Metadata.GetBeforeSaveBehaviorConfigurationSource(); - if (oldBeforeSaveBehaviorConfigurationSource.HasValue) - { - newPropertyBuilder.BeforeSave( - Metadata.GetBeforeSaveBehavior(), - oldBeforeSaveBehaviorConfigurationSource.Value); - } - - var oldAfterSaveBehaviorConfigurationSource = Metadata.GetAfterSaveBehaviorConfigurationSource(); - if (oldAfterSaveBehaviorConfigurationSource.HasValue) - { - newPropertyBuilder.AfterSave( - Metadata.GetAfterSaveBehavior(), - oldAfterSaveBehaviorConfigurationSource.Value); - } - - var oldIsNullableConfigurationSource = Metadata.GetIsNullableConfigurationSource(); - if (oldIsNullableConfigurationSource.HasValue) - { - newPropertyBuilder.IsRequired(!Metadata.IsNullable, oldIsNullableConfigurationSource.Value); - } - - var oldIsConcurrencyTokenConfigurationSource = Metadata.GetIsConcurrencyTokenConfigurationSource(); - if (oldIsConcurrencyTokenConfigurationSource.HasValue) - { - newPropertyBuilder.IsConcurrencyToken( - Metadata.IsConcurrencyToken, - oldIsConcurrencyTokenConfigurationSource.Value); - } - - var oldValueGeneratedConfigurationSource = Metadata.GetValueGeneratedConfigurationSource(); - if (oldValueGeneratedConfigurationSource.HasValue) - { - newPropertyBuilder.ValueGenerated(Metadata.ValueGenerated, oldValueGeneratedConfigurationSource.Value); - } - - var oldPropertyAccessModeConfigurationSource = Metadata.GetPropertyAccessModeConfigurationSource(); - if (oldPropertyAccessModeConfigurationSource.HasValue) - { - newPropertyBuilder.UsePropertyAccessMode( - ((IReadOnlyProperty)Metadata).GetPropertyAccessMode(), oldPropertyAccessModeConfigurationSource.Value); - } - - var oldFieldInfoConfigurationSource = Metadata.GetFieldInfoConfigurationSource(); - if (oldFieldInfoConfigurationSource.HasValue - && newPropertyBuilder.CanSetField(Metadata.FieldInfo, oldFieldInfoConfigurationSource)) - { - newPropertyBuilder.HasField(Metadata.FieldInfo, oldFieldInfoConfigurationSource.Value); - } - - var oldTypeMappingConfigurationSource = Metadata.GetTypeMappingConfigurationSource(); - if (oldTypeMappingConfigurationSource.HasValue - && newPropertyBuilder.CanSetTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource)) - { - newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value); - } - - return newPropertyBuilder; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - IConventionProperty IConventionPropertyBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.IsRequired(bool? required, bool fromDataAnnotation) - => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAnnotation) - => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.ValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) - => ValueGenerated(valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) - => CanSetValueGenerated( - valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.IsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) - => IsConcurrencyToken( - concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) - => CanSetIsConcurrencyToken( - concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) - => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => CanSetPropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasMaxLength(int? maxLength, bool fromDataAnnotation) - => HasMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnnotation) - => CanSetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasSentinel(object? sentinel, bool fromDataAnnotation) - => HasSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAnnotation) - => CanSetSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.IsUnicode(bool? unicode, bool fromDataAnnotation) - => IsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnnotation) - => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasPrecision(int? precision, bool fromDataAnnotation) - => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnnotation) - => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasScale(int? scale, bool fromDataAnnotation) - => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) - => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.BeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) - => BeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) - => CanSetBeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.AfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) - => AfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, bool fromDataAnnotation) - => CanSetAfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, - bool fromDataAnnotation) - => HasValueGenerator( - valueGeneratorType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGenerator( - Func? factory, - bool fromDataAnnotation) - => HasValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetValueGenerator(Func? factory, bool fromDataAnnotation) - => CanSetValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, - bool fromDataAnnotation) - => HasValueGeneratorFactory( - valueGeneratorFactoryType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, - bool fromDataAnnotation) - => CanSetValueGeneratorFactory( - valueGeneratorFactoryType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasConversion(ValueConverter? converter, bool fromDataAnnotation) - => HasConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool fromDataAnnotation) - => CanSetConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, - bool fromDataAnnotation) - => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, - bool fromDataAnnotation) - => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasConversion(Type? providerClrType, bool fromDataAnnotation) - => HasConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetConversion(Type? providerClrType, bool fromDataAnnotation) - => CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation) - => HasTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) - => CanSetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => HasValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => CanSetValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation) - => HasValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation) - => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => HasProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => CanSetProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation) - => HasProviderValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - bool fromDataAnnotation) - => CanSetProviderValueComparer( - comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs index 82c4be9d829..3cbab4a664c 100644 --- a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalServicePropertyBuilder : InternalPropertyBaseBuilder, IConventionServicePropertyBuilder +public class InternalServicePropertyBuilder : + InternalPropertyBaseBuilder, IConventionServicePropertyBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,6 +23,15 @@ public InternalServicePropertyBuilder(ServiceProperty property, InternalModelBui { } + /// + /// 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. + /// + protected override IConventionServicePropertyBuilder This + => this; + /// /// 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 @@ -130,8 +140,11 @@ public virtual bool CanSetParameterBinding( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata - => Metadata; + IConventionServiceProperty IConventionServicePropertyBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -139,8 +152,11 @@ IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata /// 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. /// - IConventionServiceProperty IConventionServicePropertyBuilder.Metadata - => Metadata; + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -148,8 +164,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -157,8 +175,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -166,8 +186,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -175,8 +197,8 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -184,8 +206,8 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) - => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -193,8 +215,8 @@ bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromData /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -202,11 +224,8 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -214,7 +233,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.UsePropertyAccessMode( + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -226,7 +245,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs index d0b13f59883..ca06fbeb94f 100644 --- a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class InternalSkipNavigationBuilder : InternalPropertyBaseBuilder, IConventionSkipNavigationBuilder +public class InternalSkipNavigationBuilder : + InternalPropertyBaseBuilder, IConventionSkipNavigationBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -28,28 +29,8 @@ public InternalSkipNavigationBuilder(SkipNavigation metadata, InternalModelBuild /// 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 new virtual InternalSkipNavigationBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.HasField(fieldName, configurationSource); - - /// - /// 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 new virtual InternalSkipNavigationBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.HasField(fieldInfo, configurationSource); - - /// - /// 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 new virtual InternalSkipNavigationBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.UsePropertyAccessMode(propertyAccessMode, configurationSource); + protected override InternalSkipNavigationBuilder This + => this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -227,6 +208,7 @@ public virtual bool CanSetInverse( var newSkipNavigationBuilder = entityTypeBuilder.HasSkipNavigation( Metadata.CreateMemberIdentity(), targetEntityType, + Metadata.ClrType, Metadata.GetConfigurationSource(), Metadata.IsCollection, Metadata.IsOnDependent); @@ -376,7 +358,7 @@ public virtual bool CanSetLazyLoadingEnabled(bool? lazyLoadingEnabled, Configura return null; } - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; @@ -388,60 +370,70 @@ IConventionSkipNavigation IConventionSkipNavigationBuilder.Metadata get => Metadata; } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField( - fieldName, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( - fieldInfo, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) => HasField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) => HasField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) => CanSetField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.UsePropertyAccessMode( + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -450,7 +442,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( diff --git a/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs b/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs index 0ed8fe691a0..6446a9a7bdc 100644 --- a/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs @@ -45,4 +45,22 @@ IConventionTrigger IConventionTriggerBuilder.Metadata [DebuggerStepThrough] get => Metadata; } + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs new file mode 100644 index 00000000000..009d4b07253 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 abstract class InternalTypeBaseBuilder : AnnotatableBuilder + where TMetadata : TypeBase +{ + /// + /// 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 InternalTypeBaseBuilder(TMetadata metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsIgnored(string name, ConfigurationSource? configurationSource) + { + Check.NotEmpty(name, nameof(name)); + + return configurationSource != ConfigurationSource.Explicit + && !configurationSource.OverridesStrictly(Metadata.FindIgnoredConfigurationSource(name)); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + ConfigurationSource configurationSource) + { + if (CanSetPropertyAccessMode(propertyAccessMode, configurationSource)) + { + Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) + => configurationSource.Overrides(((IConventionTypeBase)Metadata).GetPropertyAccessModeConfigurationSource()) + || ((IConventionTypeBase)Metadata).GetPropertyAccessMode() == propertyAccessMode; +} diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 9747de2a6b6..6b9908f9043 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -34,7 +34,7 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IRu private readonly Dictionary Types)> _sharedTypes = new() { - { DefaultPropertyBagType, (ConfigurationSource.Explicit, new SortedSet(EntityTypeFullNameComparer.Instance)) } + { DefaultPropertyBagType, (ConfigurationSource.Explicit, new SortedSet(TypeBaseNameComparer.Instance)) } }; private ConventionDispatcher? _conventionDispatcher; @@ -221,7 +221,7 @@ public virtual IEnumerable GetEntityTypes() } else { - var types = new SortedSet(EntityTypeFullNameComparer.Instance) { entityType }; + var types = new SortedSet(TypeBaseNameComparer.Instance) { entityType }; _sharedTypes.Add(entityType.ClrType, (entityType.GetConfigurationSource(), types)); } } @@ -551,7 +551,7 @@ public virtual bool IsShared([DynamicallyAccessedMembers(DynamicallyAccessedMemb ? existingEntityType.ClrType : null; - return ConventionDispatcher.OnEntityTypeIgnored(Builder, name, type); + return ConventionDispatcher.OnTypeIgnored(Builder, name, type); } /// @@ -768,7 +768,7 @@ public virtual void AddShared(Type type, ConfigurationSource configurationSource } else { - _sharedTypes.Add(type, (configurationSource, new SortedSet(EntityTypeFullNameComparer.Instance))); + _sharedTypes.Add(type, (configurationSource, new SortedSet(TypeBaseNameComparer.Instance))); } } diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs index d0badc2e69e..2f9f12106e2 100644 --- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs +++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs @@ -16,6 +16,7 @@ public class ModelConfiguration { private readonly Dictionary _properties = new(); private readonly Dictionary _typeMappings = new(); + private readonly Dictionary _complexProperties = new(); private readonly HashSet _ignoredTypes = new(); private readonly Dictionary _configurationTypes = new(); @@ -143,13 +144,18 @@ public virtual ModelConfiguration Validate() configurationType = TypeConfigurationType.Ignored; configuredType = type; } - - if (_properties.ContainsKey(type)) + else if (_properties.ContainsKey(type)) { EnsureCompatible(TypeConfigurationType.Property, type, configurationType, configuredType); configurationType = TypeConfigurationType.Property; configuredType = type; } + else if (_complexProperties.ContainsKey(type)) + { + EnsureCompatible(TypeConfigurationType.ComplexType, type, configurationType, configuredType); + configurationType = TypeConfigurationType.ComplexType; + configuredType = type; + } if (configurationType.HasValue) { @@ -203,7 +209,7 @@ public virtual IEnumerable GetTypeMappingConfiguratio /// 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 void ConfigureProperty(IMutableProperty property) + public virtual void ConfigureProperty(IMutablePrimitivePropertyBase property) { var types = property.ClrType.GetBaseTypesAndInterfacesInclusive(); for (var i = types.Count - 1; i >= 0; i--) @@ -217,6 +223,26 @@ public virtual void ConfigureProperty(IMutableProperty property) } } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void ConfigureComplexProperty(IMutableComplexProperty property) + { + var types = property.ClrType.GetBaseTypesAndInterfacesInclusive(); + for (var i = types.Count - 1; i >= 0; i--) + { + var type = types[i]; + + if (_complexProperties.TryGetValue(type, out var configuration)) + { + configuration.Apply(property); + } + } + } + /// /// 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 @@ -297,6 +323,46 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type) ? property : null; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexPropertyConfiguration GetOrAddComplexProperty(Type type) + { + var property = FindComplexProperty(type); + if (property == null) + { + RemoveIgnored(type); + + property = new ComplexPropertyConfiguration(type); + _complexProperties.Add(type, property); + } + + return property; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexPropertyConfiguration? FindComplexProperty(Type type) + => _complexProperties.TryGetValue(type, out var property) + ? property + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool RemoveComplexProperty(Type type) + => _complexProperties.Remove(type); + /// /// 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 @@ -306,6 +372,7 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type) public virtual void AddIgnored(Type type) { RemoveProperty(type); + RemoveComplexProperty(type); _ignoredTypes.Add(type); } diff --git a/src/EFCore/Metadata/Internal/PrimitivePropertyBase.cs b/src/EFCore/Metadata/Internal/PrimitivePropertyBase.cs new file mode 100644 index 00000000000..24437eedfdc --- /dev/null +++ b/src/EFCore/Metadata/Internal/PrimitivePropertyBase.cs @@ -0,0 +1,1855 @@ +// 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.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 abstract class PrimitivePropertyBase : + PropertyBase, IMutablePrimitivePropertyBase, IConventionPrimitivePropertyBase, IPrimitivePropertyBase +{ + private bool? _isConcurrencyToken; + private bool? _isNullable; + private object? _sentinel; + private ValueGenerated? _valueGenerated; + private CoreTypeMapping? _typeMapping; + + private ConfigurationSource? _typeConfigurationSource; + private ConfigurationSource? _isNullableConfigurationSource; + private ConfigurationSource? _sentinelConfigurationSource; + private ConfigurationSource? _isConcurrencyTokenConfigurationSource; + private ConfigurationSource? _valueGeneratedConfigurationSource; + private ConfigurationSource? _typeMappingConfigurationSource; + + /// + /// 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 PrimitivePropertyBase( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type clrType, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + TypeBase declaringType, + ConfigurationSource configurationSource, + ConfigurationSource? typeConfigurationSource) + : base(name, propertyInfo, fieldInfo, configurationSource) + { + DeclaringType = declaringType; + ClrType = clrType; + _typeConfigurationSource = typeConfigurationSource; + } + + /// + /// 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 override TypeBase DeclaringType { get; } + + /// + /// 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. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + public override Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetTypeConfigurationSource() + => _typeConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void UpdateTypeConfigurationSource(ConfigurationSource configurationSource) + => _typeConfigurationSource = _typeConfigurationSource.Max(configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsNullable + { + get => _isNullable ?? DefaultIsNullable; + set => SetIsNullable(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) + { + EnsureMutable(); + + var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; + if (nullable == null) + { + _isNullable = null; + _isNullableConfigurationSource = null; + if (isChanging) + { + OnPropertyNullableChanged(); + } + + return nullable; + } + + if (nullable.Value) + { + if (!ClrType.IsNullableType()) + { + throw new InvalidOperationException( + CoreStrings.CannotBeNullable(Name, DeclaringType.DisplayName(), ClrType.ShortDisplayName())); + } + + if (Keys != null) + { + throw new InvalidOperationException(CoreStrings.CannotBeNullablePK(Name, DeclaringType.DisplayName())); + } + } + + _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); + + _isNullable = nullable; + + return isChanging + ? OnPropertyNullableChanged() + : nullable; + } + + /// + /// 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. + /// + protected abstract bool? OnPropertyNullableChanged(); + + private bool DefaultIsNullable + => ClrType.IsNullableType(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsNullableConfigurationSource() + => _isNullableConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueGenerated ValueGenerated + { + get => _valueGenerated ?? DefaultValueGenerated; + set => SetValueGenerated(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueGenerated? SetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _valueGenerated = valueGenerated; + + _valueGeneratedConfigurationSource = valueGenerated == null + ? null + : configurationSource.Max(_valueGeneratedConfigurationSource); + + return valueGenerated; + } + + private static ValueGenerated DefaultValueGenerated + => ValueGenerated.Never; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueGeneratedConfigurationSource() + => _valueGeneratedConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsConcurrencyToken + { + get => _isConcurrencyToken ?? DefaultIsConcurrencyToken; + set => SetIsConcurrencyToken(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsConcurrencyToken(bool? concurrencyToken, ConfigurationSource configurationSource) + { + EnsureMutable(); + + if (IsConcurrencyToken != concurrencyToken) + { + _isConcurrencyToken = concurrencyToken; + } + + _isConcurrencyTokenConfigurationSource = concurrencyToken == null + ? null + : configurationSource.Max(_isConcurrencyTokenConfigurationSource); + + return concurrencyToken; + } + + private static bool DefaultIsConcurrencyToken + => false; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() + => _isConcurrencyTokenConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetMaxLength(int? maxLength, ConfigurationSource configurationSource) + { + if (maxLength is < -1) + { + throw new ArgumentOutOfRangeException(nameof(maxLength)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.MaxLength, maxLength, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetMaxLength() + => (int?)this[CoreAnnotationNames.MaxLength]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetMaxLengthConfigurationSource() + => FindAnnotation(CoreAnnotationNames.MaxLength)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsUnicode(bool? unicode, ConfigurationSource configurationSource) + => (bool?)SetOrRemoveAnnotation(CoreAnnotationNames.Unicode, unicode, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? IsUnicode() + => (bool?)this[CoreAnnotationNames.Unicode]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsUnicodeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Unicode)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetPrecision(int? precision, ConfigurationSource configurationSource) + { + if (precision != null && precision < 0) + { + throw new ArgumentOutOfRangeException(nameof(precision)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Precision, precision, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetPrecision() + => (int?)this[CoreAnnotationNames.Precision]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetPrecisionConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Precision)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetScale(int? scale, ConfigurationSource configurationSource) + { + if (scale != null && scale < 0) + { + throw new ArgumentOutOfRangeException(nameof(scale)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Scale, scale, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetScale() + => (int?)this[CoreAnnotationNames.Scale]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetScaleConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Scale)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertySaveBehavior? SetBeforeSaveBehavior( + PropertySaveBehavior? beforeSaveBehavior, + ConfigurationSource configurationSource) + => (PropertySaveBehavior?)SetOrRemoveAnnotation(CoreAnnotationNames.BeforeSaveBehavior, beforeSaveBehavior, configurationSource) + ?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertySaveBehavior GetBeforeSaveBehavior() + => (PropertySaveBehavior?)this[CoreAnnotationNames.BeforeSaveBehavior] + ?? (ValueGenerated == ValueGenerated.OnAddOrUpdate + ? PropertySaveBehavior.Ignore + : PropertySaveBehavior.Save); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetBeforeSaveBehaviorConfigurationSource() + => FindAnnotation(CoreAnnotationNames.BeforeSaveBehavior)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertySaveBehavior? SetAfterSaveBehavior( + PropertySaveBehavior? afterSaveBehavior, + ConfigurationSource configurationSource) + { + if (afterSaveBehavior != null) + { + var errorMessage = CheckAfterSaveBehavior(afterSaveBehavior.Value); + if (errorMessage != null) + { + throw new InvalidOperationException(errorMessage); + } + } + + return (PropertySaveBehavior?)SetOrRemoveAnnotation( + CoreAnnotationNames.AfterSaveBehavior, afterSaveBehavior, configurationSource) + ?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertySaveBehavior GetAfterSaveBehavior() + => (PropertySaveBehavior?)this[CoreAnnotationNames.AfterSaveBehavior] + ?? (IsKey() + ? PropertySaveBehavior.Throw + : ValueGenerated.ForUpdate() + ? PropertySaveBehavior.Ignore + : PropertySaveBehavior.Save); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetAfterSaveBehaviorConfigurationSource() + => FindAnnotation(CoreAnnotationNames.AfterSaveBehavior)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? CheckAfterSaveBehavior(PropertySaveBehavior behavior) + => behavior != PropertySaveBehavior.Throw + && IsKey() + ? CoreStrings.KeyPropertyMustBeReadOnly(Name, DeclaringType.DisplayName()) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? Sentinel + { + get => _sentinel ?? DefaultSentinel; + set => SetSentinel(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? SetSentinel(object? sentinel, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _sentinel = sentinel; + + _sentinelConfigurationSource = configurationSource.Max(_sentinelConfigurationSource); + + return sentinel; + } + + private object? DefaultSentinel + => (this is IProperty property + && property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var member, out _) + ? member!.GetMemberType() + : ClrType).GetDefaultValue(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetSentinelConfigurationSource() + => _sentinelConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func? SetValueGeneratorFactory( + Func? factory, + ConfigurationSource configurationSource) + { + RemoveAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType); + return (Func?) + SetAnnotation(CoreAnnotationNames.ValueGeneratorFactory, factory, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factoryType, + ConfigurationSource configurationSource) + { + if (factoryType != null) + { + if (!typeof(ValueGeneratorFactory).IsAssignableFrom(factoryType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueGeneratorType( + factoryType.ShortDisplayName(), typeof(ValueGeneratorFactory).ShortDisplayName())); + } + + if (factoryType.IsAbstract + || !factoryType.GetTypeInfo().DeclaredConstructors.Any(c => c.IsPublic && c.GetParameters().Length == 0)) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueGenerator(factoryType.ShortDisplayName(), nameof(SetValueGeneratorFactory))); + } + } + + RemoveAnnotation(CoreAnnotationNames.ValueGeneratorFactory); + return (Type?)SetAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType, factoryType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func? GetValueGeneratorFactory() + { + var factory = (Func?)this[CoreAnnotationNames.ValueGeneratorFactory]; + if (factory == null) + { + var factoryType = (Type?)this[CoreAnnotationNames.ValueGeneratorFactoryType]; + if (factoryType != null) + { + return ((ValueGeneratorFactory)Activator.CreateInstance(factoryType)!).Create; + } + } + + return factory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueGeneratorFactoryConfigurationSource() + => (FindAnnotation(CoreAnnotationNames.ValueGeneratorFactory) + ?? FindAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType))?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueConverter? SetValueConverter( + ValueConverter? converter, + ConfigurationSource configurationSource) + { + var errorString = CheckValueConverter(converter); + if (errorString != null) + { + throw new InvalidOperationException(errorString); + } + + RemoveAnnotation(CoreAnnotationNames.ValueConverterType); + return (ValueConverter?)SetAnnotation(CoreAnnotationNames.ValueConverter, converter, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + ConfigurationSource configurationSource) + { + ValueConverter? converter = null; + if (converterType != null) + { + if (!typeof(ValueConverter).IsAssignableFrom(converterType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueConverterType(converterType.ShortDisplayName(), typeof(ValueConverter).ShortDisplayName())); + } + + try + { + converter = (ValueConverter?)Activator.CreateInstance(converterType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueConverter( + converterType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); + } + } + + SetValueConverter(converter, configurationSource); + SetAnnotation(CoreAnnotationNames.ValueConverterType, converterType, configurationSource); + + return converterType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueConverter? GetValueConverter() + { + var annotation = FindAnnotation(CoreAnnotationNames.ValueConverter); + if (annotation != null) + { + return (ValueConverter?)annotation.Value; + } + + var property = this; + var i = 0; + for (; i < ForeignKey.LongestFkChainAllowedLength; i++) + { + Property? nextProperty = null; + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (property == foreignKey.Properties[propertyIndex]) + { + var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (principalProperty == this + || principalProperty == property) + { + break; + } + + annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverter); + if (annotation != null) + { + return (ValueConverter?)annotation.Value; + } + + nextProperty = principalProperty; + } + } + } + + if (nextProperty == null) + { + break; + } + + property = nextProperty; + } + + return i == ForeignKey.LongestFkChainAllowedLength + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + DeclaringType.DisplayName(), Name, "ValueConverter")) + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueConverterConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ValueConverter)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? CheckValueConverter(ValueConverter? converter) + => converter != null + && converter.ModelClrType.UnwrapNullableType() != ClrType.UnwrapNullableType() + ? CoreStrings.ConverterPropertyMismatch( + converter.ModelClrType.ShortDisplayName(), + DeclaringType.DisplayName(), + Name, + ClrType.ShortDisplayName()) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetProviderClrType(Type? providerClrType, ConfigurationSource configurationSource) + => (Type?)SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? GetProviderClrType() + { + var annotation = FindAnnotation(CoreAnnotationNames.ProviderClrType); + if (annotation != null) + { + return (Type?)annotation.Value; + } + + var property = this; + var i = 0; + for (; i < ForeignKey.LongestFkChainAllowedLength; i++) + { + Property? nextProperty = null; + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (property == foreignKey.Properties[propertyIndex]) + { + var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (principalProperty == this + || principalProperty == property) + { + break; + } + + annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ProviderClrType); + if (annotation != null) + { + return (Type?)annotation.Value; + } + + nextProperty = principalProperty; + } + } + } + + if (nextProperty == null) + { + break; + } + + property = nextProperty; + } + + return i == ForeignKey.LongestFkChainAllowedLength + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + DeclaringType.DisplayName(), Name, "ProviderClrType")) + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetProviderClrTypeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ProviderClrType)?.GetConfigurationSource(); + + private Type GetEffectiveProviderClrType() + => (TypeMapping?.Converter?.ProviderClrType + ?? ClrType).UnwrapNullableType(); + + /// + /// 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. + /// + [DisallowNull] + public virtual CoreTypeMapping? TypeMapping + { + get => IsReadOnly + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _typeMapping, (IPrimitivePropertyBase)this, static property => + property.DeclaringType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!) + : _typeMapping; + + set => SetTypeMapping(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual CoreTypeMapping? SetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource configurationSource) + { + _typeMapping = typeMapping; + _typeMappingConfigurationSource = typeMapping is null + ? null + : configurationSource.Max(_typeMappingConfigurationSource); + + return typeMapping; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetTypeMappingConfigurationSource() + => _typeMappingConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? SetValueComparer(ValueComparer? comparer, ConfigurationSource configurationSource) + { + var errorString = CheckValueComparer(comparer); + if (errorString != null) + { + throw new InvalidOperationException(errorString); + } + + RemoveAnnotation(CoreAnnotationNames.ValueComparerType); + return (ValueComparer?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparer, comparer, configurationSource)?.Value; + } + + /// + /// 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. + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public virtual Type? SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + ConfigurationSource configurationSource) + { + ValueComparer? comparer = null; + if (comparerType != null) + { + if (!typeof(ValueComparer).IsAssignableFrom(comparerType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName())); + } + + try + { + comparer = (ValueComparer?)Activator.CreateInstance(comparerType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueComparer( + comparerType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); + } + } + + SetValueComparer(comparer, configurationSource); + return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparerType, comparerType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? GetValueComparer() + => (GetValueComparer(null) ?? TypeMapping?.Comparer).ToNullableComparer(this); + + private ValueComparer? GetValueComparer(HashSet? checkedProperties) + { + var comparer = (ValueComparer?)this[CoreAnnotationNames.ValueComparer]; + if (comparer != null) + { + return comparer; + } + + var principal = (PrimitivePropertyBase?)this.FindFirstDifferentPrincipal(); + if (principal == null) + { + return null; + } + + if (checkedProperties == null) + { + checkedProperties = new HashSet(); + } + else if (checkedProperties.Contains(this)) + { + return null; + } + + checkedProperties.Add(this); + return principal.GetValueComparer(checkedProperties); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueComparerConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ValueComparer)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? GetKeyValueComparer() + => (GetValueComparer(null) ?? TypeMapping?.KeyComparer).ToNullableComparer(this); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? SetProviderValueComparer(ValueComparer? comparer, ConfigurationSource configurationSource) + { + RemoveAnnotation(CoreAnnotationNames.ProviderValueComparerType); + return (ValueComparer?)SetOrRemoveAnnotation(CoreAnnotationNames.ProviderValueComparer, comparer, configurationSource)?.Value; + } + + /// + /// 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. + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public virtual Type? SetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + ConfigurationSource configurationSource) + { + ValueComparer? comparer = null; + if (comparerType != null) + { + if (!typeof(ValueComparer).IsAssignableFrom(comparerType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName())); + } + + try + { + comparer = (ValueComparer?)Activator.CreateInstance(comparerType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueComparer( + comparerType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); + } + } + + SetProviderValueComparer(comparer, configurationSource); + return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.ProviderValueComparerType, comparerType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? GetProviderValueComparer() + => GetProviderValueComparer(null) + ?? (GetEffectiveProviderClrType() == ClrType.UnwrapNullableType() + ? GetKeyValueComparer() + : TypeMapping?.ProviderValueComparer); + + private ValueComparer? GetProviderValueComparer(HashSet? checkedProperties) + { + var comparer = (ValueComparer?)this[CoreAnnotationNames.ProviderValueComparer]; + if (comparer != null) + { + return comparer; + } + + var principal = (PrimitivePropertyBase?)this.FindFirstDifferentPrincipal(); + if (principal == null + || principal.GetEffectiveProviderClrType() != GetEffectiveProviderClrType()) + { + return null; + } + + if (checkedProperties == null) + { + checkedProperties = new HashSet(); + } + else if (checkedProperties.Contains(this)) + { + return null; + } + + checkedProperties.Add(this); + return principal.GetProviderValueComparer(checkedProperties); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetProviderValueComparerConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ProviderValueComparer)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? CheckValueComparer(ValueComparer? comparer) + => comparer != null + && comparer.Type.UnwrapNullableType() != ClrType.UnwrapNullableType() + ? CoreStrings.ComparerPropertyMismatch( + comparer.Type.ShortDisplayName(), + DeclaringType.DisplayName(), + Name, + ClrType.ShortDisplayName()) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() + { + return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]) + ?? TypeMapping?.JsonValueReaderWriter; + + static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType) + { + if (readerWriterType != null) + { + var instanceProperty = readerWriterType.GetAnyProperty("Instance"); + try + { + return instanceProperty != null + && instanceProperty.IsStatic() + && instanceProperty.GetMethod?.IsPublic == true + && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) + ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) + : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateJsonValueReaderWriter( + readerWriterType.ShortDisplayName()), e); + } + } + + return null; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetJsonValueReaderWriterType( + Type? readerWriterType, + ConfigurationSource configurationSource) + { + if (readerWriterType != null) + { + var genericType = readerWriterType.GetGenericTypeImplementations(typeof(JsonValueReaderWriter<>)).FirstOrDefault(); + if (genericType == null) + { + throw new InvalidOperationException(CoreStrings.BadJsonValueReaderWriterType(readerWriterType.ShortDisplayName())); + } + } + + return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.JsonValueReaderWriterType, readerWriterType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.JsonValueReaderWriterType)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyKey? PrimaryKey { get; set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual List? Keys { get; set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsKey() + => Keys != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetContainingKeys() + => Keys ?? Enumerable.Empty(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual List? ForeignKeys { get; set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsForeignKey() + => ForeignKeys != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetContainingForeignKeys() + => ForeignKeys ?? Enumerable.Empty(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual List? Indexes { get; set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsIndex() + => Indexes != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetContainingIndexes() + => Indexes ?? Enumerable.Empty(); + + /// + /// 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 static string Format(IEnumerable properties) + => "{" + + string.Join( + ", ", + properties.Select(p => string.IsNullOrEmpty(p) ? "" : "'" + p + "'")) + + "}"; + + /// + /// 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. + /// + [DebuggerStepThrough] + CoreTypeMapping? IReadOnlyPrimitivePropertyBase.FindTypeMapping() + => TypeMapping; + + /// + /// 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. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetTypeMapping(CoreTypeMapping typeMapping) + => SetTypeMapping(typeMapping, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + CoreTypeMapping? IConventionPrimitivePropertyBase.SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) + => SetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutablePrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionPrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutablePrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionPrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IMutablePrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IConventionPrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyKey? IReadOnlyPrimitivePropertyBase.FindContainingPrimaryKey() + => PrimaryKey; + + /// + /// 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. + /// + [DebuggerStepThrough] + bool? IConventionPrimitivePropertyBase.SetIsNullable(bool? nullable, bool fromDataAnnotation) + => SetIsNullable( + nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueGenerated? IConventionPrimitivePropertyBase.SetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) + => SetValueGenerated( + valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool? IConventionPrimitivePropertyBase.SetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) + => SetIsConcurrencyToken( + concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetMaxLength(int? maxLength) + => SetMaxLength(maxLength, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + int? IConventionPrimitivePropertyBase.SetMaxLength(int? maxLength, bool fromDataAnnotation) + => SetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetPrecision(int? precision) + => SetPrecision(precision, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + int? IConventionPrimitivePropertyBase.SetPrecision(int? precision, bool fromDataAnnotation) + => SetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetScale(int? scale) + => SetScale(scale, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + int? IConventionPrimitivePropertyBase.SetScale(int? scale, bool fromDataAnnotation) + => SetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetIsUnicode(bool? unicode) + => SetIsUnicode(unicode, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + bool? IConventionPrimitivePropertyBase.SetIsUnicode(bool? unicode, bool fromDataAnnotation) + => SetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior) + => SetBeforeSaveBehavior(beforeSaveBehavior, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + PropertySaveBehavior? IConventionPrimitivePropertyBase.SetBeforeSaveBehavior( + PropertySaveBehavior? beforeSaveBehavior, + bool fromDataAnnotation) + => SetBeforeSaveBehavior( + beforeSaveBehavior, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior) + => SetAfterSaveBehavior(afterSaveBehavior, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + PropertySaveBehavior? IConventionPrimitivePropertyBase.SetAfterSaveBehavior( + PropertySaveBehavior? afterSaveBehavior, + bool fromDataAnnotation) + => SetAfterSaveBehavior( + afterSaveBehavior, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + object? IConventionPrimitivePropertyBase.SetSentinel(object? sentinel, bool fromDataAnnotation) + => SetSentinel( + sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueGeneratorFactory(Func? valueGeneratorFactory) + => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + Func? IConventionPrimitivePropertyBase.SetValueGeneratorFactory( + Func? valueGeneratorFactory, + bool fromDataAnnotation) + => SetValueGeneratorFactory( + valueGeneratorFactory, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory) + => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + Type? IConventionPrimitivePropertyBase.SetValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory, + bool fromDataAnnotation) + => SetValueGeneratorFactory( + valueGeneratorFactory, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueConverter(ValueConverter? converter) + => SetValueConverter(converter, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + ValueConverter? IConventionPrimitivePropertyBase.SetValueConverter(ValueConverter? converter, bool fromDataAnnotation) + => SetValueConverter( + converter, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType) + => SetValueConverter(converterType, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + Type? IConventionPrimitivePropertyBase.SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation) + => SetValueConverter( + converterType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetProviderClrType(Type? providerClrType) + => SetProviderClrType(providerClrType, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + Type? IConventionPrimitivePropertyBase.SetProviderClrType(Type? providerClrType, bool fromDataAnnotation) + => SetProviderClrType( + providerClrType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueComparer(ValueComparer? comparer) + => SetValueComparer(comparer, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + ValueComparer? IConventionPrimitivePropertyBase.SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => SetValueComparer( + comparer, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => SetValueComparer(comparerType, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? IConventionPrimitivePropertyBase.SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => SetValueComparer( + comparerType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueComparer IPrimitivePropertyBase.GetValueComparer() + => GetValueComparer()!; + + /// + /// 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. + /// + ValueComparer IPrimitivePropertyBase.GetKeyValueComparer() + => GetKeyValueComparer()!; + + /// + /// 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. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetProviderValueComparer(ValueComparer? comparer) + => SetProviderValueComparer(comparer, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + ValueComparer? IConventionPrimitivePropertyBase.SetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => SetProviderValueComparer( + comparer, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => SetProviderValueComparer(comparerType, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? IConventionPrimitivePropertyBase.SetProviderValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + bool fromDataAnnotation) + => SetProviderValueComparer( + comparerType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueComparer IPrimitivePropertyBase.GetProviderValueComparer() + => GetProviderValueComparer()!; + + /// + /// 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. + /// + [DebuggerStepThrough] + void IMutablePrimitivePropertyBase.SetJsonValueReaderWriterType(Type? readerWriterType) + => SetJsonValueReaderWriterType(readerWriterType, ConfigurationSource.Explicit); + + /// + /// 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. + /// + [DebuggerStepThrough] + Type? IConventionPrimitivePropertyBase.SetJsonValueReaderWriterType( + Type? readerWriterType, + bool fromDataAnnotation) + => SetJsonValueReaderWriterType( + readerWriterType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + JsonValueReaderWriter? IReadOnlyPrimitivePropertyBase.GetJsonValueReaderWriter() + => GetJsonValueReaderWriter(); +} diff --git a/src/EFCore/Metadata/Internal/PrimitivePropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PrimitivePropertyBaseExtensions.cs new file mode 100644 index 00000000000..0a6a8baf80e --- /dev/null +++ b/src/EFCore/Metadata/Internal/PrimitivePropertyBaseExtensions.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 static class PrimitivePropertyBaseExtensions +{ + /// + /// 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 static IReadOnlyPrimitivePropertyBase? FindFirstDifferentPrincipal(this IReadOnlyPrimitivePropertyBase property) + { + var principal = property.FindFirstPrincipal(); + + return principal != property ? principal : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IPrimitivePropertyBase? FindGenerationProperty(this IPrimitivePropertyBase property) + { + var traversalList = new List { property }; + + var index = 0; + while (index < traversalList.Count) + { + var currentProperty = traversalList[index]; + + if (currentProperty.RequiresValueGenerator()) + { + return currentProperty; + } + + foreach (var foreignKey in currentProperty.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (currentProperty == foreignKey.Properties[propertyIndex]) + { + var nextProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (!traversalList.Contains(nextProperty)) + { + traversalList.Add(nextProperty); + } + } + } + } + + index++; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool RequiresValueGenerator(this IReadOnlyPrimitivePropertyBase property) + => (property.ValueGenerated.ForAdd() + && property.IsKey() + && (!property.IsForeignKey() + || property.IsForeignKeyToSelf() + || (property.GetContainingForeignKeys().All(fk => fk.Properties.Any(p => p != property && p.IsNullable))))) + || property.GetValueGeneratorFactory() != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool IsForeignKeyToSelf(this IReadOnlyPrimitivePropertyBase property) + { + Check.DebugAssert(property.IsKey(), "Only call this method for properties known to be part of a key."); + + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + var propertyIndex = foreignKey.Properties.IndexOf(property); + if (propertyIndex == foreignKey.PrincipalKey.Properties.IndexOf(property)) + { + return true; + } + } + + return false; + } + + /// + /// 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 static bool IsKey(this PrimitivePropertyBase property) + => property.Keys != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool MayBeStoreGenerated(this IPrimitivePropertyBase property) + { + if (property.ValueGenerated != ValueGenerated.Never + || property.IsForeignKey()) + { + return true; + } + + if (property.IsKey()) + { + var generationProperty = property.FindGenerationProperty(); + return (generationProperty != null) + && (generationProperty.ValueGenerated != ValueGenerated.Never); + } + + return false; + } +} diff --git a/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs b/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs index 22d1620b55d..e59b9a0a440 100644 --- a/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs +++ b/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs @@ -108,7 +108,7 @@ public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) { foreach (var (internalKeyBuilder, configurationSource) in Keys) { - internalKeyBuilder.Attach(entityTypeBuilder.Metadata.RootType().Builder, configurationSource); + internalKeyBuilder.Attach(entityTypeBuilder.Metadata.GetRootType().Builder, configurationSource); } } diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index a5248557df7..c6765d014f5 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage.Json; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -14,22 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class Property : PropertyBase, IMutableProperty, IConventionProperty, IProperty +public class Property : PrimitivePropertyBase, IMutableProperty, IConventionProperty, IProperty { - private bool? _isConcurrencyToken; - private object? _sentinel; - private bool? _isNullable; - private ValueGenerated? _valueGenerated; - private CoreTypeMapping? _typeMapping; private InternalPropertyBuilder? _builder; - private ConfigurationSource? _typeConfigurationSource; - private ConfigurationSource? _isNullableConfigurationSource; - private ConfigurationSource? _isConcurrencyTokenConfigurationSource; - private ConfigurationSource? _sentinelConfigurationSource; - private ConfigurationSource? _valueGeneratedConfigurationSource; - private ConfigurationSource? _typeMappingConfigurationSource; - /// /// 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 @@ -44,12 +29,8 @@ public Property( EntityType declaringEntityType, ConfigurationSource configurationSource, ConfigurationSource? typeConfigurationSource) - : base(name, propertyInfo, fieldInfo, configurationSource) + : base(name, clrType, propertyInfo, fieldInfo, declaringEntityType, configurationSource, typeConfigurationSource) { - DeclaringEntityType = declaringEntityType; - ClrType = clrType; - _typeConfigurationSource = typeConfigurationSource; - _builder = new InternalPropertyBuilder(this, declaringEntityType.Model.Builder); } @@ -59,28 +40,7 @@ public Property( /// 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 EntityType DeclaringEntityType { get; } - - /// - /// 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 override TypeBase DeclaringType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] - public override Type ClrType { get; } + public virtual EntityType DeclaringEntityType => (EntityType)DeclaringType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -119,87 +79,8 @@ public virtual void SetRemovedFromModel() /// 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 ConfigurationSource? GetTypeConfigurationSource() - => _typeConfigurationSource; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void UpdateTypeConfigurationSource(ConfigurationSource configurationSource) - => _typeConfigurationSource = _typeConfigurationSource.Max(configurationSource); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsNullable - { - get => _isNullable ?? DefaultIsNullable; - set => SetIsNullable(value, ConfigurationSource.Explicit); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) - { - EnsureMutable(); - - var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; - if (nullable == null) - { - _isNullable = null; - _isNullableConfigurationSource = null; - if (isChanging) - { - DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullableChanged(Builder); - } - - return nullable; - } - - if (nullable.Value) - { - if (!ClrType.IsNullableType()) - { - throw new InvalidOperationException( - CoreStrings.CannotBeNullable(Name, DeclaringEntityType.DisplayName(), ClrType.ShortDisplayName())); - } - - if (Keys != null) - { - throw new InvalidOperationException(CoreStrings.CannotBeNullablePK(Name, DeclaringEntityType.DisplayName())); - } - } - - _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); - - _isNullable = nullable; - - return isChanging - ? DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullableChanged(Builder) - : nullable; - } - - private bool DefaultIsNullable - => ClrType.IsNullableType(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetIsNullableConfigurationSource() - => _isNullableConfigurationSource; + protected override bool? OnPropertyNullableChanged() + => DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullabilityChanged(Builder); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -211,47 +92,17 @@ private bool DefaultIsNullable => DeclaringEntityType.Model.ConventionDispatcher.OnPropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueGenerated ValueGenerated - { - get => _valueGenerated ?? DefaultValueGenerated; - set => SetValueGenerated(value, ConfigurationSource.Explicit); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueGenerated? SetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) - { - EnsureMutable(); - - _valueGenerated = valueGenerated; - - _valueGeneratedConfigurationSource = valueGenerated == null - ? null - : configurationSource.Max(_valueGeneratedConfigurationSource); - - return valueGenerated; - } - - private static ValueGenerated DefaultValueGenerated - => ValueGenerated.Never; - - /// - /// 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. + /// Runs the conventions when an annotation was set or removed. /// - public virtual ConfigurationSource? GetValueGeneratedConfigurationSource() - => _valueGeneratedConfigurationSource; + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => DeclaringType.Model.ConventionDispatcher.OnPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -259,11 +110,16 @@ private static ValueGenerated DefaultValueGenerated /// 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 IsConcurrencyToken - { - get => _isConcurrencyToken ?? DefaultIsConcurrencyToken; - set => SetIsConcurrencyToken(value, ConfigurationSource.Explicit); - } + public static bool AreCompatible(IReadOnlyList properties, EntityType entityType) + => properties.All( + property => + property.IsShadowProperty() + || (property.IsIndexerProperty() + ? property.PropertyInfo == entityType.FindIndexerPropertyInfo() + : ((property.PropertyInfo != null + && entityType.GetRuntimeProperties().ContainsKey(property.Name)) + || (property.FieldInfo != null + && entityType.GetRuntimeFields().ContainsKey(property.Name))))); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -271,24 +127,8 @@ public virtual bool IsConcurrencyToken /// 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? SetIsConcurrencyToken(bool? concurrencyToken, ConfigurationSource configurationSource) - { - EnsureMutable(); - - if (IsConcurrencyToken != concurrencyToken) - { - _isConcurrencyToken = concurrencyToken; - } - - _isConcurrencyTokenConfigurationSource = concurrencyToken == null - ? null - : configurationSource.Max(_isConcurrencyTokenConfigurationSource); - - return concurrencyToken; - } - - private static bool DefaultIsConcurrencyToken - => false; + public override string ToString() + => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -296,8 +136,10 @@ private static bool DefaultIsConcurrencyToken /// 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 ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() - => _isConcurrencyTokenConfigurationSource; + public virtual DebugView DebugView + => new( + () => ((IReadOnlyProperty)this).ToDebugString(), + () => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -305,10 +147,10 @@ private static bool DefaultIsConcurrencyToken /// 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 object? Sentinel + IConventionPropertyBuilder IConventionProperty.Builder { - get => _sentinel ?? DefaultSentinel; - set => SetSentinel(value, ConfigurationSource.Explicit); + [DebuggerStepThrough] + get => Builder; } /// @@ -317,46 +159,22 @@ public virtual object? Sentinel /// 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 object? SetSentinel(object? sentinel, ConfigurationSource configurationSource) + IConventionAnnotatableBuilder IConventionAnnotatable.Builder { - EnsureMutable(); - - _sentinel = sentinel; - - _sentinelConfigurationSource = configurationSource.Max(_sentinelConfigurationSource); - - return sentinel; + [DebuggerStepThrough] + get => Builder; } - private object? DefaultSentinel - => (this is IProperty property - && property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var member, out _) - ? member!.GetMemberType() - : ClrType).GetDefaultValue(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetSentinelConfigurationSource() - => _sentinelConfigurationSource; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual int? SetMaxLength(int? maxLength, ConfigurationSource configurationSource) + IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType { - if (maxLength is < -1) - { - throw new ArgumentOutOfRangeException(nameof(maxLength)); - } - - return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.MaxLength, maxLength, configurationSource)?.Value; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -365,59 +183,10 @@ private object? DefaultSentinel /// 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 int? GetMaxLength() - => (int?)this[CoreAnnotationNames.MaxLength]; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetMaxLengthConfigurationSource() - => FindAnnotation(CoreAnnotationNames.MaxLength)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool? SetIsUnicode(bool? unicode, ConfigurationSource configurationSource) - => (bool?)SetOrRemoveAnnotation(CoreAnnotationNames.Unicode, unicode, configurationSource)?.Value; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool? IsUnicode() - => (bool?)this[CoreAnnotationNames.Unicode]; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetIsUnicodeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.Unicode)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual int? SetPrecision(int? precision, ConfigurationSource configurationSource) + IMutableEntityType IMutableProperty.DeclaringEntityType { - if (precision is < 0) - { - throw new ArgumentOutOfRangeException(nameof(precision)); - } - - return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Precision, precision, configurationSource)?.Value; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -426,32 +195,10 @@ private object? DefaultSentinel /// 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 int? GetPrecision() - => (int?)this[CoreAnnotationNames.Precision]; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetPrecisionConfigurationSource() - => FindAnnotation(CoreAnnotationNames.Precision)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual int? SetScale(int? scale, ConfigurationSource configurationSource) + IConventionEntityType IConventionProperty.DeclaringEntityType { - if (scale != null && scale < 0) - { - throw new ArgumentOutOfRangeException(nameof(scale)); - } - - return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Scale, scale, configurationSource)?.Value; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -460,1582 +207,9 @@ private object? DefaultSentinel /// 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 int? GetScale() - => (int?)this[CoreAnnotationNames.Scale]; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetScaleConfigurationSource() - => FindAnnotation(CoreAnnotationNames.Scale)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual PropertySaveBehavior? SetBeforeSaveBehavior( - PropertySaveBehavior? beforeSaveBehavior, - ConfigurationSource configurationSource) - => (PropertySaveBehavior?)SetOrRemoveAnnotation(CoreAnnotationNames.BeforeSaveBehavior, beforeSaveBehavior, configurationSource) - ?.Value; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual PropertySaveBehavior GetBeforeSaveBehavior() - => (PropertySaveBehavior?)this[CoreAnnotationNames.BeforeSaveBehavior] - ?? (ValueGenerated == ValueGenerated.OnAddOrUpdate - ? PropertySaveBehavior.Ignore - : PropertySaveBehavior.Save); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetBeforeSaveBehaviorConfigurationSource() - => FindAnnotation(CoreAnnotationNames.BeforeSaveBehavior)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual PropertySaveBehavior? SetAfterSaveBehavior( - PropertySaveBehavior? afterSaveBehavior, - ConfigurationSource configurationSource) + IEntityType IProperty.DeclaringEntityType { - if (afterSaveBehavior != null) - { - var errorMessage = CheckAfterSaveBehavior(afterSaveBehavior.Value); - if (errorMessage != null) - { - throw new InvalidOperationException(errorMessage); - } - } - - return (PropertySaveBehavior?)SetOrRemoveAnnotation( - CoreAnnotationNames.AfterSaveBehavior, afterSaveBehavior, configurationSource) - ?.Value; + [DebuggerStepThrough] + get => DeclaringEntityType; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual PropertySaveBehavior GetAfterSaveBehavior() - => (PropertySaveBehavior?)this[CoreAnnotationNames.AfterSaveBehavior] - ?? (IsKey() - ? PropertySaveBehavior.Throw - : ValueGenerated.ForUpdate() - ? PropertySaveBehavior.Ignore - : PropertySaveBehavior.Save); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetAfterSaveBehaviorConfigurationSource() - => FindAnnotation(CoreAnnotationNames.AfterSaveBehavior)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual string? CheckAfterSaveBehavior(PropertySaveBehavior behavior) - => behavior != PropertySaveBehavior.Throw - && IsKey() - ? CoreStrings.KeyPropertyMustBeReadOnly(Name, DeclaringEntityType.DisplayName()) - : null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func? SetValueGeneratorFactory( - Func? factory, - ConfigurationSource configurationSource) - { - RemoveAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType); - return (Func?) - SetAnnotation(CoreAnnotationNames.ValueGeneratorFactory, factory, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Type? SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? factoryType, - ConfigurationSource configurationSource) - { - if (factoryType != null) - { - if (!typeof(ValueGeneratorFactory).IsAssignableFrom(factoryType)) - { - throw new InvalidOperationException( - CoreStrings.BadValueGeneratorType( - factoryType.ShortDisplayName(), typeof(ValueGeneratorFactory).ShortDisplayName())); - } - - if (factoryType.IsAbstract - || !factoryType.GetTypeInfo().DeclaredConstructors.Any(c => c.IsPublic && c.GetParameters().Length == 0)) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateValueGenerator(factoryType.ShortDisplayName(), nameof(SetValueGeneratorFactory))); - } - } - - RemoveAnnotation(CoreAnnotationNames.ValueGeneratorFactory); - return (Type?)SetAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType, factoryType, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func? GetValueGeneratorFactory() - { - var factory = (Func?)this[CoreAnnotationNames.ValueGeneratorFactory]; - if (factory == null) - { - var factoryType = (Type?)this[CoreAnnotationNames.ValueGeneratorFactoryType]; - if (factoryType != null) - { - return ((ValueGeneratorFactory)Activator.CreateInstance(factoryType)!).Create; - } - } - - return factory; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetValueGeneratorFactoryConfigurationSource() - => (FindAnnotation(CoreAnnotationNames.ValueGeneratorFactory) - ?? FindAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType))?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueConverter? SetValueConverter( - ValueConverter? converter, - ConfigurationSource configurationSource) - { - var errorString = CheckValueConverter(converter); - if (errorString != null) - { - throw new InvalidOperationException(errorString); - } - - RemoveAnnotation(CoreAnnotationNames.ValueConverterType); - return (ValueConverter?)SetAnnotation(CoreAnnotationNames.ValueConverter, converter, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Type? SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, - ConfigurationSource configurationSource) - { - ValueConverter? converter = null; - if (converterType != null) - { - if (!typeof(ValueConverter).IsAssignableFrom(converterType)) - { - throw new InvalidOperationException( - CoreStrings.BadValueConverterType(converterType.ShortDisplayName(), typeof(ValueConverter).ShortDisplayName())); - } - - try - { - converter = (ValueConverter?)Activator.CreateInstance(converterType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateValueConverter( - converterType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); - } - } - - SetValueConverter(converter, configurationSource); - SetAnnotation(CoreAnnotationNames.ValueConverterType, converterType, configurationSource); - - return converterType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueConverter? GetValueConverter() - { - var annotation = FindAnnotation(CoreAnnotationNames.ValueConverter); - if (annotation != null) - { - return (ValueConverter?)annotation.Value; - } - - var property = this; - var i = 0; - for (; i < ForeignKey.LongestFkChainAllowedLength; i++) - { - Property? nextProperty = null; - foreach (var foreignKey in property.GetContainingForeignKeys()) - { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) - { - if (property == foreignKey.Properties[propertyIndex]) - { - var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; - if (principalProperty == this - || principalProperty == property) - { - break; - } - - annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverter); - if (annotation != null) - { - return (ValueConverter?)annotation.Value; - } - - nextProperty = principalProperty; - } - } - } - - if (nextProperty == null) - { - break; - } - - property = nextProperty; - } - - return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - DeclaringEntityType.DisplayName(), Name, "ValueConverter")) - : null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetValueConverterConfigurationSource() - => FindAnnotation(CoreAnnotationNames.ValueConverter)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual string? CheckValueConverter(ValueConverter? converter) - => converter != null - && converter.ModelClrType.UnwrapNullableType() != ClrType.UnwrapNullableType() - ? CoreStrings.ConverterPropertyMismatch( - converter.ModelClrType.ShortDisplayName(), - DeclaringEntityType.DisplayName(), - Name, - ClrType.ShortDisplayName()) - : null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Type? SetProviderClrType(Type? providerClrType, ConfigurationSource configurationSource) - => (Type?)SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType, configurationSource)?.Value; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Type? GetProviderClrType() - { - var annotation = FindAnnotation(CoreAnnotationNames.ProviderClrType); - if (annotation != null) - { - return (Type?)annotation.Value; - } - - var property = this; - var i = 0; - for (; i < ForeignKey.LongestFkChainAllowedLength; i++) - { - Property? nextProperty = null; - foreach (var foreignKey in property.GetContainingForeignKeys()) - { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) - { - if (property == foreignKey.Properties[propertyIndex]) - { - var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; - if (principalProperty == this - || principalProperty == property) - { - break; - } - - annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ProviderClrType); - if (annotation != null) - { - return (Type?)annotation.Value; - } - - nextProperty = principalProperty; - } - } - } - - if (nextProperty == null) - { - break; - } - - property = nextProperty; - } - - return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - DeclaringEntityType.DisplayName(), Name, "ProviderClrType")) - : null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetProviderClrTypeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.ProviderClrType)?.GetConfigurationSource(); - - private Type GetEffectiveProviderClrType() - => (TypeMapping?.Converter?.ProviderClrType - ?? ClrType).UnwrapNullableType(); - - /// - /// 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. - /// - [DisallowNull] - public virtual CoreTypeMapping? TypeMapping - { - get => IsReadOnly - ? NonCapturingLazyInitializer.EnsureInitialized( - ref _typeMapping, (IProperty)this, static property => - property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!) - : _typeMapping; - - set => SetTypeMapping(value, ConfigurationSource.Explicit); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual CoreTypeMapping? SetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource configurationSource) - { - _typeMapping = typeMapping; - _typeMappingConfigurationSource = typeMapping is null - ? null - : configurationSource.Max(_typeMappingConfigurationSource); - - return typeMapping; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetTypeMappingConfigurationSource() - => _typeMappingConfigurationSource; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueComparer? SetValueComparer(ValueComparer? comparer, ConfigurationSource configurationSource) - { - var errorString = CheckValueComparer(comparer); - if (errorString != null) - { - throw new InvalidOperationException(errorString); - } - - RemoveAnnotation(CoreAnnotationNames.ValueComparerType); - return (ValueComparer?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparer, comparer, configurationSource)?.Value; - } - - /// - /// 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. - /// - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public virtual Type? SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - ConfigurationSource configurationSource) - { - ValueComparer? comparer = null; - if (comparerType != null) - { - if (!typeof(ValueComparer).IsAssignableFrom(comparerType)) - { - throw new InvalidOperationException( - CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName())); - } - - try - { - comparer = (ValueComparer?)Activator.CreateInstance(comparerType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateValueComparer( - comparerType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); - } - } - - SetValueComparer(comparer, configurationSource); - return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparerType, comparerType, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueComparer? GetValueComparer() - => (GetValueComparer(null) ?? TypeMapping?.Comparer).ToNullableComparer(this); - - private ValueComparer? GetValueComparer(HashSet? checkedProperties) - { - var comparer = (ValueComparer?)this[CoreAnnotationNames.ValueComparer]; - if (comparer != null) - { - return comparer; - } - - var principal = (Property?)this.FindFirstDifferentPrincipal(); - if (principal == null) - { - return null; - } - - if (checkedProperties == null) - { - checkedProperties = new HashSet(); - } - else if (checkedProperties.Contains(this)) - { - return null; - } - - checkedProperties.Add(this); - return principal.GetValueComparer(checkedProperties); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetValueComparerConfigurationSource() - => FindAnnotation(CoreAnnotationNames.ValueComparer)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueComparer? GetKeyValueComparer() - => (GetValueComparer(null) ?? TypeMapping?.KeyComparer).ToNullableComparer(this); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueComparer? SetProviderValueComparer(ValueComparer? comparer, ConfigurationSource configurationSource) - { - RemoveAnnotation(CoreAnnotationNames.ProviderValueComparerType); - return (ValueComparer?)SetOrRemoveAnnotation(CoreAnnotationNames.ProviderValueComparer, comparer, configurationSource)?.Value; - } - - /// - /// 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. - /// - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public virtual Type? SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - ConfigurationSource configurationSource) - { - ValueComparer? comparer = null; - if (comparerType != null) - { - if (!typeof(ValueComparer).IsAssignableFrom(comparerType)) - { - throw new InvalidOperationException( - CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName())); - } - - try - { - comparer = (ValueComparer?)Activator.CreateInstance(comparerType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateValueComparer( - comparerType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); - } - } - - SetProviderValueComparer(comparer, configurationSource); - return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.ProviderValueComparerType, comparerType, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ValueComparer? GetProviderValueComparer() - => GetProviderValueComparer(null) - ?? (GetEffectiveProviderClrType() == ClrType.UnwrapNullableType() - ? GetKeyValueComparer() - : TypeMapping?.ProviderValueComparer); - - private ValueComparer? GetProviderValueComparer(HashSet? checkedProperties) - { - var comparer = (ValueComparer?)this[CoreAnnotationNames.ProviderValueComparer]; - if (comparer != null) - { - return comparer; - } - - var principal = (Property?)this.FindFirstDifferentPrincipal(); - if (principal == null - || principal.GetEffectiveProviderClrType() != GetEffectiveProviderClrType()) - { - return null; - } - - if (checkedProperties == null) - { - checkedProperties = new HashSet(); - } - else if (checkedProperties.Contains(this)) - { - return null; - } - - checkedProperties.Add(this); - return principal.GetProviderValueComparer(checkedProperties); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetProviderValueComparerConfigurationSource() - => FindAnnotation(CoreAnnotationNames.ProviderValueComparer)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual string? CheckValueComparer(ValueComparer? comparer) - => comparer != null - && comparer.Type.UnwrapNullableType() != ClrType.UnwrapNullableType() - ? CoreStrings.ComparerPropertyMismatch( - comparer.Type.ShortDisplayName(), - DeclaringEntityType.DisplayName(), - Name, - ClrType.ShortDisplayName()) - : null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() - { - return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]) - ?? TypeMapping?.JsonValueReaderWriter; - - static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType) - { - if (readerWriterType != null) - { - var instanceProperty = readerWriterType.GetAnyProperty("Instance"); - try - { - return instanceProperty != null - && instanceProperty.IsStatic() - && instanceProperty.GetMethod?.IsPublic == true - && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) - ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) - : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateJsonValueReaderWriter( - readerWriterType.ShortDisplayName()), e); - } - } - - return null; - } - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Type? SetJsonValueReaderWriterType( - Type? readerWriterType, - ConfigurationSource configurationSource) - { - if (readerWriterType != null) - { - var genericType = readerWriterType.GetGenericTypeImplementations(typeof(JsonValueReaderWriter<>)).FirstOrDefault(); - if (genericType == null) - { - throw new InvalidOperationException(CoreStrings.BadJsonValueReaderWriterType(readerWriterType.ShortDisplayName())); - } - } - - return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.JsonValueReaderWriterType, readerWriterType, configurationSource)?.Value; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.JsonValueReaderWriterType)?.GetConfigurationSource(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyKey? PrimaryKey { get; set; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual List? Keys { get; set; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsKey() - => Keys != null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable GetContainingKeys() - => Keys ?? Enumerable.Empty(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual List? ForeignKeys { get; set; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsForeignKey() - => ForeignKeys != null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable GetContainingForeignKeys() - => ForeignKeys ?? Enumerable.Empty(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual List? Indexes { get; set; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsIndex() - => Indexes != null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable GetContainingIndexes() - => Indexes ?? Enumerable.Empty(); - - /// - /// Runs the conventions when an annotation was set or removed. - /// - /// The key of the set annotation. - /// The annotation set. - /// The old annotation. - /// The annotation that was set. - protected override IConventionAnnotation? OnAnnotationSet( - string name, - IConventionAnnotation? annotation, - IConventionAnnotation? oldAnnotation) - => DeclaringType.Model.ConventionDispatcher.OnPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); - - /// - /// 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 static string Format(IEnumerable properties) - => "{" - + string.Join( - ", ", - properties.Select(p => string.IsNullOrEmpty(p) ? "" : "'" + p + "'")) - + "}"; - - /// - /// 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 static bool AreCompatible(IReadOnlyList properties, EntityType entityType) - => properties.All( - property => - property.IsShadowProperty() - || (property.IsIndexerProperty() - ? property.PropertyInfo == entityType.FindIndexerPropertyInfo() - : ((property.PropertyInfo != null - && entityType.GetRuntimeProperties().ContainsKey(property.Name)) - || (property.FieldInfo != null - && entityType.GetRuntimeFields().ContainsKey(property.Name))))); - - /// - /// 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 override string ToString() - => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual DebugView DebugView - => new( - () => ((IReadOnlyProperty)this).ToDebugString(), - () => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - - /// - /// 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. - /// - IConventionPropertyBuilder IConventionProperty.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - /// 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. - /// - IConventionAnnotatableBuilder IConventionAnnotatable.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - /// 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. - /// - IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IMutableEntityType IMutableProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IConventionEntityType IConventionProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IEntityType IProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - [DebuggerStepThrough] - CoreTypeMapping? IReadOnlyProperty.FindTypeMapping() - => TypeMapping; - - /// - /// 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. - /// - [DebuggerStepThrough] - void IMutableProperty.SetTypeMapping(CoreTypeMapping typeMapping) - => SetTypeMapping(typeMapping, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - CoreTypeMapping? IConventionProperty.SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) - => SetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IMutableProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IConventionProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IMutableProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IConventionProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IMutableProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IConventionProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - /// 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. - /// - [DebuggerStepThrough] - IReadOnlyKey? IReadOnlyProperty.FindContainingPrimaryKey() - => PrimaryKey; - - /// - /// 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. - /// - [DebuggerStepThrough] - bool? IConventionProperty.SetIsNullable(bool? nullable, bool fromDataAnnotation) - => SetIsNullable( - nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - ValueGenerated? IConventionProperty.SetValueGenerated(ValueGenerated? valueGenerated, bool fromDataAnnotation) - => SetValueGenerated( - valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool? IConventionProperty.SetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation) - => SetIsConcurrencyToken( - concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - object? IConventionProperty.SetSentinel(object? sentinel, bool fromDataAnnotation) - => SetSentinel( - sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetMaxLength(int? maxLength) - => SetMaxLength(maxLength, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - int? IConventionProperty.SetMaxLength(int? maxLength, bool fromDataAnnotation) - => SetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetPrecision(int? precision) - => SetPrecision(precision, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - int? IConventionProperty.SetPrecision(int? precision, bool fromDataAnnotation) - => SetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetScale(int? scale) - => SetScale(scale, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - int? IConventionProperty.SetScale(int? scale, bool fromDataAnnotation) - => SetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetIsUnicode(bool? unicode) - => SetIsUnicode(unicode, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - bool? IConventionProperty.SetIsUnicode(bool? unicode, bool fromDataAnnotation) - => SetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetBeforeSaveBehavior(PropertySaveBehavior? beforeSaveBehavior) - => SetBeforeSaveBehavior(beforeSaveBehavior, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - PropertySaveBehavior? IConventionProperty.SetBeforeSaveBehavior( - PropertySaveBehavior? beforeSaveBehavior, - bool fromDataAnnotation) - => SetBeforeSaveBehavior( - beforeSaveBehavior, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavior) - => SetAfterSaveBehavior(afterSaveBehavior, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - PropertySaveBehavior? IConventionProperty.SetAfterSaveBehavior( - PropertySaveBehavior? afterSaveBehavior, - bool fromDataAnnotation) - => SetAfterSaveBehavior( - afterSaveBehavior, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueGeneratorFactory(Func? valueGeneratorFactory) - => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - Func? IConventionProperty.SetValueGeneratorFactory( - Func? valueGeneratorFactory, - bool fromDataAnnotation) - => SetValueGeneratorFactory( - valueGeneratorFactory, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory) - => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - Type? IConventionProperty.SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory, - bool fromDataAnnotation) - => SetValueGeneratorFactory( - valueGeneratorFactory, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueConverter(ValueConverter? converter) - => SetValueConverter(converter, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - ValueConverter? IConventionProperty.SetValueConverter(ValueConverter? converter, bool fromDataAnnotation) - => SetValueConverter( - converter, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType) - => SetValueConverter(converterType, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - Type? IConventionProperty.SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, - bool fromDataAnnotation) - => SetValueConverter( - converterType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetProviderClrType(Type? providerClrType) - => SetProviderClrType(providerClrType, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - Type? IConventionProperty.SetProviderClrType(Type? providerClrType, bool fromDataAnnotation) - => SetProviderClrType( - providerClrType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueComparer(ValueComparer? comparer) - => SetValueComparer(comparer, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - ValueComparer? IConventionProperty.SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => SetValueComparer( - comparer, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) - => SetValueComparer(comparerType, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? IConventionProperty.SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - bool fromDataAnnotation) - => SetValueComparer( - comparerType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - ValueComparer IProperty.GetValueComparer() - => GetValueComparer()!; - - /// - /// 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. - /// - ValueComparer IProperty.GetKeyValueComparer() - => GetKeyValueComparer()!; - - /// - /// 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. - /// - [DebuggerStepThrough] - void IMutableProperty.SetProviderValueComparer(ValueComparer? comparer) - => SetProviderValueComparer(comparer, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - ValueComparer? IConventionProperty.SetProviderValueComparer(ValueComparer? comparer, bool fromDataAnnotation) - => SetProviderValueComparer( - comparer, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableProperty.SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType) - => SetProviderValueComparer(comparerType, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? IConventionProperty.SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, - bool fromDataAnnotation) - => SetProviderValueComparer( - comparerType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - ValueComparer IProperty.GetProviderValueComparer() - => GetProviderValueComparer()!; - - /// - /// 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. - /// - [DebuggerStepThrough] - void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) - => SetJsonValueReaderWriterType(readerWriterType, ConfigurationSource.Explicit); - - /// - /// 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. - /// - [DebuggerStepThrough] - Type? IConventionProperty.SetJsonValueReaderWriterType( - Type? readerWriterType, - bool fromDataAnnotation) - => SetJsonValueReaderWriterType( - readerWriterType, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - JsonValueReaderWriter? IReadOnlyProperty.GetJsonValueReaderWriter() - => GetJsonValueReaderWriter(); - - /// - /// Gets the sentinel value that indicates that this property is not set. - /// - object? IReadOnlyPropertyBase.Sentinel - => Sentinel; } diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs index 409c44f2421..96b056bde49 100644 --- a/src/EFCore/Metadata/Internal/PropertyBase.cs +++ b/src/EFCore/Metadata/Internal/PropertyBase.cs @@ -54,7 +54,19 @@ protected PropertyBase( public virtual string Name { [DebuggerStepThrough] get; } /// - /// Indicates whether the model is read-only. + /// 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. + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] + public abstract Type ClrType { get; } + + /// + /// 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 override bool IsReadOnly => DeclaringType.Model.IsReadOnly; @@ -213,6 +225,27 @@ public virtual void SetConfigurationSource(ConfigurationSource configurationSour return OnFieldInfoSet(fieldInfo, oldFieldInfo); } + /// + /// 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. + /// + protected virtual FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => newFieldInfo; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetFieldInfoConfigurationSource() + => _fieldInfoConfigurationSource; + + private void UpdateFieldInfoConfigurationSource(ConfigurationSource configurationSource) + => _fieldInfoConfigurationSource = configurationSource.Max(_fieldInfoConfigurationSource); + /// /// 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 @@ -305,43 +338,12 @@ public virtual PropertyIndexes PropertyIndexes static property => { property.EnsureReadOnly(); - - var _ = (property.DeclaringType as EntityType)?.Counts; + var _ = ((IRuntimeTypeBase)property.DeclaringType).Counts; }); set => NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); } - /// - /// 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. - /// - protected virtual FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) - => newFieldInfo; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetFieldInfoConfigurationSource() - => _fieldInfoConfigurationSource; - - private void UpdateFieldInfoConfigurationSource(ConfigurationSource configurationSource) - => _fieldInfoConfigurationSource = configurationSource.Max(_fieldInfoConfigurationSource); - - /// - /// 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. - /// - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - public abstract Type ClrType { get; } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs index 445873011d1..219b8fcea4f 100644 --- a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs +++ b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs @@ -38,7 +38,7 @@ public PropertyConfiguration(Type clrType) /// 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 void Apply(IMutableProperty property) + public virtual void Apply(IMutablePrimitivePropertyBase property) { foreach (var annotation in GetAnnotations()) { diff --git a/src/EFCore/Metadata/Internal/PropertyCounts.cs b/src/EFCore/Metadata/Internal/PropertyCounts.cs index 53e0fb8781f..4ba361ecf71 100644 --- a/src/EFCore/Metadata/Internal/PropertyCounts.cs +++ b/src/EFCore/Metadata/Internal/PropertyCounts.cs @@ -20,6 +20,7 @@ public class PropertyCounts public PropertyCounts( int propertyCount, int navigationCount, + int complexPropertyCount, int originalValueCount, int shadowCount, int relationshipCount, @@ -27,6 +28,7 @@ public PropertyCounts( { PropertyCount = propertyCount; NavigationCount = navigationCount; + ComplexPropertyCount = complexPropertyCount; OriginalValueCount = originalValueCount; ShadowCount = shadowCount; RelationshipCount = relationshipCount; @@ -49,6 +51,14 @@ public PropertyCounts( /// public virtual int NavigationCount { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int ComplexPropertyCount { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index f0903ede3de..12a896b7e8b 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -29,129 +29,6 @@ public static bool ForAdd(this ValueGenerated valueGenerated) public static bool ForUpdate(this ValueGenerated valueGenerated) => (valueGenerated & ValueGenerated.OnUpdate) != 0; - /// - /// 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 static IReadOnlyProperty? FindFirstDifferentPrincipal(this IReadOnlyProperty property) - { - var principal = property.FindFirstPrincipal(); - - return principal != property ? principal : null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static IProperty? FindGenerationProperty(this IProperty property) - { - var traversalList = new List { property }; - - var index = 0; - while (index < traversalList.Count) - { - var currentProperty = traversalList[index]; - - if (currentProperty.RequiresValueGenerator()) - { - return currentProperty; - } - - foreach (var foreignKey in currentProperty.GetContainingForeignKeys()) - { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) - { - if (currentProperty == foreignKey.Properties[propertyIndex]) - { - var nextProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; - if (!traversalList.Contains(nextProperty)) - { - traversalList.Add(nextProperty); - } - } - } - } - - index++; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static bool RequiresValueGenerator(this IReadOnlyProperty property) - => (property.ValueGenerated.ForAdd() - && property.IsKey() - && (!property.IsForeignKey() - || property.IsForeignKeyToSelf() - || (property.GetContainingForeignKeys().All(fk => fk.Properties.Any(p => p != property && p.IsNullable))))) - || property.GetValueGeneratorFactory() != null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static bool IsForeignKeyToSelf(this IReadOnlyProperty property) - { - Check.DebugAssert(property.IsKey(), "Only call this method for properties known to be part of a key."); - - foreach (var foreignKey in property.GetContainingForeignKeys()) - { - var propertyIndex = foreignKey.Properties.IndexOf(property); - if (propertyIndex == foreignKey.PrincipalKey.Properties.IndexOf(property)) - { - return true; - } - } - - return false; - } - - /// - /// 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 static bool IsKey(this Property property) - => property.Keys != null; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static bool MayBeStoreGenerated(this IProperty property) - { - if (property.ValueGenerated != ValueGenerated.Never - || property.IsForeignKey()) - { - return true; - } - - if (property.IsKey()) - { - var generationProperty = property.FindGenerationProperty(); - return (generationProperty != null) - && (generationProperty.ValueGenerated != ValueGenerated.Never); - } - - return false; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyNameComparer.cs b/src/EFCore/Metadata/Internal/PropertyNameComparer.cs index 00e0b95ccac..28705c57351 100644 --- a/src/EFCore/Metadata/Internal/PropertyNameComparer.cs +++ b/src/EFCore/Metadata/Internal/PropertyNameComparer.cs @@ -11,7 +11,17 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public sealed class PropertyNameComparer : IComparer { - private readonly IReadOnlyEntityType _entityType; + private readonly IReadOnlyEntityType? _entityType; + + /// + /// 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 PropertyNameComparer() + { + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,8 +45,7 @@ public int Compare(string? x, string? y) var xIndex = -1; var yIndex = -1; - var properties = _entityType.FindPrimaryKey()?.Properties; - + var properties = _entityType?.FindPrimaryKey()?.Properties; if (properties != null) { for (var i = 0; i < properties.Count; i++) diff --git a/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs index 5359693ab01..6fe8c1c39e0 100644 --- a/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs @@ -21,16 +21,53 @@ public class PropertyParameterBindingFactory : IPropertyParameterBindingFactory IEntityType entityType, Type parameterType, string parameterName) + => FindParameter(entityType.GetProperties(), parameterType, parameterName); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ParameterBinding? FindParameter( + IComplexType complexType, + Type parameterType, + string parameterName) + => FindParameter(complexType.GetProperties(), parameterType, parameterName); + + /// + /// 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. + /// + private static ParameterBinding? FindParameter( + IEnumerable properties, + Type parameterType, + string parameterName) { var candidateNames = GetCandidatePropertyNames(parameterName); - return entityType.GetProperties().Where( - p => p.ClrType == parameterType - && candidateNames.Any(c => c.Equals(p.Name, StringComparison.Ordinal))) - .Select(p => new PropertyParameterBinding(p)).FirstOrDefault(); + foreach (var property in properties) + { + if (property.ClrType != parameterType) + { + continue; + } + + foreach (var name in candidateNames) + { + if (name.Equals(property.Name, StringComparison.Ordinal)) + { + return new PropertyParameterBinding(property); + } + } + } + + return null; } - private static IList GetCandidatePropertyNames(string parameterName) + private static List GetCandidatePropertyNames(string parameterName) { var pascalized = char.ToUpperInvariant(parameterName[0]) + parameterName[1..]; diff --git a/src/EFCore/Metadata/Internal/SkipNavigation.cs b/src/EFCore/Metadata/Internal/SkipNavigation.cs index e80127fa9a4..a32c9f5510b 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigation.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigation.cs @@ -16,6 +16,7 @@ public class SkipNavigation : PropertyBase, IMutableSkipNavigation, IConventionS private ConfigurationSource? _foreignKeyConfigurationSource; private ConfigurationSource? _inverseConfigurationSource; private InternalSkipNavigationBuilder? _builder; + private readonly Type _type; // Warning: Never access these fields directly as access needs to be thread-safe private IClrCollectionAccessor? _collectionAccessor; @@ -30,6 +31,7 @@ public class SkipNavigation : PropertyBase, IMutableSkipNavigation, IConventionS /// public SkipNavigation( string name, + Type? navigationType, PropertyInfo? propertyInfo, FieldInfo? fieldInfo, EntityType declaringEntityType, @@ -43,6 +45,11 @@ public SkipNavigation( TargetEntityType = targetEntityType; IsCollection = collection; IsOnDependent = onDependent; + _type = navigationType + ?? this.GetIdentifyingMemberInfo()?.GetMemberType() + ?? (IsCollection + ? typeof(IEnumerable<>).MakeGenericType(TargetEntityType.ClrType) + : TargetEntityType.ClrType); _builder = new InternalSkipNavigationBuilder(this, targetEntityType.Model.Builder); } @@ -67,10 +74,7 @@ private void ProcessForeignKey(ForeignKey foreignKey) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override Type ClrType - => this.GetIdentifyingMemberInfo()?.GetMemberType() - ?? (IsCollection - ? typeof(IEnumerable<>).MakeGenericType(TargetEntityType.ClrType) - : TargetEntityType.ClrType); + => _type; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs b/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs index 331c30c6ece..2ae23a5d405 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs @@ -37,6 +37,6 @@ public int Compare(IReadOnlySkipNavigation? x, IReadOnlySkipNavigation? y) (null, null) => 0, (not null, not null) => StringComparer.Ordinal.Compare(x.Name, y.Name) is var compare && compare != 0 ? compare - : EntityTypeFullNameComparer.Instance.Compare(x.DeclaringEntityType, y.DeclaringEntityType) + : TypeBaseNameComparer.Instance.Compare(x.DeclaringEntityType, y.DeclaringEntityType) }; } diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 1041ff83716..57da21c0b5a 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -91,6 +91,22 @@ protected TypeBase( public override bool IsReadOnly => Model.IsReadOnly; + /// + /// 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 abstract bool IsInModel { get; } + + /// + /// 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 abstract void OnTypeRemoved(); + /// /// 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 @@ -115,6 +131,14 @@ public override bool IsReadOnly /// public virtual bool IsPropertyBag { [DebuggerStepThrough] get; } + /// + /// 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. + /// + protected abstract IConventionTypeBaseBuilder BaseBuilder { get; } + /// /// 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 @@ -392,6 +416,18 @@ Type IReadOnlyTypeBase.ClrType get => ClrType; } + /// + /// 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. + /// + IConventionTypeBaseBuilder IConventionTypeBase.Builder + { + [DebuggerStepThrough] + get => BaseBuilder; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs index 972ef62545d..6f35fb8f09c 100644 --- a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs @@ -11,6 +11,34 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public static class TypeBaseExtensions { + /// + /// 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 static string DisplayName(this TypeBase entityType) + => ((IReadOnlyTypeBase)entityType).DisplayName(); + + /// + /// 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 static string ShortName(this TypeBase entityType) + => ((IReadOnlyTypeBase)entityType).ShortName(); + + /// + /// 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. + /// + [DebuggerStepThrough] + public static string GetOwnedName(this IReadOnlyTypeBase type, string simpleName, string ownershipNavigation) + => type.Name + "." + ownershipNavigation + "#" + simpleName; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/TypeConfigurationType.cs b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs index 60ad541dc3e..0f1313bae28 100644 --- a/src/EFCore/Metadata/Internal/TypeConfigurationType.cs +++ b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs @@ -43,6 +43,14 @@ public enum TypeConfigurationType /// OwnedEntityType, + /// + /// 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. + /// + ComplexType, + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/KeyComparer.cs b/src/EFCore/Metadata/KeyComparer.cs index fe94c47a6ab..f71356b02eb 100644 --- a/src/EFCore/Metadata/KeyComparer.cs +++ b/src/EFCore/Metadata/KeyComparer.cs @@ -39,7 +39,7 @@ private KeyComparer() public int Compare(IReadOnlyKey? x, IReadOnlyKey? y) { var result = PropertyListComparer.Instance.Compare(x?.Properties, y?.Properties); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -60,7 +60,7 @@ public int GetHashCode(IReadOnlyKey obj) { var hashCode = new HashCode(); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/PropertyParameterBinding.cs b/src/EFCore/Metadata/PropertyParameterBinding.cs index a99860a6a07..9dce48737e0 100644 --- a/src/EFCore/Metadata/PropertyParameterBinding.cs +++ b/src/EFCore/Metadata/PropertyParameterBinding.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Describes the binding from an to a parameter in a constructor, factory method, +/// Describes the binding from an to a parameter in a constructor, factory method, /// or similar. /// /// @@ -13,10 +13,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public class PropertyParameterBinding : ParameterBinding { /// - /// Creates a new instance for the given . + /// Creates a new instance for the given . /// /// The property to bind. - public PropertyParameterBinding(IProperty property) + public PropertyParameterBinding(IPrimitivePropertyBase property) : base(property.ClrType, property) { } diff --git a/src/EFCore/Metadata/PropertySaveBehavior.cs b/src/EFCore/Metadata/PropertySaveBehavior.cs index 64f8cd10abb..4712abec7ac 100644 --- a/src/EFCore/Metadata/PropertySaveBehavior.cs +++ b/src/EFCore/Metadata/PropertySaveBehavior.cs @@ -6,8 +6,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// Indicates how changes to the value of a property will be handled by Entity Framework change tracking /// which in turn will determine whether the value set is sent to the database or not. -/// Used with and -/// +/// Used with and +/// /// /// /// See Modeling entity types and relationships and diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs new file mode 100644 index 00000000000..60378ab7d6b --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -0,0 +1,129 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of an entity type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeComplexProperty : RuntimePropertyBase, IComplexProperty +{ + private readonly bool _isNullable; + private readonly bool _isCollection; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public RuntimeComplexProperty( + string name, + Type clrType, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + RuntimeTypeBase declaringType, + PropertyAccessMode propertyAccessMode, + bool nullable, + bool collection, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + : base(name, propertyInfo, fieldInfo, propertyAccessMode) + { + DeclaringType = declaringType; + ClrType = clrType; + _isNullable = nullable; + _isCollection = collection; + ComplexType = new RuntimeComplexType( + targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag); + } + + /// + /// Gets the type of value that this property-like object holds. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + protected override Type ClrType { get; } + + /// + /// Gets the type that this property belongs to. + /// + public override RuntimeTypeBase DeclaringType { get; } + + /// + /// Gets the type of value that this property-like object holds. + /// + public virtual RuntimeComplexType ComplexType { get; } + + /// + public override object? Sentinel => null; + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual DebugView DebugView + => new( + () => ((IProperty)this).ToDebugString(), + () => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyTypeBase IReadOnlyPropertyBase.DeclaringType + { + [DebuggerStepThrough] + get => DeclaringType; + } + + /// + ITypeBase IPropertyBase.DeclaringType + { + [DebuggerStepThrough] + get => DeclaringType; + } + /// + + IComplexType IComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + IReadOnlyComplexType IReadOnlyComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + bool IReadOnlyComplexProperty.IsNullable + { + [DebuggerStepThrough] + get => _isNullable; + } + + /// + bool IReadOnlyComplexProperty.IsCollection + { + [DebuggerStepThrough] + get => _isCollection; + } +} diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs new file mode 100644 index 00000000000..973fd6d149b --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -0,0 +1,495 @@ +// 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.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents an entity type in a model. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeComplexType : RuntimeTypeBase, IRuntimeComplexType +{ + private readonly SortedDictionary _properties = + new SortedDictionary(StringComparer.Ordinal); + + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + + private readonly RuntimeComplexType? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); + private readonly ChangeTrackingStrategy _changeTrackingStrategy; + private InstantiationBinding? _constructorBinding; + private InstantiationBinding? _serviceOnlyConstructorBinding; + + // Warning: Never access these fields directly as access needs to be thread-safe + private PropertyCounts? _counts; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public RuntimeComplexType( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + RuntimeComplexProperty complexProperty, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + : base(name, type, indexerPropertyInfo, propertyBag) + { + ComplexProperty = complexProperty; + _baseType = null; + _changeTrackingStrategy = changeTrackingStrategy; + FundametalEntityType = complexProperty.DeclaringType switch + { + RuntimeEntityType entityType => entityType, + RuntimeComplexType declaringComplexType => declaringComplexType.FundametalEntityType, + _ => throw new NotImplementedException() + }; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RuntimeComplexProperty ComplexProperty { get; } + + /// + /// Gets the model that this type belongs to. + /// + public override RuntimeModel Model + => ComplexProperty.DeclaringType.Model; + + /// + /// 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. + /// + private RuntimeEntityType FundametalEntityType { get; } + + private IEnumerable GetDerivedTypes() + { + if (_directlyDerivedTypes.Count == 0) + { + return Enumerable.Empty(); + } + + var derivedTypes = new List(); + var type = this; + var currentTypeIndex = 0; + while (type != null) + { + derivedTypes.AddRange(type._directlyDerivedTypes); + type = derivedTypes.Count > currentTypeIndex + ? derivedTypes[currentTypeIndex] + : null; + currentTypeIndex++; + } + + return derivedTypes; + } + + /// + /// Adds a property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The property value to use to consider the property not set. + /// The corresponding CLR property or for a shadow property. + /// The corresponding CLR field or for a shadow property. + /// The used for this property. + /// A value indicating whether this property can contain . + /// A value indicating whether this property is used as a concurrency token. + /// A value indicating when a value for this property will be generated by the database. + /// + /// A value indicating whether or not this property can be modified before the entity is saved to the database. + /// + /// + /// A value indicating whether or not this property can be modified after the entity is saved to the database. + /// + /// The maximum length of data that is allowed in this property. + /// A value indicating whether or not the property can persist Unicode characters. + /// The precision of data that is allowed in this property. + /// The scale of data that is allowed in this property. + /// + /// The type that the property value will be converted to before being sent to the database provider. + /// + /// The factory that has been set to generate values for this property, if any. + /// The custom set for this property. + /// The for this property. + /// The to use with keys for this property. + /// The to use for the provider values for this property. + /// The for this property. + /// The for this property. + /// The newly created property. + public virtual RuntimeComplexTypeProperty AddProperty( + string name, + Type clrType, + object? sentinel = null, + PropertyInfo? propertyInfo = null, + FieldInfo? fieldInfo = null, + PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, + bool nullable = false, + bool concurrencyToken = false, + ValueGenerated valueGenerated = ValueGenerated.Never, + PropertySaveBehavior beforeSaveBehavior = PropertySaveBehavior.Save, + PropertySaveBehavior afterSaveBehavior = PropertySaveBehavior.Save, + int? maxLength = null, + bool? unicode = null, + int? precision = null, + int? scale = null, + Type? providerPropertyType = null, + Func? valueGeneratorFactory = null, + ValueConverter? valueConverter = null, + ValueComparer? valueComparer = null, + ValueComparer? keyValueComparer = null, + ValueComparer? providerValueComparer = null, + JsonValueReaderWriter? jsonValueReaderWriter = null, + CoreTypeMapping? typeMapping = null) + { + var property = new RuntimeComplexTypeProperty( + name, + clrType, + sentinel, + propertyInfo, + fieldInfo, + this, + propertyAccessMode, + nullable, + concurrencyToken, + valueGenerated, + beforeSaveBehavior, + afterSaveBehavior, + maxLength, + unicode, + precision, + scale, + providerPropertyType, + valueGeneratorFactory, + valueConverter, + valueComparer, + keyValueComparer, + providerValueComparer, + jsonValueReaderWriter, + typeMapping); + + _properties.Add(property.Name, property); + + return property; + } + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + public virtual RuntimeComplexTypeProperty? FindProperty(string name) + => FindDeclaredProperty(name) ?? _baseType?.FindProperty(name); + + private RuntimeComplexTypeProperty? FindDeclaredProperty(string name) + => _properties.TryGetValue(name, out var property) + ? property + : null; + + private IEnumerable GetDeclaredProperties() + => _properties.Values; + + private IEnumerable GetDerivedProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); + + /// + /// Finds matching properties on the given entity type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigations or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + public virtual IReadOnlyList? FindProperties(IEnumerable propertyNames) + { + var properties = new List(); + foreach (var propertyName in propertyNames) + { + var property = FindProperty(propertyName); + if (property == null) + { + return null; + } + + properties.Add(property); + } + + return properties; + } + + private IEnumerable GetProperties() + => _baseType != null + ? _baseType.GetProperties().Concat(_properties.Values) + : _properties.Values; + + /// + /// Adds a complex property to this complex type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The name of the complex type to be added. + /// The CLR type that is used to represent instances of this complex type. + /// The corresponding CLR property or for a shadow property. + /// The corresponding CLR field or for a shadow property. + /// The used for this property. + /// A value indicating whether this property can contain . + /// Indicates whether the property represents a collection. + /// The change tracking strategy for this complex type. + /// The for the indexer on the associated CLR type if one exists. + /// + /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties + /// and a method that can be used to determine whether a given indexer property contains a value. + /// + /// The newly created property. + public virtual RuntimeComplexProperty AddComplexProperty( + string name, + Type clrType, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + PropertyInfo? propertyInfo = null, + FieldInfo? fieldInfo = null, + PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, + bool nullable = false, + bool collection = false, + ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, + PropertyInfo? indexerPropertyInfo = null, + bool propertyBag = false) + { + var property = new RuntimeComplexProperty( + name, + clrType, + targetTypeName, + targetType, + propertyInfo, + fieldInfo, + this, + propertyAccessMode, + nullable, + collection, + changeTrackingStrategy, + indexerPropertyInfo, + propertyBag); + + _complexProperties.Add(property.Name, property); + + return property; + } + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + public virtual RuntimeComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(name) ?? _baseType?.FindComplexProperty(name); + + private RuntimeComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(name, out var property) + ? property + : null; + + private IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; + + private IEnumerable GetDerivedComplexProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); + + private IEnumerable GetComplexProperties() + => _baseType != null + ? _baseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + /// + /// Gets or sets the for the preferred constructor. + /// + public virtual InstantiationBinding? ConstructorBinding + { + get => !ClrType.IsAbstract + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _constructorBinding, this, static complexType => + { + ((IModel)complexType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( + complexType, + out complexType._constructorBinding, + out complexType._serviceOnlyConstructorBinding); + }) + : _constructorBinding; + + [DebuggerStepThrough] + set => _constructorBinding = value; + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual InstantiationBinding? ServiceOnlyConstructorBinding + { + [DebuggerStepThrough] + get => _serviceOnlyConstructorBinding; + + [DebuggerStepThrough] + set => _serviceOnlyConstructorBinding = value; + } + + /// + /// 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. + /// + protected override PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static complexType => complexType.CalculateCounts()); + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual DebugView DebugView + => new( + () => ((IReadOnlyComplexType)this).ToDebugString(), + () => ((IReadOnlyComplexType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + bool IReadOnlyTypeBase.HasSharedClrType + { + [DebuggerStepThrough] + get => true; + } + + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IReadOnlyComplexProperty IReadOnlyComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + IComplexProperty IComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + IReadOnlyEntityType IReadOnlyComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundametalEntityType; + } + + /// + [DebuggerStepThrough] + ChangeTrackingStrategy IReadOnlyComplexType.GetChangeTrackingStrategy() + => _changeTrackingStrategy; + + /// + [DebuggerStepThrough] + IReadOnlyList? IReadOnlyComplexType.FindProperties(IReadOnlyList propertyNames) + => FindProperties(propertyNames); + + /// + [DebuggerStepThrough] + IReadOnlyComplexTypeProperty? IReadOnlyComplexType.FindProperty(string name) + => FindProperty(name); + + /// + [DebuggerStepThrough] + IComplexTypeProperty? IComplexType.FindProperty(string name) + => FindProperty(name); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetProperties() + => GetProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetProperties() + => GetProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IComplexProperty? IComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetServiceOnlyConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); +} diff --git a/src/EFCore/Metadata/RuntimeComplexTypeProperty.cs b/src/EFCore/Metadata/RuntimeComplexTypeProperty.cs new file mode 100644 index 00000000000..9dd54c8b693 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexTypeProperty.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of an entity type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeComplexTypeProperty : RuntimePrimitivePropertyBase, IComplexTypeProperty +{ + /// + /// 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. + /// + [EntityFrameworkInternal] + public RuntimeComplexTypeProperty( + string name, + Type clrType, + object? sentinel, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + RuntimeComplexType declaringComplexType, + PropertyAccessMode propertyAccessMode, + bool nullable, + bool concurrencyToken, + ValueGenerated valueGenerated, + PropertySaveBehavior beforeSaveBehavior, + PropertySaveBehavior afterSaveBehavior, + int? maxLength, + bool? unicode, + int? precision, + int? scale, + Type? providerClrType, + Func? valueGeneratorFactory, + ValueConverter? valueConverter, + ValueComparer? valueComparer, + ValueComparer? keyValueComparer, + ValueComparer? providerValueComparer, + JsonValueReaderWriter? jsonValueReaderWriter, + CoreTypeMapping? typeMapping) + : base(name, clrType, sentinel, propertyInfo, fieldInfo, declaringComplexType, propertyAccessMode, + nullable, concurrencyToken, valueGenerated, beforeSaveBehavior, afterSaveBehavior, + maxLength, unicode, precision, scale, providerClrType, valueGeneratorFactory, + valueConverter, valueComparer, keyValueComparer, providerValueComparer, jsonValueReaderWriter, typeMapping) + { + } + + /// + /// Gets the type that this property belongs to. + /// + public virtual RuntimeComplexType DeclaringComplexType + => (RuntimeComplexType)DeclaringType; + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual DebugView DebugView + => new( + () => ((IProperty)this).ToDebugString(), + () => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyComplexType IReadOnlyComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } + + /// + IComplexType IComplexTypeProperty.DeclaringComplexType + { + [DebuggerStepThrough] + get => DeclaringComplexType; + } +} diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 4aedc345306..b259715d37f 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeEntityType : AnnotatableBase, IRuntimeEntityType +public class RuntimeEntityType : RuntimeTypeBase, IRuntimeEntityType { private readonly List _foreignKeys = new(); @@ -30,6 +30,9 @@ private readonly SortedDictionary _serviceProper private readonly SortedDictionary _properties; + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + private readonly SortedDictionary, RuntimeIndex> _unnamedIndexes = new(PropertyListComparer.Instance); @@ -45,16 +48,12 @@ private readonly SortedDictionary _triggers private RuntimeKey? _primaryKey; private readonly bool _hasSharedClrType; - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - private readonly Type _clrType; - + private RuntimeModel _model; private readonly RuntimeEntityType? _baseType; - private readonly SortedSet _directlyDerivedTypes = new(EntityTypeFullNameComparer.Instance); + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); private readonly ChangeTrackingStrategy _changeTrackingStrategy; private InstantiationBinding? _constructorBinding; private InstantiationBinding? _serviceOnlyConstructorBinding; - private readonly PropertyInfo? _indexerPropertyInfo; - private readonly bool _isPropertyBag; private readonly object? _discriminatorValue; private bool _hasServiceProperties; @@ -62,11 +61,6 @@ private readonly SortedDictionary _triggers private PropertyCounts? _counts; private Func? _relationshipSnapshotFactory; - private Func? _originalValuesFactory; - private Func? _temporaryValuesFactory; - private Func? _storeGeneratedValuesFactory; - private Func? _shadowValuesFactory; - private Func? _emptyShadowValuesFactory; private IProperty[]? _foreignKeyProperties; private IProperty[]? _valueGeneratingProperties; @@ -88,11 +82,10 @@ public RuntimeEntityType( PropertyInfo? indexerPropertyInfo, bool propertyBag, object? discriminatorValue) + : base(name, type, indexerPropertyInfo, propertyBag) { - Name = name; - _clrType = type; _hasSharedClrType = sharedClrType; - Model = model; + _model = model; if (baseType != null) { _baseType = baseType; @@ -100,30 +93,23 @@ public RuntimeEntityType( } _changeTrackingStrategy = changeTrackingStrategy; - _indexerPropertyInfo = indexerPropertyInfo; - _isPropertyBag = propertyBag; SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); _discriminatorValue = discriminatorValue; _properties = new SortedDictionary(new PropertyNameComparer(this)); } - /// - /// Gets the name of this type. - /// - public virtual string Name { [DebuggerStepThrough] get; } - /// /// Gets the model that this type belongs to. /// - public virtual RuntimeModel Model { [DebuggerStepThrough] get; private set; } + public override RuntimeModel Model => _model; /// /// Re-parents this entity type to the given model. /// /// The new parent model. public virtual void Reparent(RuntimeModel model) - => Model = model; + => _model = model; private IEnumerable GetDerivedTypes() { @@ -720,10 +706,84 @@ private IEnumerable GetProperties() ? _baseType.GetProperties().Concat(_properties.Values) : _properties.Values; - /// - [DebuggerStepThrough] - public virtual PropertyInfo? FindIndexerPropertyInfo() - => _indexerPropertyInfo; + /// + /// Adds a complex property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The name of the complex type to be added. + /// The CLR type that is used to represent instances of this complex type. + /// The corresponding CLR property or for a shadow property. + /// The corresponding CLR field or for a shadow property. + /// The used for this property. + /// A value indicating whether this property can contain . + /// Indicates whether the property represents a collection. + /// The change tracking strategy for this complex type. + /// The for the indexer on the associated CLR type if one exists. + /// + /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties + /// and a method that can be used to determine whether a given indexer property contains a value. + /// + /// The newly created property. + public virtual RuntimeComplexProperty AddComplexProperty( + string name, + Type clrType, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + PropertyInfo? propertyInfo = null, + FieldInfo? fieldInfo = null, + PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, + bool nullable = false, + bool collection = false, + ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, + PropertyInfo? indexerPropertyInfo = null, + bool propertyBag = false) + { + var property = new RuntimeComplexProperty( + name, + clrType, + targetTypeName, + targetType, + propertyInfo, + fieldInfo, + this, + propertyAccessMode, + nullable, + collection, + changeTrackingStrategy, + indexerPropertyInfo, + propertyBag); + + _complexProperties.Add(property.Name, property); + + return property; + } + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + public virtual RuntimeComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(name) ?? _baseType?.FindComplexProperty(name); + + private RuntimeComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(name, out var property) + ? property + : null; + + private IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; + + private IEnumerable GetDerivedComplexProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); + + private IEnumerable GetComplexProperties() + => _baseType != null + ? _baseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; /// /// Adds a service property to this entity type. @@ -832,7 +892,7 @@ private IEnumerable GetTriggers() /// public virtual InstantiationBinding? ConstructorBinding { - get => !_clrType.IsAbstract + get => !ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( ref _constructorBinding, this, static entityType => { @@ -864,14 +924,13 @@ public virtual InstantiationBinding? ServiceOnlyConstructorBinding } /// - /// Returns the default indexer property that takes a value if one exists. + /// 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. /// - /// The type to look for the indexer on. - /// An indexer property or . - public static PropertyInfo? FindIndexerProperty( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] - Type type) - => type.FindIndexerProperty(); + protected override PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); /// /// Returns a string that represents the current object. @@ -892,14 +951,6 @@ public virtual DebugView DebugView () => ((IReadOnlyEntityType)this).ToDebugString(), () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - /// - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - Type IReadOnlyTypeBase.ClrType - { - [DebuggerStepThrough] - get => _clrType; - } - /// [DebuggerStepThrough] ChangeTrackingStrategy IReadOnlyEntityType.GetChangeTrackingStrategy() @@ -934,13 +985,6 @@ bool IReadOnlyTypeBase.HasSharedClrType get => _hasSharedClrType; } - /// - bool IReadOnlyTypeBase.IsPropertyBag - { - [DebuggerStepThrough] - get => _isPropertyBag; - } - /// IReadOnlyModel IReadOnlyTypeBase.Model { @@ -1328,46 +1372,12 @@ IEnumerable IReadOnlyEntityType.GetDeclaredTriggers() IEnumerable IEntityType.GetDeclaredTriggers() => GetDeclaredTriggers(); - /// - PropertyCounts IRuntimeEntityType.Counts - => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); - /// Func IRuntimeEntityType.RelationshipSnapshotFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _relationshipSnapshotFactory, this, static entityType => new RelationshipSnapshotFactoryFactory().Create(entityType)); - /// - Func IRuntimeEntityType.OriginalValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _originalValuesFactory, this, - static entityType => new OriginalValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.StoreGeneratedValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _storeGeneratedValuesFactory, this, - static entityType => new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType)); - - /// - Func IRuntimeEntityType.TemporaryValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _temporaryValuesFactory, this, - static entityType => new TemporaryValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.ShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _shadowValuesFactory, this, - static entityType => new ShadowValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.EmptyShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _emptyShadowValuesFactory, this, - static entityType => new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType)); - /// [DebuggerStepThrough] IEnumerable IEntityType.GetForeignKeyProperties() @@ -1382,6 +1392,86 @@ IEnumerable IEntityType.GetValueGeneratingProperties() ref _valueGeneratingProperties, this, static entityType => { return entityType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray(); }); + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetComplexProperties() + => GetComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyEntityType.GetDerivedComplexProperties() + => GetDerivedComplexProperties(); + + /// + /// 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. + /// + [DebuggerStepThrough] + IComplexProperty? IEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + /// 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. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyEntityType.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(name); + /// [DebuggerStepThrough] IReadOnlyServiceProperty? IReadOnlyEntityType.FindServiceProperty(string name) @@ -1431,9 +1521,9 @@ PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - ConfigurationSource? IRuntimeEntityType.GetConstructorBindingConfigurationSource() + ConfigurationSource? IRuntimeTypeBase.GetConstructorBindingConfigurationSource() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - ConfigurationSource? IRuntimeEntityType.GetServiceOnlyConstructorBindingConfigurationSource() + ConfigurationSource? IRuntimeTypeBase.GetServiceOnlyConstructorBindingConfigurationSource() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); } diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 60b1161c02a..8b3395e741b 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -54,7 +54,7 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// Whether this entity type can share its ClrType with other entities. /// The base type of this entity type. /// The name of the property that will be used for storing a discriminator value. - /// The change tracking strategy for this entity type + /// The change tracking strategy for this entity type. /// The for the indexer on the associated CLR type if one exists. /// /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties @@ -93,7 +93,7 @@ public virtual RuntimeEntityType AddEntityType( } else { - var types = new SortedSet(EntityTypeFullNameComparer.Instance) { entityType }; + var types = new SortedSet(TypeBaseNameComparer.Instance) { entityType }; _sharedTypes.Add(type, types); } } diff --git a/src/EFCore/Metadata/RuntimeNavigation.cs b/src/EFCore/Metadata/RuntimeNavigation.cs index b6850fa1705..4153e9097bc 100644 --- a/src/EFCore/Metadata/RuntimeNavigation.cs +++ b/src/EFCore/Metadata/RuntimeNavigation.cs @@ -63,12 +63,16 @@ public RuntimeNavigation( /// /// Gets the entity type that this navigation property belongs to. /// - public override RuntimeEntityType DeclaringEntityType + public virtual RuntimeEntityType DeclaringEntityType { [DebuggerStepThrough] get => ((IReadOnlyNavigation)this).IsOnDependent ? ForeignKey.DeclaringEntityType : ForeignKey.PrincipalEntityType; } + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; + /// public override object? Sentinel => null; diff --git a/src/EFCore/Metadata/RuntimePrimitivePropertyBase.cs b/src/EFCore/Metadata/RuntimePrimitivePropertyBase.cs new file mode 100644 index 00000000000..be706ec8178 --- /dev/null +++ b/src/EFCore/Metadata/RuntimePrimitivePropertyBase.cs @@ -0,0 +1,407 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of an structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimePrimitivePropertyBase : RuntimePropertyBase, IPrimitivePropertyBase +{ + private readonly bool _isNullable; + private readonly ValueGenerated _valueGenerated; + private readonly bool _isConcurrencyToken; + private readonly object? _sentinel; + private readonly PropertySaveBehavior _beforeSaveBehavior; + private readonly PropertySaveBehavior _afterSaveBehavior; + private readonly Func? _valueGeneratorFactory; + private readonly ValueConverter? _valueConverter; + private ValueComparer? _valueComparer; + private ValueComparer? _keyValueComparer; + private readonly ValueComparer? _providerValueComparer; + private readonly JsonValueReaderWriter? _jsonValueReaderWriter; + private CoreTypeMapping? _typeMapping; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public RuntimePrimitivePropertyBase( + string name, + Type clrType, + object? sentinel, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + RuntimeTypeBase declaringType, + PropertyAccessMode propertyAccessMode, + bool nullable, + bool concurrencyToken, + ValueGenerated valueGenerated, + PropertySaveBehavior beforeSaveBehavior, + PropertySaveBehavior afterSaveBehavior, + int? maxLength, + bool? unicode, + int? precision, + int? scale, + Type? providerClrType, + Func? valueGeneratorFactory, + ValueConverter? valueConverter, + ValueComparer? valueComparer, + ValueComparer? keyValueComparer, + ValueComparer? providerValueComparer, + JsonValueReaderWriter? jsonValueReaderWriter, + CoreTypeMapping? typeMapping) + : base(name, propertyInfo, fieldInfo, propertyAccessMode) + { + DeclaringType = declaringType; + ClrType = clrType; + _sentinel = sentinel; + _isNullable = nullable; + _isConcurrencyToken = concurrencyToken; + _valueGenerated = valueGenerated; + _beforeSaveBehavior = beforeSaveBehavior; + _afterSaveBehavior = afterSaveBehavior; + _valueGeneratorFactory = valueGeneratorFactory; + _valueConverter = valueConverter; + + if (maxLength != null) + { + SetAnnotation(CoreAnnotationNames.MaxLength, maxLength); + } + + if (unicode != null) + { + SetAnnotation(CoreAnnotationNames.Unicode, unicode); + } + + if (precision != null) + { + SetAnnotation(CoreAnnotationNames.Precision, precision); + } + + if (scale != null) + { + SetAnnotation(CoreAnnotationNames.Scale, scale); + } + + if (providerClrType != null) + { + SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType); + } + + _typeMapping = typeMapping; + _valueComparer = valueComparer; + _keyValueComparer = keyValueComparer ?? valueComparer; + _providerValueComparer = providerValueComparer; + _jsonValueReaderWriter = jsonValueReaderWriter; + } + + /// + /// Gets the type of value that this property-like object holds. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + protected override Type ClrType { get; } + + /// + public override RuntimeTypeBase DeclaringType { get; } + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual RuntimeKey? PrimaryKey { get; set; } + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual List? Keys { get; set; } + + private IEnumerable GetContainingKeys() + => Keys ?? Enumerable.Empty(); + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual List? ForeignKeys { get; set; } + + private IEnumerable GetContainingForeignKeys() + => ForeignKeys ?? Enumerable.Empty(); + + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual List? Indexes { get; set; } + + private IEnumerable GetContainingIndexes() + => Indexes ?? Enumerable.Empty(); + + /// + /// Gets or sets the type mapping for this property. + /// + /// The type mapping. + public virtual CoreTypeMapping TypeMapping + { + get => NonCapturingLazyInitializer.EnsureInitialized( + ref _typeMapping, (IProperty)this, + static property => + property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!); + set => _typeMapping = value; + } + + private ValueComparer GetValueComparer() + => (GetValueComparer(null) ?? TypeMapping.Comparer) + .ToNullableComparer(this)!; + + private ValueComparer GetKeyValueComparer() + => (GetKeyValueComparer(null) ?? TypeMapping.KeyComparer) + .ToNullableComparer(this)!; + + private ValueComparer? GetValueComparer(HashSet? checkedProperties) + { + if (_valueComparer != null) + { + return _valueComparer; + } + + var principal = (RuntimePrimitivePropertyBase?)this.FindFirstDifferentPrincipal(); + if (principal == null) + { + return null; + } + + if (checkedProperties == null) + { + checkedProperties = new HashSet(); + } + else if (checkedProperties.Contains(this)) + { + return null; + } + + checkedProperties.Add(this); + return principal.GetValueComparer(checkedProperties); + } + + private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties) + { + if ( _keyValueComparer != null) + { + return _keyValueComparer; + } + + var principal = (RuntimePrimitivePropertyBase?)this.FindFirstDifferentPrincipal(); + if (principal == null) + { + return null; + } + + if (checkedProperties == null) + { + checkedProperties = new HashSet(); + } + else if (checkedProperties.Contains(this)) + { + return null; + } + + checkedProperties.Add(this); + return principal.GetKeyValueComparer(checkedProperties); + } + + /// + public override object? Sentinel + => _sentinel; + + /// + /// Gets the for this property, or if none is set. + /// + /// The reader/writer, or if none has been set. + public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() + => _jsonValueReaderWriter; + + /// + bool IReadOnlyPrimitivePropertyBase.IsNullable + { + [DebuggerStepThrough] + get => _isNullable; + } + + /// + ValueGenerated IReadOnlyPrimitivePropertyBase.ValueGenerated + { + [DebuggerStepThrough] + get => _valueGenerated; + } + + /// + bool IReadOnlyPrimitivePropertyBase.IsConcurrencyToken + { + [DebuggerStepThrough] + get => _isConcurrencyToken; + } + + /// + [DebuggerStepThrough] + int? IReadOnlyPrimitivePropertyBase.GetMaxLength() + => (int?)this[CoreAnnotationNames.MaxLength]; + + /// + [DebuggerStepThrough] + bool? IReadOnlyPrimitivePropertyBase.IsUnicode() + => (bool?)this[CoreAnnotationNames.Unicode]; + + /// + [DebuggerStepThrough] + int? IReadOnlyPrimitivePropertyBase.GetPrecision() + => (int?)this[CoreAnnotationNames.Precision]; + + /// + [DebuggerStepThrough] + int? IReadOnlyPrimitivePropertyBase.GetScale() + => (int?)this[CoreAnnotationNames.Scale]; + + /// + [DebuggerStepThrough] + PropertySaveBehavior IReadOnlyPrimitivePropertyBase.GetBeforeSaveBehavior() + => _beforeSaveBehavior; + + /// + [DebuggerStepThrough] + PropertySaveBehavior IReadOnlyPrimitivePropertyBase.GetAfterSaveBehavior() + => _afterSaveBehavior; + + /// + [DebuggerStepThrough] + Func? IReadOnlyPrimitivePropertyBase.GetValueGeneratorFactory() + => _valueGeneratorFactory; + + /// + [DebuggerStepThrough] + ValueConverter? IReadOnlyPrimitivePropertyBase.GetValueConverter() + => _valueConverter; + + /// + [DebuggerStepThrough] + Type? IReadOnlyPrimitivePropertyBase.GetProviderClrType() + => (Type?)this[CoreAnnotationNames.ProviderClrType]; + + /// + [DebuggerStepThrough] + CoreTypeMapping? IReadOnlyPrimitivePropertyBase.FindTypeMapping() + => TypeMapping; + + /// + [DebuggerStepThrough] + ValueComparer? IReadOnlyPrimitivePropertyBase.GetValueComparer() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _valueComparer, this, + static property => property.GetValueComparer()); + + /// + [DebuggerStepThrough] + ValueComparer IPrimitivePropertyBase.GetValueComparer() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _valueComparer, this, + static property => property.GetValueComparer()); + + /// + [DebuggerStepThrough] + ValueComparer? IReadOnlyPrimitivePropertyBase.GetKeyValueComparer() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _keyValueComparer, this, + static property => property.GetKeyValueComparer()); + + /// + [DebuggerStepThrough] + ValueComparer IPrimitivePropertyBase.GetKeyValueComparer() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _keyValueComparer, this, + static property => property.GetKeyValueComparer()); + + /// + [DebuggerStepThrough] + ValueComparer? IReadOnlyPrimitivePropertyBase.GetProviderValueComparer() + => _providerValueComparer ?? TypeMapping.ProviderValueComparer; + + /// + [DebuggerStepThrough] + ValueComparer IPrimitivePropertyBase.GetProviderValueComparer() + => _providerValueComparer ?? TypeMapping.ProviderValueComparer; + + /// + [DebuggerStepThrough] + bool IReadOnlyPrimitivePropertyBase.IsForeignKey() + => ForeignKeys != null; + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingForeignKeys() + => GetContainingForeignKeys(); + + /// + [DebuggerStepThrough] + bool IReadOnlyPrimitivePropertyBase.IsIndex() + => Indexes != null; + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingIndexes() + => GetContainingIndexes(); + + /// + [DebuggerStepThrough] + bool IReadOnlyPrimitivePropertyBase.IsKey() + => Keys != null; + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + [DebuggerStepThrough] + IEnumerable IPrimitivePropertyBase.GetContainingKeys() + => GetContainingKeys(); + + /// + [DebuggerStepThrough] + IReadOnlyKey? IReadOnlyPrimitivePropertyBase.FindContainingPrimaryKey() + => PrimaryKey; +} diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index 983b00982c4..28e1240088e 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -1,10 +1,6 @@ // 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.ChangeTracking.Internal; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -15,22 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeProperty : RuntimePropertyBase, IProperty +public class RuntimeProperty : RuntimePrimitivePropertyBase, IProperty { - private readonly bool _isNullable; - private readonly ValueGenerated _valueGenerated; - private readonly bool _isConcurrencyToken; - private readonly object? _sentinel; - private readonly PropertySaveBehavior _beforeSaveBehavior; - private readonly PropertySaveBehavior _afterSaveBehavior; - private readonly Func? _valueGeneratorFactory; - private readonly ValueConverter? _valueConverter; - private ValueComparer? _valueComparer; - private ValueComparer? _keyValueComparer; - private readonly ValueComparer? _providerValueComparer; - private readonly JsonValueReaderWriter? _jsonValueReaderWriter; - private CoreTypeMapping? _typeMapping; - /// /// 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 @@ -63,190 +45,18 @@ public RuntimeProperty( ValueComparer? providerValueComparer, JsonValueReaderWriter? jsonValueReaderWriter, CoreTypeMapping? typeMapping) - : base(name, propertyInfo, fieldInfo, propertyAccessMode) + : base(name, clrType, sentinel, propertyInfo, fieldInfo, declaringEntityType, propertyAccessMode, + nullable, concurrencyToken, valueGenerated, beforeSaveBehavior, afterSaveBehavior, + maxLength, unicode, precision, scale, providerClrType, valueGeneratorFactory, + valueConverter, valueComparer, keyValueComparer, providerValueComparer, jsonValueReaderWriter, typeMapping) { - DeclaringEntityType = declaringEntityType; - ClrType = clrType; - _sentinel = sentinel; - _isNullable = nullable; - _isConcurrencyToken = concurrencyToken; - _valueGenerated = valueGenerated; - _beforeSaveBehavior = beforeSaveBehavior; - _afterSaveBehavior = afterSaveBehavior; - _valueGeneratorFactory = valueGeneratorFactory; - _valueConverter = valueConverter; - - if (maxLength != null) - { - SetAnnotation(CoreAnnotationNames.MaxLength, maxLength); - } - - if (unicode != null) - { - SetAnnotation(CoreAnnotationNames.Unicode, unicode); - } - - if (precision != null) - { - SetAnnotation(CoreAnnotationNames.Precision, precision); - } - - if (scale != null) - { - SetAnnotation(CoreAnnotationNames.Scale, scale); - } - - if (providerClrType != null) - { - SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType); - } - - _typeMapping = typeMapping; - _valueComparer = valueComparer; - _keyValueComparer = keyValueComparer ?? valueComparer; - _providerValueComparer = providerValueComparer; - _jsonValueReaderWriter = jsonValueReaderWriter; } - /// - /// Gets the type of value that this property-like object holds. - /// - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] - protected override Type ClrType { get; } - /// /// Gets the type that this property belongs to. /// - public override RuntimeEntityType DeclaringEntityType { get; } - - /// - /// 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. - /// - [EntityFrameworkInternal] - public virtual RuntimeKey? PrimaryKey { get; set; } - - /// - /// 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. - /// - [EntityFrameworkInternal] - public virtual List? Keys { get; set; } - - private IEnumerable GetContainingKeys() - => Keys ?? Enumerable.Empty(); - - /// - /// 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. - /// - [EntityFrameworkInternal] - public virtual List? ForeignKeys { get; set; } - - private IEnumerable GetContainingForeignKeys() - => ForeignKeys ?? Enumerable.Empty(); - - /// - /// 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. - /// - [EntityFrameworkInternal] - public virtual List? Indexes { get; set; } - - private IEnumerable GetContainingIndexes() - => Indexes ?? Enumerable.Empty(); - - /// - /// Gets or sets the type mapping for this property. - /// - /// The type mapping. - public virtual CoreTypeMapping TypeMapping - { - get => NonCapturingLazyInitializer.EnsureInitialized( - ref _typeMapping, (IProperty)this, - static property => - property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!); - set => _typeMapping = value; - } - - private ValueComparer GetValueComparer() - => (GetValueComparer(null) ?? TypeMapping.Comparer) - .ToNullableComparer(this)!; - - private ValueComparer GetKeyValueComparer() - => (GetKeyValueComparer(null) ?? TypeMapping.KeyComparer) - .ToNullableComparer(this)!; - - private ValueComparer? GetValueComparer(HashSet? checkedProperties) - { - if (_valueComparer != null) - { - return _valueComparer; - } - - var principal = (RuntimeProperty?)this.FindFirstDifferentPrincipal(); - if (principal == null) - { - return null; - } - - if (checkedProperties == null) - { - checkedProperties = new HashSet(); - } - else if (checkedProperties.Contains(this)) - { - return null; - } - - checkedProperties.Add(this); - return principal.GetValueComparer(checkedProperties); - } - - private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties) - { - if (_keyValueComparer != null) - { - return _keyValueComparer; - } - - var principal = (RuntimeProperty?)this.FindFirstDifferentPrincipal(); - if (principal == null) - { - return null; - } - - if (checkedProperties == null) - { - checkedProperties = new HashSet(); - } - else if (checkedProperties.Contains(this)) - { - return null; - } - - checkedProperties.Add(this); - return principal.GetKeyValueComparer(checkedProperties); - } - - /// - /// Gets the for this property, or if none is set. - /// - /// The reader/writer, or if none has been set. - public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() - => _jsonValueReaderWriter; - - /// - public override object? Sentinel - => _sentinel; + public virtual RuntimeEntityType DeclaringEntityType + => (RuntimeEntityType)DeclaringType; /// /// Returns a string that represents the current object. @@ -267,72 +77,6 @@ public virtual DebugView DebugView () => ((IProperty)this).ToDebugString(), () => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - /// - bool IReadOnlyProperty.IsNullable - { - [DebuggerStepThrough] - get => _isNullable; - } - - /// - ValueGenerated IReadOnlyProperty.ValueGenerated - { - [DebuggerStepThrough] - get => _valueGenerated; - } - - /// - bool IReadOnlyProperty.IsConcurrencyToken - { - [DebuggerStepThrough] - get => _isConcurrencyToken; - } - - /// - [DebuggerStepThrough] - int? IReadOnlyProperty.GetMaxLength() - => (int?)this[CoreAnnotationNames.MaxLength]; - - /// - [DebuggerStepThrough] - bool? IReadOnlyProperty.IsUnicode() - => (bool?)this[CoreAnnotationNames.Unicode]; - - /// - [DebuggerStepThrough] - int? IReadOnlyProperty.GetPrecision() - => (int?)this[CoreAnnotationNames.Precision]; - - /// - [DebuggerStepThrough] - int? IReadOnlyProperty.GetScale() - => (int?)this[CoreAnnotationNames.Scale]; - - /// - [DebuggerStepThrough] - PropertySaveBehavior IReadOnlyProperty.GetBeforeSaveBehavior() - => _beforeSaveBehavior; - - /// - [DebuggerStepThrough] - PropertySaveBehavior IReadOnlyProperty.GetAfterSaveBehavior() - => _afterSaveBehavior; - - /// - [DebuggerStepThrough] - Func? IReadOnlyProperty.GetValueGeneratorFactory() - => _valueGeneratorFactory; - - /// - [DebuggerStepThrough] - ValueConverter? IReadOnlyProperty.GetValueConverter() - => _valueConverter; - - /// - [DebuggerStepThrough] - Type? IReadOnlyProperty.GetProviderClrType() - => (Type?)this[CoreAnnotationNames.ProviderClrType]; - /// IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType { @@ -346,97 +90,4 @@ IEntityType IProperty.DeclaringEntityType [DebuggerStepThrough] get => DeclaringEntityType; } - - /// - [DebuggerStepThrough] - CoreTypeMapping? IReadOnlyProperty.FindTypeMapping() - => TypeMapping; - - /// - [DebuggerStepThrough] - ValueComparer? IReadOnlyProperty.GetValueComparer() - => NonCapturingLazyInitializer.EnsureInitialized( - ref _valueComparer, this, - static property => property.GetValueComparer()); - - /// - [DebuggerStepThrough] - ValueComparer IProperty.GetValueComparer() - => NonCapturingLazyInitializer.EnsureInitialized( - ref _valueComparer, this, - static property => property.GetValueComparer()); - - /// - [DebuggerStepThrough] - ValueComparer? IReadOnlyProperty.GetKeyValueComparer() - => NonCapturingLazyInitializer.EnsureInitialized( - ref _keyValueComparer, this, - static property => property.GetKeyValueComparer()); - - /// - [DebuggerStepThrough] - ValueComparer IProperty.GetKeyValueComparer() - => NonCapturingLazyInitializer.EnsureInitialized( - ref _keyValueComparer, this, - static property => property.GetKeyValueComparer()); - - /// - [DebuggerStepThrough] - ValueComparer? IReadOnlyProperty.GetProviderValueComparer() - => _providerValueComparer ?? TypeMapping.ProviderValueComparer; - - /// - [DebuggerStepThrough] - ValueComparer IProperty.GetProviderValueComparer() - => _providerValueComparer ?? TypeMapping.ProviderValueComparer; - - /// - [DebuggerStepThrough] - bool IReadOnlyProperty.IsForeignKey() - => ForeignKeys != null; - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingForeignKeys() - => GetContainingForeignKeys(); - - /// - [DebuggerStepThrough] - bool IReadOnlyProperty.IsIndex() - => Indexes != null; - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingIndexes() - => GetContainingIndexes(); - - /// - [DebuggerStepThrough] - bool IReadOnlyProperty.IsKey() - => Keys != null; - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - [DebuggerStepThrough] - IEnumerable IProperty.GetContainingKeys() - => GetContainingKeys(); - - /// - [DebuggerStepThrough] - IReadOnlyKey? IReadOnlyProperty.FindContainingPrimaryKey() - => PrimaryKey; } diff --git a/src/EFCore/Metadata/RuntimePropertyBase.cs b/src/EFCore/Metadata/RuntimePropertyBase.cs index 6f8b598d3b7..511aba6daf3 100644 --- a/src/EFCore/Metadata/RuntimePropertyBase.cs +++ b/src/EFCore/Metadata/RuntimePropertyBase.cs @@ -55,7 +55,7 @@ protected RuntimePropertyBase( /// /// Gets the type that this property-like object belongs to. /// - public abstract RuntimeEntityType DeclaringEntityType { get; } + public abstract RuntimeTypeBase DeclaringType { get; } /// /// Gets the type of value that this property-like object holds. @@ -89,7 +89,7 @@ PropertyAccessMode IReadOnlyPropertyBase.GetPropertyAccessMode() IReadOnlyTypeBase IReadOnlyPropertyBase.DeclaringType { [DebuggerStepThrough] - get => DeclaringEntityType; + get => DeclaringType; } /// @@ -116,7 +116,7 @@ PropertyIndexes IRuntimePropertyBase.PropertyIndexes ref _indexes, this, static property => { - var _ = ((IRuntimeEntityType)property.DeclaringEntityType).Counts; + var _ = ((IRuntimeTypeBase)property.DeclaringType).Counts; }); set => NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); } diff --git a/src/EFCore/Metadata/RuntimeServiceProperty.cs b/src/EFCore/Metadata/RuntimeServiceProperty.cs index 993f359298d..7b4332617f5 100644 --- a/src/EFCore/Metadata/RuntimeServiceProperty.cs +++ b/src/EFCore/Metadata/RuntimeServiceProperty.cs @@ -42,7 +42,11 @@ public RuntimeServiceProperty( /// /// Gets the type that this property-like object belongs to. /// - public override RuntimeEntityType DeclaringEntityType { get; } + public virtual RuntimeEntityType DeclaringEntityType { get; } + + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; /// /// Gets the type of value that this property-like object holds. diff --git a/src/EFCore/Metadata/RuntimeSkipNavigation.cs b/src/EFCore/Metadata/RuntimeSkipNavigation.cs index e57aacea091..baa73917429 100644 --- a/src/EFCore/Metadata/RuntimeSkipNavigation.cs +++ b/src/EFCore/Metadata/RuntimeSkipNavigation.cs @@ -81,7 +81,11 @@ public RuntimeSkipNavigation( /// /// Gets the type that this property belongs to. /// - public override RuntimeEntityType DeclaringEntityType { get; } + public virtual RuntimeEntityType DeclaringEntityType { get; } + + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; /// /// Gets the entity type that this navigation property will hold an instance(s) of. diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs new file mode 100644 index 00000000000..e061726c053 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -0,0 +1,166 @@ +// 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.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents an entity type in a model. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase +{ + private readonly PropertyInfo? _indexerPropertyInfo; + private readonly bool _isPropertyBag; + + // Warning: Never access these fields directly as access needs to be thread-safe + private Func? _originalValuesFactory; + private Func? _temporaryValuesFactory; + private Func? _storeGeneratedValuesFactory; + private Func? _shadowValuesFactory; + private Func? _emptyShadowValuesFactory; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public RuntimeTypeBase( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + { + Name = name; + ClrType = type; + _indexerPropertyInfo = indexerPropertyInfo; + _isPropertyBag = propertyBag; + } + + /// + /// Gets the name of this type. + /// + public virtual string Name { [DebuggerStepThrough] get; } + + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] + public virtual Type ClrType { get; } + + /// + /// Gets the model that this type belongs to. + /// + public abstract RuntimeModel Model { get; } + + /// + /// 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. + /// + [EntityFrameworkInternal] + protected abstract PropertyCounts Counts { get; } + + /// + [DebuggerStepThrough] + public virtual PropertyInfo? FindIndexerPropertyInfo() + => _indexerPropertyInfo; + + /// + /// Returns the default indexer property that takes a value if one exists. + /// + /// The type to look for the indexer on. + /// An indexer property or . + public static PropertyInfo? FindIndexerProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + Type type) + => type.FindIndexerProperty(); + + /// + bool IReadOnlyTypeBase.HasSharedClrType + { + [DebuggerStepThrough] + get => true; + } + + /// + bool IReadOnlyTypeBase.IsPropertyBag + { + [DebuggerStepThrough] + get => _isPropertyBag; + } + + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + PropertyCounts IRuntimeTypeBase.Counts + { + [DebuggerStepThrough] + get => Counts; + } + + /// + Func IRuntimeTypeBase.OriginalValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _originalValuesFactory, this, + static complexType => new OriginalValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.StoreGeneratedValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _storeGeneratedValuesFactory, this, + static complexType => new StoreGeneratedValuesFactoryFactory().CreateEmpty(complexType)); + + /// + Func IRuntimeTypeBase.TemporaryValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _temporaryValuesFactory, this, + static complexType => new TemporaryValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.ShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _shadowValuesFactory, this, + static complexType => new ShadowValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.EmptyShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _emptyShadowValuesFactory, this, + static complexType => new EmptyShadowValuesFactoryFactory().CreateEmpty(complexType)); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetServiceOnlyConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); +} diff --git a/src/EFCore/Metadata/TypeBaseNameComparer.cs b/src/EFCore/Metadata/TypeBaseNameComparer.cs new file mode 100644 index 00000000000..1e85a22a074 --- /dev/null +++ b/src/EFCore/Metadata/TypeBaseNameComparer.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// +/// An implementation of and to compare +/// instances by name. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public sealed class TypeBaseNameComparer : IComparer, IEqualityComparer +{ + private TypeBaseNameComparer() + { + } + + /// + /// The singleton instance of the comparer to use. + /// + public static readonly TypeBaseNameComparer Instance = new(); + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// A negative number if 'x' is less than 'y'; a positive number if 'x' is greater than 'y'; zero otherwise. + public int Compare(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + return StringComparer.Ordinal.Compare(x.Name, y.Name); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if the specified objects are equal; otherwise, . + public bool Equals(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + => Compare(x, y) == 0; + + /// + /// Returns a hash code for the specified object. + /// + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + public int GetHashCode(IReadOnlyTypeBase obj) + => obj.Name.GetHashCode(); +} diff --git a/src/EFCore/Metadata/TypeBaseTypeComparer.cs b/src/EFCore/Metadata/TypeBaseTypeComparer.cs new file mode 100644 index 00000000000..3ee1c1424c9 --- /dev/null +++ b/src/EFCore/Metadata/TypeBaseTypeComparer.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// +/// An implementation of and to compare +/// instances by their CLR type. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public sealed class TypeBaseTypeComparer : IComparer, IEqualityComparer +{ + private TypeBaseTypeComparer() + { + } + + /// + /// The singleton instance of the comparer to use. + /// + public static readonly TypeBaseTypeComparer Instance = new(); + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// A negative number if 'x' is less than 'y'; a positive number if 'x' is greater than 'y'; zero otherwise. + public int Compare(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + return StringComparer.Ordinal.Compare(x.ClrType.FullName, y.ClrType.FullName); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if the specified objects are equal; otherwise, . + public bool Equals(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + => Compare(x, y) == 0; + + /// + /// Returns a hash code for the specified object. + /// + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + public int GetHashCode(IReadOnlyTypeBase obj) + => obj.ClrType.GetHashCode(); +} diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 95242191541..9fbc6127d84 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -322,6 +322,51 @@ public virtual ModelConfigurationBuilder DefaultTypeMapping( return this; } + /// + /// Marks the given and derived types as corresponding to complex properties. + /// + /// + /// + /// This can also be called on an interface to apply the configuration to all properties of implementing types. + /// + /// + /// See Pre-convention model building in EF Core for more information and + /// examples. + /// + /// + /// The property type to be configured. + /// An object that can be used to configure the properties. + public virtual ComplexPropertiesConfigurationBuilder ComplexProperties() + { + var property = _modelConfiguration.GetOrAddComplexProperty(typeof(TProperty)); + + return new ComplexPropertiesConfigurationBuilder(property); + } + + /// + /// Marks the given and derived types as corresponding to complex properties. + /// + /// + /// + /// This can also be called on an interface or an unbound generic type to apply the configuration to all + /// properties of implementing and constructed types. + /// + /// + /// See Pre-convention model building in EF Core for more information and + /// examples. + /// + /// + /// The property type to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertiesConfigurationBuilder ComplexProperties(Type propertyType) + { + Check.NotNull(propertyType, nameof(propertyType)); + + var property = _modelConfiguration.GetOrAddComplexProperty(propertyType); + + return new ComplexPropertiesConfigurationBuilder(property); + } + /// /// Creates the configured used to create the model. This is done automatically when using /// ; this method allows it to be run diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 176d3c2d81e..dc3fdb74844 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -35,7 +35,7 @@ public static string AbstractLeafEntityType(object? entityType) entityType); /// - /// Cannot add an entity type with type '{typeName}' to the model as it is a dynamically-generated proxy type. + /// Cannot add type '{typeName}' to the model as it is a dynamically-generated proxy type. /// public static string AddingProxyTypeAsEntityType(object? typeName) => string.Format( @@ -51,7 +51,7 @@ public static string AmbiguousDependentEntity(object? entityType, object? target entityType, targetEntryCall); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as having a required dependent since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as having a required dependent since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredDependent(object? foreignKeyProperties, object? entityType) => string.Format( @@ -59,7 +59,7 @@ public static string AmbiguousEndRequiredDependent(object? foreignKeyProperties, foreignKeyProperties, entityType); /// - /// The navigation '{entityType}.{navigation}' cannot be configured as required since the dependent side of the underlying foreign key {foreignKeyProperties} cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The navigation '{entityType}.{navigation}' cannot be configured as required since the dependent side of the underlying foreign key {foreignKeyProperties} cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredDependentNavigation(object? entityType, object? navigation, object? foreignKeyProperties) => string.Format( @@ -67,7 +67,7 @@ public static string AmbiguousEndRequiredDependentNavigation(object? entityType, entityType, navigation, foreignKeyProperties); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be inverted to entity type '{principalEntityType}' since it was configured as required before the dependent side was configured. Configure the foreign key property or the principal key before configuring the foreign key as required. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be inverted to entity type '{principalEntityType}' since it was configured as required before the dependent side was configured. Configure the foreign key property or the principal key before configuring the foreign key as required. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredInverted(object? foreignKeyProperties, object? entityType, object? principalEntityType) => string.Format( @@ -83,7 +83,7 @@ public static string AmbiguousForeignKeyPropertyCandidates(object? firstDependen firstDependentToPrincipalNavigationSpecification, firstPrincipalToDependentNavigationSpecification, secondDependentToPrincipalNavigationSpecification, secondPrincipalToDependentNavigationSpecification, foreignKeyProperties); /// - /// The dependent side could not be determined for the one-to-one relationship between '{dependentToPrincipalNavigationSpecification}' and '{principalToDependentNavigationSpecification}'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The dependent side could not be determined for the one-to-one relationship between '{dependentToPrincipalNavigationSpecification}' and '{principalToDependentNavigationSpecification}'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousOneToOneRelationship(object? dependentToPrincipalNavigationSpecification, object? principalToDependentNavigationSpecification) => string.Format( @@ -122,6 +122,14 @@ public static string ArgumentPropertyNull(object? property, object? argument) GetString("ArgumentPropertyNull", nameof(property), nameof(argument)), property, argument); + /// + /// The [{attribute}] attribute may only be specified on entity type properties. Remove the attribute from '{type}.{propertyName}'. + /// + public static string AttributeNotOnEntityTypeProperty(object? attribute, object? type, object? propertyName) + => string.Format( + GetString("AttributeNotOnEntityTypeProperty", nameof(attribute), nameof(type), nameof(propertyName)), + attribute, type, propertyName); + /// /// Cycle detected while auto-including navigations: {cycleNavigations}. To fix this issue, either don't configure at least one navigation in the cycle as auto included in `OnModelCreating` or call 'IgnoreAutoInclude' method on the query. /// @@ -448,6 +456,22 @@ public static string CompiledQueryDifferentModel(object? queryExpression) GetString("CompiledQueryDifferentModel", nameof(queryExpression)), queryExpression); + /// + /// The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + /// + public static string ComplexCollectionWrongClrType(object? property, object? type, object? clrType, object? targetType) + => string.Format( + GetString("ComplexCollectionWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), + property, type, clrType, targetType); + + /// + /// The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. + /// + public static string ComplexPropertyWrongClrType(object? property, object? type, object? clrType, object? targetType) + => string.Format( + GetString("ComplexPropertyWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), + property, type, clrType, targetType); + /// /// There are multiple properties with the [ForeignKey] attribute pointing to navigation '{1_entityType}.{0_navigation}'. To define a composite foreign key using data annotations, use the [ForeignKey] attribute on the navigation. /// @@ -495,12 +519,12 @@ public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) entity); /// - /// The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. + /// The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. /// - public static string ConflictingPropertyOrNavigation(object? member, object? entityType, object? conflictingEntityType) + public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType) => string.Format( - GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(entityType), nameof(conflictingEntityType)), - member, entityType, conflictingEntityType); + GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingType)), + member, type, conflictingType); /// /// Cannot create a relationship between '{newPrincipalNavigationSpecification}' and '{newDependentNavigationSpecification}' because a relationship already exists between '{existingPrincipalNavigationSpecification}' and '{existingDependentNavigationSpecification}'. Navigations can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation '{newDependentNavigationSpecification}' first in 'OnModelCreating'. @@ -629,16 +653,20 @@ public static string DefaultMethodInvoked => GetString("DefaultMethodInvoked"); /// - /// The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. + /// The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported on properties making up the foreign key. Remove the attribute from '{type}.{propertyName}'. /// - public static string DeleteBehaviorAttributeNotOnNavigationProperty - => GetString("DeleteBehaviorAttributeNotOnNavigationProperty"); + public static string DeleteBehaviorAttributeNotOnNavigationProperty(object? type, object? propertyName) + => string.Format( + GetString("DeleteBehaviorAttributeNotOnNavigationProperty", nameof(type), nameof(propertyName)), + type, propertyName); /// - /// The [DeleteBehavior] attribute may only be specified on dependent side of the relationship. + /// The [DeleteBehavior] attribute may only be specified on the dependent side of the relationship. Remove the attribute from '{entityType}.{navigationName}'. /// - public static string DeleteBehaviorAttributeOnPrincipalProperty - => GetString("DeleteBehaviorAttributeOnPrincipalProperty"); + public static string DeleteBehaviorAttributeOnPrincipalProperty(object? entityType, object? navigationName) + => string.Format( + GetString("DeleteBehaviorAttributeOnPrincipalProperty", nameof(entityType), nameof(navigationName)), + entityType, navigationName); /// /// You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}' but have specified a foreign key on '{entityType}'. The foreign key must be defined on a type that is part of the relationship. @@ -1224,12 +1252,12 @@ public static string IndexWrongType(object? index, object? entityType, object? o index, entityType, otherEntityType); /// - /// The property '{property}' cannot be ignored on entity type '{entityType}' because it's declared on the base entity type '{baseEntityType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. + /// The property '{property}' cannot be ignored on type '{type}' because it's declared on the base type '{baseType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. /// - public static string InheritedPropertyCannotBeIgnored(object? property, object? entityType, object? baseEntityType) + public static string InheritedPropertyCannotBeIgnored(object? property, object? type, object? baseType) => string.Format( - GetString("InheritedPropertyCannotBeIgnored", nameof(property), nameof(entityType), nameof(baseEntityType)), - property, entityType, baseEntityType); + GetString("InheritedPropertyCannotBeIgnored", nameof(property), nameof(type), nameof(baseType)), + property, type, baseType); /// /// The property '{entityType}.{navigation}' is of an interface type ('{propertyType}'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'. @@ -1247,6 +1275,14 @@ public static string InvalidAlternateKeyValue(object? entityType, object? keyPro GetString("InvalidAlternateKeyValue", nameof(entityType), nameof(keyProperty)), entityType, keyProperty); + /// + /// The specified type '{type}' must be a non-interface type with a public constructor to be used as an entity type. + /// + public static string InvalidComplexType(object? type) + => string.Format( + GetString("InvalidComplexType", nameof(type)), + type); + /// /// A previous error has left the DbContext in an invalid state. Applications should not continue to use a DbContext instance after an InvalidOperationException has been thrown. /// @@ -1278,7 +1314,7 @@ public static string InvalidEnumValue(object? value, object? argumentName, objec value, argumentName, enumType); /// - /// The expression '{expression}' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// The expression '{expression}' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see https://go.microsoft.com/fwlink/?LinkID=746393. /// public static string InvalidIncludeExpression(object? expression) => string.Format( @@ -1590,7 +1626,7 @@ public static string ModelReadOnly => GetString("ModelReadOnly"); /// - /// The filters '{filter1}' and '{filter2}' have both been configured on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// The filters '{filter1}' and '{filter2}' have both been configured on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see https://go.microsoft.com/fwlink/?LinkID=746393. /// public static string MultipleFilteredIncludesOnSameNavigation(object? filter1, object? filter2) => string.Format( @@ -1914,12 +1950,12 @@ public static string NoProperty(object? field, object? entity, object? propertyA field, entity, propertyAccessMode); /// - /// The property '{property}' cannot be added to the type '{entityType}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. + /// The property '{property}' cannot be added to the type '{type}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. /// - public static string NoPropertyType(object? property, object? entityType) + public static string NoPropertyType(object? property, object? type) => string.Format( - GetString("NoPropertyType", nameof(property), nameof(entityType)), - property, entityType); + GetString("NoPropertyType", nameof(property), nameof(type)), + property, type); /// /// No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. @@ -2042,7 +2078,7 @@ public static string OwnershipToDependent(object? navigation, object? principalE navigation, principalEntityType, dependentEntityType); /// - /// The DbContext of type '{contextType}' cannot be pooled because it does not have a public constructor accepting a parameter of type DbContextOptions or has more than one constructor. + /// The DbContext of type '{contextType}' cannot be pooled because it does not have a public constructor accepting a single parameter of type DbContextOptions or has more than one constructor. /// public static string PoolingContextCtorError(object? contextType) => string.Format( @@ -2120,12 +2156,12 @@ public static string PropertyCalledOnNavigation(object? property, object? entity property, entityType); /// - /// The indexer property '{property}' cannot be added to type '{entityType}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. + /// The indexer property '{property}' cannot be added to the type '{type}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. /// - public static string PropertyClashingNonIndexer(object? property, object? entityType) + public static string PropertyClashingNonIndexer(object? property, object? type) => string.Format( - GetString("PropertyClashingNonIndexer", nameof(property), nameof(entityType)), - property, entityType); + GetString("PropertyClashingNonIndexer", nameof(property), nameof(type)), + property, type); /// /// The property '{1_entityType}.{0_property}' contains null, but the property is marked as required. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. @@ -2144,23 +2180,23 @@ public static string PropertyConceptualNullSensitive(object? property, object? e property, entityType, keyValue); /// - /// The property '{property}' belongs to entity type '{entityType}', but is being used with an instance of entity type '{expectedType}'. + /// The property '{property}' belongs to the type '{expectedType}', but is being used with an instance of type '{actualType}'. /// - public static string PropertyDoesNotBelong(object? property, object? entityType, object? expectedType) + public static string PropertyDoesNotBelong(object? property, object? expectedType, object? actualType) => string.Format( - GetString("PropertyDoesNotBelong", nameof(property), nameof(entityType), nameof(expectedType)), - property, entityType, expectedType); + GetString("PropertyDoesNotBelong", nameof(property), nameof(expectedType), nameof(actualType)), + property, expectedType, actualType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{type}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. /// - public static string PropertyInUseForeignKey(object? property, object? entityType, object? foreignKeyProperties, object? foreignKeyType) + public static string PropertyInUseForeignKey(object? property, object? type, object? foreignKeyProperties, object? foreignKeyType) => string.Format( - GetString("PropertyInUseForeignKey", nameof(property), nameof(entityType), nameof(foreignKeyProperties), nameof(foreignKeyType)), - property, entityType, foreignKeyProperties, foreignKeyType); + GetString("PropertyInUseForeignKey", nameof(property), nameof(type), nameof(foreignKeyProperties), nameof(foreignKeyType)), + property, type, foreignKeyProperties, foreignKeyType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. /// public static string PropertyInUseIndex(object? property, object? entityType, object? index, object? indexType) => string.Format( @@ -2168,7 +2204,7 @@ public static string PropertyInUseIndex(object? property, object? entityType, ob property, entityType, index, indexType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. /// public static string PropertyInUseKey(object? property, object? entityType, object? keyProperties) => string.Format( @@ -2238,36 +2274,36 @@ public static string PropertyReadOnlyBeforeSave(object? property, object? entity property, entityType); /// - /// The property '{property}' cannot be added to type '{entityType}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. + /// The property '{property}' cannot be added to the type '{type}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. /// - public static string PropertyWrongClrType(object? property, object? entityType, object? clrType, object? propertyType) + public static string PropertyWrongClrType(object? property, object? type, object? clrType, object? propertyType) => string.Format( - GetString("PropertyWrongClrType", nameof(property), nameof(entityType), nameof(clrType), nameof(propertyType)), - property, entityType, clrType, propertyType); + GetString("PropertyWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(propertyType)), + property, type, clrType, propertyType); /// - /// The property '{property}' cannot be added to entity type '{entityType}' because it is declared on the CLR type '{clrType}'. + /// The property '{property}' cannot be added to the type '{type}' because it is declared on the CLR type '{clrType}'. /// - public static string PropertyWrongEntityClrType(object? property, object? entityType, object? clrType) + public static string PropertyWrongEntityClrType(object? property, object? type, object? clrType) => string.Format( - GetString("PropertyWrongEntityClrType", nameof(property), nameof(entityType), nameof(clrType)), - property, entityType, clrType); + GetString("PropertyWrongEntityClrType", nameof(property), nameof(type), nameof(clrType)), + property, type, clrType); /// - /// The property '{property}' cannot be added to entity type '{entityType}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. + /// The property '{property}' cannot be added to the type '{type}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. /// - public static string PropertyWrongName(object? property, object? entityType, object? clrName) + public static string PropertyWrongName(object? property, object? type, object? clrName) => string.Format( - GetString("PropertyWrongName", nameof(property), nameof(entityType), nameof(clrName)), - property, entityType, clrName); + GetString("PropertyWrongName", nameof(property), nameof(type), nameof(clrName)), + property, type, clrName); /// - /// The property '{property}' cannot be removed from the entity type '{entityType}' because it is declared on the entity type '{otherEntityType}'. + /// The property '{property}' cannot be removed from the type '{type}' because it is declared on the '{otherType}' type. /// - public static string PropertyWrongType(object? property, object? entityType, object? otherEntityType) + public static string PropertyWrongType(object? property, object? type, object? otherType) => string.Format( - GetString("PropertyWrongType", nameof(property), nameof(entityType), nameof(otherEntityType)), - property, entityType, otherEntityType); + GetString("PropertyWrongType", nameof(property), nameof(type), nameof(otherType)), + property, type, otherType); /// /// The materialization condition passed for entity shaper of entity type '{entityType}' is not of the correct shape. A materialization condition must be a 'LambdaExpression' of 'Func<ValueBuffer, IEntityType>'. @@ -2948,7 +2984,7 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.CoreStrings", typeof(CoreResources).Assembly); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as required since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property or the principal key before configuring the foreign key as required in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as required since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property or the principal key before configuring the foreign key as required in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static EventDefinition LogAmbiguousEndRequired(IDiagnosticsLogger logger) { @@ -3697,6 +3733,31 @@ public static EventDefinition LogManyServiceProvidersCreated(IDiagnosticsLogger return (EventDefinition)definition; } + /// + /// The complex property '{type}.{property}' was first mapped explicitly and then ignored. Consider not mapping the complex property in the first place. + /// + public static EventDefinition LogMappedComplexPropertyIgnored(IDiagnosticsLogger logger) + { + var definition = ((LoggingDefinitions)logger.Definitions).LogMappedComplexPropertyIgnored; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((LoggingDefinitions)logger.Definitions).LogMappedComplexPropertyIgnored, + logger, + static logger => new EventDefinition( + logger.Options, + CoreEventId.MappedComplexPropertyIgnoredWarning, + LogLevel.Warning, + "CoreEventId.MappedComplexPropertyIgnoredWarning", + level => LoggerMessage.Define( + level, + CoreEventId.MappedComplexPropertyIgnoredWarning, + _resourceManager.GetString("LogMappedComplexPropertyIgnored")!))); + } + + return (EventDefinition)definition; + } + /// /// The entity type '{entityType}' was first mapped explicitly and then ignored. Consider not mapping the entity type in the first place. /// @@ -3738,7 +3799,7 @@ public static EventDefinition LogMappedNavigationIgnored(IDiagno CoreEventId.MappedNavigationIgnoredWarning, LogLevel.Warning, "CoreEventId.MappedNavigationIgnoredWarning", - level => LoggerMessage.Define( + level => LoggerMessage.Define( level, CoreEventId.MappedNavigationIgnoredWarning, _resourceManager.GetString("LogMappedNavigationIgnored")!))); @@ -3763,7 +3824,7 @@ public static EventDefinition LogMappedPropertyIgnored(IDiagnost CoreEventId.MappedPropertyIgnoredWarning, LogLevel.Warning, "CoreEventId.MappedPropertyIgnoredWarning", - level => LoggerMessage.Define( + level => LoggerMessage.Define( level, CoreEventId.MappedPropertyIgnoredWarning, _resourceManager.GetString("LogMappedPropertyIgnored")!))); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index c0b16e0eebb..2ada17d4201 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -121,7 +121,7 @@ The corresponding CLR type for entity type '{entityType}' cannot be instantiated, and there is no derived entity type in the model that corresponds to a concrete CLR type. - Cannot add an entity type with type '{typeName}' to the model as it is a dynamically-generated proxy type. + Cannot add type '{typeName}' to the model as it is a dynamically-generated proxy type. The entity type '{entityType}' uses a shared type and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. @@ -153,6 +153,9 @@ The property '{property}' of the argument '{argument}' cannot be null. + + The [{attribute}] attribute may only be specified on entity type properties. Remove the attribute from '{type}.{propertyName}'. + Cycle detected while auto-including navigations: {cycleNavigations}. To fix this issue, either don't configure at least one navigation in the cycle as auto included in `OnModelCreating` or call 'IgnoreAutoInclude' method on the query. @@ -276,6 +279,12 @@ The compiled query '{queryExpression}' was executed with a different model than it was compiled against. Compiled queries can only be used with a single model. + + The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + + + The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. + There are multiple properties with the [ForeignKey] attribute pointing to navigation '{1_entityType}.{0_navigation}'. To define a composite foreign key using data annotations, use the [ForeignKey] attribute on the navigation. @@ -295,7 +304,7 @@ The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. - The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. + The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. Cannot create a relationship between '{newPrincipalNavigationSpecification}' and '{newDependentNavigationSpecification}' because a relationship already exists between '{existingPrincipalNavigationSpecification}' and '{existingDependentNavigationSpecification}'. Navigations can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation '{newDependentNavigationSpecification}' first in 'OnModelCreating'. @@ -349,10 +358,10 @@ The EF.Default<T> property may only be used within Entity Framework ExecuteUpdate method. - The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. + The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported on properties making up the foreign key. Remove the attribute from '{type}.{propertyName}'. - The [DeleteBehavior] attribute may only be specified on dependent side of the relationship. + The [DeleteBehavior] attribute may only be specified on the dependent side of the relationship. Remove the attribute from '{entityType}.{navigationName}'. You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}' but have specified a foreign key on '{entityType}'. The foreign key must be defined on a type that is part of the relationship. @@ -578,7 +587,7 @@ The index {index} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. - The property '{property}' cannot be ignored on entity type '{entityType}' because it's declared on the base entity type '{baseEntityType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. + The property '{property}' cannot be ignored on type '{type}' because it's declared on the base type '{baseType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. The property '{entityType}.{navigation}' is of an interface type ('{propertyType}'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'. @@ -586,6 +595,9 @@ Unable to track an entity of type '{entityType}' because alternate key property '{keyProperty}' is null. If the alternate key is not used in a relationship, then consider using a unique index instead. Unique indexes may contain nulls, while alternate keys may not. + + The specified type '{type}' must be a non-interface type with a public constructor to be used as an entity type. + A previous error has left the DbContext in an invalid state. Applications should not continue to use a DbContext instance after an InvalidOperationException has been thrown. @@ -823,17 +835,21 @@ More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. Warning CoreEventId.ManyServiceProvidersCreatedWarning + + The complex property '{type}.{property}' was first mapped explicitly and then ignored. Consider not mapping the complex property in the first place. + Warning CoreEventId.MappedComplexPropertyIgnoredWarning string string + The entity type '{entityType}' was first mapped explicitly and then ignored. Consider not mapping the entity type in the first place. - Warning CoreEventId.MappedEntityTypeIgnored string - - - The property '{entityType}.{property}' was first mapped explicitly and then ignored. Consider not mapping the property in the first place. - Warning CoreEventId.MappedPropertyIgnored string string + Warning CoreEventId.MappedEntityTypeIgnoredWarning string The navigation '{entityType}.{navigation}' was first mapped explicitly and then ignored. Consider not mapping the navigation in the first place. - Warning CoreEventId.MappedNavigationIgnored string string + Warning CoreEventId.MappedNavigationIgnoredWarning string string + + + The property '{entityType}.{property}' was first mapped explicitly and then ignored. Consider not mapping the property in the first place. + Warning CoreEventId.MappedPropertyIgnoredWarning string string There are multiple navigations ({navigations}) configured with [InverseProperty] attribute which point to the same inverse navigation '{inverseNavigation}' therefore no relationship was configured by convention. @@ -1146,7 +1162,7 @@ No property was associated with field '{field}' of entity type '{entity}'. Either configure a property or use a different '{propertyAccessMode}'. - The property '{property}' cannot be added to the type '{entityType}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. + The property '{property}' cannot be added to the type '{type}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. @@ -1227,7 +1243,7 @@ '{property}' cannot be used as a property on entity type '{entityType}' because it is configured as a navigation. - The indexer property '{property}' cannot be added to type '{entityType}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. + The indexer property '{property}' cannot be added to the type '{type}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. The property '{1_entityType}.{0_property}' contains null, but the property is marked as required. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. @@ -1236,16 +1252,16 @@ The property '{property}' contains null on entity '{entityType}' with the key value '{keyValue}', but the property is marked as required. - The property '{property}' belongs to entity type '{entityType}', but is being used with an instance of entity type '{expectedType}'. + The property '{property}' belongs to the type '{expectedType}', but is being used with an instance of type '{actualType}'. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. + The property '{property}' cannot be removed from the type '{type}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. + The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. + The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. The property '{1_entityType}.{0_property}' is being accessed using the '{propertyMethod}' method, but is defined in the model as a navigation. Use either the '{referenceMethod}' or '{collectionMethod}' method to access navigations. @@ -1272,16 +1288,16 @@ The property '{1_entityType}.{0_property}' is defined as read-only before it has been saved, but its value has been set to something other than a temporary or default value. - The property '{property}' cannot be added to type '{entityType}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. + The property '{property}' cannot be added to the type '{type}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. - The property '{property}' cannot be added to entity type '{entityType}' because it is declared on the CLR type '{clrType}'. + The property '{property}' cannot be added to the type '{type}' because it is declared on the CLR type '{clrType}'. - The property '{property}' cannot be added to entity type '{entityType}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. + The property '{property}' cannot be added to the type '{type}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. - The property '{property}' cannot be removed from the entity type '{entityType}' because it is declared on the entity type '{otherEntityType}'. + The property '{property}' cannot be removed from the type '{type}' because it is declared on the '{otherType}' type. The materialization condition passed for entity shaper of entity type '{entityType}' is not of the correct shape. A materialization condition must be a 'LambdaExpression' of 'Func<ValueBuffer, IEntityType>'. @@ -1538,4 +1554,4 @@ Cannot start tracking the entry for entity type '{entityType}' because it was created by a different StateManager instance. - + \ No newline at end of file diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index a554a2be72d..72ebf65afff 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -583,7 +583,7 @@ private BlockExpression CreateFullMaterializeExpression( concreteEntityType, "instance", _queryTrackingBehavior), materializationContextVariable); if (_queryStateManager - && concreteEntityType.ShadowPropertyCount() > 0) + && ((IRuntimeEntityType)concreteEntityType).ShadowPropertyCount > 0) { var valueBufferExpression = Expression.Call( materializationContextVariable, MaterializationContext.GetValueBufferMethod); diff --git a/src/EFCore/Storage/ITypeMappingSource.cs b/src/EFCore/Storage/ITypeMappingSource.cs index 6fc6f378e6d..b4733c6f7b4 100644 --- a/src/EFCore/Storage/ITypeMappingSource.cs +++ b/src/EFCore/Storage/ITypeMappingSource.cs @@ -30,19 +30,19 @@ namespace Microsoft.EntityFrameworkCore.Storage; public interface ITypeMappingSource { /// - /// Finds the type mapping for a given . + /// Finds the type mapping for a given . /// /// The property. /// The type mapping, or if none was found. - CoreTypeMapping? FindMapping(IProperty property); + CoreTypeMapping? FindMapping(IPrimitivePropertyBase property); /// /// 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: Only call this method if there is no available, otherwise + /// call /// /// The field or property. /// The type mapping, or if none was found. @@ -52,8 +52,8 @@ public interface ITypeMappingSource /// Finds the type mapping for a given . /// /// - /// Note: Only call this method if there is no - /// or available, otherwise call + /// Note: Only call this method if there is no + /// or available, otherwise call /// or /// /// The CLR type. @@ -64,8 +64,8 @@ public interface ITypeMappingSource /// Finds the type mapping for a given , taking pre-convention configuration into the account. /// /// - /// Note: Only call this method if there is no , - /// otherwise call . + /// Note: Only call this method if there is no , + /// otherwise call . /// /// The CLR type. /// The model. diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index 77326d1445d..71d298f7702 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -16,7 +16,7 @@ public readonly record struct TypeMappingInfo /// Creates a new instance of . /// /// The property for which mapping is needed. - public TypeMappingInfo(IProperty property) + public TypeMappingInfo(IPrimitivePropertyBase property) : this(property.GetPrincipals()) { } @@ -38,7 +38,7 @@ public TypeMappingInfo(IProperty property) /// Specifies a scale for the mapping, in case one isn't found at the core level, or for default. /// public TypeMappingInfo( - IReadOnlyList principals, + IReadOnlyList principals, bool? fallbackUnicode = null, int? fallbackSize = null, int? fallbackPrecision = null, diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index 89f4fdc18bc..c1284f21cb1 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -41,7 +41,7 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) private CoreTypeMapping? FindMappingWithConversion( in TypeMappingInfo mappingInfo, - IReadOnlyList? principals) + IReadOnlyList? principals) { Type? providerClrType = null; ValueConverter? customConverter = null; @@ -140,14 +140,14 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) this); /// - /// Finds the type mapping for a given . + /// Finds the type mapping for a given . /// /// /// Note: providers should typically not need to override this method. /// /// The property. /// The type mapping, or if none was found. - public override CoreTypeMapping? FindMapping(IProperty property) + public override CoreTypeMapping? FindMapping(IPrimitivePropertyBase property) { var principals = property.GetPrincipals(); return FindMappingWithConversion(new TypeMappingInfo(principals), principals); @@ -158,8 +158,8 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// /// /// - /// Note: Only call this method if there is no - /// or available, otherwise call + /// Note: Only call this method if there is no + /// or available, otherwise call /// or /// /// @@ -175,8 +175,8 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// Finds the type mapping for a given , taking pre-convention configuration into the account. /// /// - /// Note: Only call this method if there is no , - /// otherwise call . + /// Note: Only call this method if there is no , + /// otherwise call . /// /// The CLR type. /// The model. @@ -213,8 +213,8 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// /// /// - /// Note: Only call this method if there is no available, otherwise - /// call + /// Note: Only call this method if there is no available, otherwise + /// call /// /// /// Note: providers should typically not need to override this method. diff --git a/src/EFCore/Storage/TypeMappingSourceBase.cs b/src/EFCore/Storage/TypeMappingSourceBase.cs index 00f998c9aef..75601889f89 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -71,26 +71,26 @@ protected TypeMappingSourceBase(TypeMappingSourceDependencies dependencies) /// The property, if any. protected virtual void ValidateMapping( CoreTypeMapping? mapping, - IProperty? property) + IPrimitivePropertyBase? property) { } /// - /// Finds the type mapping for a given . + /// Finds the type mapping for a given . /// /// /// Note: providers should typically not need to override this method. /// /// The property. /// The type mapping, or if none was found. - public abstract CoreTypeMapping? FindMapping(IProperty property); + public abstract CoreTypeMapping? FindMapping(IPrimitivePropertyBase property); /// /// Finds the type mapping for a given . /// /// - /// Note: Only call this method if there is no - /// or available, otherwise call + /// Note: Only call this method if there is no + /// or available, otherwise call /// or /// /// The CLR type. @@ -101,8 +101,8 @@ protected virtual void ValidateMapping( /// Finds the type mapping for a given , taking pre-convention configuration into the account. /// /// - /// Note: Only call this method if there is no , - /// otherwise call . + /// Note: Only call this method if there is no , + /// otherwise call . /// /// The CLR type. /// The model. @@ -115,8 +115,8 @@ protected virtual void ValidateMapping( /// /// /// - /// Note: Only call this method if there is no available, otherwise - /// call + /// Note: Only call this method if there is no available, otherwise + /// call /// /// /// Note: providers should typically not need to override this method. diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index 715ba2c595c..1d026fe4a58 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -44,7 +44,14 @@ public static bool IsNullableType(this Type type) => !type.IsValueType || type.IsNullableValueType(); public static bool IsValidEntityType(this Type type) - => type is { IsClass: true, IsArray: false }; + => type is { IsClass: true, IsArray: false } + && type != typeof(string); + + public static bool IsValidComplexType(this Type type) + => !type.IsArray + && !type.IsInterface + && type != typeof(string) + && !CommonTypeDictionary.ContainsKey(type); public static bool IsPropertyBagType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs index a9788f95152..222d01e4f51 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs @@ -30,38 +30,44 @@ public class CosmosApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } + Type RuntimeExtensions)> MetadataExtensionTypes + { get; } = new() { - ( + { typeof(IReadOnlyModel), - typeof(CosmosModelExtensions), - typeof(CosmosModelExtensions), - typeof(CosmosModelExtensions), - typeof(CosmosModelBuilderExtensions), - null - ), - ( + ( + typeof(CosmosModelExtensions), + typeof(CosmosModelExtensions), + typeof(CosmosModelExtensions), + typeof(CosmosModelBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyEntityType), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeBuilderExtensions), - null - ), - ( + ( + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyProperty), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyBuilderExtensions), - null - ) + ( + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyBuilderExtensions), + null + ) + } }; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index b0f632fb609..b2b9d76b638 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -1760,10 +1760,25 @@ public async Task Can_have_non_string_property_named_Discriminator() using var context = new NonStringDiscriminatorContext(Fixture.CreateOptions()); context.Database.EnsureCreated(); - await context.AddAsync(new NonStringDiscriminator { Id = 1 }); + var entry = await context.AddAsync(new NonStringDiscriminator { Id = 1 }); await context.SaveChangesAsync(); - Assert.NotNull(await context.Set().OrderBy(e => e.Id).FirstOrDefaultAsync()); + var document = entry.Property("__jObject").CurrentValue; + Assert.NotNull(document); + Assert.Equal("0", document["Discriminator"]); + + Assert.NotNull(await context.Set() + .Where(e => e.Discriminator == EntityType.Base).OrderBy(e => e.Id).FirstOrDefaultAsync()); + + AssertSql( + context, +""" +SELECT c +FROM root c +WHERE (c["Discriminator"] = 0) +ORDER BY c["Id"] +OFFSET 0 LIMIT 1 +"""); } private class NonStringDiscriminator diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index a5c90589246..5c48c2f5fbc 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -672,5 +672,29 @@ IEnumerable IReadOnlyEntityType.GetDeclaredTriggers() IEnumerable IEntityType.GetDeclaredTriggers() => throw new NotImplementedException(); + + public IComplexProperty FindComplexProperty(string name) + => throw new NotImplementedException(); + + public IEnumerable GetComplexProperties() + => throw new NotImplementedException(); + + public IEnumerable GetDeclaredComplexProperties() + => throw new NotImplementedException(); + + IReadOnlyComplexProperty IReadOnlyEntityType.FindComplexProperty(string name) + => throw new NotImplementedException(); + + public IReadOnlyComplexProperty FindDeclaredComplexProperty(string name) + => throw new NotImplementedException(); + + IEnumerable IReadOnlyEntityType.GetComplexProperties() + => throw new NotImplementedException(); + + IEnumerable IReadOnlyEntityType.GetDeclaredComplexProperties() + => throw new NotImplementedException(); + + public IEnumerable GetDerivedComplexProperties() + => throw new NotImplementedException(); } } diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index d30276b6423..08af7fcd587 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -247,6 +247,211 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); } + public class CosmosGenericComplexType : GenericComplexType + { + public override void Properties_can_have_provider_type_set_for_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); + + modelBuilder + .Ignore() + .Entity( + b => + { + b.Property("__id").HasConversion(null); + b.ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Down").GetProviderClrType()); + Assert.Null(complexType.FindProperty("Charm").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Strange").GetProviderClrType()); + } + + [ConditionalFact] + public virtual void Partition_key_is_added_to_the_keys() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.Id), nameof(Customer.AlternateKey) }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, + entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); + + var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.NotNull(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void Partition_key_is_added_to_the_alternate_key_if_primary_key_contains_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, + entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void No_id_property_created_if_another_property_mapped_to_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Property(c => c.Name) + .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); + Assert.Single(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.GetDeclaredProperties() + .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.NotNull(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_id_property_created_if_another_property_mapped_to_id_in_pk() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Property(c => c.Name) + .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); + modelBuilder.Entity() + .Ignore(c => c.Details) + .Ignore(c => c.Orders) + .HasKey(c => c.Name); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.GetDeclaredProperties() + .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.Null(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_primary_key_contains_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.Null(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_primary_key_contains_id_and_partition_key() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_id_is_partition_key() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey)); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion().ToJsonProperty("id"); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.AlternateKey) }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + } + + protected override TestModelBuilder CreateModelBuilder(Action configure = null) + => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); + } + public class CosmosGenericInheritance : GenericInheritance { public override void Base_type_can_be_discovered_after_creating_foreign_keys_on_derived() diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 82f6d78427c..0968807c424 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -3718,28 +3718,28 @@ public virtual void Shared_owned_types_are_stored_in_snapshot() { Assert.Equal(7, o.GetEntityTypes().Count()); - var order = o.FindEntityType(typeof(Order).FullName); - Assert.Equal(1, order.PropertyCount()); + var order = (IRuntimeEntityType)o.FindEntityType(typeof(Order).FullName); + Assert.Equal(1, order.PropertyCount); - var orderInfo = order.FindNavigation(nameof(Order.OrderInfo)).TargetEntityType; - Assert.Equal(1, orderInfo.PropertyCount()); + var orderInfo = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderInfo)).TargetEntityType; + Assert.Equal(1, orderInfo.PropertyCount); - var orderInfoAddress = orderInfo.FindNavigation(nameof(OrderInfo.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderInfoAddress.PropertyCount()); + var orderInfoAddress = (IRuntimeEntityType)orderInfo.FindNavigation(nameof(OrderInfo.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderInfoAddress.PropertyCount); - var orderBillingDetails = order.FindNavigation(nameof(Order.OrderBillingDetails)).TargetEntityType; - Assert.Equal(1, orderBillingDetails.PropertyCount()); + var orderBillingDetails = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderBillingDetails)).TargetEntityType; + Assert.Equal(1, orderBillingDetails.PropertyCount); var orderBillingDetailsAddress = - orderBillingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderBillingDetailsAddress.PropertyCount()); + (IRuntimeEntityType)orderBillingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderBillingDetailsAddress.PropertyCount); - var orderShippingDetails = order.FindNavigation(nameof(Order.OrderShippingDetails)).TargetEntityType; - Assert.Equal(1, orderShippingDetails.PropertyCount()); + var orderShippingDetails = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderShippingDetails)).TargetEntityType; + Assert.Equal(1, orderShippingDetails.PropertyCount); var orderShippingDetailsAddress = - orderShippingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderShippingDetailsAddress.PropertyCount()); + (IRuntimeEntityType)orderShippingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderShippingDetailsAddress.PropertyCount); }); [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 8bdec2f1f41..6aa78459258 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2004,7 +2004,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalBaseId, principalBaseAlternateId }); @@ -2139,7 +2139,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalDerivedId, principalDerivedAlternateId, id }); @@ -3782,7 +3782,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalBaseId, principalBaseAlternateId }); @@ -3898,7 +3898,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalDerivedId, principalDerivedAlternateId, id }); diff --git a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs index 70004149bfb..291cb87beb0 100644 --- a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs +++ b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs @@ -15,6 +15,12 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); } + public class InMemoryGenericComplexTypeTestBase : GenericComplexType + { + protected override TestModelBuilder CreateModelBuilder(Action configure = null) + => CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); + } + public class InMemoryGenericInheritance : GenericInheritance { protected override TestModelBuilder CreateModelBuilder(Action configure = null) diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index db4bfbd645b..a4daa2723b4 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -339,6 +339,335 @@ public virtual void Configuring_direction_on_RowsAffectedParameter_throws() } } + public abstract class RelationalComplexTypeTestBase : ComplexTypeTestBase + { + [ConditionalFact] + public virtual void Can_use_table_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.HasDefaultSchema("dbo"); + + modelBuilder.Entity().SplitToTable( + "OrderDetails", s => + { + s.ExcludeFromMigrations(); + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + } + + [ConditionalFact] + public virtual void Can_use_table_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToTable("Order", "dbo") + .SplitToTable( + "OrderDetails", "sch", s => + s.ExcludeFromMigrations() + .Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))) + .Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order") + .SplitToView( + "OrderDetails", s => + { + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order", "dbo") + .SplitToView( + "OrderDetails", "sch", s => + s.Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order"))) + .Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedResultColumn()) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedParameter()) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_original_value_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasOriginalValueParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateOriginalValueParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddOriginalValueParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasResultColumn(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateResultColumn("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddResultColumn("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Configuring_direction_on_RowsAffectedParameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var param = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetInsertStoredProcedure()!.Parameters.Single(); + + Assert.Equal( + RelationalStrings.StoredProcedureParameterInvalidConfiguration("Direction", "RowsAffected", "BookLabel_Insert"), + Assert.Throws(() => param.Direction = ParameterDirection.Input) + .Message); + } + } + public abstract class RelationalInheritanceTestBase : InheritanceTestBase { [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 93bd17b494a..3e3654ac1e9 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -184,63 +184,73 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(OperationBuilder<>) }; - public override - List<(Type Type, - Type ReadonlyExtensions, + public override Dictionary MetadataExtensionTypes { get; } - = new() + Type RuntimeExtensions)> MetadataExtensionTypes { get; } = new() { - ( + { typeof(IReadOnlyModel), - typeof(RelationalModelExtensions), - typeof(RelationalModelExtensions), - typeof(RelationalModelExtensions), - typeof(RelationalModelBuilderExtensions), - null - ), - ( + ( + typeof(RelationalModelExtensions), + typeof(RelationalModelExtensions), + typeof(RelationalModelExtensions), + typeof(RelationalModelBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyEntityType), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeBuilderExtensions), - null - ), - ( + ( + + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyKey), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalKeyExtensions), + typeof(RelationalKeyExtensions), + typeof(RelationalKeyExtensions), + typeof(RelationalKeyBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyForeignKey), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyProperty), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyIndex), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexBuilderExtensions), - null - ) + ( + typeof(RelationalIndexExtensions), + typeof(RelationalIndexExtensions), + typeof(RelationalIndexExtensions), + typeof(RelationalIndexBuilderExtensions), + null + ) + } }; public override HashSet NonVirtualMethods { get; } diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 2604d0bab6e..76fb893ee53 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -3,6 +3,7 @@ using System.CodeDom.Compiler; using System.Runtime.CompilerServices; +using System.Xml.Linq; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore; @@ -70,16 +71,24 @@ public void Generic_fluent_api_methods_should_return_generic_types() if (method.ReturnType == type.BaseType && !Fixture.UnmatchedMetadataMethods.Contains(method)) { - var parameters = method.GetParameters() - .Select(p => GetEquivalentGenericType(p.ParameterType, type.GetGenericArguments())).ToArray(); - var hidingMethod = type.GetMethod( - method.Name, - method.GetGenericArguments().Length, - PublicInstance | BindingFlags.DeclaredOnly, - null, - parameters, - null); - if (hidingMethod == null || hidingMethod.ReturnType != type) + var methodFound = false; + foreach (var hidingMethod in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if (method.Name != hidingMethod.Name + || hidingMethod.GetGenericArguments().Length != method.GetGenericArguments().Length + || hidingMethod.ReturnType != type + || !hidingMethod.GetParameters().Select(p => p.ParameterType) + .SequenceEqual(method.GetParameters().Select( + p => GetEquivalentGenericType(p.ParameterType, hidingMethod.GetGenericArguments())))) + { + continue; + } + + methodFound = true; + break; + } + + if (!methodFound) { nonGenericMethods.Add((type.BaseType, method)); } @@ -131,17 +140,34 @@ public void Generic_fluent_api_methods_should_return_generic_types() "\r\n-- Non-generic fluent returns that aren't hidden --\r\n" + string.Join( Environment.NewLine, nonGenericMethods.Select( - m => $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}({Format(m.Method.GetParameters())})"))); + m => $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}{FormatGenericArguments(m.Method)}({Format(m.Method.GetParameters())})"))); + } + + public static string FormatGenericArguments(MethodInfo methodInfo) + { + var arguments = methodInfo.GetGenericArguments(); + return arguments.Length == 0 ? "" : $"`{arguments.Length}"; } protected Type GetEquivalentGenericType(Type parameterType, Type[] genericArguments) { if (parameterType.IsGenericType - && parameterType.GetGenericTypeDefinition() == typeof(Action<>) - && Fixture.GenericFluentApiTypes.TryGetValue(parameterType.GetGenericArguments()[0], out var genericBuilder) - && genericBuilder.GetGenericArguments().Length == genericArguments.Length) + && parameterType.GetGenericTypeDefinition() == typeof(Action<>)) { - return typeof(Action<>).MakeGenericType(genericBuilder.MakeGenericType(genericArguments)); + var builder = parameterType.GetGenericArguments()[0]; + if (Fixture.GenericFluentApiTypes.TryGetValue(builder, out var genericBuilder) + && genericBuilder.GetGenericArguments().Length == genericArguments.Length) + { + return typeof(Action<>).MakeGenericType(genericBuilder.MakeGenericType(genericArguments)); + } + else if (builder.IsGenericType) + { + var builderDefinition = builder.GetGenericTypeDefinition(); + if (builderDefinition.GetGenericArguments().Length == genericArguments.Length) + { + return typeof(Action<>).MakeGenericType(builderDefinition.MakeGenericType(genericArguments)); + } + } } return parameterType; @@ -159,7 +185,7 @@ public void Builders_have_matching_methods() { if (!Fixture.UnmatchedMetadataMethods.Contains(method)) { - MethodInfo hidingMethod = null; + MethodInfo matchingMethod = null; foreach (var targetMethod in tuple.Value.GetMethods(PublicInstance | BindingFlags.DeclaredOnly)) { if (targetMethod.Name == method.Name @@ -170,23 +196,23 @@ public void Builders_have_matching_methods() new ParameterTypeEqualityComparer(method, targetMethod, this))) { Check.DebugAssert( - hidingMethod == null, + matchingMethod == null, "There should only be one method with the expected signature. Found: " + Environment.NewLine - + Format(hidingMethod ?? targetMethod, tuple.Value) + + Format(matchingMethod ?? targetMethod, tuple.Value) + Environment.NewLine + Format(targetMethod, tuple.Value)); - hidingMethod = targetMethod; + matchingMethod = targetMethod; } } - if (hidingMethod == null) + if (matchingMethod == null) { unmatchedMethods.Add((tuple.Key, method)); } else if (method.ReturnType == tuple.Key - && hidingMethod.ReturnType != tuple.Value) + && matchingMethod.ReturnType != tuple.Value) { wrongReturnMethods.Add((tuple.Value, method)); } @@ -293,11 +319,14 @@ private string ValidateMetadata(KeyValuePair typ if (conventionBuilderType != null) { - var builderProperty = conventionType.GetProperty("Builder"); - if (builderProperty == null - || builderProperty.PropertyType != conventionBuilderType) + if (!conventionBuilderType.IsGenericType) { - return $"{conventionType.Name} expected to have a '{conventionBuilderType.Name} Builder' property"; + var builderProperty = conventionType.GetProperty("Builder"); + if (builderProperty == null + || builderProperty.PropertyType != conventionBuilderType) + { + return $"{conventionType.Name} expected to have a '{conventionBuilderType.Name} Builder' property"; + } } var metadataProperty = conventionBuilderType.GetProperty("Metadata"); @@ -527,33 +556,45 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method var declaringType = methods[0].DeclaringType; var builderType = methods[0].IsStatic ? methods[0].GetParameters()[0].ParameterType : declaringType; - var methodLookup = new Dictionary(); + var methodLookup = new Dictionary(methods.Count); foreach (var method in methods) { methodLookup[Fixture.MetadataMethodNameTransformers.TryGetValue(method, out var name) ? name : method.Name] = method; } + foreach (var interfaceType in builderType.GetDeclaredInterfaces()) + { + foreach (var method in interfaceType.GetMethods()) + { + methodLookup[Fixture.MetadataMethodNameTransformers.TryGetValue(method, out var name) ? name : method.Name] = method; + } + } + foreach (var keyValuePair in methodLookup) { var method = keyValuePair.Value; var methodName = keyValuePair.Key; + if (Fixture.UnmatchedMetadataMethods.Contains(method) - || method.ReturnType != builderType) + || method.ReturnType != builderType + || methodName.StartsWith("get_", StringComparison.Ordinal)) { continue; } var expectedName = methodName.StartsWith("HasNo", StringComparison.Ordinal) ? "CanRemove" + methodName[5..] - : "CanSet" - + (methodName.StartsWith("Has", StringComparison.Ordinal) - || methodName.StartsWith("Use", StringComparison.Ordinal) - ? methodName[3..] - : methodName.StartsWith("To", StringComparison.Ordinal) - ? methodName[2..] - : methodName.StartsWith("With", StringComparison.Ordinal) - ? methodName[4..] - : methodName); + : methodName.StartsWith("Ignore", StringComparison.Ordinal) + ? methodName + : "CanSet" + + (methodName.StartsWith("Has", StringComparison.Ordinal) + || methodName.StartsWith("Use", StringComparison.Ordinal) + ? methodName[3..] + : methodName.StartsWith("To", StringComparison.Ordinal) + ? methodName[2..] + : methodName.StartsWith("With", StringComparison.Ordinal) + ? methodName[4..] + : methodName); if (!methodLookup.TryGetValue(expectedName, out var canSetMethod)) { @@ -584,6 +625,151 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method return null; } + [ConditionalFact] + public void Convention_builder_methods_have_matching_returns() + { + var errors = + Fixture.MetadataTypes.Select(t => + ValidateConventionBuilderMethodReturns(t.Value.ConventionBuilder, t.Value.Convention)) + .Where(e => e != null) + .ToList(); + + Assert.False( + errors.Count > 0, + "\r\n-- Hiding methods missing: --\r\n" + + string.Join(Environment.NewLine, errors)); + } + + private string ValidateConventionBuilderMethodReturns(Type builderType, Type conventionType) + { + if (builderType == null) + { + return null; + } + + var extensionType = Fixture.MetadataExtensionTypes.GetValueOrDefault(builderType).ConventionBuilderExtensions; + var unmatchedMethods = new List<(Type Type, Type ReturnType, MethodInfo Method)>(); + foreach (var interfaceType in builderType.GetDeclaredInterfaces()) + { + var isInheritedInterface = false; + foreach (var otherInterfaceType in builderType.GetDeclaredInterfaces()) + { + if (otherInterfaceType == interfaceType) + { + continue; + } + + if (interfaceType.IsAssignableFrom(otherInterfaceType)) + { + isInheritedInterface = true; + } + } + + if (isInheritedInterface) + { + continue; + } + + var normalizedInterfaceType = interfaceType.IsGenericType + ? interfaceType.GetGenericTypeDefinition() + : interfaceType; + var readOnlyInterfaceType = Fixture.MetadataTypes + .FirstOrDefault(p => p.Value.ConventionBuilder == normalizedInterfaceType) + .Key; + + var methods = interfaceType.GetMethods(PublicInstance); + foreach (var method in methods) + { + if ((method.ReturnType != interfaceType + && method.ReturnType != Fixture.MetadataTypes[readOnlyInterfaceType].Convention) + || Fixture.UnmatchedMetadataMethods.Contains(method) + || Fixture.IsObsolete(method)) + { + continue; + } + + var expectedReturn = method.ReturnType == interfaceType + ? builderType.IsGenericType + ? builderType.GetGenericArguments()[0] + : builderType + : conventionType; + + var parameters = method.GetParameters() + .Select(p => GetEquivalentGenericType(p.ParameterType, builderType.GetGenericArguments())).ToArray(); + var hidingMethod = builderType.GetMethod( + method.Name, + method.GetGenericArguments().Length, + PublicInstance | BindingFlags.DeclaredOnly, + null, + parameters, + null); + if (hidingMethod == null + || hidingMethod.ReturnType != expectedReturn) + { + unmatchedMethods.Add((builderType, expectedReturn, method)); + } + } + + if (readOnlyInterfaceType == null) + { + continue; + } + + var interfaceExtensionType = Fixture.MetadataExtensionTypes.GetValueOrDefault(readOnlyInterfaceType) + .ConventionBuilderExtensions; + if (interfaceExtensionType == null) + { + continue; + } + + var conventionBuilderExtensionMethods = interfaceExtensionType + .GetMethods(BindingFlags.Public | BindingFlags.Static); + foreach (var method in methods) + { + if (method.GetParameters().First().ParameterType != interfaceType + || method.ReturnType != interfaceType + || Fixture.UnmatchedMetadataMethods.Contains(method) + || Fixture.IsObsolete(method)) + { + continue; + } + + var methodFound = false; + var expectedReturn = method.ReturnType == interfaceType ? builderType : conventionType; + if (extensionType != null) + { + var parameters = new[] { builderType }.Concat( + method.GetParameters() + .Skip(1) + .Select(p => GetEquivalentGenericType(p.ParameterType, builderType.GetGenericArguments()))) + .ToArray(); + var hidingMethod = extensionType.GetMethod( + method.Name, + method.GetGenericArguments().Length, + BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, + null, + parameters, + null); + methodFound = hidingMethod != null && hidingMethod.ReturnType == expectedReturn; + } + + if (!methodFound) + { + unmatchedMethods.Add((extensionType ?? builderType, expectedReturn, method)); + } + } + } + + if (unmatchedMethods.Count == 0) + { + return null; + } + + return string.Join( + Environment.NewLine, unmatchedMethods.Select( + m => $"{m.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}{FormatGenericArguments(m.Method)}({Format(m.Method.GetParameters())})")); + } + [ConditionalFact] public void Runtime_metadata_types_have_matching_methods() { @@ -1067,6 +1253,7 @@ protected ApiConsistencyFixtureBase() { typeof(DataBuilder), typeof(DataBuilder<>) }, { typeof(DiscriminatorBuilder), typeof(DiscriminatorBuilder<>) }, { typeof(EntityTypeBuilder), typeof(EntityTypeBuilder<>) }, + { typeof(ComplexPropertyBuilder), typeof(ComplexPropertyBuilder<>) }, { typeof(IndexBuilder), typeof(IndexBuilder<>) }, { typeof(KeyBuilder), typeof(KeyBuilder<>) }, { typeof(NavigationBuilder), typeof(NavigationBuilder<,>) }, @@ -1074,6 +1261,7 @@ protected ApiConsistencyFixtureBase() { typeof(OwnedEntityTypeBuilder), typeof(OwnedEntityTypeBuilder<>) }, { typeof(OwnershipBuilder), typeof(OwnershipBuilder<,>) }, { typeof(PropertyBuilder), typeof(PropertyBuilder<>) }, + { typeof(ComplexTypePropertyBuilder), typeof(ComplexTypePropertyBuilder<>) }, { typeof(ReferenceCollectionBuilder), typeof(ReferenceCollectionBuilder<,>) }, { typeof(ReferenceNavigationBuilder), typeof(ReferenceNavigationBuilder<,>) }, { typeof(ReferenceReferenceBuilder), typeof(ReferenceReferenceBuilder<,>) }, @@ -1127,10 +1315,16 @@ protected ApiConsistencyFixtureBase() typeof(IConventionEntityTypeBuilder), typeof(IEntityType)) }, + { + typeof(IReadOnlyComplexProperty), (typeof(IMutableComplexProperty), + typeof(IConventionComplexProperty), + typeof(IConventionComplexPropertyBuilder), + typeof(IComplexProperty)) + }, { typeof(IReadOnlyTypeBase), (typeof(IMutableTypeBase), typeof(IConventionTypeBase), - null, + typeof(IConventionTypeBaseBuilder), typeof(ITypeBase)) }, { @@ -1163,6 +1357,18 @@ protected ApiConsistencyFixtureBase() typeof(IConventionPropertyBuilder), typeof(IProperty)) }, + { + typeof(IReadOnlyComplexTypeProperty), (typeof(IMutableComplexTypeProperty), + typeof(IConventionComplexTypeProperty), + typeof(IConventionComplexTypePropertyBuilder), + typeof(IComplexTypeProperty)) + }, + { + typeof(IReadOnlyPrimitivePropertyBase), (typeof(IMutablePrimitivePropertyBase), + typeof(IConventionPrimitivePropertyBase), + typeof(IConventionPrimitivePropertyBaseBuilder<>), + typeof(IPrimitivePropertyBase)) + }, { typeof(IReadOnlyNavigation), (typeof(IMutableNavigation), typeof(IConventionNavigation), @@ -1190,7 +1396,7 @@ protected ApiConsistencyFixtureBase() { typeof(IReadOnlyPropertyBase), (typeof(IMutablePropertyBase), typeof(IConventionPropertyBase), - null, + typeof(IConventionPropertyBaseBuilder<>), typeof(IPropertyBase)) } }; @@ -1199,8 +1405,7 @@ protected ApiConsistencyFixtureBase() public Dictionary ConventionMetadataTypes { get; } = new(); public virtual - List<(Type Type, - Type ReadonlyExtensions, + Dictionary !IsObsolete(m) && m.GetParameters().First().ParameterType == type).ToArray() @@ -1284,7 +1490,7 @@ protected void AddInstanceMethods(Dictionary Attribute.IsDefined(method, typeof(ObsoleteAttribute), inherit: false); } } diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index 8cfa853c098..2fa8a0c28f2 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -63,7 +63,6 @@ public virtual void Can_read_write_long_JSON_values(long value, string json) [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":0}")] [InlineData(byte.MaxValue, "{\"Prop\":255}")] - [InlineData((byte)0, "{\"Prop\":0}")] [InlineData((byte)1, "{\"Prop\":1}")] public virtual void Can_read_write_byte_JSON_values(byte value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt8)), value, json); @@ -71,7 +70,6 @@ public virtual void Can_read_write_byte_JSON_values(byte value, string json) [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":0}")] [InlineData(ushort.MaxValue, "{\"Prop\":65535}")] - [InlineData((ushort)0, "{\"Prop\":0}")] [InlineData((ushort)1, "{\"Prop\":1}")] public virtual void Can_read_write_ushort_JSON_values(ushort value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt16)), value, json); @@ -79,7 +77,6 @@ public virtual void Can_read_write_ushort_JSON_values(ushort value, string json) [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":0}")] [InlineData(uint.MaxValue, "{\"Prop\":4294967295}")] - [InlineData((uint)0, "{\"Prop\":0}")] [InlineData((uint)1, "{\"Prop\":1}")] public virtual void Can_read_write_uint_JSON_values(uint value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt32)), value, json); @@ -87,7 +84,6 @@ public virtual void Can_read_write_uint_JSON_values(uint value, string json) [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":0}")] [InlineData(ulong.MaxValue, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)0, "{\"Prop\":0}")] [InlineData((ulong)1, "{\"Prop\":1}")] public virtual void Can_read_write_ulong_JSON_values(ulong value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt64)), value, json); @@ -119,7 +115,6 @@ public virtual void Can_read_write_decimal_JSON_values(decimal value, string jso [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] public virtual void Can_read_write_DateOnly_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -129,7 +124,6 @@ public virtual void Can_read_write_DateOnly_JSON_values(string value, string jso [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] public virtual void Can_read_write_TimeOnly_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -139,7 +133,6 @@ public virtual void Can_read_write_TimeOnly_JSON_values(string value, string jso [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31T23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29T10:52:47.2064353\"}")] public virtual void Can_read_write_DateTime_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -182,7 +175,6 @@ public virtual void Can_read_write_char_JSON_values(char value, string json) [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] public virtual void Can_read_write_GUID_JSON_values(Guid value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.Guid)), value, json); @@ -223,7 +215,6 @@ public virtual void Can_read_write_URI_JSON_values(string value, string json) [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] public virtual void Can_read_write_IP_address_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -272,7 +263,6 @@ public virtual void Can_read_write_long_enum_JSON_values(Enum64 value, string js [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":0}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":255}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":0}")] [InlineData((byte)EnumU8.One, "{\"Prop\":1}")] public virtual void Can_read_write_byte_enum_JSON_values(EnumU8 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU8)), value, json); @@ -280,7 +270,6 @@ public virtual void Can_read_write_byte_enum_JSON_values(EnumU8 value, string js [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":65535}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":1}")] public virtual void Can_read_write_ushort_enum_JSON_values(EnumU16 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU16)), value, json); @@ -288,7 +277,6 @@ public virtual void Can_read_write_ushort_enum_JSON_values(EnumU16 value, string [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":0}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":4294967295}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":0}")] [InlineData((uint)EnumU32.One, "{\"Prop\":1}")] public virtual void Can_read_write_uint_enum_JSON_values(EnumU32 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU32)), value, json); @@ -296,7 +284,6 @@ public virtual void Can_read_write_uint_enum_JSON_values(EnumU32 value, string j [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":1}")] public virtual void Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU64)), value, json); @@ -344,7 +331,6 @@ public virtual void Can_read_write_nullable_long_JSON_values(long? value, string [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":0}")] [InlineData(byte.MaxValue, "{\"Prop\":255}")] - [InlineData((byte)0, "{\"Prop\":0}")] [InlineData((byte)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_JSON_values(byte? value, string json) @@ -354,7 +340,6 @@ public virtual void Can_read_write_nullable_byte_JSON_values(byte? value, string [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":0}")] [InlineData(ushort.MaxValue, "{\"Prop\":65535}")] - [InlineData((ushort)0, "{\"Prop\":0}")] [InlineData((ushort)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_JSON_values(ushort? value, string json) @@ -364,7 +349,6 @@ public virtual void Can_read_write_nullable_ushort_JSON_values(ushort? value, st [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":0}")] [InlineData(uint.MaxValue, "{\"Prop\":4294967295}")] - [InlineData((uint)0, "{\"Prop\":0}")] [InlineData((uint)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_JSON_values(uint? value, string json) @@ -374,7 +358,6 @@ public virtual void Can_read_write_nullable_uint_JSON_values(uint? value, string [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":0}")] [InlineData(ulong.MaxValue, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)0, "{\"Prop\":0}")] [InlineData((ulong)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_JSON_values(ulong? value, string json) @@ -415,7 +398,6 @@ public virtual void Can_read_write_nullable_decimal_JSON_values(string? value, s [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) @@ -426,7 +408,6 @@ public virtual void Can_read_write_nullable_DateOnly_JSON_values(string? value, [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_TimeOnly_JSON_values(string? value, string json) @@ -437,7 +418,6 @@ public virtual void Can_read_write_nullable_TimeOnly_JSON_values(string? value, [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31T23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29T10:52:47.2064353\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateTime_JSON_values(string? value, string json) @@ -488,7 +468,6 @@ public virtual void Can_read_write_nullable_char_JSON_values(char? value, string [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_GUID_JSON_values(string? value, string json) @@ -541,7 +520,6 @@ public virtual void Can_read_write_nullable_URI_JSON_values(string? value, strin [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_IP_address_JSON_values(string? value, string json) @@ -606,7 +584,6 @@ public virtual void Can_read_write_nullable_long_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":0}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":255}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":0}")] [InlineData((byte)EnumU8.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_enum_JSON_values(object? value, string json) @@ -617,7 +594,6 @@ public virtual void Can_read_write_nullable_byte_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":65535}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_enum_JSON_values(object? value, string json) @@ -628,7 +604,6 @@ public virtual void Can_read_write_nullable_ushort_enum_JSON_values(object? valu [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":0}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":4294967295}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":0}")] [InlineData((uint)EnumU32.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_enum_JSON_values(object? value, string json) @@ -639,7 +614,6 @@ public virtual void Can_read_write_nullable_uint_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) @@ -690,7 +664,6 @@ public virtual void Can_read_write_nullable_long_as_string_JSON_values(long? val [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":\"0\"}")] [InlineData(byte.MaxValue, "{\"Prop\":\"255\"}")] - [InlineData((byte)0, "{\"Prop\":\"0\"}")] [InlineData((byte)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_as_string_JSON_values(byte? value, string json) @@ -700,7 +673,6 @@ public virtual void Can_read_write_nullable_byte_as_string_JSON_values(byte? val [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":\"0\"}")] [InlineData(ushort.MaxValue, "{\"Prop\":\"65535\"}")] - [InlineData((ushort)0, "{\"Prop\":\"0\"}")] [InlineData((ushort)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_as_string_JSON_values(ushort? value, string json) @@ -710,7 +682,6 @@ public virtual void Can_read_write_nullable_ushort_as_string_JSON_values(ushort? [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":\"0\"}")] [InlineData(uint.MaxValue, "{\"Prop\":\"4294967295\"}")] - [InlineData((uint)0, "{\"Prop\":\"0\"}")] [InlineData((uint)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_as_string_JSON_values(uint? value, string json) @@ -720,7 +691,6 @@ public virtual void Can_read_write_nullable_uint_as_string_JSON_values(uint? val [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":\"0\"}")] [InlineData(ulong.MaxValue, "{\"Prop\":\"18446744073709551615\"}")] - [InlineData((ulong)0, "{\"Prop\":\"0\"}")] [InlineData((ulong)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_as_string_JSON_values(ulong? value, string json) @@ -761,7 +731,6 @@ public virtual void Can_read_write_nullable_decimal_as_string_JSON_values(string [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateOnly_as_string_JSON_values(string? value, string json) @@ -772,7 +741,6 @@ public virtual void Can_read_write_nullable_DateOnly_as_string_JSON_values(strin [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_TimeOnly_as_string_JSON_values(string? value, string json) @@ -783,7 +751,6 @@ public virtual void Can_read_write_nullable_TimeOnly_as_string_JSON_values(strin [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01 00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31 23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01 00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29 10:52:47.2064353\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateTime_as_string_JSON_values(string? value, string json) @@ -834,7 +801,6 @@ public virtual void Can_read_write_nullable_char_as_string_JSON_values(char? val [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_as_string_GUID_JSON_values(string? value, string json) @@ -887,7 +853,6 @@ public virtual void Can_read_write_nullable_URI_as_string_JSON_values(string? va [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_IP_address_as_string_JSON_values(string? value, string json) @@ -956,7 +921,6 @@ public virtual void Can_read_write_nullable_long_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":\"Min\"}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":\"Max\"}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":\"Min\"}")] [InlineData((byte)EnumU8.One, "{\"Prop\":\"One\"}")] [InlineData((byte)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -968,7 +932,6 @@ public virtual void Can_read_write_nullable_byte_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":\"Min\"}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":\"Max\"}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":\"Min\"}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":\"One\"}")] [InlineData((ushort)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -980,7 +943,6 @@ public virtual void Can_read_write_nullable_ushort_enum_as_string_JSON_values(ob [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":\"Min\"}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":\"Max\"}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":\"Min\"}")] [InlineData((uint)EnumU32.One, "{\"Prop\":\"One\"}")] [InlineData((uint)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -992,7 +954,6 @@ public virtual void Can_read_write_nullable_uint_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":\"Min\"}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":\"Max\"}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":\"Min\"}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":\"One\"}")] [InlineData((ulong)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index 56afddf5ed8..60687254374 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -433,7 +433,7 @@ public void RemoveAllConventions() ConventionSet.EntityTypeAddedConventions.Clear(); ConventionSet.EntityTypeAnnotationChangedConventions.Clear(); ConventionSet.EntityTypeBaseTypeChangedConventions.Clear(); - ConventionSet.EntityTypeIgnoredConventions.Clear(); + ConventionSet.TypeIgnoredConventions.Clear(); ConventionSet.EntityTypeMemberIgnoredConventions.Clear(); ConventionSet.EntityTypePrimaryKeyChangedConventions.Clear(); ConventionSet.EntityTypeRemovedConventions.Clear(); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index f320b234e00..1ecaf5677d3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -41,54 +41,68 @@ public class SqlServerApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } = new() { - ( + { + typeof(IReadOnlyModel), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerModelExtensions), + typeof(SqlServerModelExtensions), + typeof(SqlServerModelExtensions), + typeof(SqlServerModelBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyEntityType), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyKey), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyProperty), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerPropertyExtensions), + typeof(SqlServerPropertyExtensions), + typeof(SqlServerPropertyExtensions), + typeof(SqlServerPropertyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyIndex), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexBuilderExtensions), - null - ) + ( + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexBuilderExtensions), + null + ) + } }; protected override void Initialize() diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 62e650279c5..f2a0dbde446 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } + public class SqlServerGenericComplexTypeTestBase : SqlServerComplexType + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); + } + public class SqlServerGenericInheritance : SqlServerInheritance { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs index 8d304107b5e..53998ac3ac3 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); } + public class SqlServerNonGenericComplexTypeTestBase : SqlServerComplexType + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + public class SqlServerNonGenericInheritance : SqlServerInheritance { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index 55ba5b6a859..be6de05160c 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -198,6 +198,196 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); } + public abstract class SqlServerComplexType : RelationalComplexTypeTestBase + { + [ConditionalFact] + public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder + .Entity(); + var indexBuilder = entityTypeBuilder + .HasIndex(ix => ix.Name) + .IsUnique(); + + var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var index = entityType.GetIndexes().Single(); + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsUnique(false); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsUnique(); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsClustered(); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsClustered(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(); + + Assert.Null(index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); + + Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); + + Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName(null); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.HasFilter("Foo"); + + Assert.Equal("Foo", index.GetFilter()); + + indexBuilder.HasFilter(null); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public void Indexes_can_have_same_name_across_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasIndex(e => e.Id, "Ix_Id") + .IsUnique(); + modelBuilder.Entity() + .HasIndex(e => e.CustomerId, "Ix_Id") + .IsUnique(); + + var model = modelBuilder.FinalizeModel(); + + var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", customerIndex.Name); + Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", customerIndex.GetDatabaseName( + StoreObjectIdentifier.Table("Customer"))); + + var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", detailsIndex.Name); + Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", detailsIndex.GetDatabaseName( + StoreObjectIdentifier.Table("CustomerDetails"))); + } + + [ConditionalFact] + public virtual void Can_set_store_type_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveColumnType("smallint"); + c.Properties().HaveColumnType("nchar(max)"); + c.Properties(typeof(Nullable<>)).HavePrecision(2); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); + Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); + var charm = entityType.FindProperty("Charm")!; + Assert.Equal("smallint", charm.GetColumnType()); + Assert.Null(charm.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); + var top = entityType.FindProperty("Top")!; + Assert.Equal("smallint", top.GetColumnType()); + Assert.Equal(2, top.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + } + + [ConditionalFact] + public virtual void Can_set_fixed_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreFixedLength(false); + c.Properties().AreFixedLength(); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); + Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); + } + + [ConditionalFact] + public virtual void Can_set_collation_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); + c.Properties().UseCollation("Latin1_General_BIN"); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } public abstract class SqlServerInheritance : RelationalInheritanceTestBase { [ConditionalFact] // #7240 diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs index 03c7069491d..f9f6ce90fcf 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs @@ -29,22 +29,25 @@ public class SqliteApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } = new() { - ( + { + typeof(IReadOnlyProperty), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyBuilderExtensions), - null - ) + ( + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyBuilderExtensions), + null + ) + } }; } } diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index bfbce84c6e0..c9a2414c397 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -44,6 +44,8 @@ protected override void Initialize() typeof(DiscriminatorBuilder<>), typeof(EntityTypeBuilder), typeof(EntityTypeBuilder<>), + typeof(ComplexPropertyBuilder), + typeof(ComplexPropertyBuilder<>), typeof(IndexBuilder), typeof(IndexBuilder<>), typeof(TriggerBuilder), @@ -60,6 +62,8 @@ protected override void Initialize() typeof(OwnershipBuilder<,>), typeof(PropertyBuilder), typeof(PropertyBuilder<>), + typeof(ComplexTypePropertyBuilder), + typeof(ComplexTypePropertyBuilder<>), typeof(ReferenceCollectionBuilder), typeof(ReferenceCollectionBuilder<,>), typeof(ReferenceNavigationBuilder), @@ -112,6 +116,10 @@ protected override void Initialize() public override HashSet UnmatchedMetadataMethods { get; } = new() { + typeof(ComplexPropertyBuilder).GetMethod( + nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(string) }), + typeof(ComplexPropertyBuilder).GetMethod( + nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(Type), typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( @@ -155,10 +163,7 @@ protected override void Initialize() typeof(IReadOnlyNavigationBase).GetMethod("get_Inverse"), typeof(IConventionAnnotatableBuilder).GetMethod(nameof(IConventionAnnotatableBuilder.HasNonNullAnnotation)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties)), - typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.Ignore)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)), - typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.Ignore), new[] { typeof(Type), typeof(bool) }), - typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.Ignore), new[] { typeof(string), typeof(bool) }), typeof(IConventionPropertyBuilder).GetMethod( nameof(IConventionPropertyBuilder.HasField), new[] { typeof(string), typeof(bool) }), typeof(IConventionPropertyBuilder).GetMethod( diff --git a/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs index dde6de1d94f..98cbcf45814 100644 --- a/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs +++ b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs @@ -23,8 +23,10 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() var foreignKey = new ForeignKey(new[] { property }, otherKey, entityType, otherEntityType, ConfigurationSource.Convention); var navigation = new Navigation("N", propertyInfo, null, foreignKey); var skipNavigation = new SkipNavigation( - "SN", propertyInfo, null, entityType, otherEntityType, true, false, ConfigurationSource.Convention); + "SN", null, propertyInfo, null, entityType, otherEntityType, true, false, ConfigurationSource.Convention); var navigationBase = new FakeNavigationBase("FNB", ConfigurationSource.Convention, entityType); + var complexProperty = entityType.AddComplexProperty( + "C", typeof(object), typeof(object), false, ConfigurationSource.Convention); entityType.Model.FinalizeModel(); var options = new DbContextOptionsBuilder() @@ -44,11 +46,13 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() { typeof(IConventionEntityType), () => entityType }, { typeof(IKey), () => new Key(new[] { property }, ConfigurationSource.Convention) }, { typeof(IPropertyBase), () => property }, + { typeof(IPrimitivePropertyBase), () => property }, { typeof(IReadOnlyProperty), () => property }, + { typeof(IComplexProperty), () => complexProperty }, { typeof(IServiceProvider), () => new FakeServiceProvider() }, { typeof(ICollection), () => new List() }, { typeof(IReadOnlyList), () => new[] { property } }, - { typeof(IReadOnlyList), () => Array.Empty() }, + { typeof(IReadOnlyList), Array.Empty }, { typeof(Func, EventDefinition, ConcurrencyExceptionEventData>), diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 6e7ec72499a..d0927fe0275 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -326,9 +326,9 @@ public void OnEntityTypeIgnored_calls_conventions_in_order(bool useBuilder, bool var convention1 = new EntityTypeIgnoredConvention(terminate: false); var convention2 = new EntityTypeIgnoredConvention(terminate: true); var convention3 = new EntityTypeIgnoredConvention(terminate: false); - conventions.EntityTypeIgnoredConventions.Add(convention1); - conventions.EntityTypeIgnoredConventions.Add(convention2); - conventions.EntityTypeIgnoredConventions.Add(convention3); + conventions.TypeIgnoredConventions.Add(convention1); + conventions.TypeIgnoredConventions.Add(convention2); + conventions.TypeIgnoredConventions.Add(convention3); var convention4 = new EntityTypeRemovedConvention(terminate: false); var convention5 = new EntityTypeRemovedConvention(terminate: true); @@ -371,7 +371,7 @@ public void OnEntityTypeIgnored_calls_conventions_in_order(bool useBuilder, bool Assert.Equal(0, convention6.Calls); } - private class EntityTypeIgnoredConvention : IEntityTypeIgnoredConvention + private class EntityTypeIgnoredConvention : ITypeIgnoredConvention { private readonly bool _terminate; public int Calls; @@ -381,7 +381,7 @@ public EntityTypeIgnoredConvention(bool terminate) _terminate = terminate; } - public void ProcessEntityTypeIgnored( + public void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type type, @@ -2093,7 +2093,7 @@ public void OnSkipNavigationAdded_calls_conventions_in_order(bool useBuilder, bo else { var result = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); Assert.Equal(!useScope, result == null); } @@ -2158,7 +2158,7 @@ public void OnSkipNavigationAnnotationChanged_calls_conventions_in_order(bool us var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2267,7 +2267,7 @@ public void OnSkipNavigationForeignKeyChanged_calls_conventions_in_order(bool us .IsUnique(false, ConfigurationSource.Convention) .Metadata; var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2358,9 +2358,9 @@ public void OnSkipNavigationInverseChanged_calls_conventions_in_order(bool useBu var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var inverse = secondEntityBuilder.Metadata.AddSkipNavigation( - nameof(Product.Orders), null, firstEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Product.Orders), null, null, firstEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2449,7 +2449,7 @@ public void OnSkipNavigationRemoved_calls_conventions_in_order(bool useScope) var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -3835,12 +3835,1259 @@ public void ProcessPropertyRemoved( } } + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyAdded_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypePropertyAddedConvention(terminate: false); + var convention2 = new ComplexTypePropertyAddedConvention(terminate: true); + var convention3 = new ComplexTypePropertyAddedConvention(terminate: false); + conventions.ComplexTypePropertyAddedConventions.Add(convention1); + conventions.ComplexTypePropertyAddedConventions.Add(convention2); + conventions.ComplexTypePropertyAddedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var complexBuilder = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + var shadowPropertyName = "ShadowProperty"; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Property(typeof(int), shadowPropertyName, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = complexBuilder.Metadata.AddProperty( + shadowPropertyName, typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = ((IMutableComplexType)complexBuilder.Metadata).AddProperty(nameof(OrderDetails.Id)); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName, nameof(OrderDetails.Id) }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName, nameof(OrderDetails.Id) }, convention2.Calls); + Assert.Empty(convention3.Calls); + + Assert.Empty(entityBuilder.Metadata.GetProperties()); + } + + private class ComplexTypePropertyAddedConvention : IComplexTypePropertyAddedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypePropertyAddedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypePropertyAdded( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(propertyBuilder.Metadata.Name); + + if (_terminate) + { + propertyBuilder.Metadata.DeclaringComplexType.RemoveProperty(propertyBuilder.Metadata.Name); + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyNullabilityChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypePropertyNullabilityChangedConvention(false); + var convention2 = new ComplexTypePropertyNullabilityChangedConvention(true); + var convention3 = new ComplexTypePropertyNullabilityChangedConvention(false); + conventions.ComplexTypePropertyNullabilityChangedConventions.Add(convention1); + conventions.ComplexTypePropertyNullabilityChangedConventions.Add(convention2); + conventions.ComplexTypePropertyNullabilityChangedConventions.Add(convention3); + + var model = new Model(conventions); + + var scope = useScope ? model.DelayConventions() : null; + + var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(typeof(string), "Name", ConfigurationSource.Convention); + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false }, convention1.Calls); + Assert.Equal(new bool?[] { false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + scope?.Dispose(); + + if (useScope) + { + Assert.Equal(new bool?[] { false, false, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, false, false }, convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, true, false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + } + + private class ComplexTypePropertyNullabilityChangedConvention : IComplexTypePropertyNullabilityChangedConvention + { + public readonly List Calls = new(); + private readonly bool _terminate; + + public ComplexTypePropertyNullabilityChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypePropertyNullabilityChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + IConventionContext context) + { + Calls.Add(propertyBuilder.Metadata.IsNullable); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyFieldChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypePropertyFieldChangedConvention(terminate: false); + var convention2 = new ComplexTypePropertyFieldChangedConvention(terminate: true); + var convention3 = new ComplexTypePropertyFieldChangedConvention(terminate: false); + conventions.ComplexTypePropertyFieldChangedConventions.Add(convention1); + conventions.ComplexTypePropertyFieldChangedConventions.Add(convention2); + conventions.ComplexTypePropertyFieldChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var propertyBuilder = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(OrderDetails.IntField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(OrderDetails.IntField), + ConfigurationSource.Convention); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(OrderDetails.IntField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(OrderDetails.IntField), + ConfigurationSource.Convention); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField((string)null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetFieldInfo( + null, + ConfigurationSource.Convention); + } + + Assert.Equal(new[] { null, nameof(Order.IntField) }, convention1.Calls); + Assert.Equal(new[] { null, nameof(Order.IntField) }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexTypePropertyFieldChangedConvention : IComplexTypePropertyFieldChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypePropertyFieldChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypePropertyFieldChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + FieldInfo newFieldInfo, + FieldInfo oldFieldInfo, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(oldFieldInfo?.Name); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypePropertyAnnotationChangedConvention(false); + var convention2 = new ComplexTypePropertyAnnotationChangedConvention(true); + var convention3 = new ComplexTypePropertyAnnotationChangedConvention(false); + conventions.ComplexTypePropertyAnnotationChangedConventions.Add(convention1); + conventions.ComplexTypePropertyAnnotationChangedConventions.Add(convention2); + conventions.ComplexTypePropertyAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + propertyBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + private class ComplexTypePropertyAnnotationChangedConvention : IComplexTypePropertyAnnotationChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypePropertyAnnotationChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypePropertyAnnotationChanged( + IConventionComplexTypePropertyBuilder propertyBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(annotation?.Value); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false)] + [InlineData(true)] + [ConditionalTheory] + public void OnComplexTypePropertyRemoved_calls_conventions_in_order(bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypePropertyRemovedConvention(terminate: false); + var convention2 = new ComplexTypePropertyRemovedConvention(terminate: true); + var convention3 = new ComplexTypePropertyRemovedConvention(terminate: false); + conventions.ComplexTypePropertyRemovedConventions.Add(convention1); + conventions.ComplexTypePropertyRemovedConventions.Add(convention2); + conventions.ComplexTypePropertyRemovedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var shadowPropertyName = "ShadowProperty"; + var property = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder.Metadata.AddProperty( + shadowPropertyName, typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + var result = property.DeclaringComplexType.RemoveProperty(property); + + if (useScope) + { + Assert.Same(property, result); + } + else + { + Assert.Null(result); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { property }, convention1.Calls); + Assert.Equal(new[] { property }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexTypePropertyRemovedConvention : IComplexTypePropertyRemovedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypePropertyRemovedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessPropertyRemoved( + IConventionComplexTypeBuilder complexTypeBuilder, + IConventionComplexTypeProperty property, + IConventionContext context) + { + Assert.NotNull(complexTypeBuilder.Metadata.Builder); + + Calls.Add(property); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyAdded_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyAddedConvention(terminate: false); + var convention2 = new ComplexPropertyAddedConvention(terminate: true); + var convention3 = new ComplexPropertyAddedConvention(terminate: false); + conventions.ComplexPropertyAddedConventions.Add(convention1); + conventions.ComplexPropertyAddedConventions.Add(convention2); + conventions.ComplexPropertyAddedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = entityBuilder.Metadata.AddComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = entityBuilder.ComplexProperty( + Order.OtherOrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = ((IMutableEntityType)entityBuilder.Metadata).AddComplexProperty( + Order.OtherOrderDetailsProperty, collection: false); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { Order.OrderDetailsProperty.Name, Order.OtherOrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name, Order.OtherOrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + + Assert.Empty(entityBuilder.Metadata.GetComplexProperties()); + } + + private class ComplexPropertyAddedConvention : IComplexPropertyAddedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyAddedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(propertyBuilder.Metadata.Name); + + if (_terminate) + { + ((IConventionEntityType)propertyBuilder.Metadata.DeclaringType).RemoveComplexProperty(propertyBuilder.Metadata.Name); + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyNullabilityChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyNullabilityChangedConvention(false); + var convention2 = new ComplexPropertyNullabilityChangedConvention(true); + var convention3 = new ComplexPropertyNullabilityChangedConvention(false); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention1); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention2); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention3); + + var model = new Model(conventions); + + var scope = useScope ? model.DelayConventions() : null; + + var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + if (useBuilder) + { + Assert.NotNull(propertyBuilder.IsRequired(true, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false }, convention1.Calls); + Assert.Equal(new bool?[] { false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + scope?.Dispose(); + + if (useScope) + { + Assert.Equal(new bool?[] { false, false, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, false, false }, convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, true, false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyNullabilityChangedConvention : IComplexPropertyNullabilityChangedConvention + { + public readonly List Calls = new(); + private readonly bool _terminate; + + public ComplexPropertyNullabilityChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Calls.Add(propertyBuilder.Metadata.IsNullable); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyFieldChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyFieldChangedConvention(terminate: false); + var convention2 = new ComplexPropertyFieldChangedConvention(terminate: true); + var convention3 = new ComplexPropertyFieldChangedConvention(terminate: false); + conventions.ComplexPropertyFieldChangedConventions.Add(convention1); + conventions.ComplexPropertyFieldChangedConventions.Add(convention2); + conventions.ComplexPropertyFieldChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var propertyBuilder = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(Order.OrderDetailsField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(Order.OrderDetailsField), + ConfigurationSource.Convention); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(Order.OrderDetailsField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(Order.OrderDetailsField), + ConfigurationSource.Convention); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField((string)null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetFieldInfo( + null, + ConfigurationSource.Convention); + } + + Assert.Equal(new[] { null, nameof(Order.OrderDetailsField) }, convention1.Calls); + Assert.Equal(new[] { null, nameof(Order.OrderDetailsField) }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyFieldChangedConvention : IComplexPropertyFieldChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyFieldChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo newFieldInfo, + FieldInfo oldFieldInfo, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(oldFieldInfo?.Name); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyAnnotationChangedConvention(false); + var convention2 = new ComplexPropertyAnnotationChangedConvention(true); + var convention3 = new ComplexPropertyAnnotationChangedConvention(false); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention1); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention2); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + propertyBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + private class ComplexPropertyAnnotationChangedConvention : IComplexPropertyAnnotationChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyAnnotationChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(annotation?.Value); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false)] + [InlineData(true)] + [ConditionalTheory] + public void OnComplexPropertyRemoved_calls_conventions_in_order(bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyRemovedConvention(terminate: false); + var convention2 = new ComplexPropertyRemovedConvention(terminate: true); + var convention3 = new ComplexPropertyRemovedConvention(terminate: false); + conventions.ComplexPropertyRemovedConventions.Add(convention1); + conventions.ComplexPropertyRemovedConventions.Add(convention2); + conventions.ComplexPropertyRemovedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var property = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .Metadata; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + var result = ((EntityType)property.DeclaringType).RemoveComplexProperty(property); + + if (useScope) + { + Assert.Same(property, result); + } + else + { + Assert.Null(result); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { property }, convention1.Calls); + Assert.Equal(new[] { property }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyRemovedConvention : IComplexPropertyRemovedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyRemovedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property, + IConventionContext context) + { + Assert.NotNull(typeBaseBuilder.Metadata.Builder); + + Calls.Add(property); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypeAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypeAnnotationChangedConvention(false); + var convention2 = new ComplexTypeAnnotationChangedConvention(true); + var convention3 = new ComplexTypeAnnotationChangedConvention(false); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention1); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention2); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var typeBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + typeBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + private class ComplexTypeAnnotationChangedConvention : IComplexTypeAnnotationChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypeAnnotationChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(annotation?.Value); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypeMemberIgnored_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypeMemberIgnoredConvention(terminate: false); + var convention2 = new ComplexTypeMemberIgnoredConvention(terminate: true); + var convention3 = new ComplexTypeMemberIgnoredConvention(terminate: false); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention1); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention2); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var complexBuilder = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + var shadowPropertyName = "ShadowProperty"; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Ignore(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + else + { + var result = complexBuilder.Metadata.AddIgnored(shadowPropertyName, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Ignore(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + else + { + var result = complexBuilder.Metadata.AddIgnored(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + if (useScope) + { + scope.Dispose(); + } + + Assert.Empty(entityBuilder.Metadata.GetIgnoredMembers()); + } + + private class ComplexTypeMemberIgnoredConvention : IComplexTypeMemberIgnoredConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypeMemberIgnoredConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypeMemberIgnored( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionContext context) + { + Assert.NotNull(complexTypeBuilder.Metadata.Builder); + + Calls.Add(name); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + private class Order { public static readonly PropertyInfo OrderIdProperty = typeof(Order).GetProperty(nameof(OrderId)); public static readonly PropertyInfo OrderDetailsProperty = typeof(Order).GetProperty(nameof(OrderDetails)); + public static readonly PropertyInfo OtherOrderDetailsProperty = typeof(Order).GetProperty(nameof(OtherOrderDetails)); public readonly int IntField = 1; + public readonly OrderDetails OrderDetailsField = default; public int OrderId { get; set; } @@ -3858,6 +5105,7 @@ private class SpecialOrder : Order private class OrderDetails { public static readonly PropertyInfo OrderProperty = typeof(OrderDetails).GetProperty(nameof(Order)); + public readonly int IntField = 1; public int Id { get; set; } public virtual Order Order { get; set; } diff --git a/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs index 3edfac832dd..7504634a73a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs @@ -89,7 +89,8 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_one_of_forei var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty, + CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + nameof(Post_On_FK_Property), nameof(Post_On_FK_Property.Blog_On_FK_PropertyId)), Assert.Throws( () => modelBuilder.Entity() .Property(e => e.Blog_On_FK_PropertyId)).Message @@ -102,7 +103,8 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_random_prope var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty, + CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + nameof(Post_On_Property), nameof(Post_On_Property.Id)), Assert.Throws( () => modelBuilder.Entity() .Property(e => e.Blog_On_PropertyId)).Message @@ -118,8 +120,9 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_principal_na .Property(e => e.Blog_On_PrincipalId); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty, - Assert.Throws(() => modelBuilder.FinalizeModel()).Message + CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + nameof(Blog_On_Principal), nameof(Blog_On_Principal.Posts)), + Assert.Throws(modelBuilder.FinalizeModel).Message ); } @@ -132,8 +135,9 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_principal_on .Property(e => e.Blog_On_PrincipalId); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty, - Assert.Throws(() => modelBuilder.FinalizeModel()).Message + CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + nameof(Blog_On_Principal_OneToOne), nameof(Blog_On_Principal_OneToOne.Post_On_Principal_OneToOne)), + Assert.Throws(modelBuilder.FinalizeModel).Message ); } diff --git a/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs index b4ce29e8681..8bc7a8f2f7c 100644 --- a/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs @@ -128,13 +128,13 @@ private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) { var context = new ConventionContext(entityTypeBuilder.Metadata.Model.ConventionDispatcher); - new NotMappedEntityTypeAttributeConvention(CreateDependencies()) + new NotMappedTypeAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); - new OwnedEntityTypeAttributeConvention(CreateDependencies()) + new OwnedAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); - new KeylessEntityTypeAttributeConvention(CreateDependencies()) + new KeylessAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); } diff --git a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs index 0a9d896bcdf..fe13c20eda1 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs @@ -383,10 +383,10 @@ public void Does_not_match_PK_name_properties() public void Does_not_match_PK_name_properties_if_subset_of_dependent_PK_and_contains_id() { var dependentTypeBuilder = DependentTypeWithCompositeKey.Builder; - var pkProperty1 = dependentTypeBuilder.Property( + var pkProperty1 = (Property)dependentTypeBuilder.Property( DependentEntityWithCompositeKey.IdProperty, ConfigurationSource.Convention) .Metadata; - var pkProperty2 = dependentTypeBuilder.Property( + var pkProperty2 = (Property)dependentTypeBuilder.Property( DependentEntityWithCompositeKey.NameProperty, ConfigurationSource.Convention) .IsRequired(true, ConfigurationSource.Convention) .Metadata; @@ -418,7 +418,7 @@ public void Does_not_match_PK_name_properties_if_subset_of_dependent_PK_and_cont public void Matches_PK_name_properties_if_subset_of_dependent_PK() { var dependentTypeBuilder = DependentType.Builder; - var pkProperty = dependentTypeBuilder.Property( + var pkProperty = (Property)dependentTypeBuilder.Property( DependentEntity.IDProperty, ConfigurationSource.Convention) .IsRequired(true, ConfigurationSource.Convention) .Metadata; diff --git a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs index 4a77a94f124..539f8532f9a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs @@ -207,7 +207,9 @@ public void RequiredAttribute_does_not_configure_skip_navigations() var navigationBuilder = postEntityTypeBuilder.HasSkipNavigation( new MemberIdentity(nameof(Post.Blogs)), blogEntityTypeBuilder.Metadata, + null, new MemberIdentity(nameof(Blog.Posts)), + null, ConfigurationSource.Convention, collections: true, onDependent: false); diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs index d2d2437a7ef..3666af6779c 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs @@ -56,9 +56,9 @@ public void Properties_with_private_setters_on_unmapped_base_types_are_discovere Assert.Single(model.GetEntityTypes()); - var entityType = model.FindEntityType(typeof(DerivedWithoutPrivates)); + var entityType = (IRuntimeEntityType)model.FindEntityType(typeof(DerivedWithoutPrivates)); - Assert.Equal(3, entityType.PropertyCount()); + Assert.Equal(3, entityType.PropertyCount); var idProperty = entityType.FindProperty(nameof(BaseWithPrivates.Id)); Assert.NotNull(idProperty.PropertyInfo); diff --git a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs index 88baa1d27e3..b5efa968452 100644 --- a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs +++ b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs @@ -21,7 +21,7 @@ public void Can_get_all_properties_and_navigations() Assert.Equal( new IReadOnlyPropertyBase[] { pk.Properties.Single(), fkProp, principalToDependent, dependentToPrincipal }, - ((IEntityType)entityType).GetPropertiesAndNavigations().ToArray()); + ((IRuntimeEntityType)entityType).GetSnapshottableMembers().ToArray()); } [ConditionalFact] diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index bc65fb04777..5dda807e7f4 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -85,13 +85,13 @@ public JsonValueReaderWriter GetJsonValueReaderWriter() public bool IsForeignKey() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingForeignKeys() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingForeignKeys() => throw new NotImplementedException(); public bool IsIndex() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingIndexes() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingIndexes() => throw new NotImplementedException(); public IReadOnlyKey FindContainingPrimaryKey() @@ -100,7 +100,7 @@ public IReadOnlyKey FindContainingPrimaryKey() public bool IsKey() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingKeys() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingKeys() => throw new NotImplementedException(); public PropertyAccessMode GetPropertyAccessMode() diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs index 96a0412884c..7b76a25bd9d 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs @@ -104,13 +104,13 @@ public JsonValueReaderWriter GetJsonValueReaderWriter() public bool IsForeignKey() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingForeignKeys() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingForeignKeys() => throw new NotImplementedException(); public bool IsIndex() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingIndexes() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingIndexes() => throw new NotImplementedException(); public IReadOnlyKey FindContainingPrimaryKey() @@ -119,7 +119,7 @@ public IReadOnlyKey FindContainingPrimaryKey() public bool IsKey() => throw new NotImplementedException(); - IEnumerable IReadOnlyProperty.GetContainingKeys() + IEnumerable IReadOnlyPrimitivePropertyBase.GetContainingKeys() => throw new NotImplementedException(); public PropertyAccessMode GetPropertyAccessMode() diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 371de95e294..8338d6d3b50 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -869,7 +869,7 @@ public void Removing_a_foreign_key_throws_if_referenced_from_skip_navigation() .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); var navigation = firstEntity.AddSkipNavigation( - nameof(Order.Products), null, secondEntity, true, false); + nameof(Order.Products), null, null, secondEntity, true, false); navigation.SetForeignKey(foreignKey); Assert.Equal( @@ -1249,7 +1249,7 @@ public void Can_add_and_remove_skip_navigation() var customerForeignKey = orderEntity .AddForeignKey(customerFkProperty, customerKey, customerEntity); var relatedNavigation = orderEntity.AddSkipNavigation( - nameof(Order.RelatedOrder), null, orderEntity, false, true); + nameof(Order.RelatedOrder), null, null, orderEntity, false, true); relatedNavigation.SetForeignKey(customerForeignKey); Assert.True(relatedNavigation.IsOnDependent); @@ -1261,7 +1261,7 @@ public void Can_add_and_remove_skip_navigation() .AddForeignKey(new[] { orderProductFkProperty }, orderKey, orderEntity); var productsNavigation = orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false); + nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); Assert.Equal(new[] { productsNavigation, relatedNavigation }, orderEntity.GetSkipNavigations()); @@ -1298,7 +1298,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ .AddForeignKey(new[] { orderProductFkProperty }, orderKey, orderEntity); var navigation = orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false); + nameof(Order.Products), null, null, productEntity, true, false); navigation.SetForeignKey(orderProductForeignKey); Assert.Equal( @@ -1306,7 +1306,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1332,7 +1332,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_navigation_ Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1355,7 +1355,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_property_th Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1378,7 +1378,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_service_pro Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1399,7 +1399,7 @@ public void Adding_CLR_skip_navigation_targetting_a_shadow_entity_type_throws() nameof(Order.Products), nameof(Order), "ICollection", "Dictionary"), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -1419,7 +1419,7 @@ public void Adding_CLR_skip_navigation_to_a_mismatched_entity_type_throws() CoreStrings.NoClrNavigation(nameof(Order.Products), nameof(Product)), Assert.Throws( () => productEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -1439,7 +1439,7 @@ public void Adding_CLR_collection_skip_navigation_with_mismatched_target_entity_ CoreStrings.NavigationCollectionWrongClrType(nameof(Order.Products), nameof(Order), "ICollection", nameof(Order)), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, orderEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, orderEntity, true, false)).Message); } [ConditionalFact] @@ -1459,7 +1459,7 @@ public void Adding_CLR_reference_skip_navigation_with_mismatched_target_entity_t CoreStrings.NavigationSingleWrongClrType(nameof(Order.Products), nameof(Order), "ICollection", nameof(Order)), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, orderEntity, false, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, orderEntity, false, false)).Message); } [ConditionalFact] @@ -1480,7 +1480,7 @@ public void Adding_skip_navigation_with_a_mismatched_memberinfo_throws() Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.RelatedOrderProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.RelatedOrderProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -2196,7 +2196,7 @@ public void Can_get_property_indexes() eb.Property("Mane_"); }); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)); + var entityType = (IRuntimeEntityType)modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)); Assert.Equal(0, entityType.FindProperty("Id_").GetIndex()); Assert.Equal(1, entityType.FindProperty("Mane_").GetIndex()); @@ -2206,7 +2206,7 @@ public void Can_get_property_indexes() Assert.Equal(1, entityType.FindProperty("Mane_").GetShadowIndex()); Assert.Equal(-1, entityType.FindProperty("Name").GetShadowIndex()); - Assert.Equal(2, entityType.ShadowPropertyCount()); + Assert.Equal(2, entityType.ShadowPropertyCount); } [ConditionalFact] @@ -2228,7 +2228,7 @@ public void Attempting_to_set_store_generated_value_for_non_generated_property_t public void Indexes_for_derived_types_are_calculated_correctly() { using var context = new Levels(); - var type = context.Model.FindEntityType(typeof(Level1)); + var type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level1)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2256,14 +2256,14 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level1Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level1Reference").GetStoreGeneratedIndex()); - Assert.Equal(4, type.PropertyCount()); - Assert.Equal(2, type.NavigationCount()); - Assert.Equal(2, type.ShadowPropertyCount()); - Assert.Equal(4, type.OriginalValueCount()); - Assert.Equal(4, type.RelationshipPropertyCount()); - Assert.Equal(2, type.StoreGeneratedCount()); + Assert.Equal(4, type.PropertyCount); + Assert.Equal(2, type.NavigationCount); + Assert.Equal(2, type.ShadowPropertyCount); + Assert.Equal(4, type.OriginalValueCount); + Assert.Equal(4, type.RelationshipPropertyCount); + Assert.Equal(2, type.StoreGeneratedCount); - type = context.Model.FindEntityType(typeof(Level2)); + type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level2)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2307,14 +2307,14 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level2Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level2Reference").GetStoreGeneratedIndex()); - Assert.Equal(6, type.PropertyCount()); - Assert.Equal(4, type.NavigationCount()); - Assert.Equal(3, type.ShadowPropertyCount()); - Assert.Equal(6, type.OriginalValueCount()); - Assert.Equal(7, type.RelationshipPropertyCount()); - Assert.Equal(3, type.StoreGeneratedCount()); + Assert.Equal(6, type.PropertyCount); + Assert.Equal(4, type.NavigationCount); + Assert.Equal(3, type.ShadowPropertyCount); + Assert.Equal(6, type.OriginalValueCount); + Assert.Equal(7, type.RelationshipPropertyCount); + Assert.Equal(3, type.StoreGeneratedCount); - type = context.Model.FindEntityType(typeof(Level3)); + type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level3)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2374,12 +2374,12 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level3Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level3Reference").GetStoreGeneratedIndex()); - Assert.Equal(8, type.PropertyCount()); - Assert.Equal(6, type.NavigationCount()); - Assert.Equal(4, type.ShadowPropertyCount()); - Assert.Equal(8, type.OriginalValueCount()); - Assert.Equal(10, type.RelationshipPropertyCount()); - Assert.Equal(4, type.StoreGeneratedCount()); + Assert.Equal(8, type.PropertyCount); + Assert.Equal(6, type.NavigationCount); + Assert.Equal(4, type.ShadowPropertyCount); + Assert.Equal(8, type.OriginalValueCount); + Assert.Equal(10, type.RelationshipPropertyCount); + Assert.Equal(4, type.StoreGeneratedCount); } private class Levels : DbContext @@ -2405,8 +2405,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() using var context = new SideBySide(); var model = context.Model; - var parent = model.FindEntityType(typeof(Parent1Entity)); - var indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + var parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent1Entity)); + var indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent1Entity.Id)]); @@ -2419,8 +2419,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() Assert.Equal((1, 1, 1, 1, 1), indexes["Id"]); Assert.Equal((2, -1, 2, -1, -1), indexes[nameof(ChildEntity.Name)]); - parent = model.FindEntityType(typeof(Parent2Entity)); - indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent2Entity)); + indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent2Entity.Id)]); @@ -2433,8 +2433,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() Assert.Equal((1, 1, 1, 1, 1), indexes["Id"]); Assert.Equal((2, -1, 2, -1, -1), indexes[nameof(ChildEntity.Name)]); - parent = model.FindEntityType(typeof(Parent3Entity)); - indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent3Entity)); + indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent3Entity.Id)]); @@ -2814,7 +2814,7 @@ public void All_properties_have_original_value_indexes_when_using_snapshot_chang var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2823,7 +2823,7 @@ public void All_properties_have_original_value_indexes_when_using_snapshot_chang Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2832,7 +2832,7 @@ public void All_relationship_properties_have_relationship_indexes_when_using_sna var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2843,7 +2843,7 @@ public void All_relationship_properties_have_relationship_indexes_when_using_sna Assert.Equal(2, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(3, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(4, entityType.RelationshipPropertyCount()); + Assert.Equal(4, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2852,7 +2852,7 @@ public void All_properties_have_original_value_indexes_when_using_changed_only_t var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2861,7 +2861,7 @@ public void All_properties_have_original_value_indexes_when_using_changed_only_t Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2870,7 +2870,7 @@ public void Collections_dont_have_relationship_indexes_when_using_changed_only_c var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2881,7 +2881,7 @@ public void Collections_dont_have_relationship_indexes_when_using_changed_only_c Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2890,7 +2890,7 @@ public void Only_concurrency_index_and_key_properties_have_original_value_indexe var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2899,7 +2899,7 @@ public void Only_concurrency_index_and_key_properties_have_original_value_indexe Assert.Equal(2, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(3, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(4, entityType.OriginalValueCount()); + Assert.Equal(4, entityType.OriginalValueCount); } [ConditionalFact] @@ -2908,7 +2908,7 @@ public void Collections_dont_have_relationship_indexes_when_using_full_notificat var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2919,7 +2919,7 @@ public void Collections_dont_have_relationship_indexes_when_using_full_notificat Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2928,7 +2928,7 @@ public void All_properties_have_original_value_indexes_when_full_notifications_w var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2937,7 +2937,7 @@ public void All_properties_have_original_value_indexes_when_full_notifications_w Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2946,7 +2946,7 @@ public void Collections_dont_have_relationship_indexes_when_full_notifications_w var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2957,7 +2957,7 @@ public void Collections_dont_have_relationship_indexes_when_full_notifications_w Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] diff --git a/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs b/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs index 41348cdb38d..9b85ae4c14e 100644 --- a/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs @@ -16,14 +16,14 @@ public void Throws_when_model_is_readonly() var joinEntityBuilder = model.AddEntityType(typeof(OrderProduct)); var orderIdProperty = joinEntityBuilder.AddProperty(OrderProduct.OrderIdProperty); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); model.FinalizeModel(); Assert.Equal( CoreStrings.ModelReadOnly, Assert.Throws( - () => firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false)).Message); + () => firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false)).Message); Assert.Equal( CoreStrings.ModelReadOnly, @@ -67,7 +67,7 @@ public void Gets_expected_default_values() var firstFk = joinEntityBuilder .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); navigation.SetForeignKey(firstFk); Assert.True(navigation.IsCollection); @@ -103,7 +103,7 @@ public void Can_set_foreign_key() var firstFk = joinEntityBuilder .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); Assert.Null(navigation.ForeignKey); Assert.Null(navigation.GetForeignKeyConfigurationSource()); @@ -131,7 +131,7 @@ public void Setting_foreign_key_to_skip_navigation_with_wrong_dependent_throws() var orderProductFkProperty = orderProductEntity.AddProperty(nameof(OrderProduct.OrderId), typeof(int)); var orderProductForeignKey = orderProductEntity.AddForeignKey(orderProductFkProperty, orderKey, orderEntity); - var navigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, true); + var navigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, true); Assert.Equal( CoreStrings.SkipNavigationForeignKeyWrongDependentType( @@ -152,7 +152,7 @@ public void Setting_foreign_key_to_skip_navigation_with_wrong_principal_throws() var orderProductForeignKey = orderProductEntity.AddForeignKey(orderProductFkProperty, orderKey, orderEntity); var navigation = orderProductEntity.AddSkipNavigation( - nameof(OrderProduct.Order), null, orderEntity, false, false); + nameof(OrderProduct.Order), null, null, orderEntity, false, false); Assert.Equal( CoreStrings.SkipNavigationForeignKeyWrongPrincipalType( @@ -178,9 +178,9 @@ public void Setting_foreign_key_with_wrong_inverse_throws() var productOrderForeignKey = productEntity .AddForeignKey(new[] { productFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); productsNavigation.SetInverse(ordersNavigation); @@ -211,10 +211,10 @@ public void Can_set_inverse() var productOrderForeignKey = orderProductEntity .AddForeignKey(new[] { productOrderFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); productsNavigation.SetInverse(ordersNavigation); @@ -257,10 +257,10 @@ public void Setting_inverse_targetting_wrong_type_throws() var productOrderForeignKey = orderProductEntity .AddForeignKey(new[] { productOrderFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = orderProductEntity.AddSkipNavigation(nameof(OrderProduct.Product), null, productEntity, false, true); + var ordersNavigation = orderProductEntity.AddSkipNavigation(nameof(OrderProduct.Product), null, null, productEntity, false, true); ordersNavigation.SetForeignKey(productOrderForeignKey); Assert.Equal( @@ -287,10 +287,10 @@ public void Setting_inverse_with_wrong_join_type_throws() var productOrderForeignKey = productEntity .AddForeignKey(new[] { productFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); Assert.Equal( diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs new file mode 100644 index 00000000000..5df13300db9 --- /dev/null +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -0,0 +1,1452 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Dynamic; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public abstract partial class ModelBuilderTest +{ + public abstract class ComplexTypeTestBase : ModelBuilderTestBase + { + [ConditionalFact] + public virtual void Can_set_complex_property_annotation() + { + var modelBuilder = CreateModelBuilder(); + + var complexPropertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .HasTypeAnnotation("foo", "bar") + .HasPropertyAnnotation("foo2", "bar2"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + + Assert.Equal("bar", complexProperty.ComplexType["foo"]); + Assert.Equal("bar2", complexProperty["foo2"]); + Assert.Equal(typeof(Customer).Name, complexProperty.Name); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(c => c.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_when_no_clr_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(Customer.NameProperty.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); + + modelBuilder.Ignore(); + var propertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(c => c.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.True(complexType.FindProperty("Down").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.True(complexType.FindProperty("Strange").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + Assert.True(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Ignore(e => e.Up); + b.Ignore(e => e.Down); + b.Ignore("Charm"); + b.Ignore("Strange"); + b.Ignore("Top"); + b.Ignore("Bottom"); + b.Ignore("Shadow"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Contains(nameof(Quarks.Id), complexType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(Quarks.Up), complexType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(Quarks.Down), complexType.GetProperties().Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void Properties_can_be_ignored_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.IgnoreAny()); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Customer); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty(nameof(Customer.AlternateKey))); + } + + [ConditionalFact] + public virtual void Can_ignore_shadow_properties_when_they_have_been_added_explicitly() + { + var modelBuilder = CreateModelBuilder(); + + var complexPropertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer); + complexPropertyBuilder.Property("Shadow"); + complexPropertyBuilder.Ignore("Shadow"); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Can_add_shadow_properties_when_they_have_been_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer, + b => + { + b.Ignore("Shadow"); + b.Property("Shadow"); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.NotNull(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsRequired(); + b.Property(e => e.Down).IsRequired(); + b.Property("Charm").IsRequired(); + b.Property("Strange").IsRequired(); + b.Property("Top").IsRequired(); + b.Property("Bottom").IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.False(complexType.FindProperty("Down").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.False(complexType.FindProperty("Strange").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + Assert.False(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Down).IsRequired(false); + b.Property("Strange").IsRequired(false); + b.Property("Bottom").IsRequired(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty("Down").IsNullable); + Assert.True(complexType.FindProperty("Strange").IsNullable); + Assert.True(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Non_nullable_properties_cannot_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.CannotBeNullable("Up", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property(e => e.Up).IsRequired(false)).Message); + + Assert.Equal( + CoreStrings.CannotBeNullable("Charm", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property("Charm").IsRequired(false)).Message); + + Assert.Equal( + CoreStrings.CannotBeNullable("Top", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property("Top").IsRequired(false)).Message); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Up"); + b.Property("Gluon"); + b.Property("Down"); + b.Property("Photon"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsShadowProperty()); + Assert.False(complexType.FindProperty("Down").IsShadowProperty()); + Assert.True(complexType.FindProperty("Gluon").IsShadowProperty()); + Assert.True(complexType.FindProperty("Photon").IsShadowProperty()); + + Assert.Equal(-1, complexType.FindProperty("Up").GetShadowIndex()); + Assert.Equal(-1, complexType.FindProperty("Down").GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Gluon").GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Photon").GetShadowIndex()); + Assert.NotEqual(complexType.FindProperty("Gluon").GetShadowIndex(), complexType.FindProperty("Photon").GetShadowIndex()); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_concurrency_tokens() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsConcurrencyToken(); + b.Property(e => e.Down).IsConcurrencyToken(false); + b.Property("Charm").IsConcurrencyToken(); + b.Property("Strange").IsConcurrencyToken(false); + b.Property("Top").IsConcurrencyToken(); + b.Property("Bottom").IsConcurrencyToken(false); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); + Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Strange").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Top").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Bottom").IsConcurrencyToken); + + Assert.Equal(-1, complexType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); + Assert.Equal(2, complexType.FindProperty("Up").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Down").GetOriginalValueIndex()); + Assert.Equal(0, complexType.FindProperty("Charm").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetOriginalValueIndex()); + Assert.Equal(1, complexType.FindProperty("Top").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Bottom").GetOriginalValueIndex()); + + Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, complexType.GetChangeTrackingStrategy()); + } + + [ConditionalFact] + public virtual void Properties_can_have_access_mode_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); + b.Property("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); + b.Property("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(PropertyAccessMode.PreferField, complexType.FindProperty("Up").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, complexType.FindProperty("Down").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, complexType.FindProperty("Charm").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, complexType.FindProperty("Strange").GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction); + b.UseDefaultPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + b.Property(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + b.Property(e => e.Down).HasField("_forDown"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties)); + Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); + + var customerType = entityType.FindComplexProperty(nameof(ComplexProperties.Customer)).ComplexType; + Assert.Equal(PropertyAccessMode.Field, customerType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, customerType.FindProperty("Id").GetPropertyAccessMode()); + + var quarksProperty = entityType.FindComplexProperty(nameof(ComplexProperties.Quarks)); + var quarksType = quarksProperty.ComplexType; + Assert.Equal(PropertyAccessMode.PreferFieldDuringConstruction, quarksProperty.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Properties_can_have_provider_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(); + b.Property("Charm").HasConversion>(); + b.Property("Strange").HasConversion( + new CustomValueComparer(), new CustomValueComparer()); + b.Property("Strange").HasConversion(null); + b.Property("Top").HasConversion(new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.IsType>(up.GetValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.Same(typeof(byte[]), down.GetProviderClrType()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.Same(typeof(long), charm.GetProviderClrType()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.Null(strange.GetProviderClrType()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + + var top = complexType.FindProperty("Top"); + Assert.Same(typeof(string), top.GetProviderClrType()); + Assert.IsType>(top.GetValueComparer()); + Assert.IsType>(top.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void Properties_can_have_provider_type_set_for_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Down").GetProviderClrType()); + Assert.Null(complexType.FindProperty("Charm").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Strange").GetProviderClrType()); + } + + [ConditionalFact] + public virtual void Properties_can_have_non_generic_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + ValueConverter stringConverter = new StringToBytesConverter(Encoding.UTF8); + ValueConverter intConverter = new CastingConverter(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(stringConverter); + b.Property("Charm").HasConversion(intConverter, null, new CustomValueComparer()); + b.Property("Strange").HasConversion(stringConverter); + b.Property("Strange").HasConversion(null); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetValueConverter()); + + var down = complexType.FindProperty("Down"); + Assert.Same(stringConverter, down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.Same(intConverter, charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + Assert.Null(complexType.FindProperty("Strange").GetValueConverter()); + } + + [ConditionalFact] + public virtual void Properties_can_have_custom_type_value_converter_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasConversion>(); + b.Property(e => e.Down) + .HasConversion, CustomValueComparer>(); + b.Property("Charm").HasConversion, CustomValueComparer>(); + b.Property("Strange").HasConversion>(); + b.Property("Strange").HasConversion(null, null); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Equal(typeof(int), up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.Null(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + private class UTF8StringToBytesConverter : StringToBytesConverter + { + public UTF8StringToBytesConverter() + : base(Encoding.UTF8) + { + } + } + + private class CustomValueComparer : ValueComparer + { + public CustomValueComparer() + : base(false) + { + } + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_set_inline() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(v => int.Parse(v), v => v.ToString()); + b.Property("Charm").HasConversion(v => (long)v, v => (int)v, new CustomValueComparer()); + b.Property("Strange").HasConversion( + v => (double)v, v => (float)v, new CustomValueComparer(), new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType>(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.IsType>(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion( + new ValueConverter(v => int.Parse(v), v => v.ToString())); + b.Property("Charm").HasConversion( + new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); + b.Property("Strange").HasConversion( + new ValueConverter(v => v, v => (float)v), new CustomValueComparer(), + new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType>(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.IsType>(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void IEnumerable_properties_with_value_converter_set_are_not_discovered_as_complex_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.DynamicProperty, + b => + { + b.Property(e => e.ExpandoObject).HasConversion( + v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)); + + var comparer = new ValueComparer( + (v1, v2) => v1.SequenceEqual(v2), + v => v.GetHashCode()); + + b.Property(e => e.ExpandoObject).Metadata.SetValueComparer(comparer); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.NotNull(complexType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueConverter()); + Assert.NotNull(complexType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueComparer()); + } + + private static ExpandoObject DeserializeExpandoObject(string value) + { + dynamic obj = new ExpandoObject(); + obj.Value = value; + + return obj; + } + + private class ExpandoObjectConverter : ValueConverter + { + public ExpandoObjectConverter() + : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) + { + } + } + + private class ExpandoObjectComparer : ValueComparer + { + public ExpandoObjectComparer() + : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + { + } + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_configured_by_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties(typeof(IWrapped<>)).AreUnicode(false); + c.Properties().HaveMaxLength(20); + c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.WrappedStringEntity); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var wrappedProperty = complexType.FindProperty(nameof(WrappedStringEntity.WrappedString)); + Assert.False(wrappedProperty.IsUnicode()); + Assert.Equal(20, wrappedProperty.GetMaxLength()); + Assert.IsType(wrappedProperty.GetValueConverter()); + Assert.IsType>(wrappedProperty.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Value_converter_configured_on_non_nullable_type_is_applied() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveConversion, CustomValueComparer>(); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Wierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var id = complexType.FindProperty("Id"); + Assert.IsType>(id.GetValueConverter()); + Assert.IsType>(id.GetValueComparer()); + + var wierd = complexType.FindProperty("Wierd"); + Assert.IsType>(wierd.GetValueConverter()); + Assert.IsType>(wierd.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveConversion, CustomValueComparer>(); + c.Properties() + .HaveConversion, CustomValueComparer, CustomValueComparer>(); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Wierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var id = complexType.FindProperty("Id"); + Assert.IsType>(id.GetValueConverter()); + Assert.IsType>(id.GetValueComparer()); + Assert.IsType>(id.GetProviderValueComparer()); + + var wierd = complexType.FindProperty("Wierd"); + Assert.IsType>(wierd.GetValueConverter()); + Assert.IsType>(wierd.GetValueComparer()); + Assert.IsType>(wierd.GetProviderValueComparer()); + } + + private class WrappedStringToStringConverter : ValueConverter + { + public WrappedStringToStringConverter() + : base(v => v.Value, v => new WrappedString { Value = v }) + { + } + } + + [ConditionalFact] + public virtual void Value_converter_type_is_checked() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.ConverterPropertyMismatch("string", "ComplexProperties.Quarks#Quarks", "Up", "int"), + Assert.Throws( + () => b.Property(e => e.Up).HasConversion( + new StringToBytesConverter(Encoding.UTF8))).Message); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty("Up").GetValueConverter()); + } + + [ConditionalFact] + public virtual void Properties_can_have_field_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Up").HasField("_forUp"); + b.Property(e => e.Down).HasField("_forDown"); + b.Property("_forWierd").HasField("_forWierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal("_forUp", complexType.FindProperty("Up").GetFieldName()); + Assert.Equal("_forDown", complexType.FindProperty("Down").GetFieldName()); + Assert.Equal("_forWierd", complexType.FindProperty("_forWierd").GetFieldName()); + } + + [ConditionalFact] + public virtual void HasField_throws_if_field_is_not_found() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.MissingBackingField("_notFound", nameof(Quarks.Down), "ComplexProperties.Quarks#Quarks"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_notFound")).Message); + }); + } + + [ConditionalFact] + public virtual void HasField_throws_if_field_is_wrong_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.BadBackingFieldType("_forUp", "int", nameof(Quarks), nameof(Quarks.Down), "string"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_forUp")).Message); + }); + } + + [ConditionalFact] + public virtual void Properties_can_be_set_to_generate_values_on_Add() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).ValueGeneratedOnAddOrUpdate(); + b.Property(e => e.Down).ValueGeneratedNever(); + b.Property("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; + b.Property("Strange").ValueGeneratedNever(); + b.Property("Top").ValueGeneratedOnAddOrUpdate(); + b.Property("Bottom").ValueGeneratedOnUpdate(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(ValueGenerated.Never, complexType.FindProperty(Customer.IdProperty.Name).ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdateSometimes, complexType.FindProperty("Charm").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Strange").ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Top").ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdate, complexType.FindProperty("Bottom").ValueGenerated); + } + + [ConditionalFact] + public virtual void Properties_can_set_row_version() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsRowVersion(); + b.Property(e => e.Down).ValueGeneratedNever(); + b.Property("Charm").IsRowVersion(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Charm").ValueGenerated); + + Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); + } + + [ConditionalFact] + public virtual void Can_set_max_length_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasMaxLength(0); + b.Property(e => e.Down).HasMaxLength(100); + b.Property("Charm").HasMaxLength(0); + b.Property("Strange").HasMaxLength(-1); + b.Property("Top").HasMaxLength(0); + b.Property("Bottom").HasMaxLength(100); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_max_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveMaxLength(0); + c.Properties().HaveMaxLength(100); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_sentinel_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasSentinel(1); + b.Property(e => e.Down).HasSentinel("100"); + b.Property("Charm").HasSentinel(-1); + b.Property("Strange").HasSentinel("-1"); + b.Property("Top").HasSentinel(77); + b.Property("Bottom").HasSentinel("100"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + Assert.Equal(1, complexType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Charm")!.Sentinel); + Assert.Equal("-1", complexType.FindProperty("Strange")!.Sentinel); + Assert.Equal(77, complexType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_sentinel_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveSentinel(-1); + c.Properties().HaveSentinel("100"); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(-1, complexType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Charm")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Strange")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_unbounded_max_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveMaxLength(0); + c.Properties().HaveMaxLength(-1); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_precision_and_scale_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasPrecision(1, 0); + b.Property(e => e.Down).HasPrecision(100, 10); + b.Property("Charm").HasPrecision(1, 0); + b.Property("Strange").HasPrecision(100, 10); + b.Property("Top").HasPrecision(1, 0); + b.Property("Bottom").HasPrecision(100, 10); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetScale()); + Assert.Equal(1, complexType.FindProperty("Up").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Up").GetScale()); + Assert.Equal(100, complexType.FindProperty("Down").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Down").GetScale()); + Assert.Equal(1, complexType.FindProperty("Charm").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Charm").GetScale()); + Assert.Equal(100, complexType.FindProperty("Strange").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Strange").GetScale()); + Assert.Equal(1, complexType.FindProperty("Top").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Top").GetScale()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); + } + + [ConditionalFact] + public virtual void Can_set_precision_and_scale_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HavePrecision(1, 0); + c.Properties().HavePrecision(100, 10); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(1, complexType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetScale()); + Assert.Equal(1, complexType.FindProperty("Up").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Up").GetScale()); + Assert.Equal(100, complexType.FindProperty("Down").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Down").GetScale()); + Assert.Equal(1, complexType.FindProperty("Charm").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Charm").GetScale()); + Assert.Equal(100, complexType.FindProperty("Strange").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Strange").GetScale()); + Assert.Equal(1, complexType.FindProperty("Top").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Top").GetScale()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); + } + + [ConditionalFact] + public virtual void Can_set_custom_value_generator_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasValueGenerator(); + b.Property(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); + b.Property("Charm").HasValueGenerator((_, __) => new CustomValueGenerator()); + b.Property("Strange").HasValueGenerator(); + b.Property("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); + b.Property("Bottom").HasValueGeneratorFactory(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); + Assert.IsType(complexType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); + } + + private class CustomValueGenerator : ValueGenerator + { + public override int Next(EntityEntry entry) + => throw new NotImplementedException(); + + public override bool GeneratesTemporaryValues + => false; + } + + private class CustomValueGeneratorFactory : ValueGeneratorFactory + { + public override ValueGenerator Create(IProperty property, IEntityType entityType) + => new CustomValueGenerator(); + } + + [ConditionalFact] + public virtual void Throws_for_bad_value_generator_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), + Assert.Throws(() => b.Property(e => e.Down).HasValueGenerator(typeof(Random))).Message); + }); + } + + [ConditionalFact] + public virtual void Throws_for_value_generator_that_cannot_be_constructed() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasValueGenerator(); + b.Property(e => e.Down).HasValueGenerator(); + }); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), + Assert.Throws( + () => complexType.FindProperty("Up").GetValueGeneratorFactory()(null, null)).Message); + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), + Assert.Throws( + () => complexType.FindProperty("Down").GetValueGeneratorFactory()(null, null)).Message); + } + + private class BadCustomValueGenerator1 : CustomValueGenerator + { + public BadCustomValueGenerator1(string foo) + { + } + } + + private abstract class BadCustomValueGenerator2 : CustomValueGenerator + { + } + + protected class StringCollectionEntity + { + public ICollection Property { get; set; } + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsUnicode(); + b.Property(e => e.Down).IsUnicode(false); + b.Property("Charm").IsUnicode(); + b.Property("Strange").IsUnicode(false); + b.Property("Top").IsUnicode(); + b.Property("Bottom").IsUnicode(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + Assert.True(complexType.FindProperty("Up").IsUnicode()); + Assert.False(complexType.FindProperty("Down").IsUnicode()); + Assert.True(complexType.FindProperty("Charm").IsUnicode()); + Assert.False(complexType.FindProperty("Strange").IsUnicode()); + Assert.True(complexType.FindProperty("Top").IsUnicode()); + Assert.False(complexType.FindProperty("Bottom").IsUnicode()); + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreUnicode(); + c.Properties().AreUnicode(false); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + Assert.True(complexType.FindProperty("Up").IsUnicode()); + Assert.False(complexType.FindProperty("Down").IsUnicode()); + Assert.True(complexType.FindProperty("Charm").IsUnicode()); + Assert.False(complexType.FindProperty("Strange").IsUnicode()); + Assert.True(complexType.FindProperty("Top").IsUnicode()); + Assert.False(complexType.FindProperty("Bottom").IsUnicode()); + } + + [ConditionalFact] + public virtual void PropertyBuilder_methods_can_be_chained() + => CreateModelBuilder() + .Entity() + .ComplexProperty(e => e.Quarks) + .Property(e => e.Up) + .IsRequired() + .HasAnnotation("A", "V") + .IsConcurrencyToken() + .ValueGeneratedNever() + .ValueGeneratedOnAdd() + .ValueGeneratedOnAddOrUpdate() + .ValueGeneratedOnUpdate() + .IsUnicode() + .HasMaxLength(100) + .HasSentinel(null) + .HasPrecision(10, 1) + .HasValueGenerator() + .HasValueGenerator(typeof(CustomValueGenerator)) + .HasValueGeneratorFactory() + .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) + .HasValueGenerator((_, __) => null) + .IsRequired(); + + [ConditionalFact] + public virtual void Can_call_Property_on_a_field() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.EntityWithFields).Property(e => e.Id); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var properties = complexType.GetProperties(); + var property = Assert.Single(properties); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_ignore_a_field() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.EntityWithFields, b => { + b.Property(e => e.Id); + b.Ignore(e => e.CompanyId); + }); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = Assert.Single(complexProperty.ComplexType.GetProperties()); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + } + } +} diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 136f94c24d5..721f4432af8 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -3,6 +3,8 @@ #nullable enable +using System.Numerics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable InconsistentNaming @@ -57,12 +59,36 @@ public virtual void Changing_propertyInfo_updates_Property() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().Property(e => ((IReplacable)e).Property); + modelBuilder.Entity().Property(e => ((IReplaceable)e).Property); modelBuilder.FinalizeModel(); var property = modelBuilder.Model.FindEntityType(typeof(DoubleProperty))!.GetProperty("Property"); - Assert.EndsWith(typeof(IReplacable).Name + "." + nameof(IReplacable.Property), property.GetIdentifyingMemberInfo()!.Name); + Assert.EndsWith(typeof(IReplaceable).Name + "." + nameof(IReplaceable.Property), property.GetIdentifyingMemberInfo()!.Name); + } + } + + public class GenericComplexType : ComplexTypeTestBase + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new GenericTestModelBuilder(testHelpers, configure); + + [ConditionalFact] + public virtual void Changing_propertyInfo_updates_Property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity().ComplexProperty(e => e.DoubleProperty).Property(e => ((IReplaceable)e).Property); + + modelBuilder.FinalizeModel(); + + var property = modelBuilder.Model.FindEntityType(typeof(ComplexProperties))!.FindComplexProperty(nameof(DoubleProperty))! + .ComplexType.FindProperty("Property")!; + Assert.EndsWith(typeof(IReplaceable).Name + "." + nameof(IReplaceable.Property), property.GetIdentifyingMemberInfo()!.Name); } } @@ -207,6 +233,29 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyExpression)); + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + + public override TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyExpression))); + + return this; + } + + public override TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + + return this; + } + public override TestNavigationBuilder Navigation( Expression> navigationExpression) where TNavigation : class @@ -404,6 +453,86 @@ public EntityTypeBuilder Instance => EntityTypeBuilder; } + protected class GenericTestComplexPropertyBuilder : + TestComplexPropertyBuilder, IInfrastructure> + { + public GenericTestComplexPropertyBuilder(ComplexPropertyBuilder complexPropertyBuilder) + { + PropertyBuilder = complexPropertyBuilder; + } + + protected ComplexPropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexPropertyBuilder Wrap(ComplexPropertyBuilder complexPropertyBuilder) + => new GenericTestComplexPropertyBuilder(complexPropertyBuilder); + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new GenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); + + public override TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder Property(Expression> propertyExpression) + where TProperty : default + => Wrap(PropertyBuilder.Property(propertyExpression)); + + public override TestComplexTypePropertyBuilder Property(string propertyName) + => Wrap(PropertyBuilder.Property(propertyName)); + + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => Wrap(PropertyBuilder.ComplexProperty(propertyExpression)); + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyExpression))); + + return this; + } + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } + + public override TestComplexPropertyBuilder Ignore(Expression> propertyExpression) + => Wrap(PropertyBuilder.Ignore(propertyExpression)); + + public override TestComplexPropertyBuilder Ignore(string propertyName) + => Wrap(PropertyBuilder.Ignore(propertyName)); + + public override TestComplexPropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => Wrap(PropertyBuilder.HasChangeTrackingStrategy(changeTrackingStrategy)); + + public override TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + + public ComplexPropertyBuilder Instance + => PropertyBuilder; + } + protected class GenericTestDiscriminatorBuilder : TestDiscriminatorBuilder { public GenericTestDiscriminatorBuilder(DiscriminatorBuilder discriminatorBuilder) @@ -600,6 +729,160 @@ PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; } + protected class GenericTestComplexTypePropertyBuilder : + TestComplexTypePropertyBuilder, IInfrastructure> + { + public GenericTestComplexTypePropertyBuilder(ComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + protected ComplexTypePropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexTypeProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new GenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexTypePropertyBuilder HasMaxLength(int maxLength) + => Wrap(PropertyBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePropertyBuilder HasSentinel(object? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision) + => Wrap(PropertyBuilder.HasPrecision(precision)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => Wrap(PropertyBuilder.HasPrecision(precision, scale)); + + public override TestComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexTypePropertyBuilder IsRowVersion() + => Wrap(PropertyBuilder.IsRowVersion()); + + public override TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PropertyBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePropertyBuilder ValueGeneratedNever() + => Wrap(PropertyBuilder.ValueGeneratedNever()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAdd() + => Wrap(PropertyBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePropertyBuilder HasValueGenerator( + Func factory) + => Wrap(PropertyBuilder.HasValueGenerator(factory)); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer, + providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexTypePropertyBuilder IInfrastructure>.Instance + => PropertyBuilder; + } + protected class GenericTestKeyBuilder : TestKeyBuilder, IInfrastructure> { public GenericTestKeyBuilder(KeyBuilder keyBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 5fa2ad3385d..408554e10c2 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -15,6 +15,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new NonGenericTestModelBuilder(testHelpers, configure); } + public class NonGenericComplexType : ComplexTypeTestBase + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new NonGenericTestModelBuilder(testHelpers, configure); + } + public class NonGenericInheritance : InheritanceTestBase { protected override TestModelBuilder CreateTestModelBuilder( @@ -252,6 +260,35 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + + public override TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + + return this; + } + + public override TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + + return this; + } + public override TestNavigationBuilder Navigation(Expression> navigationExpression) where TNavigation : class => new NonGenericTestNavigationBuilder( @@ -484,6 +521,92 @@ public EntityTypeBuilder Instance => EntityTypeBuilder; } + protected class NonGenericTestComplexPropertyBuilder : + TestComplexPropertyBuilder, IInfrastructure + { + public NonGenericTestComplexPropertyBuilder(ComplexPropertyBuilder complexPropertyBuilder) + { + PropertyBuilder = complexPropertyBuilder; + } + + protected ComplexPropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual NonGenericTestComplexPropertyBuilder Wrap(ComplexPropertyBuilder entityTypeBuilder) + => new(entityTypeBuilder); + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new NonGenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); + + public override TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder Property(Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexTypePropertyBuilder Property(string propertyName) + => Wrap(PropertyBuilder.Property(propertyName)); + + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + + return this; + } + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } + + public override TestComplexPropertyBuilder Ignore(Expression> propertyExpression) + => Wrap(PropertyBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); + + public override TestComplexPropertyBuilder Ignore(string propertyName) + => Wrap(PropertyBuilder.Ignore(propertyName)); + + public override TestComplexPropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => Wrap(PropertyBuilder.HasChangeTrackingStrategy(changeTrackingStrategy)); + + public override TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + + public ComplexPropertyBuilder Instance + => PropertyBuilder; + } + protected class NonGenericTestDiscriminatorBuilder : TestDiscriminatorBuilder { public NonGenericTestDiscriminatorBuilder(DiscriminatorBuilder discriminatorBuilder) @@ -677,6 +800,157 @@ PropertyBuilder IInfrastructure.Instance => PropertyBuilder; } + protected class NonGenericTestComplexTypePropertyBuilder : + TestComplexTypePropertyBuilder, IInfrastructure + { + public NonGenericTestComplexTypePropertyBuilder(ComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + private ComplexTypePropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexTypeProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new NonGenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexTypePropertyBuilder HasMaxLength(int maxLength) + => Wrap(PropertyBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePropertyBuilder HasSentinel(object? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision) + => Wrap(PropertyBuilder.HasPrecision(precision)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => Wrap(PropertyBuilder.HasPrecision(precision, scale)); + + public override TestComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexTypePropertyBuilder IsRowVersion() + => Wrap(PropertyBuilder.IsRowVersion()); + + public override TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PropertyBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePropertyBuilder ValueGeneratedNever() + => Wrap(PropertyBuilder.ValueGeneratedNever()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAdd() + => Wrap(PropertyBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePropertyBuilder HasValueGenerator( + Func factory) + => Wrap(PropertyBuilder.HasValueGenerator(factory)); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion))); + + public override TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion), valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion), valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression))); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression), + valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression), + valueComparer, + providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexTypePropertyBuilder IInfrastructure.Instance + => PropertyBuilder; + } + protected class NonGenericTestNavigationBuilder : TestNavigationBuilder { public NonGenericTestNavigationBuilder(NavigationBuilder navigationBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 80c7aaf02dc..bff08c371fc 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -4,6 +4,7 @@ #nullable enable using Microsoft.EntityFrameworkCore.Diagnostics.Internal; +using static Microsoft.EntityFrameworkCore.ModelBuilding.ModelBuilderTest; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -201,6 +202,17 @@ public abstract TestPropertyBuilder Property( public abstract TestPropertyBuilder Property(string propertyName); public abstract TestPropertyBuilder IndexerProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + + public abstract TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction); + + public abstract TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction); + public abstract TestNavigationBuilder Navigation( Expression> navigationExpression) where TNavigation : class; @@ -334,6 +346,39 @@ public abstract TestDiscriminatorBuilder HasDiscriminator HasNoDiscriminator(); } + public abstract class TestComplexPropertyBuilder + { + public abstract IMutableComplexProperty Metadata { get; } + public abstract TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value); + public abstract TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value); + + public abstract TestComplexTypePropertyBuilder Property( + Expression> propertyExpression); + + public abstract TestComplexTypePropertyBuilder Property(string propertyName); + public abstract TestComplexTypePropertyBuilder IndexerProperty(string propertyName); + + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction); + + public abstract TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction); + + public abstract TestComplexPropertyBuilder Ignore( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder Ignore(string propertyName); + public abstract TestComplexPropertyBuilder IsRequired(bool isRequired = true); + public abstract TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy); + public abstract TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + public abstract TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode); + } + public abstract class TestDiscriminatorBuilder { public abstract TestDiscriminatorBuilder IsComplete(bool complete); @@ -451,6 +496,89 @@ public abstract TestPropertyBuilder HasConversion + { + public abstract IMutableComplexTypeProperty Metadata { get; } + public abstract TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value); + public abstract TestComplexTypePropertyBuilder IsRequired(bool isRequired = true); + public abstract TestComplexTypePropertyBuilder HasMaxLength(int maxLength); + public abstract TestComplexTypePropertyBuilder HasSentinel(object? sentinel); + public abstract TestComplexTypePropertyBuilder HasPrecision(int precision); + public abstract TestComplexTypePropertyBuilder HasPrecision(int precision, int scale); + public abstract TestComplexTypePropertyBuilder IsUnicode(bool unicode = true); + public abstract TestComplexTypePropertyBuilder IsRowVersion(); + public abstract TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true); + + public abstract TestComplexTypePropertyBuilder ValueGeneratedNever(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnAdd(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnUpdate(); + + public abstract TestComplexTypePropertyBuilder HasValueGenerator() + where TGenerator : ValueGenerator; + + public abstract TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType); + + public abstract TestComplexTypePropertyBuilder HasValueGenerator( + Func factory); + + public abstract TestComplexTypePropertyBuilder HasValueGeneratorFactory() + where TFactory : ValueGeneratorFactory; + + public abstract TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType); + + public abstract TestComplexTypePropertyBuilder HasField(string fieldName); + public abstract TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + + public abstract TestComplexTypePropertyBuilder HasConversion(); + public abstract TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter converter); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter); + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion() + where TComparer : ValueComparer; + + public abstract TestComplexTypePropertyBuilder HasConversion() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer; + } + public abstract class TestNavigationBuilder { public abstract TestNavigationBuilder HasAnnotation(string annotation, object? value); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index edadba300ea..53541749941 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1208,27 +1208,6 @@ public virtual void Value_converter_configured_on_base_type_is_not_applied() Assert.Throws(() => modelBuilder.FinalizeModel()).Message); } - private interface IWrapped - { - T Value { get; init; } - } - - private abstract class WrappedStringBase : IWrapped - { - public abstract string Value { get; init; } - } - - private class WrappedString : WrappedStringBase - { - public override string Value { get; init; } - } - - private class WrappedStringEntity - { - public int Id { get; set; } - public WrappedString WrappedString { get; set; } - } - private class WrappedStringToStringConverter : ValueConverter { public WrappedStringToStringConverter() @@ -1792,6 +1771,21 @@ protected class ThreeDee public int[,,] Three { get; set; } } + [ConditionalFact] + public virtual void Private_property_is_not_discovered_by_convention() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Empty( + model.FindEntityType(typeof(Gamma)).GetProperties() + .Where(p => p.Name == "PrivateProperty")); + } + [ConditionalFact] protected virtual void Throws_for_int_keyed_dictionary() { @@ -2019,7 +2013,7 @@ public virtual void Can_ignore_explicit_interface_implementation_property() [ConditionalFact] public virtual void Can_set_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasKey(e => e.Id); @@ -2036,7 +2030,7 @@ public virtual void Can_set_key_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_composite_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); @@ -2112,7 +2106,7 @@ public virtual void Can_call_Property_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_index_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasNoKey().HasIndex(e => e.CompanyId); @@ -2127,7 +2121,7 @@ public virtual void Can_set_index_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_composite_index_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasNoKey().HasIndex(e => new { e.TenantId, e.CompanyId }); @@ -2227,21 +2221,6 @@ public virtual void Can_add_seed_data_anonymous_objects() Assert.Equal(-2, data.Last().Values.Single()); } - [ConditionalFact] - public virtual void Private_property_is_not_discovered_by_convention() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - Assert.Empty( - model.FindEntityType(typeof(Gamma)).GetProperties() - .Where(p => p.Name == "PrivateProperty")); - } - [ConditionalFact] public virtual void Can_add_seed_data_objects_indexed_property() { diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index f0e81f97409..a9a821328ff 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -847,17 +847,54 @@ protected class OwnedTypeInheritance2 public string? Value { get; set; } } - protected interface IReplacable + protected interface IReplaceable { int Property { get; set; } } - protected class DoubleProperty : IReplacable + protected class ComplexProperties + { + public int Id { get; set; } + public Customer Customer { get; set; } = null!; + public DoubleProperty DoubleProperty { get; set; } = null!; + public IndexedClass IndexedClass { get; set; } = null!; + public Quarks Quarks { get; set; } = null!; + + [NotMapped] + public DynamicProperty DynamicProperty { get; set; } = null!; + [NotMapped] + public EntityWithFields EntityWithFields { get; set; } = null!; + [NotMapped] + public WrappedStringEntity WrappedStringEntity { get; set; } = null!; + } + + protected interface IWrapped + { + T? Value { get; init; } + } + + protected abstract class WrappedStringBase : IWrapped + { + public abstract string? Value { get; init; } + } + + protected class WrappedString : WrappedStringBase + { + public override string? Value { get; init; } + } + + protected class WrappedStringEntity + { + public int Id { get; set; } + public WrappedString WrappedString { get; set; } = new WrappedString(); + } + + protected class DoubleProperty : IReplaceable { public int Id { get; set; } public int Property { get; set; } - int IReplacable.Property + int IReplaceable.Property { get => Property; set => Property = value;