From 360e2ca0e2dc14220aab5632cd752cc97f2a24db Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Tue, 25 Jul 2023 13:37:10 +0100 Subject: [PATCH] Metadata for primitive collection mapping Part of #30730 Some stuff remaining, but wanted to get this out there for initial review. Missing: - ElementType in compiled model - Negative cases - More tests --- .../RelationalElementTypeBuilderExtensions.cs | 131 +++ .../RelationalElementTypeExtensions.cs | 143 +++ .../RelationalPropertyExtensions.cs | 11 +- .../Metadata/RelationalAnnotationNames.cs | 5 + .../Storage/RelationalTypeMappingInfo.cs | 78 +- .../Storage/RelationalTypeMappingSource.cs | 147 ++- .../Metadata/Builders/ElementTypeBuilder.cs | 307 ++++++ .../Builders/IConventionElementTypeBuilder.cs | 320 ++++++ .../Builders/IConventionPropertyBuilder.cs | 16 + .../Metadata/Builders/PropertyBuilder.cs | 102 +- .../Metadata/Builders/PropertyBuilder`.cs | 67 +- .../Metadata/Conventions/ConventionSet.cs | 10 + ...IElementTypeAnnotationChangedConvention.cs | 28 + ...ElementTypeNullabilityChangedConvention.cs | 22 + .../ConventionDispatcher.ConventionScope.cs | 9 + ...entionDispatcher.DelayedConventionScope.cs | 57 +- ...tionDispatcher.ImmediateConventionScope.cs | 196 +++- .../Internal/ConventionDispatcher.cs | 28 + .../PropertyDiscoveryConvention.cs | 16 +- .../Conventions/RuntimeModelConvention.cs | 56 +- src/EFCore/Metadata/IConventionElementType.cs | 215 ++++ src/EFCore/Metadata/IConventionProperty.cs | 29 +- src/EFCore/Metadata/IElementType.cs | 28 + src/EFCore/Metadata/IMutableElementType.cs | 116 ++ src/EFCore/Metadata/IMutableProperty.cs | 33 +- src/EFCore/Metadata/IProperty.cs | 7 +- src/EFCore/Metadata/IReadOnlyElementType.cs | 165 +++ src/EFCore/Metadata/IReadOnlyProperty.cs | 21 +- .../Metadata/Internal/CoreAnnotationNames.cs | 11 +- src/EFCore/Metadata/Internal/ElementType.cs | 999 ++++++++++++++++++ .../Metadata/Internal/IMemberClassifier.cs | 9 +- .../Internal/InternalElementTypeBuilder.cs | 648 ++++++++++++ .../Internal/InternalPropertyBuilder.cs | 143 ++- .../Metadata/Internal/MemberClassifier.cs | 12 +- src/EFCore/Metadata/Internal/Property.cs | 47 +- src/EFCore/Metadata/RuntimeElementType.cs | 230 ++++ src/EFCore/Metadata/RuntimeProperty.cs | 32 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 50 +- src/EFCore/Properties/CoreStrings.Designer.cs | 32 + src/EFCore/Properties/CoreStrings.resx | 66 +- src/EFCore/Storage/CoreTypeMapping.cs | 45 +- src/EFCore/Storage/ITypeMappingSource.cs | 7 + src/EFCore/Storage/TypeMappingInfo.cs | 117 +- src/EFCore/Storage/TypeMappingSource.cs | 141 ++- src/EFCore/Storage/TypeMappingSourceBase.cs | 12 +- .../JsonTypesCosmosTest.cs | 4 - .../Design/CSharpMigrationsGeneratorTest.cs | 8 +- .../JsonTypesTestBase.cs | 273 +++-- .../JsonTypesSqlServerTest.cs | 6 +- .../Internal/ClrPropertyGetterFactoryTest.cs | 3 + .../Internal/ClrPropertySetterFactoryTest.cs | 3 + 51 files changed, 4729 insertions(+), 532 deletions(-) create mode 100644 src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs create mode 100644 src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs create mode 100644 src/EFCore/Metadata/Builders/ElementTypeBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs create mode 100644 src/EFCore/Metadata/Conventions/IElementTypeAnnotationChangedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IElementTypeNullabilityChangedConvention.cs create mode 100644 src/EFCore/Metadata/IConventionElementType.cs create mode 100644 src/EFCore/Metadata/IElementType.cs create mode 100644 src/EFCore/Metadata/IMutableElementType.cs create mode 100644 src/EFCore/Metadata/IReadOnlyElementType.cs create mode 100644 src/EFCore/Metadata/Internal/ElementType.cs create mode 100644 src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs create mode 100644 src/EFCore/Metadata/RuntimeElementType.cs diff --git a/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs new file mode 100644 index 00000000000..9ae6ce46f18 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs @@ -0,0 +1,131 @@ +// 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; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalElementTypeBuilderExtensions +{ + /// + /// Configures the data type of the elements of the collection. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the elements being configured. + /// The name of the data type of the elements. + /// The same builder instance so that multiple calls can be chained. + public static ElementTypeBuilder ElementsHaveDatabaseType( + this ElementTypeBuilder elementTypeBuilder, + string? typeName) + { + Check.NullButNotEmpty(typeName, nameof(typeName)); + + elementTypeBuilder.Metadata.SetStoreType(typeName); + + return elementTypeBuilder; + } + + /// + /// Configures the data type of the elements of the collection. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// builder for the elements being configured. + /// The name of the data type of the elements. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + public static IConventionElementTypeBuilder? ElementsHaveDatabaseType( + this IConventionElementTypeBuilder elementTypeBuilder, + string? typeName, + bool fromDataAnnotation = false) + { + if (!elementTypeBuilder.CanSetDatabaseType(typeName, fromDataAnnotation)) + { + return null; + } + + elementTypeBuilder.Metadata.SetStoreType(typeName, fromDataAnnotation); + return elementTypeBuilder; + } + + /// + /// Returns a value indicating whether the given data type can be set for the elements. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// builder for the elements being configured. + /// The name of the data type of the elements. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given data type can be set for the property. + public static bool CanSetDatabaseType( + this IConventionElementTypeBuilder elementTypeBuilder, + string? typeName, + bool fromDataAnnotation = false) + => elementTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.StoreType, typeName, fromDataAnnotation); + + /// + /// Configures the elements as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the elements being configured. + /// A value indicating whether the elements are constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ElementTypeBuilder ElementsAreFixedLength( + this ElementTypeBuilder elementTypeBuilder, + bool fixedLength = true) + { + elementTypeBuilder.Metadata.SetIsFixedLength(fixedLength); + + return elementTypeBuilder; + } + + /// + /// Configures the elements as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// builder for the elements being configured. + /// A value indicating whether the elements are constrained to fixed length values. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + public static IConventionElementTypeBuilder? ElementsAreFixedLength( + this IConventionElementTypeBuilder elementTypeBuilder, + bool? fixedLength, + bool fromDataAnnotation = false) + { + if (!elementTypeBuilder.CanSetFixedLength(fixedLength, fromDataAnnotation)) + { + return null; + } + + elementTypeBuilder.Metadata.SetIsFixedLength(fixedLength, fromDataAnnotation); + return elementTypeBuilder; + } + + /// + /// Returns a value indicating whether the elements can be configured as being fixed length or not. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// builder for the elements being configured. + /// A value indicating whether the elements are constrained to fixed length values. + /// Indicates whether the configuration was specified using a data annotation. + /// if the elements can be configured as being fixed length or not. + public static bool CanSetFixedLength( + this IConventionElementTypeBuilder elementTypeBuilder, + bool? fixedLength, + bool fromDataAnnotation = false) + => elementTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength, fromDataAnnotation); +} diff --git a/src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs new file mode 100644 index 00000000000..4e332f47071 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs @@ -0,0 +1,143 @@ +// 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; + +/// +/// extension methods for relational database metadata. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalElementTypeExtensions +{ + /// + /// Returns the database type of the elements, or if the database type could not be found. + /// + /// The element. + /// + /// The database type of the elements, or if the database type could not be found. + /// + public static string? GetStoreType(this IReadOnlyElementType elementType) + => (string?)(elementType.FindRelationalTypeMapping()?.StoreType + ?? elementType.FindAnnotation(RelationalAnnotationNames.StoreType)?.Value); + + /// + /// Returns the database type of the elements. + /// + /// The element. + /// The database type of the elements. + public static string GetStoreType(this IElementType elementType) + => ((IReadOnlyElementType)elementType).GetStoreType()!; + + /// + /// Sets the database type of the elements. + /// + /// The element. + /// The value to set. + public static void SetStoreType(this IMutableElementType elementType, string? value) + => elementType.SetOrRemoveAnnotation( + RelationalAnnotationNames.StoreType, + Check.NullButNotEmpty(value, nameof(value))); + + /// + /// Sets the database type of the elements. + /// + /// The element. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetStoreType( + this IConventionElementType elementType, + string? value, + bool fromDataAnnotation = false) + => (string?)elementType.SetOrRemoveAnnotation( + RelationalAnnotationNames.StoreType, + Check.NullButNotEmpty(value, nameof(value)), + fromDataAnnotation)?.Value; + + /// + /// Gets the for the database type. + /// + /// The element. + /// The for the column name. + public static ConfigurationSource? GetStoreTypeConfigurationSource(this IConventionElementType elementType) + => elementType.FindAnnotation(RelationalAnnotationNames.StoreType)?.GetConfigurationSource(); + + /// + /// Returns a flag indicating whether the elements are capable of storing only fixed-length data, such as strings. + /// + /// The element. + /// A flag indicating whether the elements arecapable of storing only fixed-length data, such as strings. + public static bool? IsFixedLength(this IReadOnlyElementType elementType) + => (bool?)elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.Value; + + /// + /// Returns a flag indicating whether the elements are capable of storing only fixed-length data, such as strings. + /// + /// The element. + /// The identifier of the table-like store object containing the column. + /// A flag indicating whether the elements are capable of storing only fixed-length data, such as strings. + public static bool? IsFixedLength(this IReadOnlyElementType elementType, in StoreObjectIdentifier storeObject) + => (bool?)elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.Value; + + /// + /// Sets a flag indicating whether the elements are capable of storing only fixed-length data, such as strings. + /// + /// The element. + /// A value indicating whether the elements are constrained to fixed length values. + public static void SetIsFixedLength(this IMutableElementType elementType, bool? fixedLength) + => elementType.SetOrRemoveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength); + + /// + /// Sets a flag indicating whether the elements are capable of storing only fixed-length data, such as strings. + /// + /// The element. + /// A value indicating whether the element are constrained to fixed length values. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static bool? SetIsFixedLength( + this IConventionElementType elementType, + bool? fixedLength, + bool fromDataAnnotation = false) + => (bool?)elementType.SetOrRemoveAnnotation( + RelationalAnnotationNames.IsFixedLength, + fixedLength, + fromDataAnnotation)?.Value; + + /// + /// Gets the for . + /// + /// The element. + /// The for . + public static ConfigurationSource? GetIsFixedLengthConfigurationSource(this IConventionElementType elementType) + => elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.GetConfigurationSource(); + + /// + /// Returns the collation to be used for the column. + /// + /// The element. + /// The collation for the column this element is mapped to. + public static string? GetCollation(this IReadOnlyElementType elementType) + => (elementType is RuntimeElementType) + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)elementType.FindAnnotation(RelationalAnnotationNames.Collation)?.Value; + + /// + /// Returns the for the given element on a finalized model. + /// + /// The element. + /// The type mapping. + [DebuggerStepThrough] + public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyElementType elementType) + => (RelationalTypeMapping)elementType.GetTypeMapping(); + + /// + /// Returns the for the given element on a finalized model. + /// + /// The element. + /// The type mapping, or if none was found. + [DebuggerStepThrough] + public static RelationalTypeMapping? FindRelationalTypeMapping(this IReadOnlyElementType elementType) + => (RelationalTypeMapping?)elementType.FindTypeMapping(); +} diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index e8fded6f6e6..1079a28a18f 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -67,7 +67,7 @@ public static string GetColumnName(this IReadOnlyProperty property) { tableFound = true; } - else if(property.DeclaringType is IReadOnlyEntityType declaringEntityType) + else if (property.DeclaringType is IReadOnlyEntityType declaringEntityType) { foreach (var containingType in declaringEntityType.GetDerivedTypesInclusive()) { @@ -84,7 +84,7 @@ public static string GetColumnName(this IReadOnlyProperty property) return null; } } - else + else { var declaringEntityType = property.DeclaringType.ContainingEntityType; if (declaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) @@ -213,7 +213,6 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) return sharedTablePrincipalConcurrencyProperty.GetColumnName(storeObject)!; } - StringBuilder? builder = null; var currentStoreObject = storeObject; if (property.DeclaringType is IReadOnlyEntityType entityType) @@ -242,8 +241,8 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) } } else if (StoreObjectIdentifier.Create(property.DeclaringType, currentStoreObject.StoreObjectType) == currentStoreObject - || property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType) - .Any(f => f.StoreObject == currentStoreObject)) + || property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType) + .Any(f => f.StoreObject == currentStoreObject)) { var complexType = (IReadOnlyComplexType)property.DeclaringType; builder ??= new StringBuilder(); @@ -1176,7 +1175,7 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj return property.IsNullable || (property.DeclaringType is IReadOnlyEntityType entityType && ((entityType.BaseType != null - && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy) + && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy) || IsOptionalSharingDependent(entityType, storeObject, 0))); } diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 94972e366ef..2aee5c9cef4 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -329,4 +329,9 @@ public static class RelationalAnnotationNames /// The JSON property name for the element that the property/navigation maps to. /// public const string JsonPropertyName = Prefix + "JsonPropertyName"; + + /// + /// The name for store (database) type annotations. + /// + public const string StoreType = Prefix + "StoreType"; } diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs index f192227bf96..dd0e93deb25 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs @@ -27,6 +27,50 @@ public RelationalTypeMappingInfo(IProperty property) { } + /// + /// Creates a new instance of . + /// + /// The collection element for which mapping is needed. + /// The provider-specific relational type name for which mapping is needed. + /// The provider-specific relational type name, with any facets removed. + /// Specifies Unicode or ANSI for the mapping or for the default. + /// Specifies a fixed length mapping, or for the default. + /// + /// Specifies a size for the mapping, in case one isn't found at the core level, or for the + /// default. + /// + /// + /// Specifies a precision for the mapping, in case one isn't found at the core level, or + /// for the default. + /// + /// + /// Specifies a scale for the mapping, in case one isn't found at the core level, or for + /// the default. + /// + public RelationalTypeMappingInfo( + IElementType elementType, + string? storeTypeName = null, + string? storeTypeNameBase = null, + bool? fallbackUnicode = null, + bool? fallbackFixedLength = null, + int? fallbackSize = null, + int? fallbackPrecision = null, + int? fallbackScale = null) + { + _coreTypeMappingInfo = new TypeMappingInfo(elementType, fallbackUnicode, fallbackSize, fallbackPrecision, fallbackScale); + + fallbackFixedLength ??= elementType.IsFixedLength(); + storeTypeName ??= elementType.GetStoreType(); + + var customConverter = elementType.GetValueConverter(); + var mappingHints = customConverter?.MappingHints; + + IsFixedLength = fallbackFixedLength ?? (mappingHints as RelationalConverterMappingHints)?.IsFixedLength; + DbType = (mappingHints as RelationalConverterMappingHints)?.DbType; + StoreTypeName = storeTypeName; + StoreTypeNameBase = storeTypeNameBase; + } + /// /// Creates a new instance of . /// @@ -164,23 +208,6 @@ public RelationalTypeMappingInfo( DbType = source.DbType ?? (mappingHints as RelationalConverterMappingHints)?.DbType; } - /// - /// Creates a new instance of with the given . for collection - /// elements. - /// - /// The source info. - /// The element mapping to use. - public RelationalTypeMappingInfo( - in RelationalTypeMappingInfo source, - RelationalTypeMapping elementMapping) - { - _coreTypeMappingInfo = source._coreTypeMappingInfo.WithElementTypeMapping(elementMapping); - StoreTypeName = source.StoreTypeName; - StoreTypeNameBase = source.StoreTypeNameBase; - IsFixedLength = source.IsFixedLength; - DbType = source.DbType; - } - /// /// Creates a new instance of . /// @@ -302,15 +329,6 @@ public Type? ClrType init => _coreTypeMappingInfo = _coreTypeMappingInfo with { ClrType = value }; } - /// - /// The element type mapping, if the mapping is for a collection of primitives, or otherwise. - /// - public CoreTypeMapping? ElementTypeMapping - { - get => _coreTypeMappingInfo.ElementTypeMapping; - init => _coreTypeMappingInfo = _coreTypeMappingInfo with { ElementTypeMapping = value }; - } - /// /// The JSON reader/writer, if one has been provided, or otherwise. /// @@ -327,12 +345,4 @@ public JsonValueReaderWriter? JsonValueReaderWriter /// The new mapping info. public RelationalTypeMappingInfo WithConverter(in ValueConverterInfo converterInfo) => new(this, converterInfo); - - /// - /// Returns a new with the given converter applied. - /// - /// The element mapping to use. - /// The new mapping info. - public RelationalTypeMappingInfo WithElementTypeMapping(in RelationalTypeMapping elementMapping) - => new(this, elementMapping); } diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index 1dae9261d22..cedd100bb77 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -31,8 +31,8 @@ namespace Microsoft.EntityFrameworkCore.Storage; /// public abstract class RelationalTypeMappingSource : TypeMappingSourceBase, IRelationalTypeMappingSource { - private readonly ConcurrentDictionary<(RelationalTypeMappingInfo, Type?, ValueConverter?), RelationalTypeMapping?> _explicitMappings - = new(); + private readonly ConcurrentDictionary<(RelationalTypeMappingInfo, Type?, ValueConverter?, CoreTypeMapping?), RelationalTypeMapping?> + _explicitMappings = new(); /// /// Initializes a new instance of this class. @@ -91,6 +91,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) { Type? providerClrType = null; ValueConverter? customConverter = null; + CoreTypeMapping? elementMapping = null; if (principals != null) { for (var i = 0; i < principals.Count; i++) @@ -113,10 +114,16 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) customConverter = converter; } } + + var element = principal.GetElementType(); + if (element != null) + { + elementMapping = FindMapping(element); + } } } - var resolvedMapping = FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + var resolvedMapping = FindMappingWithConversion(mappingInfo, providerClrType, customConverter, elementMapping); ValidateMapping(resolvedMapping, principals?[0]); @@ -126,73 +133,78 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) private RelationalTypeMapping? FindMappingWithConversion( RelationalTypeMappingInfo mappingInfo, Type? providerClrType, - ValueConverter? customConverter) + ValueConverter? customConverter, + CoreTypeMapping? elementMapping) => _explicitMappings.GetOrAdd( - (mappingInfo, providerClrType, customConverter), + (mappingInfo, providerClrType, customConverter, elementMapping), static (k, self) => { - var (info, providerType, converter) = k; - var mapping = providerType == null - || providerType == info.ClrType - ? self.FindMapping(info) - : null; + var (info, providerType, converter, elementMapping) = k; - if (mapping == null) + var sourceType = info.ClrType; + RelationalTypeMapping? mapping = null; + + if (elementMapping == null + || converter != null) { - var sourceType = info.ClrType; + mapping = providerType == null + || providerType == info.ClrType + ? self.FindMapping(info) + : null; - if (sourceType != null) + if (mapping == null) { - foreach (var converterInfo in self.Dependencies - .ValueConverterSelector - .Select(sourceType, providerType)) + if (sourceType != null) { - var mappingInfoUsed = info.WithConverter(converterInfo); - mapping = self.FindMapping(mappingInfoUsed); - - if (mapping == null - && providerType != null) + foreach (var converterInfo in self.Dependencies + .ValueConverterSelector + .Select(sourceType, providerType)) { - foreach (var secondConverterInfo in self.Dependencies - .ValueConverterSelector - .Select(providerType)) - { - mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo)); + var mappingInfoUsed = info.WithConverter(converterInfo); + mapping = self.FindMapping(mappingInfoUsed); - if (mapping != null) + if (mapping == null + && providerType != null) + { + foreach (var secondConverterInfo in self.Dependencies + .ValueConverterSelector + .Select(providerType)) { - mapping = (RelationalTypeMapping)mapping.Clone( - secondConverterInfo.Create(), - mappingInfoUsed.ElementTypeMapping, - jsonValueReaderWriter: mappingInfoUsed.JsonValueReaderWriter); - break; + mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo)); + + if (mapping != null) + { + mapping = (RelationalTypeMapping)mapping.Clone( + secondConverterInfo.Create(), + jsonValueReaderWriter: mappingInfoUsed.JsonValueReaderWriter); + break; + } } } - } - if (mapping != null) - { - mapping = (RelationalTypeMapping)mapping.Clone( - converterInfo.Create(), - info.ElementTypeMapping, - jsonValueReaderWriter: info.JsonValueReaderWriter); - break; + if (mapping != null) + { + mapping = (RelationalTypeMapping)mapping.Clone( + converterInfo.Create(), + jsonValueReaderWriter: info.JsonValueReaderWriter); + break; + } } - } - if (mapping == null) - { - mapping = self.TryFindCollectionMapping(info, sourceType, providerType); + mapping ??= self.TryFindCollectionMapping(info, sourceType, providerType, elementMapping); } } } + else if (sourceType != null) + { + mapping = self.TryFindCollectionMapping(info, sourceType, providerType, elementMapping); + } if (mapping != null && converter != null) { mapping = (RelationalTypeMapping)mapping.Clone( converter, - info.ElementTypeMapping, jsonValueReaderWriter: info.JsonValueReaderWriter); } @@ -206,14 +218,15 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) /// The mapping info being used. /// The model type. /// The provider type. - /// The type mapping, or if none was found. + /// The element mapping, if known. + /// The type mapping, or if none was found. protected virtual RelationalTypeMapping? TryFindCollectionMapping( RelationalTypeMappingInfo info, Type modelType, - Type? providerType) + Type? providerType, + CoreTypeMapping? elementMapping) => TryFindJsonCollectionMapping( - info.CoreTypeMappingInfo, modelType, providerType, out var elementMapping, - out var collectionReaderWriter) + info.CoreTypeMappingInfo, modelType, providerType, ref elementMapping, out var collectionReaderWriter) ? (RelationalTypeMapping)FindMapping( info.WithConverter( // Note that the converter info is only used temporarily here and never creates an instance. @@ -268,6 +281,35 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) principals); } + /// + /// Finds the type mapping for the given . + /// + /// + /// Note: providers should typically not need to override this method. + /// + /// The collection element. + /// The type mapping, or if none was found. + public override CoreTypeMapping? FindMapping(IElementType elementType) + { + var storeTypeName = (string?)elementType[RelationalAnnotationNames.StoreType]; + var isFixedLength = elementType.IsFixedLength(); + bool? unicode = null; + int? size = null; + int? precision = null; + int? scale = null; + var storeTypeNameBase = ParseStoreTypeName(storeTypeName, ref unicode, ref size, ref precision, ref scale); + var providerClrType = elementType.GetProviderClrType(); + var customConverter = elementType.GetValueConverter(); + + var resolvedMapping = FindMappingWithConversion( + new RelationalTypeMappingInfo(elementType, storeTypeName, storeTypeNameBase, unicode, isFixedLength, size, precision, scale), + providerClrType, customConverter, null); + + ValidateMapping(resolvedMapping, null); + + return resolvedMapping; + } + /// /// Finds the type mapping for a given . /// @@ -339,12 +381,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) scale: scale); } - if (elementMapping != null) - { - mappingInfo = mappingInfo.WithElementTypeMapping((RelationalTypeMapping)elementMapping); - } - - return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + return FindMappingWithConversion(mappingInfo, providerClrType, customConverter, (RelationalTypeMapping?)elementMapping); } /// diff --git a/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs new file mode 100644 index 00000000000..d9a023ff15e --- /dev/null +++ b/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs @@ -0,0 +1,307 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public class ElementTypeBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ElementTypeBuilder(IMutableElementType elementType) + { + Check.NotNull(elementType, nameof(elementType)); + + Builder = ((ElementType)elementType).Builder; + } + + /// + /// The internal builder being used to configure the elements of the collection. + /// + IConventionElementTypeBuilder IInfrastructure.Instance + => Builder; + + private InternalElementTypeBuilder Builder { get; } + + /// + /// The elements of the collection being configured. + /// + public virtual IMutableElementType Metadata + => Builder.Metadata; + + /// + /// Adds or updates an annotation on the elements of the collection. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether elements of the collection must have a value or can be . + /// An element can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether elements of the collection must not be . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsAreRequired(bool required = true) + { + Builder.ElementsAreRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the maximum length of data that can be stored in elements of the collection. + /// + /// + /// The maximum length of data allowed in elements of the collection. A value of -1 indicates that elements of the + /// collection have no maximum length. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveMaxLength(int maxLength) + { + Builder.ElementsHaveMaxLength(maxLength, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision and scale of elements of the collection. + /// + /// The precision of elements of the collection. + /// The scale of elements of the collection. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHavePrecision(int precision, int scale) + { + Builder.ElementsHavePrecision(precision, ConfigurationSource.Explicit); + Builder.ElementsHaveScale(scale, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision of elements of the collection. + /// + /// The precision of elements of the collection. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHavePrecision(int precision) + { + Builder.ElementsHavePrecision(precision, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether elements of the collection are capable of persisting unicode characters. + /// + /// A value indicating whether elements of the collection can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsAreUnicode(bool unicode = true) + { + Builder.ElementsAreUnicode(unicode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures elements of the collection so their values are converted before writing to the database and converted back + /// when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => ElementsHaveConversion(typeof(TConversion)); + + /// + /// Configures elements of the collection so that their values are converted before writing to the database and converted back + /// when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? conversionType) + { + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit); + } + + return this; + } + + /// + /// Configures elements of the collection so that their values are converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? converter) + => ElementsHaveConversion(converter, null); + + /// + /// Configures elements of the collection so that their values are converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion>( + ValueComparer? valueComparer) + => ElementsHaveConversion(typeof(TConversion), valueComparer); + + /// + /// Configures elements of the collection so that their values are converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + ValueComparer? valueComparer) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.ElementsHaveValueComparer(valueComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures elements of the collection so that their values are converted before + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? converter, ValueComparer? valueComparer) + { + Builder.ElementsHaveConversion(converter, ConfigurationSource.Explicit); + Builder.ElementsHaveValueComparer(valueComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures elements of the collection so that their values are converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer>() + where TComparer : ValueComparer + => ElementsHaveConversion(typeof(TConversion), typeof(TComparer)); + + /// + /// Configures elements of the collection so that their values are converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ElementTypeBuilder ElementsHaveConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.ElementsHaveValueComparer(comparerType, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs new file mode 100644 index 00000000000..ffdc0adc9cf --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs @@ -0,0 +1,320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder +{ + /// + /// Gets the collection elements being configured. + /// + new IConventionElementType Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new IConventionElementTypeBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionElementTypeBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new IConventionElementTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + + /// + /// Configures whether elements of the collection must have a value or can be . + /// An element can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether elements of the collection must not be . + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the requiredness was configured, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsAreRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether this element requiredness can be configured from the current configuration source. + /// + /// + /// A value indicating whether the elements are required, or to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the element requiredness can be configured. + bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Configures the maximum length of data that can be stored in elements of the collection. + /// + /// + /// The maximum length of data allowed in elements of the collection. A value of -1 indicates that elements of the + /// collection have no maximum length. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the maximum length of elements can be set from the current configuration source. + /// + /// The maximum length of elements in the collection. + /// Indicates whether the configuration was specified using a data annotation. + /// if the maximum length of data allowed can be set for the elements. + bool CanSetMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Configures whether elements of the collection are capable of persisting unicode characters. + /// + /// A value indicating whether elements of the collection can contain unicode characters. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsAreUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the elements can be configured as capable of persisting unicode characters + /// from the current configuration source. + /// + /// A value indicating whether the elements can contain unicode characters. + /// Indicates whether the configuration was specified using a data annotation. + /// if the capability of persisting unicode characters can be configured. + bool CanSetIsUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Configures the precision of elements of the collection. + /// + /// The precision of elements of the collection. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsHavePrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the precision of elements can be set from the current configuration source. + /// + /// The precision of the elements. + /// Indicates whether the configuration was specified using a data annotation. + /// if the precision of data allowed can be set. + bool CanSetPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Configures the scale of elements of the collection. + /// + /// The scale of elements of the collection. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the scale of elements can be set from the current configuration source. + /// + /// The scale of the elements. + /// Indicates whether the configuration was specified using a data annotation. + /// if the scale of data allowed can be set. + bool CanSetScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Configures elements of the collection so their values are converted before writing to the database and converted back + /// when reading from the database. + /// + /// The converter to use. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveConversion(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for the elements + /// from the current configuration source. + /// + /// The converter to use. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the can be configured. + /// + bool CanSetConversion(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Configures elements of the collection so their values are converted before writing to the database and converted back + /// when reading from the database. + /// + /// The type to convert to and from. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveConversion(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given type to convert values to and from + /// can be configured for the elements from the current configuration source. + /// + /// The type to convert to and from. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given type to convert values to and from can be configured. + /// + bool CanSetConversion(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Configures elements of the collection so their values are converted before writing to the database and converted back + /// when reading from the database. + /// + /// + /// A type that derives from , + /// or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, or otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the can be configured for the elements + /// from the current configuration source. + /// + /// + /// A type that derives from , + /// or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the can be configured. + /// + bool CanSetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + bool fromDataAnnotation = false); + + /// + /// Configures the for elements of the collection. + /// + /// The type mapping, or to remove any previously set type mapping. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, or otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured from the current configuration source. + /// + /// The type mapping. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured. + /// + bool CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); + + /// + /// Configures the for elements of the collection. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, or otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured from the current configuration source. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured. + /// + bool CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Configures the for elements of the collection. + /// + /// + /// A type that derives from , or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, or otherwise. + /// + IConventionElementTypeBuilder? ElementsHaveValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given + /// can be configured from the current configuration source. + /// + /// + /// A type that derives from , or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the given can be configured. + /// + bool CanSetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 7254760366e..5ce9def04d3 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -538,4 +538,20 @@ bool CanSetValueComparer( 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. + /// + IConventionPropertyBuilder? HasElementType(IElementType? 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. + /// + bool CanSetElementType(IElementType? elementType, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 1e8604eda01..4bebdd46c0f 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -85,7 +85,7 @@ public virtual PropertyBuilder IsRequired(bool required = true) /// Maximum length can only be set on array properties (including properties). /// /// - /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. /// /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasMaxLength(int maxLength) @@ -221,7 +221,8 @@ public virtual PropertyBuilder HasValueGenerator /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType) { Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit); @@ -317,7 +318,8 @@ public virtual PropertyBuilder HasValueGeneratorFactory /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType) { Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit); @@ -462,7 +464,8 @@ public virtual PropertyBuilder UsePropertyAccessMode(PropertyAccessMode property /// /// The type to convert to and from or a type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. - public virtual PropertyBuilder HasConversion<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + public virtual PropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() => HasConversion(typeof(TConversion)); /// @@ -472,7 +475,8 @@ public virtual PropertyBuilder UsePropertyAccessMode(PropertyAccessMode property /// The type to convert to and from or a type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? conversionType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? conversionType) { if (typeof(ValueConverter).IsAssignableFrom(conversionType)) { @@ -503,8 +507,9 @@ public virtual PropertyBuilder HasConversion(ValueConverter? converter) /// The type to convert to and from or a type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( - ValueComparer? valueComparer) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion>( + ValueComparer? valueComparer) => HasConversion(typeof(TConversion), valueComparer); /// @@ -529,7 +534,8 @@ public virtual PropertyBuilder HasConversion /// The comparer to use for values before conversion. /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, ValueComparer? valueComparer) => HasConversion(conversionType, valueComparer, null); @@ -543,7 +549,8 @@ public virtual PropertyBuilder HasConversion( /// The comparer to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, ValueComparer? valueComparer, ValueComparer? providerComparer) { @@ -599,8 +606,10 @@ public virtual PropertyBuilder HasConversion(ValueConverter? converter, ValueCom /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer>() where TComparer : ValueComparer => HasConversion(typeof(TConversion), typeof(TComparer)); @@ -613,9 +622,12 @@ public virtual PropertyBuilder HasConversion< /// A type that inherits from to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TProviderComparer>() where TComparer : ValueComparer where TProviderComparer : ValueComparer => HasConversion(typeof(TConversion), typeof(TComparer), typeof(TProviderComparer)); @@ -628,8 +640,10 @@ public virtual PropertyBuilder HasConversion< /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) => HasConversion(conversionType, comparerType, null); /// @@ -641,9 +655,12 @@ public virtual PropertyBuilder HasConversion( /// A type that inherits from to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerComparerType) { Check.NotNull(conversionType, nameof(conversionType)); @@ -662,6 +679,53 @@ public virtual PropertyBuilder HasConversion( return this; } + /// + /// Configures this property as a collection containing elements of a primitive type. + /// + /// + /// If , then the property is configured as a collection, otherwise + /// it is treated as a normal, non-collection property. + /// + /// A builder to configure the collection element type. + public virtual PropertyBuilder IsCollection(bool collection = true) + { + if (!collection) + { + Builder.HasElementType(null, ConfigurationSource.Explicit); + } + else + { + IsCollection(_ => { }); + } + + return this; + } + + /// + /// Configures this property as a collection containing elements of a primitive type. + /// + /// An action that performs configuration of the collection element type. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual PropertyBuilder IsCollection(Action builderAction) + { + var property = Builder.Metadata; + var elementType = property.ClrType.TryGetElementType(typeof(IEnumerable<>)); + if (elementType == null) + { + throw new InvalidOperationException(CoreStrings.NotCollection(property.DeclaringType, property)); + } + + var element = (ElementType?)property.GetElementType() + ?? new ElementType(elementType, property, ConfigurationSource.Explicit); + + property.SetElementType(element, ConfigurationSource.Explicit); + property.SetValueConverter((Type?)null, ConfigurationSource.Explicit); + + builderAction(new ElementTypeBuilder(element)); + + return this; + } + #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs index 19d1e0fbf6a..b278065ebed 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs @@ -57,7 +57,7 @@ public PropertyBuilder(IMutableProperty property) /// Maximum length can only be set on array properties (including properties). /// /// - /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. /// /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasMaxLength(int maxLength) @@ -163,7 +163,8 @@ public PropertyBuilder(IMutableProperty property) /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType) => (PropertyBuilder)base.HasValueGenerator(valueGeneratorType); /// @@ -249,7 +250,8 @@ public PropertyBuilder(IMutableProperty property) /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType) => (PropertyBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType); /// @@ -368,7 +370,8 @@ public PropertyBuilder(IMutableProperty property) /// The type to convert to and from or a type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerClrType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerClrType) => (PropertyBuilder)base.HasConversion(providerClrType); /// @@ -440,7 +443,8 @@ public virtual PropertyBuilder HasConversion(ValueConverte /// The comparer to use for values before conversion. /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, ValueComparer? valueComparer) => (PropertyBuilder)base.HasConversion(conversionType, valueComparer); @@ -453,7 +457,8 @@ public virtual PropertyBuilder HasConversion(ValueConverte /// The comparer to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, ValueComparer? valueComparer, ValueComparer? providerComparer) => (PropertyBuilder)base.HasConversion(conversionType, valueComparer, providerComparer); @@ -561,8 +566,10 @@ public virtual PropertyBuilder HasConversion( /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer>() where TComparer : ValueComparer => (PropertyBuilder)base.HasConversion(); @@ -575,9 +582,12 @@ public virtual PropertyBuilder HasConversion( /// A type that inherits from to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TProviderComparer>() where TComparer : ValueComparer where TProviderComparer : ValueComparer => (PropertyBuilder)base.HasConversion(); @@ -590,8 +600,10 @@ public virtual PropertyBuilder HasConversion( /// A type that inherits from . /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) => (PropertyBuilder)base.HasConversion(conversionType, comparerType); /// @@ -603,8 +615,31 @@ public virtual PropertyBuilder HasConversion( /// A type that inherits from to use for the provider values. /// The same builder instance so that multiple configuration calls can be chained. public new virtual PropertyBuilder HasConversion( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerComparerType) => (PropertyBuilder)base.HasConversion(conversionType, comparerType, providerComparerType); + + /// + /// Configures this property as a collection containing elements of a primitive type. + /// + /// + /// If , then the property is configured as a collection, otherwise + /// it is treated as a normal, non-collection property. + /// + /// A builder to configure the collection element type. + public new virtual PropertyBuilder IsCollection(bool collection = true) + => (PropertyBuilder)base.IsCollection(collection); + + /// + /// Configures this property as a collection containing elements of a primitive type. + /// + /// An action that performs configuration of the collection element type. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual PropertyBuilder IsCollection( + Action builderAction) + => (PropertyBuilder)base.IsCollection(builderAction); } diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs index 05716c93bfc..48cff2994fc 100644 --- a/src/EFCore/Metadata/Conventions/ConventionSet.cs +++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs @@ -267,6 +267,16 @@ public class ConventionSet /// public virtual List PropertyRemovedConventions { get; } = new(); + /// + /// Conventions to run when the nullability of the element of a collection is changed. + /// + public virtual List ElementTypeNullabilityChangedConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is changed on the element of a collection. + /// + public virtual List ElementTypeAnnotationChangedConventions { get; } = new(); + /// /// Replaces an existing convention with a derived convention. Also registers the new convention for any /// convention types not implemented by the existing convention. diff --git a/src/EFCore/Metadata/Conventions/IElementTypeAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IElementTypeAnnotationChangedConvention.cs new file mode 100644 index 00000000000..b1ea356f8cc --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IElementTypeAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on the elements of a collection property. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IElementTypeAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a . + /// + /// The builder for the property. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessPropertyAnnotationChanged( + IConventionElementTypeBuilder builder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IElementTypeNullabilityChangedConvention.cs b/src/EFCore/Metadata/Conventions/IElementTypeNullabilityChangedConvention.cs new file mode 100644 index 00000000000..71dcae74c39 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IElementTypeNullabilityChangedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the nullability on the elements of a collection property has changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IElementTypeNullabilityChangedConvention : IConvention +{ + /// + /// Called after the nullability for an is changed. + /// + /// The builder for the element. + /// Additional information associated with convention execution. + void ProcessPropertyNullabilityChanged( + IConventionElementTypeBuilder builder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index ca87a070c5a..fb7619de2bc 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -225,6 +225,12 @@ public int GetLeafCount() IConventionAnnotation? annotation, IConventionAnnotation? oldAnnotation); + public abstract IConventionAnnotation? OnElementTypeAnnotationChanged( + IConventionElementTypeBuilder builder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + public abstract FieldInfo? OnPropertyFieldChanged( IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, @@ -233,6 +239,9 @@ public int GetLeafCount() public abstract bool? OnPropertyNullabilityChanged( IConventionPropertyBuilder propertyBuilder); + public abstract bool? OnElementTypeNullabilityChanged( + IConventionElementTypeBuilder builder); + public abstract IConventionProperty? OnPropertyRemoved( IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index 8057398c93c..cb9e3e0f037 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -395,6 +395,12 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu return propertyBuilder.Metadata.IsNullable; } + public override bool? OnElementTypeNullabilityChanged(IConventionElementTypeBuilder builder) + { + Add(new OnElementTypeNullabilityChangedNode(builder)); + return builder.Metadata.IsNullable; + } + public override FieldInfo? OnPropertyFieldChanged( IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, @@ -414,6 +420,16 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu return annotation; } + public override IConventionAnnotation? OnElementTypeAnnotationChanged( + IConventionElementTypeBuilder builder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + Add(new OnElementTypeAnnotationChangedNode(builder, name, annotation, oldAnnotation)); + return annotation; + } + public override IConventionProperty OnPropertyRemoved( IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) @@ -635,7 +651,9 @@ public override void Run(ConventionDispatcher dispatcher) private sealed class OnComplexPropertyFieldChangedNode : ConventionNode { public OnComplexPropertyFieldChangedNode( - IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) { PropertyBuilder = propertyBuilder; NewFieldInfo = newFieldInfo; @@ -1192,6 +1210,19 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnPropertyNullabilityChanged(PropertyBuilder); } + private sealed class OnElementTypeNullabilityChangedNode : ConventionNode + { + public OnElementTypeNullabilityChangedNode(IConventionElementTypeBuilder builder) + { + ElementTypeBuilder = builder; + } + + public IConventionElementTypeBuilder ElementTypeBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnElementTypeNullabilityChanged(ElementTypeBuilder); + } + private sealed class OnPropertyFieldChangedNode : ConventionNode { public OnPropertyFieldChangedNode(IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) @@ -1233,6 +1264,30 @@ public override void Run(ConventionDispatcher dispatcher) PropertyBuilder, Name, Annotation, OldAnnotation); } + private sealed class OnElementTypeAnnotationChangedNode : ConventionNode + { + public OnElementTypeAnnotationChangedNode( + IConventionElementTypeBuilder elementTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + ElementTypeBuilder = elementTypeBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionElementTypeBuilder ElementTypeBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnElementTypeAnnotationChanged( + ElementTypeBuilder, Name, Annotation, OldAnnotation); + } + private sealed class OnPropertyRemovedNode : ConventionNode { public OnPropertyRemovedNode( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index 2b2fa6e1c2c..3b7f67c8ba9 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -133,7 +133,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB } #if DEBUG - Check.DebugAssert(initialValue == modelBuilder.Metadata[name], + Check.DebugAssert( + initialValue == modelBuilder.Metadata[name], $"Convention {modelConvention.GetType().Name} changed value without terminating"); #endif } @@ -158,7 +159,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _stringConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == modelBuilder.Metadata.IsIgnored(name), + Check.DebugAssert( + initialValue == modelBuilder.Metadata.IsIgnored(name), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } @@ -188,7 +190,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _entityTypeBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsInModel, + Check.DebugAssert( + initialValue == entityTypeBuilder.Metadata.IsInModel, $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } @@ -246,7 +249,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _stringConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsIgnored(name), + Check.DebugAssert( + initialValue == entityTypeBuilder.Metadata.IsIgnored(name), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } @@ -281,7 +285,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _entityTypeConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.BaseType, + Check.DebugAssert( + initialValue == entityTypeBuilder.Metadata.BaseType, $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } @@ -316,7 +321,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _keyConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.FindPrimaryKey(), + Check.DebugAssert( + initialValue == entityTypeBuilder.Metadata.FindPrimaryKey(), $"Convention {keyConvention.GetType().Name} changed value without terminating"); #endif } @@ -343,7 +349,6 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB _annotationConventionContext.ResetState(annotation); foreach (var entityTypeConvention in _conventionSet.EntityTypeAnnotationChangedConventions) { - entityTypeConvention.ProcessEntityTypeAnnotationChanged( entityTypeBuilder, name, annotation, oldAnnotation, _annotationConventionContext); if (_annotationConventionContext.ShouldStopProcessing()) @@ -351,7 +356,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(entityTypeBuilder.Metadata.IsInModel + Check.DebugAssert( + entityTypeBuilder.Metadata.IsInModel && initialValue == entityTypeBuilder.Metadata[name], $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif @@ -389,7 +395,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _stringConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsIgnored(name), + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.IsIgnored(name), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } @@ -416,7 +423,6 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB _annotationConventionContext.ResetState(annotation); foreach (var complexTypeConvention in _conventionSet.ComplexTypeAnnotationChangedConventions) { - complexTypeConvention.ProcessComplexTypeAnnotationChanged( complexTypeBuilder, name, annotation, oldAnnotation, _annotationConventionContext); if (_annotationConventionContext.ShouldStopProcessing()) @@ -424,7 +430,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(complexTypeBuilder.Metadata.IsInModel + Check.DebugAssert( + complexTypeBuilder.Metadata.IsInModel && initialValue == complexTypeBuilder.Metadata[name], $"Convention {complexTypeConvention.GetType().Name} changed value without terminating"); #endif @@ -456,7 +463,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _complexPropertyBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsInModel, + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.IsInModel, $"Convention {complexPropertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -511,7 +519,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsNullable, + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.IsNullable, $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -543,7 +552,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _fieldInfoConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.FieldInfo, + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.FieldInfo, $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -569,7 +579,6 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB _annotationConventionContext.ResetState(annotation); foreach (var propertyConvention in _conventionSet.ComplexPropertyAnnotationChangedConventions) { - propertyConvention.ProcessComplexPropertyAnnotationChanged( propertyBuilder, name, annotation, oldAnnotation, _annotationConventionContext); if (_annotationConventionContext.ShouldStopProcessing()) @@ -577,7 +586,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(propertyBuilder.Metadata.IsInModel + Check.DebugAssert( + propertyBuilder.Metadata.IsInModel && initialValue == propertyBuilder.Metadata[name], $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif @@ -612,7 +622,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _relationshipBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(relationshipBuilder.Metadata.DeclaringEntityType.IsInModel + Check.DebugAssert( + relationshipBuilder.Metadata.DeclaringEntityType.IsInModel && relationshipBuilder.Metadata.PrincipalEntityType.IsInModel && relationshipBuilder.Metadata.IsInModel, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); @@ -672,9 +683,11 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _propertyListConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialProperties == relationshipBuilder.Metadata.Properties, + Check.DebugAssert( + initialProperties == relationshipBuilder.Metadata.Properties, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); - Check.DebugAssert(initialPrincipalKey == relationshipBuilder.Metadata.PrincipalKey, + Check.DebugAssert( + initialPrincipalKey == relationshipBuilder.Metadata.PrincipalKey, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -705,7 +718,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == relationshipBuilder.Metadata.IsUnique, + Check.DebugAssert( + initialValue == relationshipBuilder.Metadata.IsUnique, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -737,7 +751,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == relationshipBuilder.Metadata.IsRequired, + Check.DebugAssert( + initialValue == relationshipBuilder.Metadata.IsRequired, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -769,7 +784,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == relationshipBuilder.Metadata.IsRequiredDependent, + Check.DebugAssert( + initialValue == relationshipBuilder.Metadata.IsRequiredDependent, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -800,7 +816,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == relationshipBuilder.Metadata.IsOwnership, + Check.DebugAssert( + initialValue == relationshipBuilder.Metadata.IsOwnership, $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -859,8 +876,9 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(relationshipBuilder.Metadata.IsInModel - && initialValue == relationshipBuilder.Metadata[name], + Check.DebugAssert( + relationshipBuilder.Metadata.IsInModel + && initialValue == relationshipBuilder.Metadata[name], $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); #endif } @@ -895,7 +913,9 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _navigationConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == (pointsToPrincipal + Check.DebugAssert( + initialValue + == (pointsToPrincipal ? relationshipBuilder.Metadata.DependentToPrincipal : relationshipBuilder.Metadata.PrincipalToDependent), $"Convention {foreignKeyConvention.GetType().Name} changed value without terminating"); @@ -924,7 +944,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _navigationConventionBuilderContext.Result; } #if DEBUG - Check.DebugAssert(navigationBuilder.Metadata.IsInModel, + Check.DebugAssert( + navigationBuilder.Metadata.IsInModel, $"Convention {navigationConvention.GetType().Name} changed value without terminating"); #endif } @@ -960,7 +981,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(relationshipBuilder.Metadata.IsInModel + Check.DebugAssert( + relationshipBuilder.Metadata.IsInModel && relationshipBuilder.Metadata.GetNavigation(navigation.IsOnDependent) == navigation && initialValue == navigation[name], $"Convention {navigationConvention.GetType().Name} changed value without terminating"); @@ -1019,7 +1041,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _skipNavigationBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(navigationBuilder.Metadata.IsInModel, + Check.DebugAssert( + navigationBuilder.Metadata.IsInModel, $"Convention {skipNavigationConvention.GetType().Name} changed value without terminating"); #endif } @@ -1060,7 +1083,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(navigationBuilder.Metadata.IsInModel + Check.DebugAssert( + navigationBuilder.Metadata.IsInModel && initialValue == navigationBuilder.Metadata[name], $"Convention {skipNavigationConvention.GetType().Name} changed value without terminating"); #endif @@ -1100,7 +1124,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _foreignKeyConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == navigationBuilder.Metadata.ForeignKey, + Check.DebugAssert( + initialValue == navigationBuilder.Metadata.ForeignKey, $"Convention {skipNavigationConvention.GetType().Name} changed value without terminating"); #endif } @@ -1134,7 +1159,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _skipNavigationConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == navigationBuilder.Metadata.Inverse, + Check.DebugAssert( + initialValue == navigationBuilder.Metadata.Inverse, $"Convention {skipNavigationConvention.GetType().Name} changed value without terminating"); #endif } @@ -1187,7 +1213,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _triggerBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(triggerBuilder.Metadata.IsInModel, + Check.DebugAssert( + triggerBuilder.Metadata.IsInModel, $"Convention {triggerConvention.GetType().Name} changed value without terminating"); #endif } @@ -1237,7 +1264,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _keyBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(keyBuilder.Metadata.IsInModel, + Check.DebugAssert( + keyBuilder.Metadata.IsInModel, $"Convention {keyConvention.GetType().Name} changed value without terminating"); #endif } @@ -1290,7 +1318,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(keyBuilder.Metadata.IsInModel + Check.DebugAssert( + keyBuilder.Metadata.IsInModel && initialValue == keyBuilder.Metadata[name], $"Convention {keyConvention.GetType().Name} changed value without terminating"); #endif @@ -1323,7 +1352,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _indexBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(indexBuilder.Metadata.IsInModel, + Check.DebugAssert( + indexBuilder.Metadata.IsInModel, $"Convention {indexConvention.GetType().Name} changed value without terminating"); #endif } @@ -1371,7 +1401,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == indexBuilder.Metadata.IsUnique, + Check.DebugAssert( + initialValue == indexBuilder.Metadata.IsUnique, $"Convention {indexConvention.GetType().Name} changed value without terminating"); #endif } @@ -1401,7 +1432,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolListConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == indexBuilder.Metadata.IsDescending, + Check.DebugAssert( + initialValue == indexBuilder.Metadata.IsDescending, $"Convention {indexConvention.GetType().Name} changed value without terminating"); #endif } @@ -1435,7 +1467,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(indexBuilder.Metadata.IsInModel + Check.DebugAssert( + indexBuilder.Metadata.IsInModel && initialValue == indexBuilder.Metadata[name], $"Convention {indexConvention.GetType().Name} changed value without terminating"); #endif @@ -1468,7 +1501,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _propertyBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(propertyBuilder.Metadata.IsInModel, + Check.DebugAssert( + propertyBuilder.Metadata.IsInModel, $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -1502,7 +1536,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _boolConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsNullable, + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.IsNullable, $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -1511,6 +1546,41 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result; } + public override bool? OnElementTypeNullabilityChanged(IConventionElementTypeBuilder builder) + { + if (!builder.Metadata.CollectionProperty.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = builder.Metadata.IsNullable; +#endif + using (_dispatcher.DelayConventions()) + { + _boolConventionContext.ResetState(builder.Metadata.IsNullable); + foreach (var elementConvention in _conventionSet.ElementTypeNullabilityChangedConventions) + { + if (!builder.Metadata.IsInModel) + { + return null; + } + + elementConvention.ProcessPropertyNullabilityChanged(builder, _boolConventionContext); + if (_boolConventionContext.ShouldStopProcessing()) + { + return _boolConventionContext.Result; + } +#if DEBUG + Check.DebugAssert( + initialValue == builder.Metadata.IsNullable, + $"Convention {elementConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !builder.Metadata.IsInModel ? null : _boolConventionContext.Result; + } + public override FieldInfo? OnPropertyFieldChanged( IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, @@ -1534,7 +1604,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _fieldInfoConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == propertyBuilder.Metadata.FieldInfo, + Check.DebugAssert( + initialValue == propertyBuilder.Metadata.FieldInfo, $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } @@ -1569,7 +1640,8 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(propertyBuilder.Metadata is { IsInModel: true, DeclaringType.IsInModel: true } + Check.DebugAssert( + propertyBuilder.Metadata is { IsInModel: true, DeclaringType.IsInModel: true } && initialValue == propertyBuilder.Metadata[name], $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif @@ -1579,6 +1651,44 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !propertyBuilder.Metadata.IsInModel ? null : annotation; } + public override IConventionAnnotation? OnElementTypeAnnotationChanged( + IConventionElementTypeBuilder builder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!builder.Metadata.IsInModel + || !builder.Metadata.CollectionProperty.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = builder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var elementConvention in _conventionSet.ElementTypeAnnotationChangedConventions) + { + elementConvention.ProcessPropertyAnnotationChanged( + builder, name, annotation, oldAnnotation, _annotationConventionContext); + + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert( + builder.Metadata is { IsInModel: true, CollectionProperty.IsInModel: true } + && initialValue == builder.Metadata[name], + $"Convention {elementConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !builder.Metadata.IsInModel ? null : annotation; + } + public override IConventionProperty? OnPropertyRemoved( IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 6d860e316d1..b2b049d38dc 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -668,6 +668,15 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder public virtual bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) => _scope.OnPropertyNullabilityChanged(propertyBuilder); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? OnElementTypeNullabilityChanged(IConventionElementTypeBuilder builder) + => _scope.OnElementTypeNullabilityChanged(builder); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -704,6 +713,25 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder oldAnnotation); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnElementTypeAnnotationChanged( + IConventionElementTypeBuilder builder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => CoreAnnotationNames.AllNames.Contains(name) + ? annotation + : _scope.OnElementTypeAnnotationChanged( + builder, + name, + annotation, + oldAnnotation); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index 78d27ecf6fe..905768dadf2 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -45,7 +45,7 @@ public void ProcessComplexPropertyAdded( var model = complexType.Model; foreach (var propertyInfo in complexType.GetRuntimeProperties().Values) { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model)) + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model, out _)) { continue; } @@ -55,7 +55,7 @@ public void ProcessComplexPropertyAdded( foreach (var fieldInfo in complexType.GetRuntimeFields().Values) { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(fieldInfo, model)) + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(fieldInfo, model, out _)) { continue; } @@ -85,13 +85,21 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder) var model = entityType.Model; foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model) + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model, out var mapping) || ((Model)model).FindIsComplexConfigurationSource(propertyInfo.GetMemberType().UnwrapNullableType()) != null) { continue; } - entityTypeBuilder.Property(propertyInfo); + var builder = entityTypeBuilder.Property(propertyInfo); + if (mapping?.ElementTypeMapping != null) + { + builder?.Metadata.SetElementType( + new ElementType( + mapping.ElementTypeMapping.ClrType, + (Property)builder.Metadata, + ConfigurationSource.Convention)); + } } } } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index f158cbe1bca..4dbf58eb5ee 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -29,7 +29,7 @@ public RuntimeModelConvention( /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// + /// public virtual IModel ProcessModelFinalized(IModel model) => Create(model); @@ -363,7 +363,8 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim property.GetKeyValueComparer(), property.GetProviderValueComparer(), property.GetJsonValueReaderWriter(), - property.GetTypeMapping()) + property.GetTypeMapping(), + property.GetElementType()) : ((RuntimeComplexType)runtimeType).AddProperty( property.Name, property.ClrType, @@ -387,7 +388,8 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim property.GetKeyValueComparer(), property.GetProviderValueComparer(), property.GetJsonValueReaderWriter(), - property.GetTypeMapping()); + property.GetTypeMapping(), + property.GetElementType()); /// /// Updates the property annotations that will be set on the read-only object. @@ -450,18 +452,18 @@ protected virtual void ProcessServicePropertyAnnotations( private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeEntityType runtimeEntityType) { var runtimeComplexProperty = runtimeEntityType.AddComplexProperty( - complexProperty.Name, - complexProperty.ClrType, - complexProperty.ComplexType.Name, - complexProperty.ComplexType.ClrType, - complexProperty.PropertyInfo, - complexProperty.FieldInfo, - complexProperty.GetPropertyAccessMode(), - complexProperty.IsNullable, - complexProperty.IsCollection, - complexProperty.ComplexType.GetChangeTrackingStrategy(), - complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag); + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); var complexType = complexProperty.ComplexType; var runtimeComplexType = runtimeComplexProperty.ComplexType; @@ -488,18 +490,18 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeE private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeComplexType runtimeComplexType) { var runtimeComplexProperty = runtimeComplexType.AddComplexProperty( - complexProperty.Name, - complexProperty.ClrType, - complexProperty.ComplexType.Name, - complexProperty.ComplexType.ClrType, - complexProperty.PropertyInfo, - complexProperty.FieldInfo, - complexProperty.GetPropertyAccessMode(), - complexProperty.IsNullable, - complexProperty.IsCollection, - complexProperty.ComplexType.GetChangeTrackingStrategy(), - complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag); + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); var complexType = complexProperty.ComplexType; var newRuntimeComplexType = runtimeComplexProperty.ComplexType; diff --git a/src/EFCore/Metadata/IConventionElementType.cs b/src/EFCore/Metadata/IConventionElementType.cs new file mode 100644 index 00000000000..5ff6fb71a66 --- /dev/null +++ b/src/EFCore/Metadata/IConventionElementType.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the elements of a collection property. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionElementType : IReadOnlyElementType, IConventionAnnotatable +{ + /// + /// Gets the collection property for which this represents the element. + /// + new IConventionProperty CollectionProperty { get; } + + /// + /// Returns the configuration source for this element. + /// + /// The configuration source. + ConfigurationSource GetConfigurationSource(); + + /// + /// Gets the builder that can be used to configure this element. + /// + /// If the element has been removed from the model. + new IConventionElementTypeBuilder Builder { get; } + + /// + /// Sets a value indicating whether elements in the collection can be . + /// + /// + /// A value indicating whether whether elements in the collection can be , or to + /// reset to the default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsNullable(bool? nullable, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsNullableConfigurationSource(); + + /// + /// Sets the for the given element. + /// + /// The for this element. + /// Indicates whether the configuration was specified using a data annotation. + CoreTypeMapping? SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation = false); + + /// + /// Gets the for of the element. + /// + /// The for of the element. + ConfigurationSource? GetTypeMappingConfigurationSource(); + + /// + /// Sets the maximum length of data that is allowed in elements of the collection. For example, if the element type is + /// a then this is the maximum number of characters. + /// + /// The maximum length of data that is allowed in each element. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured property. + int? SetMaxLength(int? maxLength, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetMaxLengthConfigurationSource(); + + /// + /// Sets the precision of data that is allowed in elements of the collection. + /// For example, if the element type is a , then this is the maximum number of digits. + /// + /// The maximum number of digits that is allowed in each element. + /// Indicates whether the configuration was specified using a data annotation. + int? SetPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetPrecisionConfigurationSource(); + + /// + /// Sets the scale of data that is allowed in this elements of the collection. + /// For example, if the element type is a , then this is the maximum number of decimal places. + /// + /// The maximum number of decimal places that is allowed in each element. + /// Indicates whether the configuration was specified using a data annotation. + int? SetScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetScaleConfigurationSource(); + + /// + /// Sets a value indicating whether elements of the collection can persist Unicode characters. + /// + /// + /// if the elements of the collection accept Unicode characters, if they do not, + /// or to clear the setting. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsUnicode(bool? unicode, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsUnicodeConfigurationSource(); + + /// + /// Sets the custom for this elements of the collection. + /// + /// The converter, or to remove any previously set converter. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueConverter? SetValueConverter(ValueConverter? converter, bool fromDataAnnotation = false); + + /// + /// Sets the custom for this elements of the collection. + /// + /// + /// A type that inherits from , or to remove any previously set converter. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueConverterConfigurationSource(); + + /// + /// Sets the type that the elements of the collection will be converted to before being sent to the database provider. + /// + /// The type to use, or to remove any previously set type. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetProviderClrType(Type? providerClrType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetProviderClrTypeConfigurationSource(); + + /// + /// Sets the custom for elements of the collection. + /// + /// The comparer, or to remove any previously set comparer. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + ValueComparer? SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false); + + /// + /// Sets the custom for elements of the collection. + /// + /// + /// A type that inherits from , or to remove any previously set comparer. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetValueComparerConfigurationSource(); + + /// + /// Sets the type of to use for elements of the collection. + /// + /// + /// A type that inherits from , or to use the reader/writer + /// from the type mapping. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + Type? SetJsonValueReaderWriterType(Type? readerWriterType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource(); +} diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index a2674a41936..d2a9bf40283 100644 --- a/src/EFCore/Metadata/IConventionProperty.cs +++ b/src/EFCore/Metadata/IConventionProperty.cs @@ -30,7 +30,8 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas /// Gets the entity type that this property belongs to. /// [Obsolete("Use DeclaringType and cast to IConventionEntityType or IConventionComplexType")] - new IConventionEntityType DeclaringEntityType => (IConventionEntityType)DeclaringType; + new IConventionEntityType DeclaringEntityType + => (IConventionEntityType)DeclaringType; /// /// Returns the configuration source for . @@ -335,7 +336,8 @@ bool IsImplicitlyCreated() /// Indicates whether the configuration was specified using a data annotation. /// The configured value. Type? SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactory, bool fromDataAnnotation = false); /// @@ -361,7 +363,8 @@ bool IsImplicitlyCreated() /// Indicates whether the configuration was specified using a data annotation. /// The configured value. Type? SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, bool fromDataAnnotation = false); /// @@ -402,7 +405,8 @@ bool IsImplicitlyCreated() /// The configured value. [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, bool fromDataAnnotation = false); /// @@ -429,7 +433,8 @@ bool IsImplicitlyCreated() /// The configured value. [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, bool fromDataAnnotation = false); /// @@ -454,4 +459,18 @@ bool IsImplicitlyCreated() /// /// The configuration source for . ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource(); + + /// + /// Sets the configuration for elements of the primitive collection represented by this property. + /// + /// The element configuration to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configuration for the elements. + IElementType? SetElementType(IElementType? element, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetElementTypeConfigurationSource(); } diff --git a/src/EFCore/Metadata/IElementType.cs b/src/EFCore/Metadata/IElementType.cs new file mode 100644 index 00000000000..117e602d9c2 --- /dev/null +++ b/src/EFCore/Metadata/IElementType.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the elements of a collection property. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IElementType : IReadOnlyElementType, IAnnotatable +{ + /// + /// Gets the collection property for which this represents the element. + /// + new IProperty CollectionProperty + { + [DebuggerStepThrough] + get => (IProperty)((IReadOnlyElementType)this).CollectionProperty; + } + + /// + /// Gets the for elements of the collection. + /// + /// The comparer. + new ValueComparer GetValueComparer(); +} diff --git a/src/EFCore/Metadata/IMutableElementType.cs b/src/EFCore/Metadata/IMutableElementType.cs new file mode 100644 index 00000000000..db729ca754d --- /dev/null +++ b/src/EFCore/Metadata/IMutableElementType.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the elements of a collection property. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableElementType : IReadOnlyElementType, IMutableAnnotatable +{ + /// + /// Gets the collection property for which this represents the element. + /// + new IMutableProperty CollectionProperty { get; } + + /// + /// Gets or sets a value indicating whether elements of the collection can be . + /// + new bool IsNullable { get; set; } + + /// + /// Sets the maximum length of data that is allowed in elements of the collection. For example, if the element type is + /// a then this is the maximum number of characters. + /// + /// The maximum length of data that is allowed in this elements of the collection. + void SetMaxLength(int? maxLength); + + /// + /// Sets the precision of data that is allowed in elements of the collection. + /// For example, if the element type is a , then this is the maximum number of digits. + /// + /// The maximum number of digits that is allowed in each element. + void SetPrecision(int? precision); + + /// + /// Sets the scale of data that is allowed in this elements of the collection. + /// For example, if the element type is a , then this is the maximum number of decimal places. + /// + /// The maximum number of decimal places that is allowed in each element. + void SetScale(int? scale); + + /// + /// Sets a value indicating whether elements of the collection can persist Unicode characters. + /// + /// + /// if the elements of the collection accept Unicode characters, if they do not, + /// or to clear the setting. + /// + void SetIsUnicode(bool? unicode); + + /// + /// Sets the custom for this elements of the collection. + /// + /// The converter, or to remove any previously set converter. + void SetValueConverter(ValueConverter? converter); + + /// + /// Sets the custom for this elements of the collection. + /// + /// + /// A type that inherits from , or to remove any previously set converter. + /// + void SetValueConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType); + + /// + /// Sets the type that the elements of the collection will be converted to before being sent to the database provider. + /// + /// The type to use, or to remove any previously set type. + void SetProviderClrType(Type? providerClrType); + + /// + /// Sets the for the given element. + /// + /// The for this element. + void SetTypeMapping(CoreTypeMapping typeMapping); + + /// + /// Sets the custom for elements of the collection. + /// + /// The comparer, or to remove any previously set comparer. + void SetValueComparer(ValueComparer? comparer); + + /// + /// Sets the custom for elements of the collection. + /// + /// + /// A type that inherits from , or to remove any previously set comparer. + /// + void SetValueComparer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); + + /// + /// Sets the type of to use for elements of the collection. + /// + /// + /// A type that inherits from , or to use the reader/writer + /// from the type mapping. + /// + void SetJsonValueReaderWriterType(Type? readerWriterType); + + /// + bool IReadOnlyElementType.IsNullable + => IsNullable; +} diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index fa4cabc33bb..26d482fa7e8 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -25,7 +25,8 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// Gets the entity type that this property belongs to. /// [Obsolete("Use DeclaringType and cast to IMutableEntityType or IMutableComplexType")] - new IMutableEntityType DeclaringEntityType => (IMutableEntityType)DeclaringType; + new IMutableEntityType DeclaringEntityType + => (IMutableEntityType)DeclaringType; /// /// Gets or sets a value indicating whether this property can contain . @@ -204,7 +205,8 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// clear any previously set factory. /// void SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory); + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactory); /// /// Sets the custom for this property. @@ -259,7 +261,8 @@ void SetValueGeneratorFactory( /// A type that derives from , or to remove any previously set comparer. /// void SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType); + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType); /// /// Sets the type of to use for this property for this property. @@ -270,15 +273,21 @@ void SetProviderValueComparer( /// void SetJsonValueReaderWriterType(Type? readerWriterType); - /// - bool IReadOnlyProperty.IsNullable => - IsNullable; + /// + /// Sets the configuration for elements of the primitive collection represented by this property. + /// + /// The element configuration to use. + void SetElementType(IElementType? elementType); + + /// + bool IReadOnlyProperty.IsNullable + => IsNullable; - /// - ValueGenerated IReadOnlyProperty.ValueGenerated => - ValueGenerated; + /// + ValueGenerated IReadOnlyProperty.ValueGenerated + => ValueGenerated; - /// - bool IReadOnlyProperty.IsConcurrencyToken => - IsConcurrencyToken; + /// + bool IReadOnlyProperty.IsConcurrencyToken + => IsConcurrencyToken; } diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 8b7bc5337c2..cbf7f7c3429 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -17,7 +18,8 @@ public interface IProperty : IReadOnlyProperty, IPropertyBase /// Gets the entity type that this property belongs to. /// [Obsolete("Use DeclaringType and cast to IEntityType or IComplexType")] - new IEntityType DeclaringEntityType => (IEntityType)DeclaringType; + new IEntityType DeclaringEntityType + => (IEntityType)DeclaringType; /// /// Creates an for values of the given property type. @@ -98,8 +100,7 @@ IEqualityComparer CreateKeyEqualityComparer() /// The comparer. new ValueComparer GetProviderValueComparer(); - - internal const System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = + internal const DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties diff --git a/src/EFCore/Metadata/IReadOnlyElementType.cs b/src/EFCore/Metadata/IReadOnlyElementType.cs new file mode 100644 index 00000000000..c4b4e79e4eb --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyElementType.cs @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the elements of a collection property. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyElementType : IReadOnlyAnnotatable +{ + /// + /// Gets the collection property for which this represents the element. + /// + IReadOnlyProperty CollectionProperty { get; } + + /// + /// The type of elements in the collection. + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes | IProperty.DynamicallyAccessedMemberTypes)] + Type ClrType { get; } + + /// + /// Gets a value indicating whether elements of the collection can be . + /// + bool IsNullable { get; } + + /// + /// Returns the for the elements of the collection from a finalized model. + /// + /// The type mapping. + CoreTypeMapping GetTypeMapping() + { + var mapping = FindTypeMapping(); + if (mapping == null) + { + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); + } + + return mapping; + } + + /// + /// Returns the type mapping for elements of the collection. + /// + /// The type mapping, or if none was found. + CoreTypeMapping? FindTypeMapping(); + + /// + /// Gets the maximum length of data that is allowed in elements of the collection. For example, if the element type is + /// a then this is the maximum number of characters. + /// + /// + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. + /// + int? GetMaxLength(); + + /// + /// Gets the precision of data that is allowed in elements of the collection. + /// For example, if the element type is a , then this is the maximum number of digits. + /// + /// The precision, or if none is defined. + int? GetPrecision(); + + /// + /// Gets the scale of data that is allowed in this elements of the collection. + /// For example, if the element type is a , then this is the maximum number of decimal places. + /// + /// The scale, or if none is defined. + int? GetScale(); + + /// + /// Gets a value indicating whether elements of the collection can persist Unicode characters. + /// + /// The Unicode setting, or if none is defined. + bool? IsUnicode(); + + /// + /// Gets the custom for this elements of the collection. + /// + /// The converter, or if none has been set. + ValueConverter? GetValueConverter(); + + /// + /// Gets the type that the elements of the collection will be converted to before being sent to the database provider. + /// + /// The provider type, or if none has been set. + Type? GetProviderClrType(); + + /// + /// Gets the custom for elements of the collection. + /// + /// The comparer, or if none has been set. + ValueComparer? GetValueComparer(); + + /// + /// Gets the type of to use for elements of the collection. + /// + /// The reader/writer, or if none has been set. + JsonValueReaderWriter? GetJsonValueReaderWriter(); + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append("Element type: "); + } + + builder.Append(ClrType.ShortDisplayName()); + + if (!IsNullable) + { + builder.Append(" Required"); + } + + if (GetMaxLength() != null) + { + builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); + } + + if (IsUnicode() == false) + { + builder.Append(" ANSI"); + } + + if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs index 79b18681f02..9af37ca3d29 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; -using System.Text; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -19,7 +19,8 @@ public interface IReadOnlyProperty : IReadOnlyPropertyBase /// Gets the entity type that this property belongs to. /// [Obsolete("Use DeclaringType and cast to IReadOnlyEntityType or IReadOnlyComplexType")] - IReadOnlyEntityType DeclaringEntityType => (IReadOnlyEntityType)DeclaringType; + IReadOnlyEntityType DeclaringEntityType + => (IReadOnlyEntityType)DeclaringType; /// /// Gets a value indicating whether this property can contain . @@ -70,8 +71,8 @@ CoreTypeMapping GetTypeMapping() /// then this is the maximum number of characters. /// /// - /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been - /// set. + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. /// int? GetMaxLength(); @@ -172,6 +173,12 @@ CoreTypeMapping GetTypeMapping() /// The reader/writer, or if none has been set. JsonValueReaderWriter? GetJsonValueReaderWriter(); + /// + /// Gets the configuration for elements of the primitive collection represented by this property. + /// + /// The configuration for the elements. + IElementType? GetElementType(); + /// /// Finds the first principal property that the given property is constrained by /// if the given property is part of a foreign key. @@ -413,6 +420,12 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); } + var elementType = GetElementType(); + if (elementType != null) + { + builder.Append(" Element type:").Append(elementType.ToDebugString()); + } + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 && ((AnnotatableBase)this).IsReadOnly) { diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index 998d7b70837..5d969565376 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -332,6 +332,14 @@ public static class CoreAnnotationNames /// public const string JsonValueReaderWriterType = "JsonValueReaderWriterType"; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string ElementType = "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 @@ -381,6 +389,7 @@ public static class CoreAnnotationNames DuplicateServiceProperties, FullChangeTrackingNotificationsRequired, AdHocModel, - JsonValueReaderWriterType + JsonValueReaderWriterType, + ElementType }; } diff --git a/src/EFCore/Metadata/Internal/ElementType.cs b/src/EFCore/Metadata/Internal/ElementType.cs new file mode 100644 index 00000000000..6e48913ecc0 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ElementType.cs @@ -0,0 +1,999 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class ElementType : ConventionAnnotatable, IMutableElementType, IConventionElementType, IElementType +{ + private InternalElementTypeBuilder? _builder; + + private bool? _isNullable; + private CoreTypeMapping? _typeMapping; + + private ConfigurationSource _configurationSource; + private ConfigurationSource? _isNullableConfigurationSource; + private ConfigurationSource? _typeMappingConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public ElementType( + Type clrType, + Property collectionProperty, + ConfigurationSource configurationSource) + { + ClrType = clrType; + CollectionProperty = collectionProperty; + _configurationSource = configurationSource; + _builder = new InternalElementTypeBuilder(this, collectionProperty.DeclaringType.Model.Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual 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; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code 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 Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsInModel + => _builder is not null + && CollectionProperty.DeclaringType.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + { + CollectionProperty.SetElementType(null, ConfigurationSource.Explicit); + _builder = null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected new virtual IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => CollectionProperty.DeclaringType.Model.ConventionDispatcher.OnElementTypeAnnotationChanged( + Builder, name, annotation, oldAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Property CollectionProperty { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + public virtual Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsNullable + { + get => _isNullable ?? DefaultIsNullable; + set => SetIsNullable(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) + { + EnsureMutable(); + + var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; + if (nullable == null) + { + _isNullable = null; + _isNullableConfigurationSource = null; + if (isChanging) + { + OnElementTypeNullableChanged(); + } + + return nullable; + } + + if (nullable.Value && !ClrType.IsNullableType()) + { + throw new InvalidOperationException( + CoreStrings.CannotBeNullableElement( + CollectionProperty.DeclaringType.DisplayName(), CollectionProperty.Name, ClrType.ShortDisplayName())); + } + + _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); + + _isNullable = nullable; + + return isChanging + ? OnElementTypeNullableChanged() + : nullable; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual bool? OnElementTypeNullableChanged() + => CollectionProperty.DeclaringType.Model.ConventionDispatcher.OnElementTypeNullabilityChanged(Builder); + + private bool DefaultIsNullable + => ClrType.IsNullableType(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsNullableConfigurationSource() + => _isNullableConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetMaxLength(int? maxLength, ConfigurationSource configurationSource) + { + if (maxLength is < -1) + { + throw new ArgumentOutOfRangeException(nameof(maxLength)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.MaxLength, maxLength, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetMaxLength() + => (int?)this[CoreAnnotationNames.MaxLength]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetMaxLengthConfigurationSource() + => FindAnnotation(CoreAnnotationNames.MaxLength)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsUnicode(bool? unicode, ConfigurationSource configurationSource) + => (bool?)SetOrRemoveAnnotation(CoreAnnotationNames.Unicode, unicode, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? IsUnicode() + => (bool?)this[CoreAnnotationNames.Unicode]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsUnicodeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Unicode)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetPrecision(int? precision, ConfigurationSource configurationSource) + { + if (precision != null && precision < 0) + { + throw new ArgumentOutOfRangeException(nameof(precision)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Precision, precision, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetPrecision() + => (int?)this[CoreAnnotationNames.Precision]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetPrecisionConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Precision)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetScale(int? scale, ConfigurationSource configurationSource) + { + if (scale != null && scale < 0) + { + throw new ArgumentOutOfRangeException(nameof(scale)); + } + + return (int?)SetOrRemoveAnnotation(CoreAnnotationNames.Scale, scale, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? GetScale() + => (int?)this[CoreAnnotationNames.Scale]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetScaleConfigurationSource() + => FindAnnotation(CoreAnnotationNames.Scale)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueConverter? SetValueConverter( + ValueConverter? converter, + ConfigurationSource configurationSource) + { + var errorString = CheckValueConverter(converter); + if (errorString != null) + { + throw new InvalidOperationException(errorString); + } + + RemoveAnnotation(CoreAnnotationNames.ValueConverterType); + return (ValueConverter?)SetAnnotation(CoreAnnotationNames.ValueConverter, converter, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + ConfigurationSource configurationSource) + { + ValueConverter? converter = null; + if (converterType != null) + { + if (!typeof(ValueConverter).IsAssignableFrom(converterType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueConverterType(converterType.ShortDisplayName(), typeof(ValueConverter).ShortDisplayName())); + } + + try + { + converter = (ValueConverter?)Activator.CreateInstance(converterType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueConverter( + converterType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); + } + } + + SetValueConverter(converter, configurationSource); + SetAnnotation(CoreAnnotationNames.ValueConverterType, converterType, configurationSource); + + return converterType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueConverter? GetValueConverter() + => (ValueConverter?)FindAnnotation(CoreAnnotationNames.ValueConverter)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueConverterConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ValueConverter)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? CheckValueConverter(ValueConverter? converter) + => converter != null + && converter.ModelClrType.UnwrapNullableType() != ClrType.UnwrapNullableType() + ? CoreStrings.ConverterPropertyMismatchElement( + converter.ModelClrType.ShortDisplayName(), + CollectionProperty.DeclaringType.DisplayName(), + CollectionProperty.Name, + ClrType.ShortDisplayName()) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetProviderClrType(Type? providerClrType, ConfigurationSource configurationSource) + => (Type?)SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? GetProviderClrType() + => (Type?)FindAnnotation(CoreAnnotationNames.ProviderClrType)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetProviderClrTypeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ProviderClrType)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DisallowNull] + public virtual CoreTypeMapping? TypeMapping + { + get => IsReadOnly + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _typeMapping, (IElementType)this, static elementType => + elementType.CollectionProperty.DeclaringType.Model.GetModelDependencies().TypeMappingSource.FindMapping(elementType)!) + : _typeMapping; + + set => SetTypeMapping(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual CoreTypeMapping? SetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource configurationSource) + { + _typeMapping = typeMapping; + _typeMappingConfigurationSource = typeMapping is null + ? null + : configurationSource.Max(_typeMappingConfigurationSource); + + return typeMapping; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetTypeMappingConfigurationSource() + => _typeMappingConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? SetValueComparer(ValueComparer? comparer, ConfigurationSource configurationSource) + { + var errorString = CheckValueComparer(comparer); + if (errorString != null) + { + throw new InvalidOperationException(errorString); + } + + RemoveAnnotation(CoreAnnotationNames.ValueComparerType); + return (ValueComparer?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparer, comparer, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public virtual Type? SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + ConfigurationSource configurationSource) + { + ValueComparer? comparer = null; + if (comparerType != null) + { + if (!typeof(ValueComparer).IsAssignableFrom(comparerType)) + { + throw new InvalidOperationException( + CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName())); + } + + try + { + comparer = (ValueComparer?)Activator.CreateInstance(comparerType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateValueComparer( + comparerType.ShortDisplayName(), nameof(PropertyBuilder.HasConversion)), e); + } + } + + SetValueComparer(comparer, configurationSource); + return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.ValueComparerType, comparerType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ValueComparer? GetValueComparer() + => (ValueComparer?)this[CoreAnnotationNames.ValueComparer]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetValueComparerConfigurationSource() + => FindAnnotation(CoreAnnotationNames.ValueComparer)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? CheckValueComparer(ValueComparer? comparer) + => comparer != null + && comparer.Type.UnwrapNullableType() != ClrType.UnwrapNullableType() + ? CoreStrings.ComparerPropertyMismatchElement( + comparer.Type.ShortDisplayName(), + CollectionProperty.DeclaringType.DisplayName(), + CollectionProperty.Name, + ClrType.ShortDisplayName()) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() + { + return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]); + + static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType) + { + if (readerWriterType != null) + { + var instanceProperty = readerWriterType.GetAnyProperty("Instance"); + try + { + return instanceProperty != null + && instanceProperty.IsStatic() + && instanceProperty.GetMethod?.IsPublic == true + && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) + ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) + : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateJsonValueReaderWriter( + readerWriterType.ShortDisplayName()), e); + } + } + + return null; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type? SetJsonValueReaderWriterType( + Type? readerWriterType, + ConfigurationSource configurationSource) + { + if (readerWriterType != null) + { + var genericType = readerWriterType.GetGenericTypeImplementations(typeof(JsonValueReaderWriter<>)).FirstOrDefault(); + if (genericType == null) + { + throw new InvalidOperationException(CoreStrings.BadJsonValueReaderWriterType(readerWriterType.ShortDisplayName())); + } + } + + return (Type?)SetOrRemoveAnnotation(CoreAnnotationNames.JsonValueReaderWriterType, readerWriterType, configurationSource)?.Value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.JsonValueReaderWriterType)?.GetConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyElementType)this).ToDebugString(), + () => ((IReadOnlyElementType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override string ToString() + => ((IReadOnlyElementType)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IReadOnlyProperty IReadOnlyElementType.CollectionProperty + { + [DebuggerStepThrough] + get => CollectionProperty; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IMutableProperty IMutableElementType.CollectionProperty + { + [DebuggerStepThrough] + get => CollectionProperty; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionProperty IConventionElementType.CollectionProperty + { + [DebuggerStepThrough] + get => CollectionProperty; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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 IConventionElementType.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + CoreTypeMapping? IReadOnlyElementType.FindTypeMapping() + => TypeMapping; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetTypeMapping(CoreTypeMapping typeMapping) + => SetTypeMapping(typeMapping, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + CoreTypeMapping? IConventionElementType.SetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) + => SetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool? IConventionElementType.SetIsNullable(bool? nullable, bool fromDataAnnotation) + => SetIsNullable( + nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetMaxLength(int? maxLength) + => SetMaxLength(maxLength, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + int? IConventionElementType.SetMaxLength(int? maxLength, bool fromDataAnnotation) + => SetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetPrecision(int? precision) + => SetPrecision(precision, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + int? IConventionElementType.SetPrecision(int? precision, bool fromDataAnnotation) + => SetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetScale(int? scale) + => SetScale(scale, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + int? IConventionElementType.SetScale(int? scale, bool fromDataAnnotation) + => SetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetIsUnicode(bool? unicode) + => SetIsUnicode(unicode, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool? IConventionElementType.SetIsUnicode(bool? unicode, bool fromDataAnnotation) + => SetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetValueConverter(ValueConverter? converter) + => SetValueConverter(converter, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueConverter? IConventionElementType.SetValueConverter(ValueConverter? converter, bool fromDataAnnotation) + => SetValueConverter( + converter, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType) + => SetValueConverter(converterType, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + Type? IConventionElementType.SetValueConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + bool fromDataAnnotation) + => SetValueConverter( + converterType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetProviderClrType(Type? providerClrType) + => SetProviderClrType(providerClrType, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + Type? IConventionElementType.SetProviderClrType(Type? providerClrType, bool fromDataAnnotation) + => SetProviderClrType( + providerClrType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetValueComparer(ValueComparer? comparer) + => SetValueComparer(comparer, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueComparer? IConventionElementType.SetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => SetValueComparer( + comparer, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) + => SetValueComparer(comparerType, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? IConventionElementType.SetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation) + => SetValueComparer( + comparerType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ValueComparer IElementType.GetValueComparer() + => GetValueComparer()!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableElementType.SetJsonValueReaderWriterType(Type? readerWriterType) + => SetJsonValueReaderWriterType(readerWriterType, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + Type? IConventionElementType.SetJsonValueReaderWriterType( + Type? readerWriterType, + bool fromDataAnnotation) + => SetJsonValueReaderWriterType( + readerWriterType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs index d6d3dd2440c..139aac305fd 100644 --- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs @@ -40,7 +40,7 @@ public interface IMemberClassifier /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model); + bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model, out CoreTypeMapping? typeMapping); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,8 +48,11 @@ public interface IMemberClassifier /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, - out Type? elementType, out bool explicitlyConfigured); + bool IsCandidateComplexProperty( + MemberInfo memberInfo, + IConventionModel model, + out Type? elementType, + out bool explicitlyConfigured); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs new file mode 100644 index 00000000000..6ee2c4d7829 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs @@ -0,0 +1,648 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class InternalElementTypeBuilder : AnnotatableBuilder, IConventionElementTypeBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public InternalElementTypeBuilder(ElementType property, InternalModelBuilder modelBuilder) + : base(property, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual IConventionElementTypeBuilder This + => this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsAreRequired(bool? required, ConfigurationSource configurationSource) + { + if (configurationSource != ConfigurationSource.Explicit + && !CanSetIsRequired(required, configurationSource)) + { + return null; + } + + Metadata.SetIsNullable(!required, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource) + => ((configurationSource.HasValue + && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource())) + || (Metadata.IsNullable == !required)) + && (required != false + || Metadata.ClrType.IsNullableType()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveMaxLength(int? maxLength, ConfigurationSource configurationSource) + { + if (CanSetMaxLength(maxLength, configurationSource)) + { + Metadata.SetMaxLength(maxLength, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) + || Metadata.GetMaxLength() == maxLength; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHavePrecision(int? precision, ConfigurationSource configurationSource) + { + if (CanSetPrecision(precision, configurationSource)) + { + Metadata.SetPrecision(precision, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetPrecisionConfigurationSource()) + || Metadata.GetPrecision() == precision; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveScale(int? scale, ConfigurationSource configurationSource) + { + if (CanSetScale(scale, configurationSource)) + { + Metadata.SetScale(scale, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetScaleConfigurationSource()) + || Metadata.GetScale() == scale; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsAreUnicode(bool? unicode, ConfigurationSource configurationSource) + { + if (CanSetIsUnicode(unicode, configurationSource)) + { + Metadata.SetIsUnicode(unicode, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetIsUnicodeConfigurationSource()) + || Metadata.IsUnicode() == unicode; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveConversion(ValueConverter? converter, ConfigurationSource configurationSource) + { + if (CanSetConversion(converter, configurationSource)) + { + Metadata.SetProviderClrType(null, configurationSource); + Metadata.SetValueConverter(converter, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConversion( + ValueConverter? converter, + ConfigurationSource? configurationSource) + => (configurationSource == ConfigurationSource.Explicit + || (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) + && Metadata.CheckValueConverter(converter) == null) + || (Metadata[CoreAnnotationNames.ValueConverterType] == null + && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter)) + && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveConversion(Type? providerClrType, ConfigurationSource configurationSource) + { + if (CanSetConversion(providerClrType, configurationSource)) + { + Metadata.SetValueConverter((ValueConverter?)null, configurationSource); + Metadata.SetProviderClrType(providerClrType, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? configurationSource) + => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()) + || Metadata.GetProviderClrType() == providerClrType) + && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + ConfigurationSource configurationSource) + { + if (CanSetConverter(converterType, configurationSource)) + { + Metadata.SetProviderClrType(null, configurationSource); + Metadata.SetValueConverter(converterType, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueConverter] == null + && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveTypeMapping( + CoreTypeMapping? typeMapping, + ConfigurationSource configurationSource) + { + if (CanSetTypeMapping(typeMapping, configurationSource)) + { + Metadata.SetTypeMapping(typeMapping, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetTypeMappingConfigurationSource()) + || Metadata.TypeMapping == typeMapping; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveValueComparer( + ValueComparer? comparer, + ConfigurationSource configurationSource) + { + if (CanSetValueComparer(comparer, configurationSource)) + { + Metadata.SetValueComparer(comparer, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource) + { + if (configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource())) + { + var errorString = Metadata.CheckValueComparer(comparer); + if (errorString != null) + { + if (configurationSource == ConfigurationSource.Explicit) + { + throw new InvalidOperationException(errorString); + } + + return false; + } + + return true; + } + + return Metadata[CoreAnnotationNames.ValueComparerType] == null + && Metadata[CoreAnnotationNames.ValueComparer] == comparer; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalElementTypeBuilder? ElementsHaveValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + ConfigurationSource configurationSource) + { + if (CanSetValueComparer(comparerType, configurationSource)) + { + Metadata.SetValueComparer(comparerType, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource()) + || (Metadata[CoreAnnotationNames.ValueComparer] == null + && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementType IConventionElementTypeBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsAreRequired(bool? required, bool fromDataAnnotation) + => ElementsAreRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetIsRequired(bool? required, bool fromDataAnnotation) + => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveMaxLength(int? maxLength, bool fromDataAnnotation) + => ElementsHaveMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnnotation) + => CanSetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsAreUnicode(bool? unicode, bool fromDataAnnotation) + => ElementsAreUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnnotation) + => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHavePrecision(int? precision, bool fromDataAnnotation) + => ElementsHavePrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetPrecision(int? precision, bool fromDataAnnotation) + => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveScale(int? scale, bool fromDataAnnotation) + => ElementsHaveScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetScale(int? scale, bool fromDataAnnotation) + => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConversion(ValueConverter? converter, bool fromDataAnnotation) + => ElementsHaveConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetConversion(ValueConverter? converter, bool fromDataAnnotation) + => CanSetConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + bool fromDataAnnotation) + => ElementsHaveConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetConverter( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, + bool fromDataAnnotation) + => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConversion(Type? providerClrType, bool fromDataAnnotation) + => ElementsHaveConversion( + providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetConversion(Type? providerClrType, bool fromDataAnnotation) + => CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveTypeMapping( + CoreTypeMapping? typeMapping, + bool fromDataAnnotation) + => ElementsHaveTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + bool IConventionElementTypeBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation) + => CanSetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => ElementsHaveValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetValueComparer(ValueComparer? comparer, bool fromDataAnnotation) + => CanSetValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation) + => ElementsHaveValueComparer( + comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionElementTypeBuilder.CanSetValueComparer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + bool fromDataAnnotation) + => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 882374d2ac4..dff353f5eb6 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -31,7 +31,8 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override IConventionPropertyBuilder This => this; + protected override IConventionPropertyBuilder This + => this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -385,7 +386,8 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType, ConfigurationSource configurationSource) { if (valueGeneratorType == null) @@ -443,7 +445,8 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? factory, ConfigurationSource configurationSource) { if (CanSetValueGeneratorFactory(factory, configurationSource)) @@ -476,7 +479,8 @@ public virtual bool CanSetValueGenerator( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool CanSetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factory, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? factory, ConfigurationSource? configurationSource) => configurationSource.Overrides(Metadata.GetValueGeneratorFactoryConfigurationSource()) || (Metadata[CoreAnnotationNames.ValueGeneratorFactory] == null @@ -554,11 +558,13 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, ConfigurationSource configurationSource) { if (CanSetConverter(converterType, configurationSource)) { + Metadata.SetElementType(null, configurationSource); Metadata.SetProviderClrType(null, configurationSource); Metadata.SetValueConverter(converterType, configurationSource); @@ -575,7 +581,8 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool CanSetConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, ConfigurationSource? configurationSource) => configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()) || (Metadata[CoreAnnotationNames.ValueConverter] == null @@ -666,7 +673,8 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, ConfigurationSource configurationSource) { if (CanSetValueComparer(comparerType, configurationSource)) @@ -734,7 +742,8 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, ConfigurationSource configurationSource) { if (CanSetProviderValueComparer(comparerType, configurationSource)) @@ -754,12 +763,39 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool CanSetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, ConfigurationSource? configurationSource) => configurationSource.Overrides(Metadata.GetProviderValueComparerConfigurationSource()) || (Metadata[CoreAnnotationNames.ProviderValueComparer] == null && (Type?)Metadata[CoreAnnotationNames.ProviderValueComparerType] == comparerType); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? HasElementType(IElementType? elementType, ConfigurationSource configurationSource) + { + if (CanSetElementType(elementType, configurationSource)) + { + Metadata.SetElementType(elementType, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetElementType(IElementType? elementType, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetElementTypeConfigurationSource()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -902,10 +938,15 @@ IConventionProperty IConventionPropertyBuilder.Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation( + string name, + object? value, + bool fromDataAnnotation) => base.HasAnnotation( - name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - == null ? null : this; + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -914,10 +955,15 @@ IConventionProperty IConventionPropertyBuilder.Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation) => base.HasNonNullAnnotation( - name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - == null ? null : this; + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -926,10 +972,14 @@ IConventionProperty IConventionPropertyBuilder.Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation( + string name, + bool fromDataAnnotation) => base.HasNoAnnotation( - name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - == null ? null : this; + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null + ? null + : this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1012,7 +1062,9 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField( + string? fieldName, + bool fromDataAnnotation) => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -1021,7 +1073,9 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField( + FieldInfo? fieldInfo, + bool fromDataAnnotation) => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -1060,7 +1114,9 @@ bool IConventionPropertyBaseBuilder.CanSetField(Fiel /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1179,7 +1235,8 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGenerator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType, bool fromDataAnnotation) => HasValueGenerator( valueGeneratorType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1211,7 +1268,8 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType, bool fromDataAnnotation) => HasValueGeneratorFactory( valueGeneratorFactoryType, @@ -1224,7 +1282,8 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType, bool fromDataAnnotation) => CanSetValueGeneratorFactory( valueGeneratorFactoryType, @@ -1255,7 +1314,8 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionPropertyBuilder? IConventionPropertyBuilder.HasConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, bool fromDataAnnotation) => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1266,7 +1326,8 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool /// doing so can result in application failures when updating to a new Entity Framework Core release. /// bool IConventionPropertyBuilder.CanSetConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? converterType, bool fromDataAnnotation) => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1321,7 +1382,8 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, bool fromDataAnnotation) => HasValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1332,7 +1394,8 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo /// doing so can result in application failures when updating to a new Entity Framework Core release. /// bool IConventionPropertyBuilder.CanSetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, bool fromDataAnnotation) => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1361,7 +1424,8 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionPropertyBuilder? IConventionPropertyBuilder.HasProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, bool fromDataAnnotation) => HasProviderValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1372,8 +1436,27 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa /// doing so can result in application failures when updating to a new Entity Framework Core release. /// bool IConventionPropertyBuilder.CanSetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, 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. + /// + IConventionPropertyBuilder? IConventionPropertyBuilder.HasElementType(IElementType? elementType, bool fromDataAnnotation) + => HasElementType(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. + /// + public virtual bool CanSetElementType(IElementType? elementType, bool fromDataAnnotation = false) + => CanSetElementType(elementType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs index 4591b604af6..e8253b14625 100644 --- a/src/EFCore/Metadata/Internal/MemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs @@ -167,8 +167,9 @@ private bool IsCandidateNavigationPropertyType( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model) + public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConventionModel model, out CoreTypeMapping? typeMapping) { + typeMapping = null; if (!memberInfo.IsCandidateProperty()) { return false; @@ -176,7 +177,7 @@ public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConvent var configurationType = ((Model)model).Configuration?.GetConfigurationType(memberInfo.GetMemberType()); return configurationType == TypeConfigurationType.Property - || (configurationType == null && _typeMappingSource.FindMapping(memberInfo) != null); + || (configurationType == null && (typeMapping = _typeMappingSource.FindMapping(memberInfo)) != null); } /// @@ -185,8 +186,11 @@ public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConvent /// any release. You should only use it directly in your code 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 IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, - out Type? elementType, out bool explicitlyConfigured) + public virtual bool IsCandidateComplexProperty( + MemberInfo memberInfo, + IConventionModel model, + out Type? elementType, + out bool explicitlyConfigured) { explicitlyConfigured = false; elementType = null; diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 70258360bb7..058ef519d12 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -1226,6 +1226,35 @@ public virtual CoreTypeMapping? TypeMapping public virtual ConfigurationSource? GetJsonValueReaderWriterTypeConfigurationSource() => FindAnnotation(CoreAnnotationNames.JsonValueReaderWriterType)?.GetConfigurationSource(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IElementType? GetElementType() + => (IElementType?)this[CoreAnnotationNames.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. + /// + public virtual IElementType? SetElementType( + IElementType? elementType, + ConfigurationSource configurationSource) + => (IElementType?)SetOrRemoveAnnotation(CoreAnnotationNames.ElementType, elementType, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? 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 @@ -1971,6 +2000,20 @@ void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - JsonValueReaderWriter? IReadOnlyProperty.GetJsonValueReaderWriter() - => GetJsonValueReaderWriter(); + IElementType? IConventionProperty.SetElementType( + IElementType? 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(IElementType? elementType) + => SetElementType(elementType, ConfigurationSource.Explicit); } diff --git a/src/EFCore/Metadata/RuntimeElementType.cs b/src/EFCore/Metadata/RuntimeElementType.cs new file mode 100644 index 00000000000..39ed68634b2 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeElementType.cs @@ -0,0 +1,230 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the elements of a collection property. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeElementType : AnnotatableBase, IElementType +{ + private readonly bool _isNullable; + private readonly ValueConverter? _valueConverter; + private readonly ValueComparer _valueComparer; + private readonly JsonValueReaderWriter? _jsonValueReaderWriter; + private readonly CoreTypeMapping? _typeMapping; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeElementType( + Type clrType, + RuntimeProperty collectionProperty, + bool nullable, + int? maxLength, + bool? unicode, + int? precision, + int? scale, + Type? providerClrType, + ValueConverter? valueConverter, + ValueComparer valueComparer, + JsonValueReaderWriter? jsonValueReaderWriter, + CoreTypeMapping? typeMapping) + { + CollectionProperty = collectionProperty; + ClrType = clrType; + _isNullable = nullable; + _valueConverter = valueConverter; + + if (maxLength != null) + { + SetAnnotation(CoreAnnotationNames.MaxLength, maxLength); + } + + if (unicode != null) + { + SetAnnotation(CoreAnnotationNames.Unicode, unicode); + } + + if (precision != null) + { + SetAnnotation(CoreAnnotationNames.Precision, precision); + } + + if (scale != null) + { + SetAnnotation(CoreAnnotationNames.Scale, scale); + } + + if (providerClrType != null) + { + SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType); + } + + _typeMapping = typeMapping; + _valueComparer = valueComparer; + _jsonValueReaderWriter = jsonValueReaderWriter; + } + + /// + /// Gets the collection property for which this represents the element. + /// + public virtual IProperty CollectionProperty { get; } + + /// + public virtual Type ClrType { get; } + + /// + /// Gets a value indicating whether elements of the collection can be . + /// + public virtual bool IsNullable + => _isNullable; + + /// + /// Returns the type mapping for elements of the collection. + /// + /// The type mapping, or if none was found. + public virtual CoreTypeMapping? FindTypeMapping() + => _typeMapping; + + /// + /// Gets the maximum length of data that is allowed in elements of the collection. For example, if the element type is + /// a then this is the maximum number of characters. + /// + /// + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. + /// + [DebuggerStepThrough] + public virtual int? GetMaxLength() + => (int?)this[CoreAnnotationNames.MaxLength]; + + /// + /// Gets the precision of data that is allowed in elements of the collection. + /// For example, if the element type is a , then this is the maximum number of digits. + /// + /// The precision, or if none is defined. + [DebuggerStepThrough] + public virtual int? GetPrecision() + => (int?)this[CoreAnnotationNames.Precision]; + + /// + /// Gets the scale of data that is allowed in this elements of the collection. + /// For example, if the element type is a , then this is the maximum number of decimal places. + /// + /// The scale, or if none is defined. + [DebuggerStepThrough] + public virtual int? GetScale() + => (int?)this[CoreAnnotationNames.Scale]; + + /// + /// Gets a value indicating whether elements of the collection can persist Unicode characters. + /// + /// The Unicode setting, or if none is defined. + [DebuggerStepThrough] + public virtual bool? IsUnicode() + => (bool?)this[CoreAnnotationNames.Unicode]; + + /// + /// Gets the custom for this elements of the collection. + /// + /// The converter, or if none has been set. + [DebuggerStepThrough] + public virtual ValueConverter? GetValueConverter() + => _valueConverter; + + /// + /// Gets the custom for elements of the collection. + /// + /// The comparer, or if none has been set. + [DebuggerStepThrough] + public virtual ValueComparer GetValueComparer() + => _valueComparer; + + /// + /// Gets the type that the elements of the collection will be converted to before being sent to the database provider. + /// + /// The provider type, or if none has been set. + public virtual Type? GetProviderClrType() + => (Type?)FindAnnotation(CoreAnnotationNames.ProviderClrType)?.Value; + + /// + public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() + => _jsonValueReaderWriter; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyElementType)this).ToDebugString(), + () => ((IReadOnlyElementType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override string ToString() + => ((IReadOnlyElementType)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + IReadOnlyProperty IReadOnlyElementType.CollectionProperty + => CollectionProperty; + + /// + bool IReadOnlyElementType.IsNullable + { + [DebuggerStepThrough] + get => _isNullable; + } + + /// + [DebuggerStepThrough] + int? IReadOnlyElementType.GetMaxLength() + => (int?)this[CoreAnnotationNames.MaxLength]; + + /// + [DebuggerStepThrough] + bool? IReadOnlyElementType.IsUnicode() + => (bool?)this[CoreAnnotationNames.Unicode]; + + /// + [DebuggerStepThrough] + int? IReadOnlyElementType.GetPrecision() + => (int?)this[CoreAnnotationNames.Precision]; + + /// + [DebuggerStepThrough] + int? IReadOnlyElementType.GetScale() + => (int?)this[CoreAnnotationNames.Scale]; + + /// + [DebuggerStepThrough] + ValueConverter? IReadOnlyElementType.GetValueConverter() + => _valueConverter; + + /// + [DebuggerStepThrough] + Type? IReadOnlyElementType.GetProviderClrType() + => (Type?)this[CoreAnnotationNames.ProviderClrType]; + + /// + [DebuggerStepThrough] + CoreTypeMapping IReadOnlyElementType.FindTypeMapping() + => FindTypeMapping()!; +} diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index 11ea05e75b1..715631a9a51 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; @@ -62,7 +62,8 @@ public RuntimeProperty( ValueComparer? keyValueComparer, ValueComparer? providerValueComparer, JsonValueReaderWriter? jsonValueReaderWriter, - CoreTypeMapping? typeMapping) + CoreTypeMapping? typeMapping, + IElementType? element) : base(name, propertyInfo, fieldInfo, propertyAccessMode) { DeclaringType = declaringType; @@ -106,6 +107,24 @@ public RuntimeProperty( _keyValueComparer = keyValueComparer ?? valueComparer; _providerValueComparer = providerValueComparer; _jsonValueReaderWriter = jsonValueReaderWriter; + + if (element != null) + { + SetAnnotation( + CoreAnnotationNames.ElementType, new RuntimeElementType( + element.ClrType, + this, + element.IsNullable, + element.GetMaxLength(), + element.IsUnicode(), + element.GetPrecision(), + element.GetScale(), + element.GetProviderClrType(), + element.GetValueConverter(), + element.GetValueComparer(), + element.GetJsonValueReaderWriter(), + element.FindTypeMapping())); + } } /// @@ -211,7 +230,7 @@ private ValueComparer GetKeyValueComparer() private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties) { - if ( _keyValueComparer != null) + if (_keyValueComparer != null) { return _keyValueComparer; } @@ -246,6 +265,13 @@ public override object? Sentinel public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() => _jsonValueReaderWriter; + /// + /// Gets the configuration for elements of the primitive collection represented by this property. + /// + /// The configuration for the elements. + public virtual IElementType? GetElementType() + => (IElementType?)this[CoreAnnotationNames.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/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index 63c5c60230a..6b09ee2a16a 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -22,8 +22,7 @@ public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); private readonly SortedDictionary _properties; - private readonly SortedDictionary _complexProperties = - new SortedDictionary(StringComparer.Ordinal); + private readonly SortedDictionary _complexProperties = new(StringComparer.Ordinal); private readonly PropertyInfo? _indexerPropertyInfo; private readonly bool _isPropertyBag; @@ -60,6 +59,7 @@ public RuntimeTypeBase( _baseType = baseType; baseType._directlyDerivedTypes.Add(this); } + _changeTrackingStrategy = changeTrackingStrategy; _indexerPropertyInfo = indexerPropertyInfo; _isPropertyBag = propertyBag; @@ -84,13 +84,15 @@ public RuntimeTypeBase( /// Gets the base type of this type. Returns if this is not a /// derived type in an inheritance hierarchy. /// - public virtual RuntimeTypeBase? BaseType => _baseType; + public virtual RuntimeTypeBase? BaseType + => _baseType; /// /// Gets all types in the model that directly derive from this type. /// /// The derived types. - public virtual SortedSet DirectlyDerivedTypes => _directlyDerivedTypes; + public virtual SortedSet DirectlyDerivedTypes + => _directlyDerivedTypes; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -172,6 +174,10 @@ protected virtual IEnumerable GetDerivedTypes() /// The to use for the provider values for this property. /// The for this property. /// The for this property. + /// + /// The for this property, or if the property is not a + /// collection. + /// /// The newly created property. public virtual RuntimeProperty AddProperty( string name, @@ -196,7 +202,8 @@ public virtual RuntimeProperty AddProperty( ValueComparer? keyValueComparer = null, ValueComparer? providerValueComparer = null, JsonValueReaderWriter? jsonValueReaderWriter = null, - CoreTypeMapping? typeMapping = null) + CoreTypeMapping? typeMapping = null, + IElementType? elementType = null) { var property = new RuntimeProperty( name, @@ -222,7 +229,8 @@ public virtual RuntimeProperty AddProperty( keyValueComparer, providerValueComparer, jsonValueReaderWriter, - typeMapping); + typeMapping, + elementType); _properties.Add(property.Name, property); @@ -288,7 +296,7 @@ private IEnumerable GetDerivedProperties() } /// - /// Gets the properties with the given name on this type, base types or derived types. + /// Gets the properties with the given name on this type, base types or derived types. /// /// Type properties. public virtual IEnumerable FindPropertiesInHierarchy(string propertyName) @@ -325,7 +333,8 @@ protected virtual IEnumerable GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual SortedDictionary Properties => _properties; + protected virtual SortedDictionary Properties + => _properties; /// [DebuggerStepThrough] @@ -433,7 +442,7 @@ public virtual IEnumerable GetComplexProperties() : _complexProperties.Values; /// - /// Gets the complex properties with the given name on this type, base types or derived types. + /// Gets the complex properties with the given name on this type, base types or derived types. /// /// Type complex properties. public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName) @@ -448,7 +457,7 @@ private IEnumerable FindDerivedComplexProperties(string return _directlyDerivedTypes.Count == 0 ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() - .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); + .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); } /// @@ -491,9 +500,7 @@ private IEnumerable FindDerivedComplexProperties(string /// [EntityFrameworkInternal] public virtual void SetOriginalValuesFactory(Func factory) - { - _originalValuesFactory = factory; - } + => _originalValuesFactory = factory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -503,9 +510,7 @@ public virtual void SetOriginalValuesFactory(Func [EntityFrameworkInternal] public virtual void SetStoreGeneratedValuesFactory(Func factory) - { - _storeGeneratedValuesFactory = factory; - } + => _storeGeneratedValuesFactory = factory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -515,9 +520,7 @@ public virtual void SetStoreGeneratedValuesFactory(Func factory) /// [EntityFrameworkInternal] public virtual void SetTemporaryValuesFactory(Func factory) - { - _temporaryValuesFactory = factory; - } + => _temporaryValuesFactory = factory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -527,9 +530,7 @@ public virtual void SetTemporaryValuesFactory(Func [EntityFrameworkInternal] public virtual void SetShadowValuesFactory(Func factory) - { - _shadowValuesFactory = factory; - } + => _shadowValuesFactory = factory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -537,12 +538,9 @@ public virtual void SetShadowValuesFactory(Func factory) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [EntityFrameworkInternal] public virtual void SetEmptyShadowValuesFactory(Func factory) - { - _emptyShadowValuesFactory = factory; - } + => _emptyShadowValuesFactory = factory; /// /// Gets or sets the for the preferred constructor. diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 662e5874937..de5ddaa2eaf 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -226,6 +226,14 @@ public static string CannotBeNullable(object? property, object? entityType, obje GetString("CannotBeNullable", "0_property", "1_entityType", nameof(propertyType)), property, entityType, propertyType); + /// + /// The element type of property '{entityType}.{property}' cannot be marked as nullable/optional because the type of the element is '{elementType}' which is not a nullable type. Any element type can be marked as non-nullable/required, but only elements of nullable types can be marked as nullable/optional. + /// + public static string CannotBeNullableElement(object? entityType, object? property, object? elementType) + => string.Format( + GetString("CannotBeNullableElement", "entityType", "property", "elementType"), + property, entityType); + /// /// The property '{1_entityType}.{0_property}' cannot be marked as nullable/optional because the property is a part of a key. Any property can be marked as non-nullable/required, but only properties of nullable types and which are not part of a key can be marked as nullable/optional. /// @@ -448,6 +456,14 @@ public static string ComparerPropertyMismatch(object? type, object? entityType, GetString("ComparerPropertyMismatch", nameof(type), nameof(entityType), nameof(propertyName), nameof(propertyType)), type, entityType, propertyName, propertyType); + /// + /// The comparer for element type '{type}' cannot be used for '{entityType}.{propertyName}' because its element type is '{elementType}'. + /// + public static string ComparerPropertyMismatchElement(object? type, object? entityType, object? propertyName, object? elementType) + => string.Format( + GetString("ComparerPropertyMismatchElement", nameof(type), nameof(entityType), nameof(propertyName), nameof(elementType)), + type, entityType, propertyName, elementType); + /// /// The compiled query '{queryExpression}' was executed with a different model than it was compiled against. Compiled queries can only be used with a single model. /// @@ -618,6 +634,14 @@ public static string ConverterPropertyMismatch(object? converterType, object? en GetString("ConverterPropertyMismatch", nameof(converterType), nameof(entityType), nameof(propertyName), nameof(propertyType)), converterType, entityType, propertyName, propertyType); + /// + /// Converter for element type '{converterType}' cannot be used for '{entityType}.{propertyName}' because its element type is '{elementType}'. + /// + public static string ConverterPropertyMismatchElement(object? converterType, object? entityType, object? propertyName, object? elementType) + => string.Format( + GetString("ConverterPropertyMismatchElement", nameof(converterType), nameof(entityType), nameof(propertyName), nameof(elementType)), + converterType, entityType, propertyName, elementType); + /// /// Cannot compose converter from '{typeOneIn}' to '{typeOneOut}' with converter from '{typeTwoIn}' to '{typeTwoOut}' because the output type of the first converter doesn't match the input type of the second converter. /// @@ -2049,6 +2073,14 @@ public static string NotAnEFService(object? service) GetString("NotAnEFService", nameof(service)), service); + /// + /// The property '{entityType}.{property}' cannot be mapped as a collection since it does not implement 'IEnumerable<T>'. + /// + public static string NotCollection(object? entityType, object? property) + => string.Format( + GetString(nameof(entityType), nameof(property)), + entityType, property); + /// /// The database provider attempted to register an implementation of the '{service}' service. This is a service defined by Entity Framework and as such must not be registered using the 'TryAddProviderSpecificServices' method. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index f300271c954..11c71f83448 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -192,6 +192,9 @@ The property '{1_entityType}.{0_property}' cannot be marked as nullable/optional because the type of the property is '{propertyType}' which is not a nullable type. Any property can be marked as non-nullable/required, but only properties of nullable types can be marked as nullable/optional. + + The element type of property '{entityType}.{property}' cannot be marked as nullable/optional because the type of the element is '{elementType}' which is not a nullable type. Any element type can be marked as non-nullable/required, but only elements of nullable types can be marked as nullable/optional. + The property '{1_entityType}.{0_property}' cannot be marked as nullable/optional because the property is a part of a key. Any property can be marked as non-nullable/required, but only properties of nullable types and which are not part of a key can be marked as nullable/optional. @@ -276,6 +279,9 @@ The comparer for type '{type}' cannot be used for '{entityType}.{propertyName}' because its type is '{propertyType}'. + + The comparer for element type '{type}' cannot be used for '{entityType}.{propertyName}' because its element type is '{elementType}'. + The compiled query '{queryExpression}' was executed with a different model than it was compiled against. Compiled queries can only be used with a single model. @@ -342,6 +348,9 @@ Converter for model type '{converterType}' cannot be used for '{entityType}.{propertyName}' because its type is '{propertyType}'. + + Converter for element type '{converterType}' cannot be used for '{entityType}.{propertyName}' because its element type is '{elementType}'. + Cannot compose converter from '{typeOneIn}' to '{typeOneOut}' with converter from '{typeTwoIn}' to '{typeTwoOut}' because the output type of the first converter doesn't match the input type of the second converter. @@ -1200,6 +1209,9 @@ The database provider attempted to register an implementation of the '{service}' service. This is not a service defined by Entity Framework and as such must be registered as a provider-specific service using the 'TryAddProviderSpecificServices' method. + + The property '{entityType}.{property}' cannot be mapped as a collection since it does not implement 'IEnumerable<T>'. + The database provider attempted to register an implementation of the '{service}' service. This is a service defined by Entity Framework and as such must not be registered using the 'TryAddProviderSpecificServices' method. diff --git a/src/EFCore/Storage/CoreTypeMapping.cs b/src/EFCore/Storage/CoreTypeMapping.cs index 7bf49bc0a5a..4c5654263f3 100644 --- a/src/EFCore/Storage/CoreTypeMapping.cs +++ b/src/EFCore/Storage/CoreTypeMapping.cs @@ -107,8 +107,8 @@ public CoreTypeMappingParameters( /// converter composed with any existing converter and set on the new parameter object. /// /// The converter. - /// The element mapping, or for non-collection mappings. - /// The JSON reader/writer, or to leave unchanged. + /// The element mapping, or for non-collection mappings. + /// The JSON reader/writer, or to leave unchanged. /// The new parameter object. public CoreTypeMappingParameters WithComposedConverter( ValueConverter? converter, @@ -132,39 +132,6 @@ public CoreTypeMappingParameters WithComposedConverter( typeof(JsonConvertedValueReaderWriter<,>).MakeGenericType(converter.ModelClrType, JsonValueReaderWriter.ValueType), JsonValueReaderWriter, converter)!)); } - - /// - /// Creates a new parameter object with the given - /// element type mapping. - /// - /// The element type mapping. - /// The new parameter object. - public CoreTypeMappingParameters WithElementTypeMapping(CoreTypeMapping elementMapping) - => new( - ClrType, - Converter, - Comparer, - KeyComparer, - ProviderValueComparer, - ValueGeneratorFactory, - elementMapping, - JsonValueReaderWriter); - - /// - /// Creates a new parameter object with the given JSON reader/writer. - /// - /// The element type mapping. - /// The new parameter object. - public CoreTypeMappingParameters WithJsonValueReaderWriter(JsonValueReaderWriter jsonValueReaderWriter) - => new( - ClrType, - Converter, - Comparer, - KeyComparer, - ProviderValueComparer, - ValueGeneratorFactory, - ElementTypeMapping, - jsonValueReaderWriter); } private ValueComparer? _comparer; @@ -281,11 +248,13 @@ public virtual ValueComparer ProviderValueComparer /// added. /// /// The converter to use. - /// The element mapping, or for non-collection mappings. - /// The JSON reader/writer, or to leave unchanged. + /// The element mapping, or for non-collection mappings. + /// The JSON reader/writer, or to leave unchanged. /// A new type mapping public abstract CoreTypeMapping Clone( - ValueConverter? converter, CoreTypeMapping? elementMapping = null, JsonValueReaderWriter? jsonValueReaderWriter = null); + ValueConverter? converter, + CoreTypeMapping? elementMapping = null, + JsonValueReaderWriter? jsonValueReaderWriter = null); /// /// Creates a an expression tree that can be used to generate code for the literal value. diff --git a/src/EFCore/Storage/ITypeMappingSource.cs b/src/EFCore/Storage/ITypeMappingSource.cs index fabfda2a008..944b3880cc6 100644 --- a/src/EFCore/Storage/ITypeMappingSource.cs +++ b/src/EFCore/Storage/ITypeMappingSource.cs @@ -36,6 +36,13 @@ public interface ITypeMappingSource /// The type mapping, or if none was found. CoreTypeMapping? FindMapping(IProperty property); + /// + /// Finds the type mapping for a given . + /// + /// The collection element. + /// The type mapping, or if none was found. + CoreTypeMapping? FindMapping(IElementType elementType); + /// /// Finds the type mapping for a given representing /// a field or a property of a CLR type. diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index 32cef9c53b6..8d7b7c9d849 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -23,6 +23,87 @@ public TypeMappingInfo(IProperty property) { } + /// + /// Creates a new instance of . + /// + /// The collection element for the property for which mapping is needed. + /// + /// Specifies Unicode or ANSI for the mapping or for the default. + /// + /// + /// Specifies a size for the mapping, in case one isn't found at the core level, or for the default. + /// + /// + /// Specifies a precision for the mapping, in case one isn't found at the core level, or for the default. + /// + /// + /// Specifies a scale for the mapping, in case one isn't found at the core level, or for the default. + /// + public TypeMappingInfo( + IElementType elementType, + bool? fallbackUnicode = null, + int? fallbackSize = null, + int? fallbackPrecision = null, + int? fallbackScale = null) + { + ValueConverter? customConverter = null; + if (customConverter == null) + { + var converter = elementType.GetValueConverter(); + if (converter != null) + { + customConverter = converter; + } + } + + if (fallbackSize == null) + { + var maxLength = elementType.GetMaxLength(); + if (maxLength != null) + { + fallbackSize = maxLength; + } + } + + if (fallbackPrecision == null) + { + var precisionFromProperty = elementType.GetPrecision(); + if (precisionFromProperty != null) + { + fallbackPrecision = precisionFromProperty; + } + } + + if (fallbackScale == null) + { + var scaleFromProperty = elementType.GetScale(); + if (scaleFromProperty != null) + { + fallbackScale = scaleFromProperty; + } + } + + if (fallbackUnicode == null) + { + var unicode = elementType.IsUnicode(); + if (unicode != null) + { + fallbackUnicode = unicode; + } + } + + var mappingHints = customConverter?.MappingHints; + + IsKeyOrIndex = false; + Size = fallbackSize ?? mappingHints?.Size; + IsUnicode = fallbackUnicode ?? mappingHints?.IsUnicode; + IsRowVersion = false; + ClrType = (customConverter?.ProviderClrType ?? elementType.ClrType).UnwrapNullableType(); + Scale = fallbackScale ?? mappingHints?.Scale; + Precision = fallbackPrecision ?? mappingHints?.Precision; + JsonValueReaderWriter = elementType.GetJsonValueReaderWriter(); + } + /// /// Creates a new instance of . /// @@ -106,7 +187,6 @@ public TypeMappingInfo( ClrType = (customConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); Scale = fallbackScale ?? mappingHints?.Scale; Precision = fallbackPrecision ?? mappingHints?.Precision; - ElementTypeMapping = null; // TODO: set from property JsonValueReaderWriter = property.GetJsonValueReaderWriter(); } @@ -190,28 +270,6 @@ public TypeMappingInfo( ClrType = converter.ProviderClrType.UnwrapNullableType(); - ElementTypeMapping = source.ElementTypeMapping; - JsonValueReaderWriter = source.JsonValueReaderWriter; - } - - /// - /// Creates a new instance of with the given . for collection - /// elements. - /// - /// The source info. - /// The element mapping to use. - public TypeMappingInfo( - TypeMappingInfo source, - CoreTypeMapping elementMapping) - { - IsRowVersion = source.IsRowVersion; - IsKeyOrIndex = source.IsKeyOrIndex; - Size = source.Size; - IsUnicode = source.IsUnicode; - Scale = source.Scale; - Precision = source.Precision; - ClrType = source.ClrType; - ElementTypeMapping = elementMapping; JsonValueReaderWriter = source.JsonValueReaderWriter; } @@ -223,14 +281,6 @@ public TypeMappingInfo( public TypeMappingInfo WithConverter(in ValueConverterInfo converterInfo) => new(this, converterInfo); - /// - /// Returns a new with the given converter applied. - /// - /// The element mapping to use. - /// The new mapping info. - public TypeMappingInfo WithElementTypeMapping(in CoreTypeMapping elementMapping) - => new(this, elementMapping); - /// /// Indicates whether or not the mapping is part of a key or index. /// @@ -267,11 +317,6 @@ public TypeMappingInfo WithElementTypeMapping(in CoreTypeMapping elementMapping) /// public Type? ClrType { get; init; } - /// - /// The element type mapping, if the mapping is for a collection of primitives, or otherwise. - /// - public CoreTypeMapping? ElementTypeMapping { get; init; } - /// /// The JSON reader/writer, if one has been provided, or otherwise. /// diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index 45d52c12c4f..c6b9397fe4d 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -28,7 +28,8 @@ namespace Microsoft.EntityFrameworkCore.Storage; /// public abstract class TypeMappingSource : TypeMappingSourceBase { - private readonly ConcurrentDictionary<(TypeMappingInfo, Type?, ValueConverter?), CoreTypeMapping?> _explicitMappings = new(); + private readonly ConcurrentDictionary<(TypeMappingInfo, Type?, ValueConverter?, CoreTypeMapping?), CoreTypeMapping?> + _explicitMappings = new(); /// /// Initializes a new instance of this class. @@ -45,6 +46,7 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) { Type? providerClrType = null; ValueConverter? customConverter = null; + CoreTypeMapping? elementMapping = null; if (principals != null) { for (var i = 0; i < principals.Count; i++) @@ -67,10 +69,16 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) customConverter = converter; } } + + var element = principal.GetElementType(); + if (element != null) + { + elementMapping = FindMapping(element); + } } } - var resolvedMapping = FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + var resolvedMapping = FindMappingWithConversion(mappingInfo, providerClrType, customConverter, elementMapping); ValidateMapping(resolvedMapping, principals?[0]); @@ -80,72 +88,78 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) private CoreTypeMapping? FindMappingWithConversion( TypeMappingInfo mappingInfo, Type? providerClrType, - ValueConverter? customConverter) + ValueConverter? customConverter, + CoreTypeMapping? elementMapping) => _explicitMappings.GetOrAdd( - (mappingInfo, providerClrType, customConverter), + (mappingInfo, providerClrType, customConverter, elementMapping), static (k, self) => { - var (info, providerType, converter) = k; - var mapping = providerType == null - || providerType == info.ClrType - ? self.FindMapping(info) - : null; + var (info, providerType, converter, elementMapping) = k; - if (mapping == null) + var sourceType = info.ClrType; + CoreTypeMapping? mapping = null; + + if (elementMapping == null + || converter != null) { - var sourceType = info.ClrType; - if (sourceType != null) + mapping = providerType == null + || providerType == info.ClrType + ? self.FindMapping(info) + : null; + + if (mapping == null) { - foreach (var converterInfo in self.Dependencies - .ValueConverterSelector - .Select(sourceType, providerType)) + if (sourceType != null) { - var mappingInfoUsed = info.WithConverter(converterInfo); - mapping = self.FindMapping(mappingInfoUsed); - - if (mapping == null - && providerType != null) + foreach (var converterInfo in self.Dependencies + .ValueConverterSelector + .Select(sourceType, providerType)) { - foreach (var secondConverterInfo in self.Dependencies - .ValueConverterSelector - .Select(providerType)) - { - mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo)); + var mappingInfoUsed = info.WithConverter(converterInfo); + mapping = self.FindMapping(mappingInfoUsed); - if (mapping != null) + if (mapping == null + && providerType != null) + { + foreach (var secondConverterInfo in self.Dependencies + .ValueConverterSelector + .Select(providerType)) { - mapping = mapping.Clone( - secondConverterInfo.Create(), - info.ElementTypeMapping, - jsonValueReaderWriter: mappingInfoUsed.JsonValueReaderWriter); - break; + mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo)); + + if (mapping != null) + { + mapping = mapping.Clone( + secondConverterInfo.Create(), + jsonValueReaderWriter: mappingInfoUsed.JsonValueReaderWriter); + break; + } } } - } - if (mapping != null) - { - mapping = mapping.Clone( - converterInfo.Create(), - info.ElementTypeMapping, - jsonValueReaderWriter: info.JsonValueReaderWriter); - break; + if (mapping != null) + { + mapping = mapping.Clone( + converterInfo.Create(), + jsonValueReaderWriter: info.JsonValueReaderWriter); + break; + } } - } - if (mapping == null) - { - mapping = self.TryFindCollectionMapping(info, sourceType, providerType); + mapping ??= self.TryFindCollectionMapping(info, sourceType, providerType, elementMapping); } } } + else if (sourceType != null) + { + mapping = self.TryFindCollectionMapping(info, sourceType, providerType, elementMapping); + } if (mapping != null && converter != null) { mapping = mapping.Clone( converter, - info.ElementTypeMapping, jsonValueReaderWriter: info.JsonValueReaderWriter); } @@ -159,14 +173,15 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// The mapping info being used. /// The model type. /// The provider type. - /// The type mapping, or if none was found. + /// The element mapping, if known. + /// The type mapping, or if none was found. protected virtual CoreTypeMapping? TryFindCollectionMapping( TypeMappingInfo info, Type modelType, - Type? providerType) + Type? providerType, + CoreTypeMapping? elementMapping) => TryFindJsonCollectionMapping( - info, modelType, providerType, out var elementMapping, - out var collectionReaderWriter) + info, modelType, providerType, ref elementMapping, out var collectionReaderWriter) ? FindMapping( info.WithConverter( // Note that the converter info is only used temporarily here and never creates an instance. @@ -194,6 +209,29 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) return FindMappingWithConversion(new TypeMappingInfo(principals), principals); } + /// + /// Finds the type mapping for a given . + /// + /// + /// Note: providers should typically not need to override this method. + /// + /// The property. + /// The type mapping, or if none was found. + public override CoreTypeMapping? FindMapping(IElementType elementType) + { + var providerClrType = elementType.GetProviderClrType(); + var customConverter = elementType.GetValueConverter(); + + var resolvedMapping = FindMappingWithConversion( + new TypeMappingInfo( + elementType, elementType.IsUnicode(), elementType.GetMaxLength(), elementType.GetPrecision(), elementType.GetScale()), + providerClrType, customConverter, null); + + ValidateMapping(resolvedMapping, null); + + return resolvedMapping; + } + /// /// Finds the type mapping for a given . /// @@ -246,12 +284,7 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) scale: typeConfiguration.GetScale()); } - if (elementMapping != null) - { - mappingInfo = mappingInfo.WithElementTypeMapping(elementMapping); - } - - return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); + return FindMappingWithConversion(mappingInfo, providerClrType, customConverter, elementMapping); } /// diff --git a/src/EFCore/Storage/TypeMappingSourceBase.cs b/src/EFCore/Storage/TypeMappingSourceBase.cs index 425b4eabb47..920a7666bef 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -87,6 +87,13 @@ protected virtual void ValidateMapping( /// The type mapping, or if none was found. public abstract CoreTypeMapping? FindMapping(IProperty property); + /// + /// Finds the type mapping for a given . + /// + /// The collection element. + /// The type mapping, or if none was found. + public abstract CoreTypeMapping? FindMapping(IElementType elementType); + /// /// Finds the type mapping for a given . /// @@ -142,7 +149,7 @@ protected virtual bool TryFindJsonCollectionMapping( TypeMappingInfo mappingInfo, Type modelClrType, Type? providerClrType, - out CoreTypeMapping? elementMapping, + ref CoreTypeMapping? elementMapping, out JsonValueReaderWriter? collectionReaderWriter) { if ((providerClrType == null || providerClrType == typeof(string)) @@ -150,8 +157,7 @@ protected virtual bool TryFindJsonCollectionMapping( && elementType != modelClrType && !modelClrType.GetGenericTypeImplementations(typeof(IDictionary<,>)).Any()) { - elementMapping = mappingInfo.ElementTypeMapping - ?? FindMapping(elementType); + elementMapping ??= FindMapping(elementType); if (elementMapping is { ElementTypeMapping: null, JsonValueReaderWriter: not null }) { diff --git a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs index a175b10d3fa..48a3b335a72 100644 --- a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs @@ -108,10 +108,6 @@ public override void Can_read_write_collection_of_TimeSpan_JSON_values() // Cosmos currently uses a different mechanism for primitive collections => Assert.Throws(() => base.Can_read_write_collection_of_TimeSpan_JSON_values()); - public override void Can_read_write_collection_of_bool_JSON_values() - // Cosmos currently uses a different mechanism for primitive collections - => Assert.Throws(() => base.Can_read_write_collection_of_bool_JSON_values()); - public override void Can_read_write_collection_of_char_JSON_values() // Cosmos currently uses a different mechanism for primitive collections => Assert.Throws(() => base.Can_read_write_collection_of_char_JSON_values()); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 5c5d872aefb..d798e071d2a 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -98,6 +98,7 @@ public void Test_new_annotations_handled_for_entity_types() #pragma warning disable CS0618 RelationalAnnotationNames.ContainerColumnTypeMapping, #pragma warning restore CS0618 + RelationalAnnotationNames.StoreType }; // Add a line here if the code generator is supposed to handle this annotation @@ -269,6 +270,7 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.ContainerColumnTypeMapping, #pragma warning restore CS0618 RelationalAnnotationNames.JsonPropertyName, + RelationalAnnotationNames.StoreType, }; var columnMapping = $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_int_mapping"")"; @@ -560,7 +562,7 @@ public void Migrations_compile() }, Array.Empty()); Assert.Equal( -""" + """ using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -631,7 +633,7 @@ protected override void Down(MigrationBuilder migrationBuilder) "20150511161616_MyMigration", finalizedModel); Assert.Equal( -""" + """ // using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore; @@ -767,7 +769,7 @@ public void Snapshots_compile() "MySnapshot", finalizedModel); Assert.Equal( -""" + """ // using System; using System.Text.RegularExpressions; diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index c45a2f3012b..f174d029d3c 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -1208,7 +1208,8 @@ public virtual void Can_read_write_collection_of_sbyte_JSON_values() 0, sbyte.MaxValue }, - """{"Prop":[-128,0,127]}"""); + """{"Prop":[-128,0,127]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_short_JSON_values() @@ -1220,7 +1221,8 @@ public virtual void Can_read_write_collection_of_short_JSON_values() 0, short.MaxValue }, - """{"Prop":[-32768,0,32767]}"""); + """{"Prop":[-32768,0,32767]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_int_JSON_values() @@ -1232,7 +1234,8 @@ public virtual void Can_read_write_collection_of_int_JSON_values() 0, int.MaxValue }, - """{"Prop":[-2147483648,0,2147483647]}"""); + """{"Prop":[-2147483648,0,2147483647]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_long_JSON_values() @@ -1244,7 +1247,8 @@ public virtual void Can_read_write_collection_of_long_JSON_values() 0, long.MaxValue }, - """{"Prop":[-9223372036854775808,0,9223372036854775807]}"""); + """{"Prop":[-9223372036854775808,0,9223372036854775807]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_byte_JSON_values() @@ -1256,7 +1260,8 @@ public virtual void Can_read_write_collection_of_byte_JSON_values() 1, byte.MaxValue }, - """{"Prop":[0,1,255]}"""); + """{"Prop":[0,1,255]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_ushort_JSON_values() @@ -1268,7 +1273,8 @@ public virtual void Can_read_write_collection_of_ushort_JSON_values() 1, ushort.MaxValue }, - """{"Prop":[0,1,65535]}"""); + """{"Prop":[0,1,65535]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_uint_JSON_values() @@ -1280,7 +1286,8 @@ public virtual void Can_read_write_collection_of_uint_JSON_values() 1, uint.MaxValue }, - """{"Prop":[0,1,4294967295]}"""); + """{"Prop":[0,1,4294967295]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_ulong_JSON_values() @@ -1293,6 +1300,7 @@ public virtual void Can_read_write_collection_of_ulong_JSON_values() ulong.MaxValue }, """{"Prop":[0,1,18446744073709551615]}""", + mappedCollection: true, new ObservableCollection()); [ConditionalFact] @@ -1305,7 +1313,8 @@ public virtual void Can_read_write_collection_of_float_JSON_values() 0, float.MaxValue }, - """{"Prop":[-3.4028235E+38,0,3.4028235E+38]}"""); + """{"Prop":[-3.4028235E+38,0,3.4028235E+38]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_double_JSON_values() @@ -1317,7 +1326,8 @@ public virtual void Can_read_write_collection_of_double_JSON_values() 0, double.MaxValue }, - """{"Prop":[-1.7976931348623157E+308,0,1.7976931348623157E+308]}"""); + """{"Prop":[-1.7976931348623157E+308,0,1.7976931348623157E+308]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_decimal_JSON_values() @@ -1329,7 +1339,8 @@ public virtual void Can_read_write_collection_of_decimal_JSON_values() 0, decimal.MaxValue }, - """{"Prop":[-79228162514264337593543950335,0,79228162514264337593543950335]}"""); + """{"Prop":[-79228162514264337593543950335,0,79228162514264337593543950335]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_DateOnly_JSON_values() @@ -1341,7 +1352,8 @@ public virtual void Can_read_write_collection_of_DateOnly_JSON_values() new(2023, 5, 29), DateOnly.MaxValue }, - """{"Prop":["0001-01-01","2023-05-29","9999-12-31"]}"""); + """{"Prop":["0001-01-01","2023-05-29","9999-12-31"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_TimeOnly_JSON_values() @@ -1354,6 +1366,7 @@ public virtual void Can_read_write_collection_of_TimeOnly_JSON_values() TimeOnly.MaxValue }, """{"Prop":["00:00:00.0000000","11:05:02.0030040","23:59:59.9999999"]}""", + mappedCollection: true, new List()); [ConditionalFact] @@ -1366,7 +1379,8 @@ public virtual void Can_read_write_collection_of_DateTime_JSON_values() new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue }, - """{"Prop":["0001-01-01T00:00:00","2023-05-29T10:52:47","9999-12-31T23:59:59.9999999"]}"""); + """{"Prop":["0001-01-01T00:00:00","2023-05-29T10:52:47","9999-12-31T23:59:59.9999999"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_DateTimeOffset_JSON_values() @@ -1380,7 +1394,8 @@ public virtual void Can_read_write_collection_of_DateTimeOffset_JSON_values() new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(2, 0, 0)), DateTimeOffset.MaxValue }, - """{"Prop":["0001-01-01T00:00:00+00:00","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47+00:00","2023-05-29T10:52:47+02:00","9999-12-31T23:59:59.9999999+00:00"]}"""); + """{"Prop":["0001-01-01T00:00:00+00:00","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47+00:00","2023-05-29T10:52:47+02:00","9999-12-31T23:59:59.9999999+00:00"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_TimeSpan_JSON_values() @@ -1392,14 +1407,16 @@ public virtual void Can_read_write_collection_of_TimeSpan_JSON_values() new(1, 2, 3, 4, 5), TimeSpan.MaxValue }, - """{"Prop":["-10675199:2:48:05.4775808","1:2:03:04.005","10675199:2:48:05.4775807"]}"""); + """{"Prop":["-10675199:2:48:05.4775808","1:2:03:04.005","10675199:2:48:05.4775807"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_bool_JSON_values() => Can_read_and_write_JSON_value( Fixture.EntityType().GetProperty(nameof(PrimitiveTypeCollections.Boolean)), new List { false, true }, - """{"Prop":[false,true]}"""); + """{"Prop":[false,true]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_char_JSON_values() @@ -1411,7 +1428,8 @@ public virtual void Can_read_write_collection_of_char_JSON_values() 'X', char.MaxValue }, - """{"Prop":["\u0000","X","\uFFFF"]}"""); + """{"Prop":["\u0000","X","\uFFFF"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_GUID_JSON_values() @@ -1423,7 +1441,8 @@ public virtual void Can_read_write_collection_of_GUID_JSON_values() new("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD"), Guid.Parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") }, - """{"Prop":["00000000-0000-0000-0000-000000000000","8c44242f-8e3f-4a20-8be8-98c7c1aadebd","ffffffff-ffff-ffff-ffff-ffffffffffff"]}"""); + """{"Prop":["00000000-0000-0000-0000-000000000000","8c44242f-8e3f-4a20-8be8-98c7c1aadebd","ffffffff-ffff-ffff-ffff-ffffffffffff"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_string_JSON_values() @@ -1435,7 +1454,8 @@ public virtual void Can_read_write_collection_of_string_JSON_values() "❤❥웃유♋☮✌☏☢☠✔☑♚▲♪฿Ɖ⛏♥❣♂♀☿👍✍✉☣☤✘☒♛▼♫⌘⌛¡♡ღツ☼☁❅♾️✎©®™Σ✪✯☭➳Ⓐ✞℃℉°✿⚡☃☂✄¢€£∞✫★½☯✡☪", "MaxValue" }, - """{"Prop":["MinValue","\u2764\u2765\uC6C3\uC720\u264B\u262E\u270C\u260F\u2622\u2620\u2714\u2611\u265A\u25B2\u266A\u0E3F\u0189\u26CF\u2665\u2763\u2642\u2640\u263F\uD83D\uDC4D\u270D\u2709\u2623\u2624\u2718\u2612\u265B\u25BC\u266B\u2318\u231B\u00A1\u2661\u10E6\u30C4\u263C\u2601\u2745\u267E\uFE0F\u270E\u00A9\u00AE\u2122\u03A3\u272A\u272F\u262D\u27B3\u24B6\u271E\u2103\u2109\u00B0\u273F\u26A1\u2603\u2602\u2704\u00A2\u20AC\u00A3\u221E\u272B\u2605\u00BD\u262F\u2721\u262A","MaxValue"]}"""); + """{"Prop":["MinValue","\u2764\u2765\uC6C3\uC720\u264B\u262E\u270C\u260F\u2622\u2620\u2714\u2611\u265A\u25B2\u266A\u0E3F\u0189\u26CF\u2665\u2763\u2642\u2640\u263F\uD83D\uDC4D\u270D\u2709\u2623\u2624\u2718\u2612\u265B\u25BC\u266B\u2318\u231B\u00A1\u2661\u10E6\u30C4\u263C\u2601\u2745\u267E\uFE0F\u270E\u00A9\u00AE\u2122\u03A3\u272A\u272F\u262D\u27B3\u24B6\u271E\u2103\u2109\u00B0\u273F\u26A1\u2603\u2602\u2704\u00A2\u20AC\u00A3\u221E\u272B\u2605\u00BD\u262F\u2721\u262A","MaxValue"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_binary_JSON_values() @@ -1448,7 +1468,8 @@ public virtual void Can_read_write_collection_of_binary_JSON_values() Array.Empty(), new byte[] { 1, 2, 3, 4 } }, - """{"Prop":["AAAAAQ==","/////w==","","AQIDBA=="]}"""); + """{"Prop":["AAAAAQ==","/////w==","","AQIDBA=="]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_URI_JSON_values() @@ -1459,7 +1480,8 @@ public virtual void Can_read_write_collection_of_URI_JSON_values() new("https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName"), new("file:///C:/test/path/file.txt") }, - """{"Prop":["https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1\u0026q2=v2#FragmentName","file:///C:/test/path/file.txt"]}"""); + """{"Prop":["https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1\u0026q2=v2#FragmentName","file:///C:/test/path/file.txt"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_IP_address_JSON_values() @@ -1475,7 +1497,8 @@ public virtual void Can_read_write_collection_of_IP_address_JSON_values() IPAddress.Parse("::"), IPAddress.Parse("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"), }, - """{"Prop":["127.0.0.1","0.0.0.0","255.255.255.255","192.168.1.156","::1","::","2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"]}"""); + """{"Prop":["127.0.0.1","0.0.0.0","255.255.255.255","192.168.1.156","::1","::","2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_physical_address_JSON_values() @@ -1488,7 +1511,8 @@ public virtual void Can_read_write_collection_of_physical_address_JSON_values() PhysicalAddress.Parse("00-11-22-33-44-55"), PhysicalAddress.Parse("0011.2233.4455") }, - """{"Prop":["","001122334455","001122334455","001122334455"]}"""); + """{"Prop":["","001122334455","001122334455","001122334455"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_sbyte_enum_JSON_values() @@ -1502,7 +1526,8 @@ public virtual void Can_read_write_collection_of_sbyte_enum_JSON_values() Enum8.One, (Enum8)(-8) }, - """{"Prop":[-128,127,0,1,-8]}"""); + """{"Prop":[-128,127,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_short_enum_JSON_values() @@ -1516,7 +1541,8 @@ public virtual void Can_read_write_collection_of_short_enum_JSON_values() Enum16.One, (Enum16)(-8) }, - """{"Prop":[-32768,32767,0,1,-8]}"""); + """{"Prop":[-32768,32767,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_int_enum_JSON_values() @@ -1530,7 +1556,8 @@ public virtual void Can_read_write_collection_of_int_enum_JSON_values() Enum32.One, (Enum32)(-8) }, - """{"Prop":[-2147483648,2147483647,0,1,-8]}"""); + """{"Prop":[-2147483648,2147483647,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_long_enum_JSON_values() @@ -1544,7 +1571,8 @@ public virtual void Can_read_write_collection_of_long_enum_JSON_values() Enum64.One, (Enum64)(-8) }, - """{"Prop":[-9223372036854775808,9223372036854775807,0,1,-8]}"""); + """{"Prop":[-9223372036854775808,9223372036854775807,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_byte_enum_JSON_values() @@ -1558,7 +1586,8 @@ public virtual void Can_read_write_collection_of_byte_enum_JSON_values() EnumU8.One, (EnumU8)8 }, - """{"Prop":[0,255,0,1,8]}"""); + """{"Prop":[0,255,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_ushort_enum_JSON_values() @@ -1572,7 +1601,8 @@ public virtual void Can_read_write_collection_of_ushort_enum_JSON_values() EnumU16.One, (EnumU16)8 }, - """{"Prop":[0,65535,0,1,8]}"""); + """{"Prop":[0,65535,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_uint_enum_JSON_values() @@ -1587,6 +1617,7 @@ public virtual void Can_read_write_collection_of_uint_enum_JSON_values() (EnumU32)8 }, """{"Prop":[0,4294967295,0,1,8]}""", + mappedCollection: true, new ObservableCollection()); [ConditionalFact] @@ -1601,7 +1632,8 @@ public virtual void Can_read_write_collection_of_ulong_enum_JSON_values() EnumU64.One, (EnumU64)8 }, - """{"Prop":[0,18446744073709551615,0,1,8]}"""); + """{"Prop":[0,18446744073709551615,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_sbyte_JSON_values() @@ -1614,7 +1646,8 @@ public virtual void Can_read_write_collection_of_nullable_sbyte_JSON_values() 0, sbyte.MaxValue }, - """{"Prop":[null,-128,0,127]}"""); + """{"Prop":[null,-128,0,127]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_short_JSON_values() @@ -1627,7 +1660,8 @@ public virtual void Can_read_write_collection_of_nullable_short_JSON_values() 0, short.MaxValue }, - """{"Prop":[-32768,null,0,32767]}"""); + """{"Prop":[-32768,null,0,32767]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_int_JSON_values() @@ -1640,7 +1674,8 @@ public virtual void Can_read_write_collection_of_nullable_int_JSON_values() null, int.MaxValue }, - """{"Prop":[-2147483648,0,null,2147483647]}"""); + """{"Prop":[-2147483648,0,null,2147483647]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_long_JSON_values() @@ -1653,7 +1688,8 @@ public virtual void Can_read_write_collection_of_nullable_long_JSON_values() long.MaxValue, null }, - """{"Prop":[-9223372036854775808,0,9223372036854775807,null]}"""); + """{"Prop":[-9223372036854775808,0,9223372036854775807,null]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_byte_JSON_values() @@ -1666,7 +1702,8 @@ public virtual void Can_read_write_collection_of_nullable_byte_JSON_values() 1, byte.MaxValue }, - """{"Prop":[null,0,1,255]}"""); + """{"Prop":[null,0,1,255]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_ushort_JSON_values() @@ -1679,7 +1716,8 @@ public virtual void Can_read_write_collection_of_nullable_ushort_JSON_values() 1, ushort.MaxValue }, - """{"Prop":[0,null,1,65535]}"""); + """{"Prop":[0,null,1,65535]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_uint_JSON_values() @@ -1692,7 +1730,8 @@ public virtual void Can_read_write_collection_of_nullable_uint_JSON_values() null, uint.MaxValue }, - """{"Prop":[0,1,null,4294967295]}"""); + """{"Prop":[0,1,null,4294967295]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_ulong_JSON_values() @@ -1705,7 +1744,8 @@ public virtual void Can_read_write_collection_of_nullable_ulong_JSON_values() ulong.MaxValue, null }, - """{"Prop":[0,1,18446744073709551615,null]}"""); + """{"Prop":[0,1,18446744073709551615,null]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_float_JSON_values() @@ -1718,7 +1758,8 @@ public virtual void Can_read_write_collection_of_nullable_float_JSON_values() 0, float.MaxValue }, - """{"Prop":[null,-3.4028235E+38,0,3.4028235E+38]}"""); + """{"Prop":[null,-3.4028235E+38,0,3.4028235E+38]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_double_JSON_values() @@ -1731,7 +1772,8 @@ public virtual void Can_read_write_collection_of_nullable_double_JSON_values() 0, double.MaxValue }, - """{"Prop":[-1.7976931348623157E+308,null,0,1.7976931348623157E+308]}"""); + """{"Prop":[-1.7976931348623157E+308,null,0,1.7976931348623157E+308]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_decimal_JSON_values() @@ -1744,7 +1786,8 @@ public virtual void Can_read_write_collection_of_nullable_decimal_JSON_values() null, decimal.MaxValue }, - """{"Prop":[-79228162514264337593543950335,0,null,79228162514264337593543950335]}"""); + """{"Prop":[-79228162514264337593543950335,0,null,79228162514264337593543950335]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_DateOnly_JSON_values() @@ -1757,7 +1800,8 @@ public virtual void Can_read_write_collection_of_nullable_DateOnly_JSON_values() DateOnly.MaxValue, null }, - """{"Prop":["0001-01-01","2023-05-29","9999-12-31",null]}"""); + """{"Prop":["0001-01-01","2023-05-29","9999-12-31",null]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_TimeOnly_JSON_values() @@ -1770,7 +1814,8 @@ public virtual void Can_read_write_collection_of_nullable_TimeOnly_JSON_values() new(11, 5, 2, 3, 4), TimeOnly.MaxValue }, - """{"Prop":[null,"00:00:00.0000000","11:05:02.0030040","23:59:59.9999999"]}"""); + """{"Prop":[null,"00:00:00.0000000","11:05:02.0030040","23:59:59.9999999"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_DateTime_JSON_values() @@ -1783,7 +1828,8 @@ public virtual void Can_read_write_collection_of_nullable_DateTime_JSON_values() new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue }, - """{"Prop":["0001-01-01T00:00:00",null,"2023-05-29T10:52:47","9999-12-31T23:59:59.9999999"]}"""); + """{"Prop":["0001-01-01T00:00:00",null,"2023-05-29T10:52:47","9999-12-31T23:59:59.9999999"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values() @@ -1798,7 +1844,8 @@ public virtual void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_va new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(2, 0, 0)), DateTimeOffset.MaxValue }, - """{"Prop":["0001-01-01T00:00:00+00:00","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47+00:00",null,"2023-05-29T10:52:47+02:00","9999-12-31T23:59:59.9999999+00:00"]}"""); + """{"Prop":["0001-01-01T00:00:00+00:00","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47+00:00",null,"2023-05-29T10:52:47+02:00","9999-12-31T23:59:59.9999999+00:00"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_TimeSpan_JSON_values() @@ -1811,7 +1858,8 @@ public virtual void Can_read_write_collection_of_nullable_TimeSpan_JSON_values() TimeSpan.MaxValue, null }, - """{"Prop":["-10675199:2:48:05.4775808","1:2:03:04.005","10675199:2:48:05.4775807",null]}"""); + """{"Prop":["-10675199:2:48:05.4775808","1:2:03:04.005","10675199:2:48:05.4775807",null]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_bool_JSON_values() @@ -1823,7 +1871,8 @@ public virtual void Can_read_write_collection_of_nullable_bool_JSON_values() null, true }, - """{"Prop":[false,null,true]}"""); + """{"Prop":[false,null,true]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_char_JSON_values() @@ -1836,7 +1885,8 @@ public virtual void Can_read_write_collection_of_nullable_char_JSON_values() char.MaxValue, null }, - """{"Prop":["\u0000","X","\uFFFF",null]}"""); + """{"Prop":["\u0000","X","\uFFFF",null]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_GUID_JSON_values() @@ -1849,7 +1899,8 @@ public virtual void Can_read_write_collection_of_nullable_GUID_JSON_values() new("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD"), Guid.Parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") }, - """{"Prop":["00000000-0000-0000-0000-000000000000",null,"8c44242f-8e3f-4a20-8be8-98c7c1aadebd","ffffffff-ffff-ffff-ffff-ffffffffffff"]}"""); + """{"Prop":["00000000-0000-0000-0000-000000000000",null,"8c44242f-8e3f-4a20-8be8-98c7c1aadebd","ffffffff-ffff-ffff-ffff-ffffffffffff"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_string_JSON_values() @@ -1862,7 +1913,8 @@ public virtual void Can_read_write_collection_of_nullable_string_JSON_values() "❤❥웃유♋☮✌☏☢☠✔☑♚▲♪฿Ɖ⛏♥❣♂♀☿👍✍✉☣☤✘☒♛▼♫⌘⌛¡♡ღツ☼☁❅♾️✎©®™Σ✪✯☭➳Ⓐ✞℃℉°✿⚡☃☂✄¢€£∞✫★½☯✡☪", "MaxValue" }, - """{"Prop":["MinValue",null,"\u2764\u2765\uC6C3\uC720\u264B\u262E\u270C\u260F\u2622\u2620\u2714\u2611\u265A\u25B2\u266A\u0E3F\u0189\u26CF\u2665\u2763\u2642\u2640\u263F\uD83D\uDC4D\u270D\u2709\u2623\u2624\u2718\u2612\u265B\u25BC\u266B\u2318\u231B\u00A1\u2661\u10E6\u30C4\u263C\u2601\u2745\u267E\uFE0F\u270E\u00A9\u00AE\u2122\u03A3\u272A\u272F\u262D\u27B3\u24B6\u271E\u2103\u2109\u00B0\u273F\u26A1\u2603\u2602\u2704\u00A2\u20AC\u00A3\u221E\u272B\u2605\u00BD\u262F\u2721\u262A","MaxValue"]}"""); + """{"Prop":["MinValue",null,"\u2764\u2765\uC6C3\uC720\u264B\u262E\u270C\u260F\u2622\u2620\u2714\u2611\u265A\u25B2\u266A\u0E3F\u0189\u26CF\u2665\u2763\u2642\u2640\u263F\uD83D\uDC4D\u270D\u2709\u2623\u2624\u2718\u2612\u265B\u25BC\u266B\u2318\u231B\u00A1\u2661\u10E6\u30C4\u263C\u2601\u2745\u267E\uFE0F\u270E\u00A9\u00AE\u2122\u03A3\u272A\u272F\u262D\u27B3\u24B6\u271E\u2103\u2109\u00B0\u273F\u26A1\u2603\u2602\u2704\u00A2\u20AC\u00A3\u221E\u272B\u2605\u00BD\u262F\u2721\u262A","MaxValue"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_binary_JSON_values() @@ -1876,7 +1928,8 @@ public virtual void Can_read_write_collection_of_nullable_binary_JSON_values() Array.Empty(), new byte[] { 1, 2, 3, 4 } }, - """{"Prop":["AAAAAQ==",null,"/////w==","","AQIDBA=="]}"""); + """{"Prop":["AAAAAQ==",null,"/////w==","","AQIDBA=="]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_URI_JSON_values() @@ -1888,7 +1941,8 @@ public virtual void Can_read_write_collection_of_nullable_URI_JSON_values() null, new("file:///C:/test/path/file.txt") }, - """{"Prop":["https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1\u0026q2=v2#FragmentName",null,"file:///C:/test/path/file.txt"]}"""); + """{"Prop":["https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1\u0026q2=v2#FragmentName",null,"file:///C:/test/path/file.txt"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_IP_address_JSON_values() @@ -1905,7 +1959,8 @@ public virtual void Can_read_write_collection_of_nullable_IP_address_JSON_values IPAddress.Parse("::"), IPAddress.Parse("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"), }, - """{"Prop":["127.0.0.1",null,"0.0.0.0","255.255.255.255","192.168.1.156","::1","::","2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"]}"""); + """{"Prop":["127.0.0.1",null,"0.0.0.0","255.255.255.255","192.168.1.156","::1","::","2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_physical_address_JSON_values() @@ -1919,7 +1974,8 @@ public virtual void Can_read_write_collection_of_nullable_physical_address_JSON_ PhysicalAddress.Parse("00-11-22-33-44-55"), PhysicalAddress.Parse("0011.2233.4455") }, - """{"Prop":["",null,"001122334455","001122334455","001122334455"]}"""); + """{"Prop":["",null,"001122334455","001122334455","001122334455"]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_sbyte_enum_JSON_values() @@ -1934,7 +1990,8 @@ public virtual void Can_read_write_collection_of_nullable_sbyte_enum_JSON_values Enum8.One, (Enum8)(-8) }, - """{"Prop":[-128,null,127,0,1,-8]}"""); + """{"Prop":[-128,null,127,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_short_enum_JSON_values() @@ -1949,7 +2006,8 @@ public virtual void Can_read_write_collection_of_nullable_short_enum_JSON_values Enum16.One, (Enum16)(-8) }, - """{"Prop":[-32768,null,32767,0,1,-8]}"""); + """{"Prop":[-32768,null,32767,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_int_enum_JSON_values() @@ -1964,7 +2022,8 @@ public virtual void Can_read_write_collection_of_nullable_int_enum_JSON_values() Enum32.One, (Enum32)(-8) }, - """{"Prop":[-2147483648,null,2147483647,0,1,-8]}"""); + """{"Prop":[-2147483648,null,2147483647,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_long_enum_JSON_values() @@ -1979,7 +2038,8 @@ public virtual void Can_read_write_collection_of_nullable_long_enum_JSON_values( Enum64.One, (Enum64)(-8) }, - """{"Prop":[-9223372036854775808,null,9223372036854775807,0,1,-8]}"""); + """{"Prop":[-9223372036854775808,null,9223372036854775807,0,1,-8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_byte_enum_JSON_values() @@ -1994,7 +2054,8 @@ public virtual void Can_read_write_collection_of_nullable_byte_enum_JSON_values( EnumU8.One, (EnumU8)8 }, - """{"Prop":[0,null,255,0,1,8]}"""); + """{"Prop":[0,null,255,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_ushort_enum_JSON_values() @@ -2009,7 +2070,8 @@ public virtual void Can_read_write_collection_of_nullable_ushort_enum_JSON_value EnumU16.One, (EnumU16)8 }, - """{"Prop":[0,null,65535,0,1,8]}"""); + """{"Prop":[0,null,65535,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_uint_enum_JSON_values() @@ -2024,7 +2086,8 @@ public virtual void Can_read_write_collection_of_nullable_uint_enum_JSON_values( EnumU32.One, (EnumU32)8 }, - """{"Prop":[0,null,4294967295,0,1,8]}"""); + """{"Prop":[0,null,4294967295,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() @@ -2039,7 +2102,8 @@ public virtual void Can_read_write_collection_of_nullable_ulong_enum_JSON_values EnumU64.One, (EnumU64)8 }, - """{"Prop":[0,null,18446744073709551615,0,1,8]}"""); + """{"Prop":[0,null,18446744073709551615,0,1,8]}""", + mappedCollection: true); [ConditionalFact] public virtual void Can_read_write_collection_of_sbyte_values_with_converter_as_JSON_string() @@ -2323,10 +2387,47 @@ public virtual void Can_read_write_collection_of_nullable_ulong_enum_values_with }, """{"Prop":"[0,null,18446744073709551615,0,1,8]"}"""); + [ConditionalFact] + public virtual void Can_read_write_collection_of_int_with_converter_JSON_values() + => Can_read_and_write_JSON_value( + Fixture.EntityType().GetProperty(nameof(PrimitiveTypeCollections.DddId)), + new List + { + new() { Id = int.MinValue }, + new() { Id = 0 }, + new() { Id = int.MaxValue } + }, + """{"Prop":[-2147483648,0,2147483647]}""", + mappedCollection: true); + + [ConditionalFact] + public virtual void Can_read_write_collection_of_nullable_int_with_converter_JSON_values() + => Can_read_and_write_JSON_value( + Fixture.EntityType().GetProperty(nameof(NullablePrimitiveTypeCollections.DddId)), + new List + { + null, + new() { Id = int.MinValue }, + null, + new() { Id = 0 }, + new() { Id = int.MaxValue } + }, + """{"Prop":[null,-2147483648,null,0,2147483647]}""", + mappedCollection: true); + + [ConditionalFact] + public virtual void Can_read_write_binary_as_collection() + => Can_read_and_write_JSON_value( + Fixture.EntityType().GetProperty(nameof(PrimitiveTypeCollections.BinaryAsJson)), + new byte[] { 77, 78, 79, 80 }, + """{"Prop":[77,78,79,80]}""", + mappedCollection: true); + protected virtual void Can_read_and_write_JSON_value( IProperty property, TModel value, string json, + bool mappedCollection = false, object? existingObject = null) { using var stream = new MemoryStream(); @@ -2374,6 +2475,16 @@ protected virtual void Can_read_and_write_JSON_value( Assert.Equal(value, fromJson); } + + var element = property.GetElementType(); + if (mappedCollection) + { + Assert.NotNull(element); + } + else + { + Assert.Null(element); + } } protected class Types @@ -2670,8 +2781,8 @@ protected class PrimitiveTypeCollections public IList CharacterConverted { get; set; } = null!; public List Enum32Converted { get; set; } = null!; public IList EnumU64Converted { get; set; } = null!; - - //public IList DddId { get; set; } = null!; // TODO Custom collection element + public IList DddId { get; set; } = null!; + public byte[] BinaryAsJson { get; set; } = null!; } protected class NullablePrimitiveTypeCollections @@ -2729,8 +2840,7 @@ protected class NullablePrimitiveTypeCollections public IList CharacterConverted { get; set; } = null!; public List Enum32Converted { get; set; } = null!; public IList EnumU64Converted { get; set; } = null!; - - //public IList DddId { get; set; } = null!; // TODO Custom collection element + public IList DddId { get; set; } = null!; } public class CustomCollectionConverter : ValueConverter @@ -2949,8 +3059,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasConversion, string>, CustomCollectionComparer, string>>(); b.Property(e => e.BytesConverted) .HasConversion, byte[]>, CustomCollectionComparer, byte[]>>(); - b.Property(e => e.BooleanConverted) - .HasConversion, bool>, CustomCollectionComparer, bool>>(); b.Property(e => e.CharacterConverted) .HasConversion, char>, CustomCollectionComparer, char>>(); b.Property(e => e.Enum32Converted) @@ -2996,6 +3104,37 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasConversion, EnumU64?>, CustomCollectionComparer, EnumU64?>>(); }); + + modelBuilder + .Entity( + b => + { + b.Property(e => e.DddId).IsCollection( + b => + { + b.ElementsHaveConversion(); + b.ElementsAreRequired(); + }); + + b.Property(e => e.BinaryAsJson).IsCollection(); + + b.Property(e => e.BooleanConverted) + .IsCollection(_ => { }) + .HasConversion, bool>, CustomCollectionComparer, bool>>(); + + b.Property(e => e.Boolean) + .HasConversion, bool>, CustomCollectionComparer, bool>>() + .IsCollection(); + }); + + modelBuilder + .Entity() + .Property(e => e.DddId) + .IsCollection( + b => + { + b.ElementsHaveConversion(); + }); } public DbContext StaticContext diff --git a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs index e869ccb85c1..ce2ac31d082 100644 --- a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs @@ -43,7 +43,8 @@ public override void Can_read_write_collection_of_ulong_enum_JSON_values() EnumU64.One, (EnumU64)8 }, - """{"Prop":[0,-1,0,1,8]}"""); // Because ulong is converted to long on SQL Server + """{"Prop":[0,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server + mappedCollection: true); public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() => Can_read_and_write_JSON_value( @@ -57,7 +58,8 @@ public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_value EnumU64.One, (EnumU64)8 }, - """{"Prop":[0,null,-1,0,1,8]}"""); // Because ulong is converted to long on SQL Server + """{"Prop":[0,null,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server + mappedCollection: true); public class JsonTypesSqlServerFixture : JsonTypesFixtureBase { diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index 087a06dfcf6..79f741b22b6 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -82,6 +82,9 @@ public ValueComparer GetProviderValueComparer() public JsonValueReaderWriter GetJsonValueReaderWriter() => throw new NotImplementedException(); + public IElementType GetElementType() + => throw new NotImplementedException(); + public bool IsForeignKey() => throw new NotImplementedException(); diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs index 73b8bf1a464..482694c17f7 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs @@ -100,6 +100,9 @@ public ValueComparer GetProviderValueComparer() public JsonValueReaderWriter GetJsonValueReaderWriter() => throw new NotImplementedException(); + public IElementType GetElementType() + => throw new NotImplementedException(); + public bool IsForeignKey() => throw new NotImplementedException();