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 f8776f1671f..ba7a63c9afd 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;
@@ -63,7 +63,8 @@ public RuntimeProperty(
ValueComparer? keyValueComparer,
ValueComparer? providerValueComparer,
JsonValueReaderWriter? jsonValueReaderWriter,
- CoreTypeMapping? typeMapping)
+ CoreTypeMapping? typeMapping,
+ IElementType? element)
: base(name, propertyInfo, fieldInfo, propertyAccessMode)
{
DeclaringType = declaringType;
@@ -107,6 +108,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()));
+ }
}
///
@@ -214,7 +233,7 @@ private ValueComparer GetKeyValueComparer()
private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties)
{
- if ( _keyValueComparer != null)
+ if (_keyValueComparer != null)
{
return _keyValueComparer;
}
@@ -249,6 +268,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 cf020c65607..49252391d30 100644
--- a/src/EFCore/Metadata/RuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/RuntimeTypeBase.cs
@@ -23,8 +23,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;
@@ -61,6 +60,7 @@ public RuntimeTypeBase(
_baseType = baseType;
baseType._directlyDerivedTypes.Add(this);
}
+
_changeTrackingStrategy = changeTrackingStrategy;
_indexerPropertyInfo = indexerPropertyInfo;
_isPropertyBag = propertyBag;
@@ -85,13 +85,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
@@ -173,6 +175,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,
@@ -197,7 +203,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,
@@ -223,7 +230,8 @@ public virtual RuntimeProperty AddProperty(
keyValueComparer,
providerValueComparer,
jsonValueReaderWriter,
- typeMapping);
+ typeMapping,
+ elementType);
_properties.Add(property.Name, property);
@@ -289,7 +297,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)
@@ -326,7 +334,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]
@@ -434,7 +443,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)
@@ -449,7 +458,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);
}
///
@@ -492,9 +501,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
@@ -504,9 +511,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
@@ -516,9 +521,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
@@ -528,9 +531,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
@@ -538,12 +539,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 9a226491d78..7ebdec9036d 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;
@@ -284,11 +251,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();