diff --git a/src/EFCore.Relational/EFCore.Relational.baseline.json b/src/EFCore.Relational/EFCore.Relational.baseline.json index cd08c5c1aa8..f41a789b4e2 100644 --- a/src/EFCore.Relational/EFCore.Relational.baseline.json +++ b/src/EFCore.Relational/EFCore.Relational.baseline.json @@ -10205,6 +10205,9 @@ { "Member": "RelationalAnnotationProvider(Microsoft.EntityFrameworkCore.Metadata.RelationalAnnotationProviderDependencies dependencies);" }, + { + "Member": "virtual Microsoft.EntityFrameworkCore.Metadata.RelationalJsonIndex CreateJsonIndex(Microsoft.EntityFrameworkCore.Metadata.IIndex modelIndex, Microsoft.EntityFrameworkCore.Metadata.ITableIndex tableIndex);" + }, { "Member": "static Microsoft.EntityFrameworkCore.Metadata.IRelationalJsonElement FindJsonElement(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase property, Microsoft.EntityFrameworkCore.Metadata.ITable table);" }, @@ -10264,9 +10267,6 @@ }, { "Member": "virtual System.Collections.Generic.IEnumerable For(Microsoft.EntityFrameworkCore.Metadata.ITrigger trigger, bool designTime);" - }, - { - "Member": "virtual Microsoft.EntityFrameworkCore.Metadata.RelationalJsonIndex CreateJsonIndex(Microsoft.EntityFrameworkCore.Metadata.IIndex modelIndex, Microsoft.EntityFrameworkCore.Metadata.ITableIndex tableIndex);" } ], "Properties": [ @@ -13742,7 +13742,7 @@ "Member": "override bool IsCandidateNavigationProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out System.Type? elementType, out bool? shouldBeOwned, out bool explicitlyConfigured);" }, { - "Member": "override bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out bool explicitlyConfigured);" + "Member": "override bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out System.Type? elementType, out bool explicitlyConfigured);" } ] }, @@ -14569,20 +14569,16 @@ "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder HasJsonPropertyName(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder navigationBuilder, string? name);" }, { - "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder);", - "Stage": "Obsolete" + "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder);" }, { - "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder);", - "Stage": "Obsolete" + "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder);" }, { - "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder, string? jsonColumnName);", - "Stage": "Obsolete" + "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder, string? jsonColumnName);" }, { - "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder, string? jsonColumnName);", - "Stage": "Obsolete" + "Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder ToJson(this Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder builder, string? jsonColumnName);" } ] }, @@ -17610,9 +17606,6 @@ { "Member": "virtual void ConfigureParameter(System.Data.Common.DbParameter parameter);" }, - { - "Member": "virtual object? GetDefaultProviderValue();" - }, { "Member": "virtual System.Data.Common.DbParameter CreateParameter(System.Data.Common.DbCommand command, string name, object? value, bool? nullable = null, System.Data.ParameterDirection direction = System.Data.ParameterDirection.Input);" }, @@ -17634,6 +17627,9 @@ { "Member": "static System.Reflection.MethodInfo GetDataReaderMethod(System.Type type);" }, + { + "Member": "virtual object? GetDefaultProviderValue();" + }, { "Member": "virtual string ProcessStoreType(Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping.RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase);" }, diff --git a/src/EFCore.Relational/Metadata/RelationalMemberClassifier.cs b/src/EFCore.Relational/Metadata/RelationalMemberClassifier.cs index 2771de5e7c4..824d3f3a109 100644 --- a/src/EFCore.Relational/Metadata/RelationalMemberClassifier.cs +++ b/src/EFCore.Relational/Metadata/RelationalMemberClassifier.cs @@ -62,9 +62,10 @@ public override bool IsCandidatePrimitiveProperty( IConventionModel model, bool useAttributes, out CoreTypeMapping? typeMapping, + out Type? elementType, out bool explicitlyConfigured) { - if (base.IsCandidatePrimitiveProperty(memberInfo, model, useAttributes, out typeMapping, out explicitlyConfigured)) + if (base.IsCandidatePrimitiveProperty(memberInfo, model, useAttributes, out typeMapping, out elementType, out explicitlyConfigured)) { return true; } @@ -77,6 +78,7 @@ public override bool IsCandidatePrimitiveProperty( && HasExplicitColumnType(memberInfo)) { typeMapping = null; + elementType = null; explicitlyConfigured = true; return true; } diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 5d19f141e9c..c97484452b9 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -2795,9 +2795,6 @@ { "Member": "virtual System.Collections.Generic.List PropertyAutoLoadChangedConventions { get; }" }, - { - "Member": "virtual System.Collections.Generic.List PropertyElementTypeChangedConventions { get; }" - }, { "Member": "virtual System.Collections.Generic.List PropertyFieldChangedConventions { get; }" }, @@ -6970,22 +6967,6 @@ } ] }, - { - "Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.ElementMappingConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IModelFinalizingConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention", - "Methods": [ - { - "Member": "ElementMappingConvention(Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies dependencies);" - }, - { - "Member": "void ProcessModelFinalizing(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionModelBuilder modelBuilder, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" - } - ], - "Properties": [ - { - "Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies Dependencies { get; }" - } - ] - }, { "Type": "class Microsoft.EntityFrameworkCore.Metadata.Builders.ElementTypeBuilder : Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure", "Methods": [ @@ -7047,28 +7028,6 @@ } ] }, - { - "Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.ElementTypeChangedConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyElementTypeChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IForeignKeyAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IForeignKeyPropertiesChangedConvention", - "Methods": [ - { - "Member": "ElementTypeChangedConvention(Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies dependencies);" - }, - { - "Member": "void ProcessForeignKeyAdded(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionForeignKeyBuilder foreignKeyBuilder, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" - }, - { - "Member": "void ProcessForeignKeyPropertiesChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionForeignKeyBuilder relationshipBuilder, System.Collections.Generic.IReadOnlyList oldDependentProperties, Microsoft.EntityFrameworkCore.Metadata.IConventionKey oldPrincipalKey, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext> context);" - }, - { - "Member": "void ProcessPropertyElementTypeChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder propertyBuilder, Microsoft.EntityFrameworkCore.Metadata.IElementType? newElementType, Microsoft.EntityFrameworkCore.Metadata.IElementType? oldElementType, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" - } - ], - "Properties": [ - { - "Member": "virtual Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies Dependencies { get; }" - } - ] - }, { "Type": "class Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry : Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure", "Methods": [ @@ -11484,9 +11443,6 @@ { "Member": "Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior? SetBeforeSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior? beforeSaveBehavior, bool fromDataAnnotation = false);" }, - { - "Member": "Microsoft.EntityFrameworkCore.Metadata.IConventionElementType? SetElementType(System.Type? elementType, bool fromDataAnnotation = false);" - }, { "Member": "bool? SetIsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation = false);" }, @@ -12115,6 +12071,12 @@ { "Member": "bool CanHaveIndexerProperty(System.Type propertyType, string propertyName, bool fromDataAnnotation = false);" }, + { + "Member": "bool CanHavePrimitiveCollection(System.Type? propertyType, string propertyName, System.Type? elementType = null, bool fromDataAnnotation = false);" + }, + { + "Member": "bool CanHavePrimitiveCollection(System.Reflection.MemberInfo memberInfo, System.Type? elementType = null, bool fromDataAnnotation = false);" + }, { "Member": "bool CanHaveProperty(System.Type? propertyType, string propertyName, bool fromDataAnnotation = false);" }, @@ -12203,6 +12165,12 @@ { "Member": "bool IsIgnored(string memberName, bool fromDataAnnotation = false);" }, + { + "Member": "Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder? PrimitiveCollection(System.Type propertyType, string propertyName, System.Type? elementType = null, bool setTypeConfigurationSource = true, bool fromDataAnnotation = false);" + }, + { + "Member": "Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder? PrimitiveCollection(System.Reflection.MemberInfo memberInfo, System.Type? elementType = null, bool fromDataAnnotation = false);" + }, { "Member": "Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder? Property(System.Type propertyType, string propertyName, bool setTypeConfigurationSource = true, bool fromDataAnnotation = false);" }, @@ -13521,7 +13489,7 @@ "Member": "bool IsCandidateNavigationProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out System.Type? elementType, out bool? shouldBeOwned, out bool explicitlyConfigured);" }, { - "Member": "bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out bool explicitlyConfigured);" + "Member": "bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out System.Type? elementType, out bool explicitlyConfigured);" }, { "Member": "bool IsCandidateServiceProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Metadata.IParameterBindingFactory? bindingFactory, out bool explicitlyConfigured);" @@ -14330,9 +14298,6 @@ { "Member": "void SetBeforeSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior? beforeSaveBehavior);" }, - { - "Member": "void SetElementType(System.Type? elementType);" - }, { "Member": "void SetIsUnicode(bool? unicode);" }, @@ -14496,6 +14461,9 @@ { "Member": "Microsoft.EntityFrameworkCore.Metadata.IMutableProperty AddProperty(string name, System.Type propertyType);" }, + { + "Member": "Microsoft.EntityFrameworkCore.Metadata.IMutableProperty AddProperty(string name, System.Type propertyType, System.Type elementType);" + }, { "Member": "Microsoft.EntityFrameworkCore.Metadata.IMutableProperty AddProperty(string name, System.Type propertyType, System.Reflection.MemberInfo memberInfo);" }, @@ -15273,14 +15241,6 @@ } ] }, - { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyElementTypeChangedConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention", - "Methods": [ - { - "Member": "void ProcessPropertyElementTypeChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder propertyBuilder, Microsoft.EntityFrameworkCore.Metadata.IElementType? newElementType, Microsoft.EntityFrameworkCore.Metadata.IElementType? oldElementType, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" - } - ] - }, { "Type": "interface Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyFieldChangedConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention", "Methods": [ @@ -18279,7 +18239,7 @@ "Member": "virtual bool IsCandidateNavigationProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out System.Type? elementType, out bool? shouldBeOwned, out bool explicitlyConfigured);" }, { - "Member": "virtual bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out bool explicitlyConfigured);" + "Member": "virtual bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? typeMapping, out System.Type? elementType, out bool explicitlyConfigured);" }, { "Member": "virtual bool IsCandidateServiceProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionModel model, bool useAttributes, out Microsoft.EntityFrameworkCore.Metadata.IParameterBindingFactory? bindingFactory, out bool explicitlyConfigured);" @@ -19418,7 +19378,7 @@ ] }, { - "Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.NonNullableReferencePropertyConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.NonNullableConventionBase, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyFieldChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyElementTypeChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IComplexPropertyAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IComplexPropertyFieldChangedConvention", + "Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.NonNullableReferencePropertyConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.NonNullableConventionBase, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyFieldChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IComplexPropertyAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IComplexPropertyFieldChangedConvention", "Methods": [ { "Member": "NonNullableReferencePropertyConvention(Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies dependencies);" @@ -19432,9 +19392,6 @@ { "Member": "virtual void ProcessPropertyAdded(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder propertyBuilder, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" }, - { - "Member": "virtual void ProcessPropertyElementTypeChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder propertyBuilder, Microsoft.EntityFrameworkCore.Metadata.IElementType? newElementType, Microsoft.EntityFrameworkCore.Metadata.IElementType? oldElementType, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" - }, { "Member": "virtual void ProcessPropertyFieldChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder propertyBuilder, System.Reflection.FieldInfo? newFieldInfo, System.Reflection.FieldInfo? oldFieldInfo, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" } @@ -20799,7 +20756,7 @@ "Member": "virtual System.Collections.Generic.IEnumerable GetMembers(Microsoft.EntityFrameworkCore.Metadata.IConventionTypeBase structuralType);" }, { - "Member": "virtual bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionTypeBase structuralType, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? mapping);" + "Member": "virtual bool IsCandidatePrimitiveProperty(System.Reflection.MemberInfo memberInfo, Microsoft.EntityFrameworkCore.Metadata.IConventionTypeBase structuralType, out Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping? mapping, out System.Type? elementType);" }, { "Member": "void ProcessComplexPropertyAdded(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionComplexPropertyBuilder propertyBuilder, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext context);" diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs index cc7e8f2ee12..91b6e6501d7 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs @@ -123,7 +123,7 @@ public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName Check.NotEmpty(propertyName); var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); - return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// @@ -145,7 +145,7 @@ public virtual ComplexCollectionTypePropertyBuilder Property @@ -168,7 +168,7 @@ public virtual ComplexCollectionTypePropertyBuilder Property(Type propertyType, Check.NotEmpty(propertyName); var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); - return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs index 54211d2a78e..2b370ccfaad 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs @@ -78,7 +78,7 @@ public virtual ComplexCollectionTypePropertyBuilder Property diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index 55483368e87..5abde21ab0b 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -161,7 +161,7 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName) Check.NotEmpty(propertyName); var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); - return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// @@ -183,7 +183,7 @@ public virtual ComplexTypePropertyBuilder Property(string Check.NotEmpty(propertyName); var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); - return new(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// @@ -206,7 +206,7 @@ public virtual ComplexTypePropertyBuilder Property(Type propertyType, string pro Check.NotEmpty(propertyName); var (innerBuilder, leafName) = TypeBuilder.ResolveComplexChainByName(propertyName); - return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index f5eae964eff..c27ef18d160 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -101,7 +101,7 @@ public virtual ComplexTypePropertyBuilder Property(Express var memberChain = propertyExpression.GetMemberAccessChain(nameof(propertyExpression)); var (innerBuilder, leafMember) = TypeBuilder.ResolveComplexChain(memberChain); - return new(innerBuilder.Property(leafMember, ConfigurationSource.Explicit)!.Metadata); + return new(innerBuilder.Property(leafMember, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index ae87c28bcb9..356462209d8 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -125,7 +125,7 @@ public virtual PropertyBuilder Property(string propertyName) Check.NotEmpty(propertyName); var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); - return new PropertyBuilder(innerBuilder.Property(leafName, ConfigurationSource.Explicit)!.Metadata); + return new PropertyBuilder(innerBuilder.Property(leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// @@ -147,7 +147,7 @@ public virtual PropertyBuilder Property(string propertyNam Check.NotEmpty(propertyName); var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); - return new PropertyBuilder(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit)!.Metadata); + return new PropertyBuilder(innerBuilder.Property(typeof(TProperty), leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// @@ -170,7 +170,7 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName) Check.NotEmpty(propertyName); var (innerBuilder, leafName) = Builder.ResolveComplexChainByName(propertyName); - return new PropertyBuilder(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit)!.Metadata); + return new PropertyBuilder(innerBuilder.Property(propertyType, leafName, ConfigurationSource.Explicit, isCollection: false)!.Metadata); } /// diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 612289e57e8..fd11efaf5f1 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -152,7 +152,7 @@ public virtual PropertyBuilder Property(Expression diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 1a24761eac9..531d5a70427 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -570,22 +570,4 @@ bool CanSetProviderValueComparer( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, bool fromDataAnnotation = 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. - /// - [EntityFrameworkInternal] - IConventionElementTypeBuilder? SetElementType(Type? elementType, bool fromDataAnnotation = 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. - /// - [EntityFrameworkInternal] - bool CanSetElementType(Type? elementType, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs index f9220d047bb..cd38f45cd8a 100644 --- a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -111,6 +111,68 @@ bool CanHaveProperty( /// if the property can be added. bool CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); + /// + /// Returns an object that can be used to configure the primitive collection 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 element type of the collection, or to discover it. + /// 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 type, + /// otherwise. + /// + IConventionPropertyBuilder? PrimitiveCollection( + Type propertyType, + string propertyName, + Type? elementType = null, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the primitive collection with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The or of the property. + /// The element type of the collection, or to discover it. + /// 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 type, + /// otherwise. + /// + IConventionPropertyBuilder? PrimitiveCollection( + MemberInfo memberInfo, + Type? elementType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given primitive collection can be added to this type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The element type of the collection, or to discover it. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHavePrimitiveCollection( + Type? propertyType, + string propertyName, + Type? elementType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given primitive collection can be added to this type. + /// + /// The or of the property. + /// The element type of the collection, or to discover it. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHavePrimitiveCollection( + MemberInfo memberInfo, + Type? elementType = null, + 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. diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 0c099f29c5b..f25d0820adb 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -32,7 +32,7 @@ public PropertyBuilder(IMutableProperty property) { Check.NotNull(property); - Builder = ((Property)property).Builder; + _builder = ((Property)property).Builder; } /// @@ -41,7 +41,22 @@ public PropertyBuilder(IMutableProperty property) IConventionPropertyBuilder IInfrastructure.Instance => Builder; - private InternalPropertyBuilder Builder { get; } + private InternalPropertyBuilder _builder; + + private InternalPropertyBuilder Builder + { + get + { + if (!_builder.Metadata.IsInModel + && _builder.Metadata.DeclaringType.FindProperty(_builder.Metadata.Name) is { } property) + { + // The property may have been recreated, so re-resolve the current builder to keep chained calls working. + _builder = property.Builder; + } + + return _builder; + } + } /// /// The property being configured. diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs index 77ec5d71286..b564d69afce 100644 --- a/src/EFCore/Metadata/Conventions/ConventionSet.cs +++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs @@ -277,11 +277,6 @@ public class ConventionSet /// public virtual List PropertyFieldChangedConventions { get; } = []; - /// - /// Conventions to run when the field of a property is changed. - /// - public virtual List PropertyElementTypeChangedConventions { get; } = []; - /// /// Conventions to run when an annotation is changed on a property. /// @@ -643,12 +638,6 @@ public virtual void Replace(TImplementation newConvention) PropertyRemovedConventions.Add(propertyRemovedConvention); } - if (newConvention is IPropertyElementTypeChangedConvention propertyElementTypeChangedConvention - && !Replace(PropertyElementTypeChangedConventions, propertyElementTypeChangedConvention, oldConventionType)) - { - PropertyElementTypeChangedConventions.Add(propertyElementTypeChangedConvention); - } - if (newConvention is IElementTypeNullabilityChangedConvention elementTypeNullabilityChangedConvention && !Replace(ElementTypeNullabilityChangedConventions, elementTypeNullabilityChangedConvention, oldConventionType)) { @@ -974,11 +963,6 @@ public virtual void Add(IConvention convention) PropertyFieldChangedConventions.Add(propertyFieldChangedConvention); } - if (convention is IPropertyElementTypeChangedConvention propertyElementTypeChangedConvention) - { - PropertyElementTypeChangedConventions.Add(propertyElementTypeChangedConvention); - } - if (convention is IPropertyAnnotationChangedConvention propertyAnnotationChangedConvention) { PropertyAnnotationChangedConventions.Add(propertyAnnotationChangedConvention); @@ -1337,11 +1321,6 @@ public virtual void Remove(Type conventionType) Remove(PropertyRemovedConventions, conventionType); } - if (typeof(IPropertyElementTypeChangedConvention).IsAssignableFrom(conventionType)) - { - Remove(PropertyElementTypeChangedConventions, conventionType); - } - if (typeof(IElementTypeNullabilityChangedConvention).IsAssignableFrom(conventionType)) { Remove(ElementTypeNullabilityChangedConventions, conventionType); diff --git a/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs b/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs deleted file mode 100644 index bb02b589115..00000000000 --- a/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs +++ /dev/null @@ -1,53 +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 ensures property mappings have any ElementMapping discovered by the type mapper. -/// -/// -/// -/// See Model building conventions for more information and examples. -/// -/// -public class ElementMappingConvention : IModelFinalizingConvention -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - public ElementMappingConvention(ProviderConventionSetBuilderDependencies dependencies) - => Dependencies = dependencies; - - /// - /// Dependencies for this service. - /// - protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - - /// - public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) - { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) - { - Validate(entityType); - } - - void Validate(IConventionTypeBase typeBase) - { - foreach (var property in typeBase.GetDeclaredProperties()) - { - var typeMapping = Dependencies.TypeMappingSource.FindMapping((IProperty)property); - if (typeMapping is { ElementTypeMapping: not null }) - { - property.Builder.SetElementType(property.ClrType.TryGetElementType(typeof(IEnumerable<>))); - } - } - - foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) - { - Validate(complexProperty.ComplexType); - } - } - } -} diff --git a/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs b/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs deleted file mode 100644 index f9dac029672..00000000000 --- a/src/EFCore/Metadata/Conventions/ElementTypeChangedConvention.cs +++ /dev/null @@ -1,76 +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 reacts to changes made to element types of primitive collections. -/// -/// -/// See Model building conventions for more information and examples. -/// -public class ElementTypeChangedConvention : - IPropertyElementTypeChangedConvention, - IForeignKeyAddedConvention, - IForeignKeyPropertiesChangedConvention -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - public ElementTypeChangedConvention(ProviderConventionSetBuilderDependencies dependencies) - => Dependencies = dependencies; - - /// - /// Dependencies for this service. - /// - protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - - /// - public void ProcessPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType, - IConventionContext context) - { - var keyProperty = propertyBuilder.Metadata; - foreach (var key in keyProperty.GetContainingKeys()) - { - var index = key.Properties.IndexOf(keyProperty); - foreach (var foreignKey in key.GetReferencingForeignKeys()) - { - var foreignKeyProperty = foreignKey.Properties[index]; - foreignKeyProperty.Builder.SetElementType(newElementType?.ClrType); - } - } - } - - /// - public void ProcessForeignKeyAdded( - IConventionForeignKeyBuilder foreignKeyBuilder, - IConventionContext context) - => ProcessForeignKey(foreignKeyBuilder); - - /// - public void ProcessForeignKeyPropertiesChanged( - IConventionForeignKeyBuilder relationshipBuilder, - IReadOnlyList oldDependentProperties, - IConventionKey oldPrincipalKey, - IConventionContext> context) - { - if (relationshipBuilder.Metadata.IsInModel) - { - ProcessForeignKey(relationshipBuilder); - } - } - - private static void ProcessForeignKey(IConventionForeignKeyBuilder foreignKeyBuilder) - { - var foreignKeyProperties = foreignKeyBuilder.Metadata.Properties; - var principalKeyProperties = foreignKeyBuilder.Metadata.PrincipalKey.Properties; - for (var i = 0; i < foreignKeyProperties.Count; i++) - { - foreignKeyProperties[i].Builder.SetElementType(principalKeyProperties[i].GetElementType()?.ClrType); - } - } -} diff --git a/src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs b/src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs deleted file mode 100644 index 45062b7b94e..00000000000 --- a/src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs +++ /dev/null @@ -1,26 +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; - -/// -/// Represents an operation that should be performed when the for a property is changed. -/// -/// -/// See Model building conventions for more information and examples. -/// -public interface IPropertyElementTypeChangedConvention : IConvention -{ - /// - /// Called after the element type for a property is changed. - /// - /// The builder for the property. - /// The new element type. - /// The old element type. - /// Additional information associated with convention execution. - void ProcessPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType, - IConventionContext context); -} diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index f92e934b0b4..0fa8cfc5fa0 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -100,8 +100,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.Add(new QueryFilterRewritingConvention(Dependencies)); conventionSet.Add(new AutoLoadConvention(Dependencies)); conventionSet.Add(new RuntimeModelConvention(Dependencies)); - conventionSet.Add(new ElementMappingConvention(Dependencies)); - conventionSet.Add(new ElementTypeChangedConvention(Dependencies)); return conventionSet; } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index e239ba6b771..962d27f7b51 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -252,11 +252,6 @@ public int GetLeafCount() IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property); - public abstract IElementType? OnPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType); - 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 819dd93dac3..104d09eba94 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -457,15 +457,6 @@ public override IConventionProperty OnPropertyRemoved( Add(new OnPropertyRemovedNode(typeBaseBuilder, property)); return property; } - - public override IElementType? OnPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType) - { - Add(new OnPropertyElementTypeChangedNode(propertyBuilder, newElementType, oldElementType)); - return newElementType; - } } private sealed class OnModelAnnotationChangedNode( @@ -1043,20 +1034,6 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnPropertyFieldChanged(PropertyBuilder, NewFieldInfo, OldFieldInfo); } - private sealed class OnPropertyElementTypeChangedNode( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType) - : ConventionNode - { - public IConventionPropertyBuilder PropertyBuilder { get; } = propertyBuilder; - public IElementType? NewElementType { get; } = newElementType; - public IElementType? OldElementType { get; } = oldElementType; - - public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnPropertyElementTypeChanged(PropertyBuilder, NewElementType, OldElementType); - } - private sealed class OnPropertyAnnotationChangedNode( IConventionPropertyBuilder propertyBuilder, string name, diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index c82166298b2..a39a60031b9 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -31,7 +31,6 @@ private sealed class ImmediateConventionScope(ConventionSet conventionSet, Conve private readonly ConventionContext _stringConventionContext = new(dispatcher); private readonly ConventionContext _nullableStringConventionContext = new(dispatcher); private readonly ConventionContext _fieldInfoConventionContext = new(dispatcher); - private readonly ConventionContext _elementTypeConventionContext = new(dispatcher); private readonly ConventionContext _boolConventionContext = new(dispatcher); private readonly ConventionContext?> _boolListConventionContext = new(dispatcher); @@ -1710,38 +1709,6 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _fieldInfoConventionContext.Result; } - public override IElementType? OnPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType) - { - if (!propertyBuilder.Metadata.IsInModel - || !propertyBuilder.Metadata.DeclaringType.IsInModel) - { - return null; - } -#if DEBUG - var initialValue = propertyBuilder.Metadata.GetElementType(); -#endif - _elementTypeConventionContext.ResetState(newElementType); - foreach (var propertyConvention in conventionSet.PropertyElementTypeChangedConventions) - { - propertyConvention.ProcessPropertyElementTypeChanged( - propertyBuilder, newElementType, oldElementType, _elementTypeConventionContext); - if (_elementTypeConventionContext.ShouldStopProcessing()) - { - return _elementTypeConventionContext.Result; - } -#if DEBUG - Check.DebugAssert( - initialValue == propertyBuilder.Metadata.GetElementType(), - $"Convention {propertyConvention.GetType().Name} changed value without terminating"); -#endif - } - - return _elementTypeConventionContext.Result; - } - public override IConventionAnnotation? OnPropertyAnnotationChanged( IConventionPropertyBuilder propertyBuilder, string name, diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 2885a935cd5..ce56fbf99c7 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -724,18 +724,6 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder FieldInfo? oldFieldInfo) => _scope.OnPropertyFieldChanged(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 IElementType? OnPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType) - => _scope.OnPropertyElementTypeChanged(propertyBuilder, newElementType, oldElementType); - /// /// This is an internal API that supports the 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/NonNullableReferencePropertyConvention.cs b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs index 1a07dfc22f1..c5854e6c87a 100644 --- a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs +++ b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs @@ -15,7 +15,6 @@ public class NonNullableReferencePropertyConvention(ProviderConventionSetBuilder : NonNullableConventionBase(dependencies), IPropertyAddedConvention, IPropertyFieldChangedConvention, - IPropertyElementTypeChangedConvention, IComplexPropertyAddedConvention, IComplexPropertyFieldChangedConvention { @@ -69,19 +68,6 @@ public virtual void ProcessPropertyFieldChanged( } } - /// - public virtual void ProcessPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType? newElementType, - IElementType? oldElementType, - IConventionContext context) - { - if (newElementType != null) - { - Process(propertyBuilder); - } - } - /// public virtual void ProcessComplexPropertyAdded( IConventionComplexPropertyBuilder propertyBuilder, diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index b127b8bce5d..b9cc1bd1d26 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -79,19 +79,18 @@ protected virtual void DiscoverPrimitiveProperties( var structuralType = structuralTypeBuilder.Metadata; foreach (var propertyInfo in GetMembers(structuralType)) { - if (!IsCandidatePrimitiveProperty(propertyInfo, structuralType, out var mapping)) + if (!IsCandidatePrimitiveProperty(propertyInfo, structuralType, out _, out var elementType)) { continue; } - var propertyBuilder = structuralTypeBuilder.Property(propertyInfo); - if (mapping?.ElementTypeMapping != null) + if (elementType != null) { - var elementType = propertyInfo.GetMemberType().TryGetElementType(typeof(IEnumerable<>)); - if (elementType != null) - { - propertyBuilder?.SetElementType(elementType); - } + structuralTypeBuilder.PrimitiveCollection(propertyInfo, elementType); + } + else + { + structuralTypeBuilder.Property(propertyInfo); } } } @@ -113,10 +112,13 @@ protected virtual IEnumerable GetMembers(IConventionTypeBase structu /// The member. /// The type for which the properties will be discovered. /// The type mapping for the property. + /// The element type if the member is a primitive collection; otherwise . protected virtual bool IsCandidatePrimitiveProperty( MemberInfo memberInfo, IConventionTypeBase structuralType, - out CoreTypeMapping? mapping) - => Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(memberInfo, structuralType.Model, UseAttributes, out mapping, out _) + out CoreTypeMapping? mapping, + out Type? elementType) + => Dependencies.MemberClassifier.IsCandidatePrimitiveProperty( + memberInfo, structuralType.Model, UseAttributes, out mapping, out elementType, out _) && ((Model)structuralType.Model).FindIsComplexConfigurationSource(memberInfo.GetMemberType().UnwrapNullableType()) == null; } diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index 11f8080dfe1..a611864941c 100644 --- a/src/EFCore/Metadata/IConventionProperty.cs +++ b/src/EFCore/Metadata/IConventionProperty.cs @@ -475,20 +475,6 @@ bool IsImplicitlyCreated() /// The configuration for the elements. new IConventionElementType? GetElementType(); - /// - /// Sets the configuration for elements of the primitive collection represented by this property. - /// - /// If , then the type mapping has an element type, otherwise it is removed. - /// Indicates whether the configuration was specified using a data annotation. - /// The configuration for the elements. - IConventionElementType? SetElementType(Type? elementType, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetElementTypeConfigurationSource(); - /// IReadOnlyElementType? IReadOnlyProperty.GetElementType() => GetElementType(); diff --git a/src/EFCore/Metadata/IMemberClassifier.cs b/src/EFCore/Metadata/IMemberClassifier.cs index d024768ab30..9f5b34f822e 100644 --- a/src/EFCore/Metadata/IMemberClassifier.cs +++ b/src/EFCore/Metadata/IMemberClassifier.cs @@ -143,6 +143,10 @@ bool IsCandidateNavigationProperty( /// The model. /// Whether attributes found on the member should be considered. /// When this method returns, the type mapping for the member, if one was found. + /// + /// When this method returns , the element type if the member is a primitive collection; + /// otherwise . + /// /// When this method returns, indicates whether the type was explicitly configured. /// if the member is a candidate primitive property; otherwise . bool IsCandidatePrimitiveProperty( @@ -150,6 +154,7 @@ bool IsCandidatePrimitiveProperty( IConventionModel model, bool useAttributes, out CoreTypeMapping? typeMapping, + out Type? elementType, out bool explicitlyConfigured); /// diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index f6027246e8c..e57f44b6aa4 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -278,12 +278,6 @@ void SetProviderValueComparer( /// The configuration for the elements. new IMutableElementType? GetElementType(); - /// - /// Sets the configuration for elements of the primitive collection represented by this property. - /// - /// If , then this is a collection of primitive elements. - void SetElementType(Type? elementType); - /// bool IReadOnlyProperty.IsNullable => IsNullable; diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index f3e9027a662..fe7cc83d96c 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -145,6 +145,18 @@ IMutableProperty AddProperty(MemberInfo memberInfo) /// The newly created property. IMutableProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); + /// + /// Adds a primitive collection property to this type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The element type of the primitive collection. + /// The newly created property. + IMutableProperty AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + Type elementType); + /// /// Adds a property to this type. /// diff --git a/src/EFCore/Metadata/Internal/ElementType.cs b/src/EFCore/Metadata/Internal/ElementType.cs index 651a7dae85b..a228a4e708d 100644 --- a/src/EFCore/Metadata/Internal/ElementType.cs +++ b/src/EFCore/Metadata/Internal/ElementType.cs @@ -20,7 +20,6 @@ public class ElementType : ConventionAnnotatable, IMutableElementType, IConventi private bool? _isNullable; private CoreTypeMapping? _typeMapping; - private ConfigurationSource _configurationSource; private ConfigurationSource? _isNullableConfigurationSource; private ConfigurationSource? _typeMappingConfigurationSource; @@ -32,12 +31,10 @@ public class ElementType : ConventionAnnotatable, IMutableElementType, IConventi /// public ElementType( Type clrType, - Property collectionProperty, - ConfigurationSource configurationSource) + Property collectionProperty) { ClrType = clrType; CollectionProperty = collectionProperty; - _configurationSource = configurationSource; _builder = new InternalElementTypeBuilder(this, collectionProperty.DeclaringType.Model.Builder); } @@ -48,27 +45,7 @@ public ElementType( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource GetConfigurationSource() - => _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 void UpdateConfigurationSource(ConfigurationSource configurationSource) - => _configurationSource = configurationSource.Max(_configurationSource); - - // Needed for a workaround before reference counting is implemented - // Issue #15898 - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code 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 SetConfigurationSource(ConfigurationSource configurationSource) - => _configurationSource = configurationSource; + => CollectionProperty.GetConfigurationSource(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index c21aa505d49..bb3e694c930 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -1721,7 +1721,8 @@ public virtual bool CanInvert( dependentProperties: []); } - properties = dependentEntityType.Builder.GetActualProperties(properties, configurationSource)!; + properties = dependentEntityType.Builder.GetActualProperties( + properties, configurationSource, Metadata.PrincipalKey.Properties)!; if (Metadata.Properties.SequenceEqual(properties)) { Metadata.UpdateConfigurationSource(configurationSource); @@ -3660,7 +3661,10 @@ private static IReadOnlyList FindRelationships( } else { - dependentProperties = dependentEntityTypeBuilder.GetActualProperties(Metadata.Properties, configurationSource) + dependentProperties = dependentEntityTypeBuilder.GetActualProperties( + Metadata.Properties, + configurationSource, + principalProperties.Count != 0 ? principalProperties : Metadata.PrincipalKey.Properties) ?? new List(); } diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index a0988a7683e..e82921e1534 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -543,20 +543,16 @@ public virtual bool CanSetValueGeneratorFactory( /// public virtual InternalPropertyBuilder? HasConversion(ValueConverter? converter, ConfigurationSource configurationSource) { - if (CanSetConversion(converter, configurationSource)) + if (!CanSetConversion(converter, configurationSource)) { - if (converter != null) - { - Metadata.SetElementType(null, configurationSource); - } - - Metadata.SetProviderClrType(null, configurationSource); - Metadata.SetValueConverter(converter, configurationSource); - - return this; + return null; } - return null; + var builder = converter == null ? this : EnsureNotCollection(configurationSource)!; + builder.Metadata.SetProviderClrType(null, configurationSource); + builder.Metadata.SetValueConverter(converter, configurationSource); + + return builder; } /// @@ -574,7 +570,9 @@ public virtual bool CanSetConversion( || (Metadata[CoreAnnotationNames.ValueConverterType] == null && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter)) && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()) - && (converter == null || CanSetElementType(null, configurationSource)); + && (converter == null + || Metadata.GetElementType() == null + || configurationSource.Overrides(Metadata.GetConfigurationSource())); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -584,20 +582,16 @@ public virtual bool CanSetConversion( /// public virtual InternalPropertyBuilder? HasConversion(Type? providerClrType, ConfigurationSource configurationSource) { - if (CanSetConversion(providerClrType, configurationSource)) + if (!CanSetConversion(providerClrType, configurationSource)) { - if (providerClrType != null) - { - Metadata.SetElementType(null, configurationSource); - } - - Metadata.SetValueConverter((ValueConverter?)null, configurationSource); - Metadata.SetProviderClrType(providerClrType, configurationSource); - - return this; + return null; } - return null; + var builder = providerClrType == null ? this : EnsureNotCollection(configurationSource)!; + builder.Metadata.SetValueConverter((ValueConverter?)null, configurationSource); + builder.Metadata.SetProviderClrType(providerClrType, configurationSource); + + return builder; } /// @@ -610,7 +604,9 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()) || Metadata.GetProviderClrType() == providerClrType) && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) - && (providerClrType == null || CanSetElementType(null, configurationSource)); + && (providerClrType == null + || Metadata.GetElementType() == null + || configurationSource.Overrides(Metadata.GetConfigurationSource())); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -623,20 +619,16 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? Type? converterType, ConfigurationSource configurationSource) { - if (CanSetConverter(converterType, configurationSource)) + if (!CanSetConverter(converterType, configurationSource)) { - if (converterType != null) - { - Metadata.SetElementType(null, configurationSource); - } - - Metadata.SetProviderClrType(null, configurationSource); - Metadata.SetValueConverter(converterType, configurationSource); - - return this; + return null; } - return null; + var builder = converterType == null ? this : EnsureNotCollection(configurationSource)!; + builder.Metadata.SetProviderClrType(null, configurationSource); + builder.Metadata.SetValueConverter(converterType, configurationSource); + + return builder; } /// @@ -652,7 +644,22 @@ public virtual bool CanSetConverter( => (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) || (Metadata[CoreAnnotationNames.ValueConverter] == null && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType)) - && (converterType == null || CanSetElementType(null, configurationSource)); + && (converterType == null + || Metadata.GetElementType() == null + || configurationSource.Overrides(Metadata.GetConfigurationSource())); + + // When a value converter (or provider type) is configured on a primitive collection the property is no longer a + // collection, so it is recreated as a scalar property (the element type is a creation-time concern). Returns the + // builder for the recreated property. + private InternalPropertyBuilder? EnsureNotCollection(ConfigurationSource configurationSource) + => Metadata.GetElementType() == null + ? this + : Metadata.DeclaringType.Builder.Property( + Metadata.ClrType, + Metadata.Name, + Metadata.GetTypeConfigurationSource(), + configurationSource, + isCollection: false); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -836,45 +843,6 @@ public virtual bool CanSetProviderValueComparer( || (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 InternalElementTypeBuilder? SetElementType(Type? elementType, ConfigurationSource configurationSource) - { - if (CanSetElementType(elementType, configurationSource)) - { - Metadata.SetElementType(elementType, configurationSource); - if (elementType != null) - { - Metadata.SetValueConverter((Type?)null, configurationSource); - } - - if (elementType == null - && CanSetConversion((Type?)null, configurationSource)) - { - Metadata.RemoveAnnotation(CoreAnnotationNames.ValueConverter); - } - - return new InternalElementTypeBuilder(Metadata.GetElementType()!, ModelBuilder); - } - - 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 CanSetElementType(Type? elementType, ConfigurationSource? configurationSource) - => (configurationSource.Overrides(Metadata.GetElementTypeConfigurationSource()) - && (elementType == null || CanSetConversion((Type?)null, configurationSource))) - || elementType == Metadata.GetElementType()?.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 @@ -904,13 +872,19 @@ public virtual bool CanSetElementType(Type? elementType, ConfigurationSource? co else { var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo(); + var isCollection = Metadata.GetElementType() != null; newPropertyBuilder = Metadata.IsIndexerProperty() ? typeBaseBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) : identifyingMemberInfo == null - ? typeBaseBuilder.Property( - Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) - : typeBaseBuilder.Property(identifyingMemberInfo, configurationSource); + ? isCollection + ? typeBaseBuilder.PrimitiveCollection( + Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) + : typeBaseBuilder.Property( + Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) + : isCollection + ? typeBaseBuilder.PrimitiveCollection(identifyingMemberInfo, configurationSource) + : typeBaseBuilder.Property(identifyingMemberInfo, configurationSource); if (newPropertyBuilder is null) { @@ -1625,22 +1599,4 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer( bool fromDataAnnotation) => CanSetProviderValueComparer( 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. - /// - IConventionElementTypeBuilder? IConventionPropertyBuilder.SetElementType(Type? elementType, bool fromDataAnnotation) - => SetElementType(elementType, 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.CanSetElementType(Type? elementType, bool fromDataAnnotation) - => CanSetElementType(elementType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index 552966606a2..3d17e3ad820 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -90,12 +90,14 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing Type? propertyType, string propertyName, ConfigurationSource? configurationSource, - bool skipTypeCheck = false) + bool skipTypeCheck = false, + bool? isCollection = null) => Property( propertyType, propertyName, memberInfo: null, typeConfigurationSource: configurationSource, configurationSource: configurationSource, - skipTypeCheck); + skipTypeCheck, + isCollection: isCollection); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -108,12 +110,14 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing string propertyName, ConfigurationSource? typeConfigurationSource, ConfigurationSource? configurationSource, - bool skipTypeCheck = false) + bool skipTypeCheck = false, + bool? isCollection = null) => Property( propertyType, propertyName, memberInfo: null, typeConfigurationSource, configurationSource, - skipTypeCheck); + skipTypeCheck, + isCollection: isCollection); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -121,8 +125,8 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing /// any release. You should only use it directly in your code 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? Property(string propertyName, ConfigurationSource? configurationSource) - => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource); + public virtual InternalPropertyBuilder? Property(string propertyName, ConfigurationSource? configurationSource, bool? isCollection = null) + => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource, isCollection: isCollection); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -130,8 +134,8 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing /// any release. You should only use it directly in your code 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? Property(MemberInfo memberInfo, ConfigurationSource? configurationSource) - => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource); + public virtual InternalPropertyBuilder? Property(MemberInfo memberInfo, ConfigurationSource? configurationSource, bool? isCollection = null) + => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource, isCollection: isCollection); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -152,7 +156,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); } - return Property(propertyType, propertyName, indexerPropertyInfo, configurationSource, configurationSource, skipTypeCheck); + return Property(propertyType, propertyName, indexerPropertyInfo, configurationSource, configurationSource, skipTypeCheck, isCollection: false); } /// @@ -167,8 +171,14 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing MemberInfo? memberInfo, ConfigurationSource? typeConfigurationSource, ConfigurationSource? configurationSource, - bool skipTypeCheck = false) + bool skipTypeCheck = false, + Type? elementType = null, + bool? isCollection = null) { + Check.DebugAssert( + isCollection == true || elementType == null, + $"An element type was supplied for non-collection property '{propertyName}'."); + var structuralType = Metadata; List? propertiesToDetach = null; var existingProperty = structuralType.FindProperty(propertyName); @@ -185,7 +195,10 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing } if (IsCompatible(memberInfo, existingProperty) - && (propertyType == null || propertyType == existingProperty.ClrType)) + && (propertyType == null || propertyType == existingProperty.ClrType) + && (isCollection == false + ? existingProperty.GetElementType() == null + : elementType == null || elementType == existingProperty.GetElementType()?.ClrType)) { if (configurationSource.HasValue) { @@ -201,6 +214,13 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing return existingProperty.Builder; } + if (isCollection == false + && existingProperty.GetElementType() is { } existingElementType + && !configurationSource.Overrides(existingElementType.GetConfigurationSource())) + { + return null; + } + if (memberInfo == null || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) { @@ -219,6 +239,10 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing } propertyType ??= existingProperty.ClrType; + if (isCollection != false) + { + elementType ??= existingProperty.GetElementType()?.ClrType; + } propertiesToDetach = [existingProperty]; } @@ -275,7 +299,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing } builder = structuralType.AddProperty( - propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; + propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value, elementType)!.Builder; detachedProperties?.Attach(this); } @@ -344,22 +368,24 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing string propertyName, MemberInfo? memberInfo, ConfigurationSource? typeConfigurationSource, - ConfigurationSource? configurationSource) + ConfigurationSource? configurationSource, + Type? elementType = null) { - var builder = Property(propertyType, propertyName, memberInfo, typeConfigurationSource, configurationSource); - - if (builder != null) + // Resolve the element type up front so it is set when the property is created. + var effectiveType = propertyType + ?? memberInfo?.GetMemberType() + ?? (Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault()?.GetMemberType()); + elementType ??= effectiveType?.TryGetElementType(typeof(IEnumerable<>)); + if (elementType == null + && effectiveType != null) { - var elementClrType = builder.Metadata.ClrType.TryGetElementType(typeof(IEnumerable<>)); - if (elementClrType == null) - { - throw new InvalidOperationException(CoreStrings.NotCollection(builder.Metadata.ClrType.ShortDisplayName(), propertyName)); - } - - builder.SetElementType(elementClrType, configurationSource!.Value); + throw new InvalidOperationException(CoreStrings.NotCollection(effectiveType.ShortDisplayName(), propertyName)); } - return builder; + return Property( + propertyType, propertyName, memberInfo, typeConfigurationSource, configurationSource, elementType: elementType, isCollection: true); } /// @@ -565,13 +591,17 @@ public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties( return null; } + var elementType = referencedProperties?[i].GetElementType()?.ClrType; var propertyBuilder = typeBuilder.Property( required ? type : type?.MakeNullable(), leafName, + memberInfo: null, typeConfigurationSource: null, - configurationSource.Value); + configurationSource.Value, + elementType: elementType, + isCollection: elementType != null); if (propertyBuilder == null) { @@ -1021,9 +1051,10 @@ public static int CountComplexCollectionsInPath(IReadOnlyPropertyBase property) /// public virtual IReadOnlyList? GetActualProperties( IReadOnlyList? properties, - ConfigurationSource? configurationSource) + ConfigurationSource? configurationSource, + IReadOnlyList? referencedProperties = null) { - var actual = GetActualProperties((IReadOnlyList?)properties, configurationSource); + var actual = GetActualProperties((IReadOnlyList?)properties, configurationSource, referencedProperties); if (actual == null) { return null; @@ -1037,7 +1068,13 @@ public static int CountComplexCollectionsInPath(IReadOnlyPropertyBase property) var result = new Property[actual.Count]; for (var i = 0; i < result.Length; i++) { - result[i] = (Property)actual[i]; + var property = actual[i] as Property; + if (property == null) + { + return null; + } + + result[i] = property; } return result; @@ -1051,7 +1088,8 @@ public static int CountComplexCollectionsInPath(IReadOnlyPropertyBase property) /// public virtual IReadOnlyList? GetActualProperties( IReadOnlyList? properties, - ConfigurationSource? configurationSource) + ConfigurationSource? configurationSource, + IReadOnlyList? referencedProperties = null) { if (properties == null) { @@ -1063,24 +1101,13 @@ public static int CountComplexCollectionsInPath(IReadOnlyPropertyBase property) return properties; } - for (var i = 0;; i++) - { - if (!IsActual(properties[i])) - { - break; - } - - if (i == properties.Count - 1) - { - return properties; - } - } - - var actualProperties = new PropertyBase[properties.Count]; - for (var i = 0; i < actualProperties.Length; i++) + PropertyBase[]? actualProperties = null; + for (var i = 0; i < properties.Count; i++) { var member = properties[i]; - if (member is not Property property) + var shouldBeCollection = ShouldBeCollection(referencedProperties, i); + if (shouldBeCollection == null + && member is not Property _) { if (!IsActual(member)) { @@ -1093,37 +1120,75 @@ public static int CountComplexCollectionsInPath(IReadOnlyPropertyBase property) member = resolved; } - - actualProperties[i] = member; - continue; } + else + { + if (!IsActual(member) + || shouldBeCollection != null) + { + var typeConfigurationSource = (member as Property)?.GetTypeConfigurationSource(); + var typeBuilder = member.IsInModel + && member.DeclaringType is ComplexType ownerComplex + && ownerComplex.ContainingEntityType.IsAssignableFrom(Metadata) + ? ownerComplex.Builder + : this; + + var builder = typeBuilder.Property( + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) + || (member.IsInModel && typeBuilder.Metadata.IsAssignableFrom(member.DeclaringType)) + ? member.ClrType + : null, + member.Name, + member.GetIdentifyingMemberInfo(), + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, + configurationSource, + isCollection: shouldBeCollection); + + if (builder == null) + { + return null; + } - var typeConfigurationSource = property.GetTypeConfigurationSource(); - var typeBuilder = property.IsInModel - && property.DeclaringType is ComplexType ownerComplex - && ownerComplex.ContainingEntityType.IsAssignableFrom(Metadata) - ? ownerComplex.Builder - : this; - - var builder = typeBuilder.Property( - typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) - || (property.IsInModel && typeBuilder.Metadata.IsAssignableFrom(property.DeclaringType)) - ? property.ClrType - : null, - property.Name, - property.GetIdentifyingMemberInfo(), - typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, - configurationSource); + member = builder.Metadata; + } + } - if (builder == null) + if (actualProperties == null) { - return null; + if (member == properties[i]) + { + continue; + } + + actualProperties = new PropertyBase[properties.Count]; + for (var j = 0; j < i; j++) + { + actualProperties[j] = properties[j]; + } } - actualProperties[i] = builder.Metadata; + actualProperties[i] = member; } - return actualProperties; + return actualProperties ?? properties; + } + + private static bool? ShouldBeCollection( + IReadOnlyList? referencedProperties, + int index) + { + if (referencedProperties == null + || index >= referencedProperties.Count) + { + return null; + } + + var referenced = referencedProperties[index]; + var declaringType = referenced.DeclaringType; + var actualReferenced = declaringType.IsInModel && declaringType.Builder.IsActual(referenced) + ? referenced + : declaringType.FindProperty(referenced.Name); + return actualReferenced == null ? null : actualReferenced.GetElementType() != null; } private bool IsActual(PropertyBase property) @@ -2214,6 +2279,105 @@ bool IConventionTypeBaseBuilder.CanHaveProperty(MemberInfo memberInfo, bool from 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] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.PrimitiveCollection( + Type propertyType, + string propertyName, + Type? elementType, + bool setTypeConfigurationSource, + bool fromDataAnnotation) + => PrimitiveCollection( + propertyType, + propertyName, + memberInfo: null, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + elementType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.PrimitiveCollection( + MemberInfo memberInfo, + Type? elementType, + bool fromDataAnnotation) + => PrimitiveCollection( + memberInfo.GetMemberType(), + memberInfo.GetSimpleMemberName(), + memberInfo, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + elementType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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.CanHavePrimitiveCollection( + Type? propertyType, + string propertyName, + Type? elementType, + bool fromDataAnnotation) + { + var effectiveType = propertyType + ?? (Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault()?.GetMemberType()); + if (effectiveType != null + && elementType == null + && effectiveType.TryGetElementType(typeof(IEnumerable<>)) == null) + { + return false; + } + + return 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 IConventionTypeBaseBuilder.CanHavePrimitiveCollection(MemberInfo memberInfo, Type? elementType, bool fromDataAnnotation) + { + if (elementType == null + && memberInfo.GetMemberType().TryGetElementType(typeof(IEnumerable<>)) == null) + { + return false; + } + + return 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 @@ -2230,7 +2394,8 @@ bool IConventionTypeBaseBuilder.CanHaveProperty(MemberInfo memberInfo, bool from propertyName, Metadata.FindIndexerPropertyInfo(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + isCollection: false); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 5f6c0ae2dd8..6441e568ddb 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -26,6 +26,7 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IRu private CoreTypeMapping? _typeMapping; private ValueComparer? _valueComparer; private ValueComparer? _keyValueComparer; + private readonly ElementType? _elementType; private ConfigurationSource? _typeConfigurationSource; private ConfigurationSource? _isNullableConfigurationSource; @@ -48,13 +49,19 @@ public Property( FieldInfo? fieldInfo, TypeBase declaringType, ConfigurationSource configurationSource, - ConfigurationSource? typeConfigurationSource) + ConfigurationSource? typeConfigurationSource, + Type? elementType = null) : base(name, propertyInfo, fieldInfo, configurationSource) { DeclaringType = declaringType; ClrType = clrType; _typeConfigurationSource = typeConfigurationSource; _builder = new InternalPropertyBuilder(this, declaringType.Model.Builder); + + if (elementType != null) + { + _elementType = new ElementType(elementType, this); + } } /// @@ -1409,7 +1416,7 @@ public virtual CoreTypeMapping? TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ElementType? GetElementType() - => (ElementType?)this[CoreAnnotationNames.ElementType]; + => _elementType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1429,56 +1436,6 @@ public virtual bool IsPrimitiveCollection } } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ElementType? SetElementType( - Type? elementType, - ConfigurationSource configurationSource) - { - var existingElementType = GetElementType(); - if (elementType != null - && elementType != existingElementType?.ClrType) - { - var newElementType = new ElementType(elementType, this, configurationSource); - SetAnnotation(CoreAnnotationNames.ElementType, newElementType, configurationSource); - OnElementTypeSet(newElementType, null); - return newElementType; - } - - if (elementType == null - && existingElementType != null) - { - existingElementType.SetRemovedFromModel(); - RemoveAnnotation(CoreAnnotationNames.ElementType); - OnElementTypeSet(null, existingElementType); - return null; - } - - return existingElementType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in 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 IElementType? OnElementTypeSet(IElementType? newElementType, IElementType? oldElementType) - => DeclaringType.Model.ConventionDispatcher.OnPropertyElementTypeChanged(Builder, newElementType, oldElementType); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code 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? GetElementTypeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.ElementType)?.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 @@ -2212,28 +2169,6 @@ void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) 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] - IConventionElementType? IConventionProperty.SetElementType(Type? elementType, bool fromDataAnnotation) - => SetElementType( - elementType, - 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.SetElementType(Type? elementType) - => SetElementType(elementType, 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 diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 33fd5296817..c9d6bc73f8d 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -633,7 +633,8 @@ private void CheckDiscriminatorProperty(Property? property) [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo? memberInfo, ConfigurationSource? typeConfigurationSource, - ConfigurationSource configurationSource) + ConfigurationSource configurationSource, + Type? elementType = null) { Check.NotNull(name); Check.NotNull(propertyType); @@ -685,7 +686,7 @@ private void CheckDiscriminatorProperty(Property? property) var property = new Property( name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, - configurationSource, typeConfigurationSource); + configurationSource, typeConfigurationSource, elementType); _properties.Add(property.Name, property); @@ -2003,6 +2004,25 @@ IMutableProperty IMutableTypeBase.AddProperty( 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] + IMutableProperty IMutableTypeBase.AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + Type elementType) + => AddProperty( + name, + propertyType, + memberInfo: null, + ConfigurationSource.Explicit, + ConfigurationSource.Explicit, + elementType)!; + /// /// This is an internal API that supports the 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/MemberClassifier.cs b/src/EFCore/Metadata/MemberClassifier.cs index 934aba4e9d3..ba4146b5127 100644 --- a/src/EFCore/Metadata/MemberClassifier.cs +++ b/src/EFCore/Metadata/MemberClassifier.cs @@ -120,9 +120,11 @@ public virtual bool IsCandidatePrimitiveProperty( IConventionModel model, bool useAttributes, out CoreTypeMapping? typeMapping, + out Type? elementType, out bool explicitlyConfigured) { typeMapping = null; + elementType = null; explicitlyConfigured = false; if (!memberInfo.IsCandidateProperty()) { @@ -131,9 +133,17 @@ public virtual bool IsCandidatePrimitiveProperty( var configurationType = GetConfigurationType(memberInfo.GetMemberType(), model); explicitlyConfigured = configurationType != null; - return configurationType == TypeConfigurationType.Property + var isCandidate = configurationType == TypeConfigurationType.Property || (configurationType == null && (typeMapping = Dependencies.TypeMappingSource.FindMapping(memberInfo, (IModel)model, useAttributes)) != null); + + if (isCandidate + && typeMapping?.ElementTypeMapping != null) + { + elementType = memberInfo.GetMemberType().TryGetElementType(typeof(IEnumerable<>)); + } + + return isCandidate; } /// diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs index 45bad720f10..a6de2c2dd98 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs @@ -32,7 +32,7 @@ public virtual Task Basic_cosmos_model() eb.HasKey("Id", "PartitionId"); eb.ToContainer("DataContainer"); eb.Property>("Map"); - eb.Property>>("List"); + eb.PrimitiveCollection>>("List"); eb.Property>("Bytes"); eb.UseETagConcurrency(); eb.HasNoDiscriminator(); diff --git a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs index 3dfe9ec25e4..a31725cd1e8 100644 --- a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs @@ -2394,7 +2394,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con e => e.Teams, "TeamPropertyBag", teamBuilder => { teamBuilder.Property("Name"); - teamBuilder.Property>("Members"); + teamBuilder.PrimitiveCollection>("Members"); teamBuilder.Property("Founded"); teamBuilder.Property("IsActive"); teamBuilder.Property("Rating"); @@ -2404,7 +2404,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con e => e.FeaturedTeam, "FeaturedTeamPropertyBag", featuredTeamBuilder => { featuredTeamBuilder.Property("Name"); - featuredTeamBuilder.Property>("Members"); + featuredTeamBuilder.PrimitiveCollection>("Members"); featuredTeamBuilder.Property("Founded"); featuredTeamBuilder.Property("IsActive"); featuredTeamBuilder.Property("Rating"); diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonRelationship.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonRelationship.cs index 74c6a53c28a..da90b0253bc 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonRelationship.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonRelationship.cs @@ -2670,6 +2670,24 @@ public virtual void Can_override_navigations_as_primitive_collections() Assert.NotNull(property.GetElementType()); } + [Fact] + public virtual void Can_override_primitive_collection_as_scalar_property() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().PrimitiveCollection(e => e.Up); + + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + Assert.NotNull(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()); + + modelBuilder.Entity().Property(e => e.Up); + + var property = entityType.FindProperty(nameof(CollectionQuarks.Up)); + Assert.NotNull(property); + Assert.Null(property.GetElementType()); + } + [Fact] public virtual void Primitive_collections_can_be_made_required() { diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs index 7a1e78a83fb..1a3d5049313 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs @@ -1147,14 +1147,17 @@ public virtual void Can_configure_relationship_with_PK_ValueConverter_shadow_FK( var ownedPkProperty = owned.FindPrimaryKey().Properties.Single(); Assert.NotNull(ownedPkProperty.GetValueConverter()); + Assert.Null(ownedPkProperty.GetElementType()); var category = model.FindEntityType(typeof(ValueCategory)); Assert.Null(category.FindProperty("TempId")); + Assert.Null(category.FindPrimaryKey().Properties.Single().GetElementType()); var categoryNavigation = owned.GetDeclaredNavigations().Single(n => !n.ForeignKey.IsOwnership); Assert.Same(category, categoryNavigation.TargetEntityType); var fkProperty = categoryNavigation.ForeignKey.Properties.Single(); Assert.Equal("CategoryId", fkProperty.Name); + Assert.Null(fkProperty.GetElementType()); Assert.Equal(3, model.GetEntityTypes().Count()); } @@ -1185,6 +1188,45 @@ public virtual void Can_configure_relationship_with_PK_ValueConverter() var ownedPkProperty = owned.FindPrimaryKey().Properties.Single(); Assert.NotNull(ownedPkProperty.GetValueConverter()); + Assert.Null(ownedPkProperty.GetElementType()); + + Assert.DoesNotContain(owned.GetDeclaredNavigations(), n => !n.ForeignKey.IsOwnership); + Assert.Equal(2, model.GetEntityTypes().Count()); + } + + [Fact] + public virtual void Can_configure_relationship_with_PK_ValueConverter_after_FK() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(eb => + { + eb.OwnsOne(q => q.Value) + .WithOwner() + .HasForeignKey(q => q.CategoryId); + }); + + // Configure the value converter on the principal key after the foreign key has been created. + // This recreates the principal key property as a scalar, which must also recreate the dependent + // foreign key property (a primitive collection by default) as a scalar. + modelBuilder.Entity() + .Property(x => x.Id) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); + + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var result = model.FindEntityType(typeof(QueryResult)); + Assert.Null(result.FindProperty("TempId")); + Assert.Null(result.FindPrimaryKey().Properties.Single().GetElementType()); + + var owned = result.GetDeclaredNavigations().Single().TargetEntityType; + Assert.Null(owned.FindProperty("TempId")); + + var ownedPkProperty = owned.FindPrimaryKey().Properties.Single(); + Assert.NotNull(ownedPkProperty.GetValueConverter()); + Assert.Null(ownedPkProperty.GetElementType()); Assert.DoesNotContain(owned.GetDeclaredNavigations(), n => !n.ForeignKey.IsOwnership); Assert.Equal(2, model.GetEntityTypes().Count()); diff --git a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs index bba3abce0f9..e31c1fb0acb 100644 --- a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs +++ b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs @@ -214,10 +214,10 @@ protected virtual void BuildBigModel(ModelBuilder modelBuilder, bool jsonColumns b.PrimitiveCollection(e => e.EnumU32AsStringArray).ElementType(b => b.HasConversion()); b.PrimitiveCollection(e => e.EnumU64AsStringArray).ElementType(b => b.HasConversion()); - b.Property(e => e.BoolReadOnlyCollection); - b.Property(e => e.UInt8ReadOnlyCollection).HasField("_uInt8ReadOnlyCollection"); - b.Property(e => e.Int32ReadOnlyCollection); - b.Property(e => e.StringReadOnlyCollection).HasField("_stringReadOnlyCollection"); + b.PrimitiveCollection(e => e.BoolReadOnlyCollection); + b.PrimitiveCollection(e => e.UInt8ReadOnlyCollection).HasField("_uInt8ReadOnlyCollection"); + b.PrimitiveCollection(e => e.Int32ReadOnlyCollection); + b.PrimitiveCollection(e => e.StringReadOnlyCollection).HasField("_stringReadOnlyCollection"); b.PrimitiveCollection(e => e.IPAddressReadOnlyCollection) .ElementType(b => b.HasConversion()) diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index dc33435177a..db1d572e3c2 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -3784,101 +3784,6 @@ public void ProcessPropertyFieldChanged( } } - [InlineData(false, false), InlineData(true, false), InlineData(false, true), InlineData(true, true), Theory] - public void OnPropertyElementTypeChanged_calls_conventions_in_order(bool useBuilder, bool useScope) - { - var conventions = new ConventionSet(); - - var convention1 = new PropertyElementTypeChangedConvention(terminate: false); - var convention2 = new PropertyElementTypeChangedConvention(terminate: true); - var convention3 = new PropertyElementTypeChangedConvention(terminate: false); - conventions.Add(convention1); - conventions.Add(convention2); - conventions.Add(convention3); - - var builder = new InternalModelBuilder(new Model(conventions)); - var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention)!; - var propertyBuilder = entityBuilder.Property(Order.OrderIdsProperty, ConfigurationSource.Convention)!; - - var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; - - ElementType elementType; - - if (useBuilder) - { - Assert.NotNull(propertyBuilder.SetElementType(typeof(int), ConfigurationSource.Convention)); - elementType = propertyBuilder.Metadata.GetElementType()!; - } - else - { - elementType = propertyBuilder.Metadata.SetElementType(typeof(int), ConfigurationSource.Convention); - } - - if (useScope) - { - Assert.Empty(convention1.Calls); - Assert.Empty(convention2.Calls); - scope.Dispose(); - } - - Assert.Equal(new (object, object)[] { (null, elementType) }, convention1.Calls); - Assert.Equal(new (object, object)[] { (null, elementType) }, convention2.Calls); - Assert.Empty(convention3.Calls); - - if (useBuilder) - { - Assert.NotNull(propertyBuilder.SetElementType(typeof(int), ConfigurationSource.Convention)); - elementType = propertyBuilder.Metadata.GetElementType()!; - } - else - { - elementType = propertyBuilder.Metadata.SetElementType(typeof(int), ConfigurationSource.Convention); - } - - Assert.Equal(new (object, object)[] { (null, elementType) }, convention1.Calls); - Assert.Equal(new (object, object)[] { (null, elementType) }, convention2.Calls); - Assert.Empty(convention3.Calls); - - if (useBuilder) - { - Assert.NotNull(propertyBuilder.SetElementType(null, ConfigurationSource.Convention)); - } - else - { - Assert.Null(propertyBuilder.Metadata.SetElementType(null, ConfigurationSource.Convention)); - } - - Assert.Equal(new (object, object)[] { (null, elementType), (elementType, null) }, convention1.Calls); - Assert.Equal(new (object, object)[] { (null, elementType), (elementType, null) }, convention2.Calls); - Assert.Empty(convention3.Calls); - - AssertSetOperations( - new PropertyElementTypeChangedConvention(terminate: true), - conventions, conventions.PropertyElementTypeChangedConventions); - } - - private class PropertyElementTypeChangedConvention(bool terminate) : IPropertyElementTypeChangedConvention - { - private readonly bool _terminate = terminate; - public readonly List<(object, object)> Calls = []; - - public void ProcessPropertyElementTypeChanged( - IConventionPropertyBuilder propertyBuilder, - IElementType newElementType, - IElementType oldElementType, - IConventionContext context) - { - Assert.True(propertyBuilder.Metadata.IsInModel); - - Calls.Add((oldElementType, newElementType)); - - if (_terminate) - { - context.StopProcessing(); - } - } - } - [InlineData(false, false), InlineData(true, false), InlineData(false, true), InlineData(true, true), Theory] public void OnPropertyAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) { @@ -5121,8 +5026,8 @@ public void OnElementTypeAnnotationChanged_calls_conventions_in_order(bool useBu var builder = new InternalModelBuilder(new Model(conventions)); var elementTypeBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention)! - .Property(nameof(SpecialOrder.OrderIds), ConfigurationSource.Convention)! - .SetElementType(typeof(int), ConfigurationSource.Convention)!; + .PrimitiveCollection(nameof(SpecialOrder.OrderIds), ConfigurationSource.Convention)! + .Metadata.GetElementType()!.Builder; var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -5221,8 +5126,8 @@ public void OnElementTypeNullabilityChanged_calls_conventions_in_order(bool useB var builder = new InternalModelBuilder(model); var elementTypeBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention)! - .Property(nameof(SpecialOrder.Notes), ConfigurationSource.Convention)! - .SetElementType(typeof(string), ConfigurationSource.Convention)!; + .PrimitiveCollection(nameof(SpecialOrder.Notes), ConfigurationSource.Convention)! + .Metadata.GetElementType()!.Builder; if (useBuilder) { diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs index 1be0026734f..7230151f46a 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs @@ -507,8 +507,7 @@ public void Can_set_element_type_for_primitive_collection() { var model = CreateModel(); var entityType = model.AddEntityType(typeof(object)); - var property = entityType.AddProperty("Random", typeof(IList)); - property.SetElementType(typeof(int)); + var property = entityType.AddProperty("Random", typeof(IList), typeof(int)); Assert.Equal(typeof(int), property.GetElementType()!.ClrType); Assert.True(property.IsPrimitiveCollection); @@ -519,8 +518,7 @@ public void Can_set_derived_element_type_for_primitive_collection() { var model = CreateModel(); var entityType = model.AddEntityType(typeof(object)); - var property = entityType.AddProperty("Random", typeof(IList)); - property.SetElementType(typeof(int)); + var property = entityType.AddProperty("Random", typeof(IList), typeof(int)); Assert.Equal(typeof(int), property.GetElementType()!.ClrType); Assert.True(property.IsPrimitiveCollection); @@ -531,8 +529,7 @@ public void Can_set_element_type_for_non_primitive_collection() { var model = CreateModel(); var entityType = model.AddEntityType(typeof(object)); - var property = entityType.AddProperty("Random", typeof(Random)); - property.SetElementType(typeof(int)); + var property = entityType.AddProperty("Random", typeof(Random), typeof(int)); Assert.Equal(typeof(int), property.GetElementType()!.ClrType); Assert.False(property.IsPrimitiveCollection);