diff --git a/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..d06f96a391c
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,91 @@
+// 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.Cosmos.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore;
+
+///
+/// Cosmos-specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing Azure Cosmos DB with EF Core for more information and examples.
+///
+public static class CosmosPrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures the property name that the property is mapped to when targeting Azure Cosmos.
+ ///
+ ///
+ ///
+ /// If an empty string is supplied, the property will not be persisted.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The name of the property.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder ToJsonProperty(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property name that the property is mapped to when targeting Azure Cosmos.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the property.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder ToJsonProperty(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string name)
+ => (PrimitiveCollectionBuilder)ToJsonProperty((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name);
+
+ ///
+ /// Configures this property to be the etag concurrency token.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder IsETagConcurrency(this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder
+ .IsConcurrencyToken()
+ .ToJsonProperty("_etag")
+ .ValueGeneratedOnAddOrUpdate();
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures this property to be the etag concurrency token.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder IsETagConcurrency(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (PrimitiveCollectionBuilder)IsETagConcurrency((PrimitiveCollectionBuilder)primitiveCollectionBuilder);
+}
diff --git a/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..048a41845e0
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,518 @@
+// 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 RelationalComplexTypePrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The name of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnName(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ primitiveCollectionBuilder.Metadata.SetColumnName(name);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnName(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ => (ComplexTypePrimitiveCollectionBuilder)HasColumnName((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, name);
+
+ ///
+ /// Configures the order of the column the property is mapped to.
+ ///
+ /// The builder of the property being configured.
+ /// The column order.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnOrder(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, int? order)
+ {
+ primitiveCollectionBuilder.Metadata.SetColumnOrder(order);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the order of the column the property is mapped to.
+ ///
+ /// The builder of the property being configured.
+ /// The column order.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnOrder(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, int? order)
+ => (ComplexTypePrimitiveCollectionBuilder)HasColumnOrder((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, order);
+
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnType(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? typeName)
+ {
+ Check.NullButNotEmpty(typeName, nameof(typeName));
+
+ primitiveCollectionBuilder.Metadata.SetColumnType(typeName);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasColumnType(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? typeName)
+ => (ComplexTypePrimitiveCollectionBuilder)HasColumnType((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, typeName);
+
+ ///
+ /// Configures the property 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 property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder IsFixedLength(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool fixedLength = true)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsFixedLength(fixedLength);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property as capable of storing only fixed-length data, such as strings.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder IsFixedLength(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool fixedLength = true)
+ => (ComplexTypePrimitiveCollectionBuilder)IsFixedLength((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, fixedLength);
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a
+ /// relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default value constraint of
+ /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
+ /// existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValueSql(string.Empty);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression for the default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ {
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ primitiveCollectionBuilder.Metadata.SetDefaultValueSql(sql);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a
+ /// relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default value constraint of
+ /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
+ /// existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValueSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression for the default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValueSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sql);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no arguments, this method tells EF that a column is computed without needing to
+ /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
+ /// database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetComputedColumnSql(string.Empty);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => HasComputedColumnSql(primitiveCollectionBuilder, sql, null);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ ///
+ /// If , the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If , the value is computed when the value is read, and does not occupy any actual storage.
+ /// selects the database provider default.
+ ///
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql,
+ bool? stored)
+ {
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ primitiveCollectionBuilder.Metadata.SetComputedColumnSql(sql);
+
+ if (stored != null)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsStored(stored);
+ }
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no arguments, this method tells EF that a column is computed without needing to
+ /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
+ /// database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (ComplexTypePrimitiveCollectionBuilder)HasComputedColumnSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => HasComputedColumnSql(primitiveCollectionBuilder, sql, null);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ ///
+ /// If , the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If , the value is computed when the value is read, and does not occupy any actual storage.
+ /// selects the database provider default.
+ ///
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql,
+ bool? stored)
+ => (ComplexTypePrimitiveCollectionBuilder)HasComputedColumnSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sql, stored);
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default
+ /// value constraint of some sort without needing to specify exactly what it is.
+ /// This can be useful when mapping EF to an existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValue(DBNull.Value);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ object? value)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValue(value);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default
+ /// value constraint of some sort without needing to specify exactly what it is.
+ /// This can be useful when mapping EF to an existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValue((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ object? value)
+ => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValue((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, value);
+
+ ///
+ /// Configures a comment to be applied to the column
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The comment for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComment(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? comment)
+ {
+ primitiveCollectionBuilder.Metadata.SetComment(comment);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures a comment to be applied to the column
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The comment for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasComment(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? comment)
+ => (ComplexTypePrimitiveCollectionBuilder)HasComment((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, comment);
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ ///
+ /// See Database collations for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder UseCollation(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, string? collation)
+ {
+ Check.NullButNotEmpty(collation, nameof(collation));
+
+ primitiveCollectionBuilder.Metadata.SetCollation(collation);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ ///
+ /// See Database collations for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder UseCollation(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? collation)
+ => (ComplexTypePrimitiveCollectionBuilder)UseCollation((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, collation);
+
+ ///
+ /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property,
+ /// rather than using the entity property name.
+ ///
+ /// The builder for the property being configured.
+ /// JSON property name to be used.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasJsonPropertyName(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property,
+ /// rather than using the entity property name.
+ ///
+ /// The builder for the property being configured.
+ /// JSON property name to be used.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ComplexTypePrimitiveCollectionBuilder HasJsonPropertyName(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ => (ComplexTypePrimitiveCollectionBuilder)HasJsonPropertyName((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, name);
+}
diff --git a/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs
index e2fe7925290..6b28a917b6c 100644
--- a/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
diff --git a/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs
new file mode 100644
index 00000000000..b9ee3cd2bdd
--- /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 HasStoreType(
+ 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? HasStoreType(
+ this IConventionElementTypeBuilder elementTypeBuilder,
+ string? typeName,
+ bool fromDataAnnotation = false)
+ {
+ if (!elementTypeBuilder.CanSetStoreType(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 CanSetStoreType(
+ 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 IsFixedLength(
+ 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? IsFixedLength(
+ 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..c005562fe1f
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs
@@ -0,0 +1,133 @@
+// 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.FindAnnotation(RelationalAnnotationNames.StoreType)?.Value
+ ?? elementType.FindRelationalTypeMapping()?.StoreType);
+
+ ///
+ /// 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 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/RelationalPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..10980241fc8
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,518 @@
+// 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 RelationalPrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The name of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnName(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ primitiveCollectionBuilder.Metadata.SetColumnName(name);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnName(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ => (PrimitiveCollectionBuilder)HasColumnName((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name);
+
+ ///
+ /// Configures the order of the column the property is mapped to.
+ ///
+ /// The builder of the property being configured.
+ /// The column order.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnOrder(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int? order)
+ {
+ primitiveCollectionBuilder.Metadata.SetColumnOrder(order);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the order of the column the property is mapped to.
+ ///
+ /// The builder of the property being configured.
+ /// The column order.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnOrder(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int? order)
+ => (PrimitiveCollectionBuilder)HasColumnOrder((PrimitiveCollectionBuilder)primitiveCollectionBuilder, order);
+
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnType(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? typeName)
+ {
+ Check.NullButNotEmpty(typeName, nameof(typeName));
+
+ primitiveCollectionBuilder.Metadata.SetColumnType(typeName);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasColumnType(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? typeName)
+ => (PrimitiveCollectionBuilder)HasColumnType((PrimitiveCollectionBuilder)primitiveCollectionBuilder, typeName);
+
+ ///
+ /// Configures the property 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 property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static PrimitiveCollectionBuilder IsFixedLength(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool fixedLength = true)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsFixedLength(fixedLength);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property as capable of storing only fixed-length data, such as strings.
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static PrimitiveCollectionBuilder IsFixedLength(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool fixedLength = true)
+ => (PrimitiveCollectionBuilder)IsFixedLength((PrimitiveCollectionBuilder)primitiveCollectionBuilder, fixedLength);
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a
+ /// relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default value constraint of
+ /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
+ /// existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValueSql(this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValueSql(string.Empty);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression for the default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValueSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ {
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ primitiveCollectionBuilder.Metadata.SetDefaultValueSql(sql);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a
+ /// relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default value constraint of
+ /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
+ /// existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValueSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (PrimitiveCollectionBuilder)HasDefaultValueSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the default value expression for the column that the property maps to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression for the default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValueSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => (PrimitiveCollectionBuilder)HasDefaultValueSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sql);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no arguments, this method tells EF that a column is computed without needing to
+ /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
+ /// database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetComputedColumnSql(string.Empty);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => HasComputedColumnSql(primitiveCollectionBuilder, sql, null);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ ///
+ /// If , the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If , the value is computed when the value is read, and does not occupy any actual storage.
+ /// selects the database provider default.
+ ///
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql,
+ bool? stored)
+ {
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ primitiveCollectionBuilder.Metadata.SetComputedColumnSql(sql);
+
+ if (stored != null)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsStored(stored);
+ }
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no arguments, this method tells EF that a column is computed without needing to
+ /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
+ /// database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (PrimitiveCollectionBuilder)HasComputedColumnSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql)
+ => HasComputedColumnSql(primitiveCollectionBuilder, sql, null);
+
+ ///
+ /// Configures the property to map to a computed column when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The SQL expression that computes values for the column.
+ ///
+ /// If , the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If , the value is computed when the value is read, and does not occupy any actual storage.
+ /// selects the database provider default.
+ ///
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComputedColumnSql(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? sql,
+ bool? stored)
+ => (PrimitiveCollectionBuilder)HasComputedColumnSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sql, stored);
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default
+ /// value constraint of some sort without needing to specify exactly what it is.
+ /// This can be useful when mapping EF to an existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValue(this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValue(DBNull.Value);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValue(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ object? value)
+ {
+ primitiveCollectionBuilder.Metadata.SetDefaultValue(value);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ ///
+ /// When called with no argument, this method tells EF that a column has a default
+ /// value constraint of some sort without needing to specify exactly what it is.
+ /// This can be useful when mapping EF to an existing database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValue(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => (PrimitiveCollectionBuilder)HasDefaultValue((PrimitiveCollectionBuilder)primitiveCollectionBuilder);
+
+ ///
+ /// Configures the default value for the column that the property maps
+ /// to when targeting a relational database.
+ ///
+ ///
+ /// See Database default values for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The default value of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasDefaultValue(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ object? value)
+ => (PrimitiveCollectionBuilder)HasDefaultValue((PrimitiveCollectionBuilder)primitiveCollectionBuilder, value);
+
+ ///
+ /// Configures a comment to be applied to the column
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The comment for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComment(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? comment)
+ {
+ primitiveCollectionBuilder.Metadata.SetComment(comment);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures a comment to be applied to the column
+ ///
+ ///
+ /// See Modeling entity types and relationships for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The comment for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasComment(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? comment)
+ => (PrimitiveCollectionBuilder)HasComment((PrimitiveCollectionBuilder)primitiveCollectionBuilder, comment);
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ ///
+ /// See Database collations for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder UseCollation(this PrimitiveCollectionBuilder primitiveCollectionBuilder, string? collation)
+ {
+ Check.NullButNotEmpty(collation, nameof(collation));
+
+ primitiveCollectionBuilder.Metadata.SetCollation(collation);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ ///
+ /// See Database collations for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder UseCollation(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? collation)
+ => (PrimitiveCollectionBuilder)UseCollation((PrimitiveCollectionBuilder)primitiveCollectionBuilder, collation);
+
+ ///
+ /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property,
+ /// rather than using the entity property name.
+ ///
+ /// The builder for the property being configured.
+ /// JSON property name to be used.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasJsonPropertyName(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property,
+ /// rather than using the entity property name.
+ ///
+ /// The builder for the property being configured.
+ /// JSON property name to be used.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasJsonPropertyName(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ string? name)
+ => (PrimitiveCollectionBuilder)HasJsonPropertyName((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name);
+}
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index e8fded6f6e6..187f3297208 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();
@@ -414,6 +413,8 @@ public static void SetColumnOrder(this IMutableProperty property, int? order)
/// be found.
///
public static string? GetColumnType(this IReadOnlyProperty property)
+ // Note that the type-mapped store type is used in preference to the annotation, since the annotation may
+ // be an incomplete type name like `varchar` which will become `varchar(64)` after the max length facet is required.
=> (string?)(property.FindRelationalTypeMapping()?.StoreType
?? property.FindAnnotation(RelationalAnnotationNames.ColumnType)?.Value);
@@ -1176,7 +1177,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 00c3f096b09..3c21d0202ed 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 ??= (string?)elementType[RelationalAnnotationNames.StoreType];
+
+ 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 .
///
@@ -305,15 +332,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.
///
@@ -330,12 +348,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 65e27444da9..681060684f2 100644
--- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs
+++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs
@@ -30,8 +30,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.
@@ -90,6 +90,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++)
@@ -112,10 +113,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]);
@@ -125,74 +132,79 @@ 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 (mappingInfo, providerClrType, customConverter, elementMapping) = k;
- if (mapping == null)
+ var sourceType = mappingInfo.ClrType;
+ RelationalTypeMapping? mapping = null;
+
+ if (elementMapping == null
+ || customConverter != null)
{
- var sourceType = info.ClrType;
+ mapping = providerClrType == null
+ || providerClrType == mappingInfo.ClrType
+ ? self.FindMapping(mappingInfo)
+ : 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, providerClrType))
{
- foreach (var secondConverterInfo in self.Dependencies
- .ValueConverterSelector
- .Select(providerType))
- {
- mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
+ var mappingInfoUsed = mappingInfo.WithConverter(converterInfo);
+ mapping = self.FindMapping(mappingInfoUsed);
- if (mapping != null)
+ if (mapping == null
+ && providerClrType != null)
+ {
+ foreach (var secondConverterInfo in self.Dependencies
+ .ValueConverterSelector
+ .Select(providerClrType))
{
- 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: mappingInfo.JsonValueReaderWriter);
+ break;
+ }
}
- }
- if (mapping == null)
- {
- mapping = self.TryFindCollectionMapping(info, sourceType, providerType);
+ mapping ??= self.TryFindCollectionMapping(mappingInfo, sourceType, providerClrType, elementMapping);
}
}
}
+ else if (sourceType != null)
+ {
+ mapping = self.TryFindCollectionMapping(mappingInfo, sourceType, providerClrType, elementMapping);
+ }
if (mapping != null
- && converter != null)
+ && customConverter != null)
{
mapping = (RelationalTypeMapping)mapping.Clone(
- converter,
- info.ElementTypeMapping,
- jsonValueReaderWriter: info.JsonValueReaderWriter);
+ customConverter,
+ jsonValueReaderWriter: mappingInfo.JsonValueReaderWriter);
}
return mapping;
@@ -205,14 +217,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.
@@ -267,6 +280,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 .
///
@@ -338,12 +380,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.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..afd29bd2087
--- /dev/null
+++ b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,53 @@
+// 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;
+
+///
+/// SQL Server specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing SQL Server and SQL Azure databases with EF Core
+/// for more information and examples.
+///
+public static class SqlServerComplexTypePrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures whether the property's column is created as sparse when targeting SQL Server.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples. Also see
+ /// Sparse columns for
+ /// general information on SQL Server sparse columns.
+ ///
+ /// The builder for the property being configured.
+ /// A value indicating whether the property's column is created as sparse.
+ /// A builder to further configure the property.
+ public static ComplexTypePrimitiveCollectionBuilder IsSparse(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, bool sparse = true)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsSparse(sparse);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures whether the property's column is created as sparse when targeting SQL Server.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples. Also see
+ /// Sparse columns for
+ /// general information on SQL Server sparse columns.
+ ///
+ /// The builder for the property being configured.
+ /// A value indicating whether the property's column is created as sparse.
+ /// A builder to further configure the property.
+ public static ComplexTypePrimitiveCollectionBuilder IsSparse(
+ this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool sparse = true)
+ => (ComplexTypePrimitiveCollectionBuilder)IsSparse((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sparse);
+}
diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..17dd2d26dfe
--- /dev/null
+++ b/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,53 @@
+// 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;
+
+///
+/// SQL Server specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing SQL Server and SQL Azure databases with EF Core
+/// for more information and examples.
+///
+public static class SqlServerPrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures whether the property's column is created as sparse when targeting SQL Server.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples. Also see
+ /// Sparse columns for
+ /// general information on SQL Server sparse columns.
+ ///
+ /// The builder for the property being configured.
+ /// A value indicating whether the property's column is created as sparse.
+ /// A builder to further configure the property.
+ public static PrimitiveCollectionBuilder IsSparse(this PrimitiveCollectionBuilder primitiveCollectionBuilder, bool sparse = true)
+ {
+ primitiveCollectionBuilder.Metadata.SetIsSparse(sparse);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures whether the property's column is created as sparse when targeting SQL Server.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples. Also see
+ /// Sparse columns for
+ /// general information on SQL Server sparse columns.
+ ///
+ /// The builder for the property being configured.
+ /// A value indicating whether the property's column is created as sparse.
+ /// A builder to further configure the property.
+ public static PrimitiveCollectionBuilder IsSparse(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ bool sparse = true)
+ => (PrimitiveCollectionBuilder)IsSparse((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sparse);
+}
diff --git a/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs
new file mode 100644
index 00000000000..0a675c038ff
--- /dev/null
+++ b/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs
@@ -0,0 +1,46 @@
+// 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;
+
+///
+/// SQLite-specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing SQLite databases with EF Core for more information and examples.
+///
+public static class SqlitePrimitiveCollectionBuilderExtensions
+{
+ ///
+ /// Configures the SRID of the column that the property maps to when targeting SQLite.
+ ///
+ ///
+ /// See Spatial data, and
+ /// Accessing SQLite databases with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SRID.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasSrid(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int srid)
+ {
+ primitiveCollectionBuilder.Metadata.SetSrid(srid);
+
+ return primitiveCollectionBuilder;
+ }
+
+ ///
+ /// Configures the SRID of the column that the property maps to when targeting SQLite.
+ ///
+ ///
+ /// See Spatial data, and
+ /// Accessing SQLite databases with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The SRID.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PrimitiveCollectionBuilder HasSrid(
+ this PrimitiveCollectionBuilder primitiveCollectionBuilder,
+ int srid)
+ => (PrimitiveCollectionBuilder)HasSrid((PrimitiveCollectionBuilder)primitiveCollectionBuilder, srid);
+}
diff --git a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
index 5df96374e28..cf00bc77dec 100644
--- a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
@@ -49,10 +49,14 @@ public virtual IComparer Create(IPropertyBase propertyBase)
var nonNullableProviderType = providerType.UnwrapNullableType();
if (IsGenericComparable(providerType, nonNullableProviderType))
{
+ var elementType = property.GetElementType();
+ var modelBaseType = elementType != null
+ ? typeof(IEnumerable<>).MakeGenericType(elementType.ClrType)
+ : modelType;
var comparerType = modelType.IsClass
- ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
+ ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType)
: modelType == converter.ModelClrType
- ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
+ ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType)
: typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType(
nonNullableModelType, providerType);
diff --git a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs
index cde5d6d4a9f..b0beec1b922 100644
--- a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs
+++ b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs
@@ -17,25 +17,25 @@ public static class ValueComparerExtensions
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public static ValueComparer? ToNullableComparer(this ValueComparer? valueComparer, IReadOnlyProperty property)
+ public static ValueComparer? ToNullableComparer(this ValueComparer? valueComparer, Type clrType)
{
if (valueComparer == null
- || !property.ClrType.IsNullableValueType()
+ || !clrType.IsNullableValueType()
|| valueComparer.Type.IsNullableValueType())
{
return valueComparer;
}
- var newEqualsParam1 = Expression.Parameter(property.ClrType, "v1");
- var newEqualsParam2 = Expression.Parameter(property.ClrType, "v2");
- var newHashCodeParam = Expression.Parameter(property.ClrType, "v");
- var newSnapshotParam = Expression.Parameter(property.ClrType, "v");
- var hasValueProperty = property.ClrType.GetProperty("HasValue")!;
+ var newEqualsParam1 = Expression.Parameter(clrType, "v1");
+ var newEqualsParam2 = Expression.Parameter(clrType, "v2");
+ var newHashCodeParam = Expression.Parameter(clrType, "v");
+ var newSnapshotParam = Expression.Parameter(clrType, "v");
+ var hasValueProperty = clrType.GetProperty("HasValue")!;
var v1HasValue = Expression.MakeMemberAccess(newEqualsParam1, hasValueProperty);
var v2HasValue = Expression.MakeMemberAccess(newEqualsParam2, hasValueProperty);
return (ValueComparer)Activator.CreateInstance(
- typeof(ValueComparer<>).MakeGenericType(property.ClrType),
+ typeof(ValueComparer<>).MakeGenericType(clrType),
Expression.Lambda(
Expression.OrElse(
Expression.AndAlso(
@@ -61,8 +61,8 @@ public static class ValueComparerExtensions
Expression.MakeMemberAccess(newSnapshotParam, hasValueProperty),
Expression.Convert(
valueComparer.ExtractSnapshotBody(
- Expression.Convert(newSnapshotParam, valueComparer.Type)), property.ClrType),
- Expression.Default(property.ClrType)),
+ Expression.Convert(newSnapshotParam, valueComparer.Type)), clrType),
+ Expression.Default(clrType)),
newSnapshotParam))!;
}
}
diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs
index 750d8db0368..b4c43eb787d 100644
--- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs
@@ -202,6 +202,65 @@ public virtual ComplexTypePropertyBuilder Property(Type propertyType, string pro
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata);
+ ///
+ /// Returns an object that can be used to configure a property of the complex type.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property with this overload the property name must match the
+ /// name of a CLR property or field on the complex type. This overload cannot be used to
+ /// add a new shadow state property.
+ ///
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => new(
+ TypeBuilder.Property(
+ Check.NotEmpty(propertyName, nameof(propertyName)),
+ ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
+ ///
+ /// Returns an object that can be used to configure a property of the complex type.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => new(
+ TypeBuilder.Property(
+ typeof(TProperty),
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
+ ///
+ /// Returns an object that can be used to configure a property of the complex type.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName)
+ => new(
+ TypeBuilder.Property(
+ Check.NotNull(propertyType, nameof(propertyType)),
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
///
/// Returns an object that can be used to configure a property of the complex type.
/// If no property with the given name exists, then a new property will be added.
diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs
index 01c9b8066de..bb1380a8afc 100644
--- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs
@@ -100,6 +100,20 @@ public virtual ComplexTypePropertyBuilder Property(Express
Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)!
.Metadata);
+ ///
+ /// Returns an object that can be used to configure a primitive collection property of the entity type.
+ /// If the specified property is not already part of the model, it will be added.
+ ///
+ ///
+ /// A lambda expression representing the property to be configured (
+ /// blog => blog.Url).
+ ///
+ /// An object that can be used to configure the property.
+ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Expression> propertyExpression)
+ => new(TypeBuilder.Property(
+ Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)!
+ .Metadata);
+
///
/// Configures a complex property of the complex type.
/// If no property with the given name exists, then a new property will be added.
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs
new file mode 100644
index 00000000000..d03b58f7b21
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs
@@ -0,0 +1,434 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+///
+/// Instances of this class are returned from methods when using the API
+/// and it is not designed to be directly constructed in your application code.
+///
+///
+/// See Modeling complex types and relationships for more information and
+/// examples.
+///
+///
+public class ComplexTypePrimitiveCollectionBuilder : 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 ComplexTypePrimitiveCollectionBuilder(IMutableProperty property)
+ {
+ Check.NotNull(property, nameof(property));
+
+ Builder = ((Property)property).Builder;
+ }
+
+ ///
+ /// The internal builder being used to configure the property.
+ ///
+ IConventionPropertyBuilder IInfrastructure.Instance
+ => Builder;
+
+ private InternalPropertyBuilder Builder { get; }
+
+ ///
+ /// The property being configured.
+ ///
+ public virtual IMutableProperty Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Adds or updates an annotation on the property. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether this property must have a value assigned or is a valid value.
+ /// A property can only be configured as non-required if it is based on a CLR type that can be
+ /// assigned .
+ ///
+ /// A value indicating whether the property is required.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder IsRequired(bool required = true)
+ {
+ Builder.IsRequired(required, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ ///
+ /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasMaxLength(int maxLength)
+ {
+ Builder.HasMaxLength(maxLength, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the
+ /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of
+ /// the property.
+ ///
+ /// The sentinel value.
+ /// The same builder instance if the configuration was applied, otherwise.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel)
+ {
+ Builder.HasSentinel(sentinel, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true)
+ {
+ Builder.IsUnicode(unicode, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator
+ <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>()
+ where TGenerator : ValueGenerator
+ {
+ Builder.HasValueGenerator(typeof(TGenerator), ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType)
+ {
+ Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory
+ <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>()
+ where TFactory : ValueGeneratorFactory
+ => HasValueGeneratorFactory(typeof(TFactory));
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory(
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType)
+ {
+ Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether this property should be used as a concurrency token. When a property is configured
+ /// as a concurrency token the value in the database will be checked when an instance of this complex type
+ /// is updated or deleted during to ensure it has not changed since
+ /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the
+ /// changes will not be applied to the database.
+ ///
+ /// A value indicating whether this property is a concurrency token.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder IsConcurrencyToken(bool concurrencyToken = true)
+ {
+ Builder.IsConcurrencyToken(concurrencyToken, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to never have a value generated by the database when an instance of this
+ /// complex type is saved.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ ///
+ /// Note that values may still be generated by a client-side value generator, if one is set explicitly or by a convention.
+ ///
+ public virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedNever()
+ {
+ Builder.ValueGenerated(ValueGenerated.Never, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated only when saving a new entity, unless a non-null,
+ /// non-temporary value has been set, in which case the set value will be saved instead. The value
+ /// may be generated by a client-side value generator or may be generated by the database as part
+ /// of saving the entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAdd()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnAdd, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated when saving a new or existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdate()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnUpdate, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated under certain conditions when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdateSometimes()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Sets the backing field to use for this property.
+ ///
+ ///
+ ///
+ /// Backing fields are normally found by convention.
+ /// This method is useful for setting backing fields explicitly in cases where the
+ /// correct field is not found by convention.
+ ///
+ ///
+ /// By default, the backing field, if one is found or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. This can be changed by calling
+ /// .
+ ///
+ ///
+ /// See Backing fields for more information and examples.
+ ///
+ ///
+ /// The field name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder HasField(string fieldName)
+ {
+ Check.NotEmpty(fieldName, nameof(fieldName));
+
+ Builder.HasField(fieldName, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// A builder to configure the collection element type.
+ public virtual ElementTypeBuilder ElementType()
+ => new((IMutableElementType)Builder.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata.GetElementType()!);
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// An action that performs configuration of the collection element type.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder ElementType(Action builderAction)
+ {
+ builderAction(ElementType());
+
+ return this;
+ }
+
+ ///
+ /// Sets the to use for this property.
+ ///
+ ///
+ ///
+ /// By default, the backing field, if one is found by convention or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. Calling this method will change that behavior
+ /// for this property as described in the enum.
+ ///
+ ///
+ /// Calling this method overrides for this property any access mode that was set on the
+ /// complex type or model.
+ ///
+ ///
+ /// The to use for this property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual ComplexTypePrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
+ {
+ Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+}
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs
new file mode 100644
index 00000000000..1ef05f1a311
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs
@@ -0,0 +1,311 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+///
+/// Instances of this class are returned from methods when using the API
+/// and it is not designed to be directly constructed in your application code.
+///
+///
+/// See Modeling complex types and relationships for more information and
+/// examples.
+///
+///
+public class ComplexTypePrimitiveCollectionBuilder : ComplexTypePrimitiveCollectionBuilder
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in 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 ComplexTypePrimitiveCollectionBuilder(IMutableProperty property)
+ : base(property)
+ {
+ }
+
+ ///
+ /// Adds or updates an annotation on the property. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasAnnotation(string annotation, object? value)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasAnnotation(annotation, value);
+
+ ///
+ /// Configures whether this property must have a value assigned or whether null is a valid value.
+ /// A property can only be configured as non-required if it is based on a CLR type that can be
+ /// assigned .
+ ///
+ /// A value indicating whether the property is required.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder IsRequired(bool required = true)
+ => (ComplexTypePrimitiveCollectionBuilder)base.IsRequired(required);
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ ///
+ /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasMaxLength(int maxLength)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasMaxLength(maxLength);
+
+ ///
+ /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the
+ /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of
+ /// the property.
+ ///
+ /// The sentinel value.
+ /// The same builder instance if the configuration was applied, otherwise.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasSentinel(sentinel);
+
+ ///
+ /// Configures the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true)
+ => (ComplexTypePrimitiveCollectionBuilder)base.IsUnicode(unicode);
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator
+ <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>()
+ where TGenerator : ValueGenerator
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGenerator();
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting null does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGenerator(valueGeneratorType);
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory
+ <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>()
+ where TFactory : ValueGeneratorFactory
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGeneratorFactory();
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the complex type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory(
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType);
+
+ ///
+ /// Configures whether this property should be used as a concurrency token. When a property is configured
+ /// as a concurrency token the value in the database will be checked when an instance of this complex type
+ /// is updated or deleted during to ensure it has not changed since
+ /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the
+ /// changes will not be applied to the database.
+ ///
+ /// A value indicating whether this property is a concurrency token.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder IsConcurrencyToken(bool concurrencyToken = true)
+ => (ComplexTypePrimitiveCollectionBuilder)base.IsConcurrencyToken(concurrencyToken);
+
+ ///
+ /// Configures a property to never have a value generated when an instance of this
+ /// complex type is saved.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ ///
+ /// Note that temporary values may still be generated for use internally before a
+ /// new entity is saved.
+ ///
+ public new virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedNever()
+ => (ComplexTypePrimitiveCollectionBuilder)base.ValueGeneratedNever();
+
+ ///
+ /// Configures a property to have a value generated only when saving a new entity, unless a non-null,
+ /// non-temporary value has been set, in which case the set value will be saved instead. The value
+ /// may be generated by a client-side value generator or may be generated by the database as part
+ /// of saving the entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAdd()
+ => (ComplexTypePrimitiveCollectionBuilder)base.ValueGeneratedOnAdd();
+
+ ///
+ /// Configures a property to have a value generated when saving a new or existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate()
+ => (ComplexTypePrimitiveCollectionBuilder)base.ValueGeneratedOnAddOrUpdate();
+
+ ///
+ /// Configures a property to have a value generated when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdate()
+ => (ComplexTypePrimitiveCollectionBuilder)base.ValueGeneratedOnUpdate();
+
+ ///
+ /// Configures a property to have a value generated under certain conditions when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdateSometimes()
+ => (ComplexTypePrimitiveCollectionBuilder)base.ValueGeneratedOnUpdateSometimes();
+
+ ///
+ /// Sets the backing field to use for this property.
+ ///
+ ///
+ ///
+ /// Backing fields are normally found by convention.
+ /// This method is useful for setting backing fields explicitly in cases where the
+ /// correct field is not found by convention.
+ ///
+ ///
+ /// By default, the backing field, if one is found or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. This can be changed by calling
+ /// .
+ ///
+ ///
+ /// See Backing fields for more information and examples.
+ ///
+ ///
+ /// The field name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder HasField(string fieldName)
+ => (ComplexTypePrimitiveCollectionBuilder)base.HasField(fieldName);
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// 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 ComplexTypePrimitiveCollectionBuilder ElementType(Action builderAction)
+ {
+ builderAction(ElementType());
+
+ return this;
+ }
+
+ ///
+ /// Sets the to use for this property.
+ ///
+ ///
+ ///
+ /// By default, the backing field, if one is found by convention or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. Calling this method will change that behavior
+ /// for this property as described in the enum.
+ ///
+ ///
+ /// Calling this method overrides for this property any access mode that was set on the
+ /// complex type or model.
+ ///
+ ///
+ /// The to use for this property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual ComplexTypePrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
+ => (ComplexTypePrimitiveCollectionBuilder)base.UsePropertyAccessMode(propertyAccessMode);
+}
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
index b6c57e0effb..47e6af20fda 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
@@ -228,37 +228,6 @@ public virtual ComplexTypePropertyBuilder HasValueGenerator(
return this;
}
- ///
- /// Configures a factory for creating a to use to generate values
- /// for this property.
- ///
- ///
- ///
- /// Values are generated when the entity is added to the context using, for example,
- /// . Values are generated only when the property is assigned
- /// the CLR default value ( for string, 0 for int,
- /// Guid.Empty for Guid, etc.).
- ///
- ///
- /// This factory will be invoked once to create a single instance of the value generator, and
- /// this will be used to generate values for this property in all instances of the complex type.
- ///
- ///
- /// This method is intended for use with custom value generation. Value generation for common cases is
- /// usually handled automatically by the database provider.
- ///
- ///
- /// A delegate that will be used to create value generator instances.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory)
- {
- Check.NotNull(factory, nameof(factory));
-
- Builder.HasValueGenerator(factory, ConfigurationSource.Explicit);
-
- return this;
- }
-
///
/// Configures the for creating a
/// to use to generate values for this property.
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
index 7f0e24503b5..318c1966d9c 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
@@ -166,31 +166,6 @@ public ComplexTypePropertyBuilder(IMutableProperty property)
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType)
=> (ComplexTypePropertyBuilder)base.HasValueGenerator(valueGeneratorType);
- ///
- /// Configures a factory for creating a to use to generate values
- /// for this property.
- ///
- ///
- ///
- /// Values are generated when the entity is added to the context using, for example,
- /// . Values are generated only when the property is assigned
- /// the CLR default value ( for string, 0 for int,
- /// Guid.Empty for Guid, etc.).
- ///
- ///
- /// This factory will be invoked once to create a single instance of the value generator, and
- /// this will be used to generate values for this property in all instances of the complex type.
- ///
- ///
- /// This method is intended for use with custom value generation. Value generation for common cases is
- /// usually handled automatically by the database provider.
- ///
- ///
- /// A delegate that will be used to create value generator instances.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory)
- => (ComplexTypePropertyBuilder)base.HasValueGenerator(factory);
-
///
/// Configures the for creating a
/// to use to generate values for this property.
diff --git a/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs
new file mode 100644
index 00000000000..96aaab63982
--- /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 the of a primitive collection.
+///
+///
+///
+/// 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 element type.
+ ///
+ IConventionElementTypeBuilder IInfrastructure.Instance
+ => Builder;
+
+ private InternalElementTypeBuilder Builder { get; }
+
+ ///
+ /// The element type being configured.
+ ///
+ public virtual IMutableElementType Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Adds or updates an annotation on the element type. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual 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 IsRequired(bool required = true)
+ {
+ Builder.IsRequired(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 HasMaxLength(int maxLength)
+ {
+ Builder.HasMaxLength(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 HasPrecision(int precision, int scale)
+ {
+ Builder.HasPrecision(precision, ConfigurationSource.Explicit);
+ Builder.HasScale(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 HasPrecision(int precision)
+ {
+ Builder.HasPrecision(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 IsUnicode(bool unicode = true)
+ {
+ Builder.IsUnicode(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 HasConversion<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>()
+ => HasConversion(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 HasConversion(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? conversionType)
+ {
+ if (typeof(ValueConverter).IsAssignableFrom(conversionType))
+ {
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
+ }
+ else
+ {
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Configures 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 HasConversion(ValueConverter? converter)
+ => HasConversion(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 HasConversion<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ TConversion>(
+ ValueComparer? valueComparer)
+ => HasConversion(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 HasConversion(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type conversionType,
+ ValueComparer? valueComparer)
+ {
+ Check.NotNull(conversionType, nameof(conversionType));
+
+ if (typeof(ValueConverter).IsAssignableFrom(conversionType))
+ {
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
+ }
+ else
+ {
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
+ }
+
+ Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);
+
+ 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 HasConversion(ValueConverter? converter, ValueComparer? valueComparer)
+ {
+ Builder.HasConversion(converter, ConfigurationSource.Explicit);
+ Builder.HasValueComparer(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 HasConversion<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ TConversion,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ TComparer>()
+ where TComparer : ValueComparer
+ => HasConversion(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 HasConversion(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type conversionType,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? comparerType)
+ {
+ Check.NotNull(conversionType, nameof(conversionType));
+
+ if (typeof(ValueConverter).IsAssignableFrom(conversionType))
+ {
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
+ }
+ else
+ {
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
+ }
+
+ Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit);
+
+ 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/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
index c0398c28246..506da585a50 100644
--- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
@@ -169,6 +169,68 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName)
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata);
+ ///
+ /// Returns an object that can be used to configure a property of the entity type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property with this overload the property name must match the
+ /// name of a CLR property or field on the entity type. This overload cannot be used to
+ /// add a new shadow state property.
+ ///
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => new(
+ Builder.Property(
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
+ ///
+ /// Returns an object that can be used to configure a property of the entity type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => new(
+ Builder.Property(
+ typeof(TProperty),
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
+ ///
+ /// Returns an object that can be used to configure a property of the entity type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName)
+ => new(
+ Builder.Property(
+ Check.NotNull(propertyType, nameof(propertyType)),
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
///
/// Returns an object that can be used to configure a property of the entity type.
/// If no property with the given name exists, then a new property will be added.
diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
index 1168686bba9..b7913f6b3de 100644
--- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
@@ -152,6 +152,21 @@ public virtual PropertyBuilder Property(Expression
+ /// Returns an object that can be used to configure a property of the entity type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ ///
+ ///
+ /// A lambda expression representing the property to be configured (
+ /// blog => blog.Url).
+ ///
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(
+ Expression> propertyExpression)
+ => new(
+ Builder.Property(Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(),
+ ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
///
/// Configures a complex property of the entity type.
/// If no property with the given name exists, then a new property will be added.
diff --git a/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs
new file mode 100644
index 00000000000..0538d82a83c
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs
@@ -0,0 +1,321 @@
+// 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 for a primitive collection
+/// 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 element type 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? IsRequired(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? HasMaxLength(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? IsUnicode(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? HasPrecision(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? HasScale(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? HasConversion(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? HasConversion(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? HasConverter(
+ [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? HasTypeMapping(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? HasValueComparer(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? HasValueComparer(
+ [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..09771a49187 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.
+ ///
+ IConventionElementTypeBuilder? PrimitiveCollection(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 CanSetPrimitiveCollection(bool fromDataAnnotation = false);
}
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
index d86b245d5fe..58e65f710da 100644
--- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
+++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
@@ -191,6 +191,71 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName)
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata);
+ ///
+ /// Returns an object that can be used to configure a property of the owned type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property with this overload the property name must match the
+ /// name of a CLR property or field on the entity type. This overload cannot be used to
+ /// add a new shadow state property.
+ ///
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => UpdateBuilder(
+ () => new PrimitiveCollectionBuilder(
+ DependentEntityType.Builder.Property(
+ Check.NotEmpty(propertyName, nameof(propertyName)),
+ ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata));
+
+ ///
+ /// Returns an object that can be used to configure a property of the owned type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => UpdateBuilder(
+ () => new PrimitiveCollectionBuilder(
+ DependentEntityType.Builder.Property(
+ typeof(TProperty),
+ Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata));
+
+ ///
+ /// Returns an object that can be used to configure a property of the owned type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ /// If no property with the given name exists, then a new property will be added.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ /// The name of the property to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, string propertyName)
+ => new(
+ DependentEntityType.Builder.Property(
+ Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)),
+ ConfigurationSource.Explicit)!
+ .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata);
+
///
/// Returns an object that can be used to configure a property of the entity type.
/// If no property with the given name exists, then a new property will be added.
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
index 9168293a31a..84b9c7dff7e 100644
--- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
@@ -95,6 +95,31 @@ public virtual PropertyBuilder Property(
Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(),
ConfigurationSource.Explicit)!.Metadata));
+ ///
+ /// Returns an object that can be used to configure a property of the owned type where that property represents
+ /// a collection of primitive values, such as strings or integers.
+ ///
+ ///
+ /// When adding a new property, if a property with the same name exists in the entity class
+ /// then it will be added to the model. If no property exists in the entity class, then
+ /// a new shadow state property will be added. A shadow state property is one that does not have a
+ /// corresponding property in the entity class. The current value for the property is stored in
+ /// the rather than being stored in instances of the entity class.
+ ///
+ /// The type of the property to be configured.
+ ///
+ /// A lambda expression representing the property to be configured (
+ /// blog => blog.Url).
+ ///
+ /// An object that can be used to configure the property.
+ public virtual PropertyBuilder PrimitiveCollection(
+ Expression> propertyExpression)
+ => UpdateBuilder(
+ () => new PropertyBuilder(
+ DependentEntityType.Builder.Property(
+ Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(),
+ ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata));
+
///
/// Returns an object that can be used to configure an existing navigation property
/// from the owned type to its owner. It is an error for the navigation property
diff --git a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs
new file mode 100644
index 00000000000..4241964f374
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs
@@ -0,0 +1,436 @@
+// 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 PrimitiveCollectionBuilder : 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 PrimitiveCollectionBuilder(IMutableProperty property)
+ {
+ Check.NotNull(property, nameof(property));
+
+ Builder = ((Property)property).Builder;
+ }
+
+ ///
+ /// The internal builder being used to configure the property.
+ ///
+ IConventionPropertyBuilder IInfrastructure.Instance
+ => Builder;
+
+ private InternalPropertyBuilder Builder { get; }
+
+ ///
+ /// The property being configured.
+ ///
+ public virtual IMutableProperty Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Adds or updates an annotation on the property. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether this property must have a value assigned or is a valid value.
+ /// A property can only be configured as non-required if it is based on a CLR type that can be
+ /// assigned .
+ ///
+ /// A value indicating whether the property is required.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder IsRequired(bool required = true)
+ {
+ Builder.IsRequired(required, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ ///
+ /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasMaxLength(int maxLength)
+ {
+ Builder.HasMaxLength(maxLength, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the
+ /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of
+ /// the property.
+ ///
+ /// The sentinel value.
+ /// The same builder instance if the configuration was applied, otherwise.
+ public virtual PrimitiveCollectionBuilder HasSentinel(object? sentinel)
+ {
+ Builder.HasSentinel(sentinel, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder IsUnicode(bool unicode = true)
+ {
+ Builder.IsUnicode(unicode, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasValueGenerator
+ <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>()
+ where TGenerator : ValueGenerator
+ {
+ Builder.HasValueGenerator(typeof(TGenerator), ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasValueGenerator(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? valueGeneratorType)
+ {
+ Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasValueGeneratorFactory
+ <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>()
+ where TFactory : ValueGeneratorFactory
+ => HasValueGeneratorFactory(typeof(TFactory));
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasValueGeneratorFactory(
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)]
+ Type? valueGeneratorFactoryType)
+ {
+ Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether this property should be used as a concurrency token. When a property is configured
+ /// as a concurrency token the value in the database will be checked when an instance of this entity type
+ /// is updated or deleted during to ensure it has not changed since
+ /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the
+ /// changes will not be applied to the database.
+ ///
+ /// A value indicating whether this property is a concurrency token.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder IsConcurrencyToken(bool concurrencyToken = true)
+ {
+ Builder.IsConcurrencyToken(concurrencyToken, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to never have a value generated by the database when an instance of this
+ /// entity type is saved.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ ///
+ /// Note that values may still be generated by a client-side value generator, if one is set explicitly or by a convention.
+ ///
+ public virtual PrimitiveCollectionBuilder ValueGeneratedNever()
+ {
+ Builder.ValueGenerated(ValueGenerated.Never, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated only when saving a new entity, unless a non-null,
+ /// non-temporary value has been set, in which case the set value will be saved instead. The value
+ /// may be generated by a client-side value generator or may be generated by the database as part
+ /// of saving the entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder ValueGeneratedOnAdd()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnAdd, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated when saving a new or existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder ValueGeneratedOnUpdate()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnUpdate, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures a property to have a value generated under certain conditions when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder ValueGeneratedOnUpdateSometimes()
+ {
+ Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Sets the backing field to use for this property.
+ ///
+ ///
+ ///
+ /// Backing fields are normally found by convention.
+ /// This method is useful for setting backing fields explicitly in cases where the
+ /// correct field is not found by convention.
+ ///
+ ///
+ /// By default, the backing field, if one is found or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. This can be changed by calling
+ /// .
+ ///
+ ///
+ /// See Backing fields for more information and examples.
+ ///
+ ///
+ /// The field name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder HasField(string fieldName)
+ {
+ Check.NotEmpty(fieldName, nameof(fieldName));
+
+ Builder.HasField(fieldName, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// A builder to configure the collection element type.
+ public virtual ElementTypeBuilder ElementType()
+ => new((IMutableElementType)Builder.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata.GetElementType()!);
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// An action that performs configuration of the collection element type.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder ElementType(Action builderAction)
+ {
+ builderAction(ElementType());
+
+ return this;
+ }
+
+ ///
+ /// Sets the to use for this property.
+ ///
+ ///
+ ///
+ /// By default, the backing field, if one is found by convention or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. Calling this method will change that behavior
+ /// for this property as described in the enum.
+ ///
+ ///
+ /// Calling this method overrides for this property any access mode that was set on the
+ /// entity type or model.
+ ///
+ ///
+ /// The to use for this property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
+ {
+ Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+}
diff --git a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs
new file mode 100644
index 00000000000..c9547adf2ab
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs
@@ -0,0 +1,313 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+///
+/// Instances of this class are returned from methods when using the API
+/// and it is not designed to be directly constructed in your application code.
+///
+///
+/// See Modeling entity types and relationships for more information and
+/// examples.
+///
+///
+public class PrimitiveCollectionBuilder : PrimitiveCollectionBuilder
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in 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 PrimitiveCollectionBuilder(IMutableProperty property)
+ : base(property)
+ {
+ }
+
+ ///
+ /// Adds or updates an annotation on the property. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasAnnotation(string annotation, object? value)
+ => (PrimitiveCollectionBuilder)base.HasAnnotation(annotation, value);
+
+ ///
+ /// Configures whether this property must have a value assigned or whether null is a valid value.
+ /// A property can only be configured as non-required if it is based on a CLR type that can be
+ /// assigned .
+ ///
+ /// A value indicating whether the property is required.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder IsRequired(bool required = true)
+ => (PrimitiveCollectionBuilder)base.IsRequired(required);
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ ///
+ /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasMaxLength(int maxLength)
+ => (PrimitiveCollectionBuilder)base.HasMaxLength(maxLength);
+
+ ///
+ /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the
+ /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of
+ /// the property.
+ ///
+ /// The sentinel value.
+ /// The same builder instance if the configuration was applied, otherwise.
+ public new virtual PrimitiveCollectionBuilder HasSentinel(object? sentinel)
+ => (PrimitiveCollectionBuilder)base.HasSentinel(sentinel);
+
+ ///
+ /// Configures the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder IsUnicode(bool unicode = true)
+ => (PrimitiveCollectionBuilder)base.IsUnicode(unicode);
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasValueGenerator
+ <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>()
+ where TGenerator : ValueGenerator
+ => (PrimitiveCollectionBuilder)base.HasValueGenerator();
+
+ ///
+ /// Configures the that will generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting null does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasValueGenerator(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? valueGeneratorType)
+ => (PrimitiveCollectionBuilder)base.HasValueGenerator(valueGeneratorType);
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasValueGeneratorFactory
+ <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>()
+ where TFactory : ValueGeneratorFactory
+ => (PrimitiveCollectionBuilder)base.HasValueGeneratorFactory();
+
+ ///
+ /// Configures the for creating a
+ /// to use to generate values for this property.
+ ///
+ ///
+ ///
+ /// Values are generated when the entity is added to the context using, for example,
+ /// . Values are generated only when the property is assigned
+ /// the CLR default value ( for string, 0 for int,
+ /// Guid.Empty for Guid, etc.).
+ ///
+ ///
+ /// A single instance of this type will be created and used to generate values for this property in all
+ /// instances of the entity type. The type must be instantiable and have a parameterless constructor.
+ ///
+ ///
+ /// This method is intended for use with custom value generation. Value generation for common cases is
+ /// usually handled automatically by the database provider.
+ ///
+ ///
+ /// Setting does not disable value generation for this property, it just clears any generator explicitly
+ /// configured for this property. The database provider may still have a value generator for the property type.
+ ///
+ ///
+ /// A type that inherits from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasValueGeneratorFactory(
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)]
+ Type? valueGeneratorFactoryType)
+ => (PrimitiveCollectionBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType);
+
+ ///
+ /// Configures whether this property should be used as a concurrency token. When a property is configured
+ /// as a concurrency token the value in the database will be checked when an instance of this entity type
+ /// is updated or deleted during to ensure it has not changed since
+ /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the
+ /// changes will not be applied to the database.
+ ///
+ /// A value indicating whether this property is a concurrency token.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder IsConcurrencyToken(bool concurrencyToken = true)
+ => (PrimitiveCollectionBuilder)base.IsConcurrencyToken(concurrencyToken);
+
+ ///
+ /// Configures a property to never have a value generated when an instance of this
+ /// entity type is saved.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ ///
+ /// Note that temporary values may still be generated for use internally before a
+ /// new entity is saved.
+ ///
+ public new virtual PrimitiveCollectionBuilder ValueGeneratedNever()
+ => (PrimitiveCollectionBuilder)base.ValueGeneratedNever();
+
+ ///
+ /// Configures a property to have a value generated only when saving a new entity, unless a non-null,
+ /// non-temporary value has been set, in which case the set value will be saved instead. The value
+ /// may be generated by a client-side value generator or may be generated by the database as part
+ /// of saving the entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder ValueGeneratedOnAdd()
+ => (PrimitiveCollectionBuilder)base.ValueGeneratedOnAdd();
+
+ ///
+ /// Configures a property to have a value generated when saving a new or existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate()
+ => (PrimitiveCollectionBuilder)base.ValueGeneratedOnAddOrUpdate();
+
+ ///
+ /// Configures a property to have a value generated when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder ValueGeneratedOnUpdate()
+ => (PrimitiveCollectionBuilder)base.ValueGeneratedOnUpdate();
+
+ ///
+ /// Configures a property to have a value generated under certain conditions when saving an existing entity.
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder ValueGeneratedOnUpdateSometimes()
+ => (PrimitiveCollectionBuilder)base.ValueGeneratedOnUpdateSometimes();
+
+ ///
+ /// Sets the backing field to use for this property.
+ ///
+ ///
+ ///
+ /// Backing fields are normally found by convention.
+ /// This method is useful for setting backing fields explicitly in cases where the
+ /// correct field is not found by convention.
+ ///
+ ///
+ /// By default, the backing field, if one is found or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. This can be changed by calling
+ /// .
+ ///
+ ///
+ /// See Backing fields for more information and examples.
+ ///
+ ///
+ /// The field name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder HasField(string fieldName)
+ => (PrimitiveCollectionBuilder)base.HasField(fieldName);
+
+ ///
+ /// Configures the elements of this collection.
+ ///
+ /// 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 PrimitiveCollectionBuilder ElementType(Action builderAction)
+ {
+ builderAction(ElementType());
+
+ return this;
+ }
+
+ ///
+ /// Sets the to use for this property.
+ ///
+ ///
+ ///
+ /// By default, the backing field, if one is found by convention or has been specified, is used when
+ /// new objects are constructed, typically when entities are queried from the database.
+ /// Properties are used for all other accesses. Calling this method will change that behavior
+ /// for this property as described in the enum.
+ ///
+ ///
+ /// Calling this method overrides for this property any access mode that was set on the
+ /// entity type or model.
+ ///
+ ///
+ /// The to use for this property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
+ => (PrimitiveCollectionBuilder)base.UsePropertyAccessMode(propertyAccessMode);
+}
diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs
index 1e8604eda01..aa4405b5ca6 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));
diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs
index 19d1e0fbf6a..05c6550660f 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,11 @@ 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);
}
diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs
index 05716c93bfc..3b314b0430b 100644
--- a/src/EFCore/Metadata/Conventions/ConventionSet.cs
+++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs
@@ -257,6 +257,11 @@ public class ConventionSet
///
public virtual List PropertyFieldChangedConventions { get; } = new();
+ ///
+ /// Conventions to run when the field of a property is changed.
+ ///
+ public virtual List PropertyElementTypeChangedConventions { get; } = new();
+
///
/// Conventions to run when an annotation is changed on a property.
///
@@ -267,6 +272,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/IPropertyElementTypeChangedConvention.cs b/src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs
new file mode 100644
index 00000000000..5a1ec6bfa01
--- /dev/null
+++ b/src/EFCore/Metadata/Conventions/IPropertyElementTypeChangedConvention.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// Represents an operation that should be performed when the for a property is changed.
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public interface IPropertyElementTypeChangedConvention : IConvention
+{
+ ///
+ /// Called after the element type for a property is changed.
+ ///
+ /// The builder for the property.
+ /// The new element type.
+ /// The old element type.
+ /// Additional information associated with convention execution.
+ void ProcessPropertyElementTypeChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IElementType? newElementType,
+ IElementType? oldElementType,
+ IConventionContext context);
+}
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
index ca87a070c5a..2cf04f3c32f 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
@@ -237,8 +237,22 @@ public int GetLeafCount()
IConventionTypeBaseBuilder typeBaseBuilder,
IConventionProperty property);
+ public abstract IElementType? OnPropertyElementTypeChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IElementType? newElementType,
+ IElementType? oldElementType);
+
public abstract IConventionTriggerBuilder? OnTriggerAdded(IConventionTriggerBuilder triggerBuilder);
public abstract IConventionTrigger? OnTriggerRemoved(IConventionEntityTypeBuilder entityTypeBuilder, IConventionTrigger trigger);
+
+ public abstract IConventionAnnotation? OnElementTypeAnnotationChanged(
+ IConventionElementTypeBuilder builder,
+ string name,
+ IConventionAnnotation? annotation,
+ IConventionAnnotation? oldAnnotation);
+
+ public abstract bool? OnElementTypeNullabilityChanged(
+ IConventionElementTypeBuilder builder);
}
}
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs
index 8057398c93c..321f5590d89 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)
@@ -421,6 +437,16 @@ public override IConventionProperty OnPropertyRemoved(
Add(new OnPropertyRemovedNode(typeBaseBuilder, property));
return property;
}
+
+ public override IElementType? OnPropertyElementTypeChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IElementType? newElementType,
+ IElementType? oldElementType)
+ {
+ Add(new OnPropertyElementTypeChangedNode(propertyBuilder, newElementType, oldElementType));
+ return newElementType;
+
+ }
}
private sealed class OnModelAnnotationChangedNode : ConventionNode
@@ -635,7 +661,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 +1220,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)
@@ -1209,6 +1250,24 @@ public override void Run(ConventionDispatcher dispatcher)
=> dispatcher._immediateConventionScope.OnPropertyFieldChanged(PropertyBuilder, NewFieldInfo, OldFieldInfo);
}
+ private sealed class OnPropertyElementTypeChangedNode : ConventionNode
+ {
+ public OnPropertyElementTypeChangedNode(
+ IConventionPropertyBuilder propertyBuilder, IElementType? newElementType, IElementType? oldElementType)
+ {
+ PropertyBuilder = propertyBuilder;
+ NewElementType = newElementType;
+ OldElementType = oldElementType;
+ }
+
+ public IConventionPropertyBuilder PropertyBuilder { get; }
+ public IElementType? NewElementType { get; }
+ public IElementType? OldElementType { get; }
+
+ public override void Run(ConventionDispatcher dispatcher)
+ => dispatcher._immediateConventionScope.OnPropertyElementTypeChanged(PropertyBuilder, NewElementType, OldElementType);
+ }
+
private sealed class OnPropertyAnnotationChangedNode : ConventionNode
{
public OnPropertyAnnotationChangedNode(
@@ -1233,6 +1292,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..e7ae3187a1a 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs
@@ -34,6 +34,7 @@ private sealed class ImmediateConventionScope : ConventionScope
private readonly ConventionContext> _propertyListConventionContext;
private readonly ConventionContext _stringConventionContext;
private readonly ConventionContext _fieldInfoConventionContext;
+ private readonly ConventionContext _elementTypeConventionContext;
private readonly ConventionContext _boolConventionContext;
private readonly ConventionContext?> _boolListConventionContext;
@@ -66,6 +67,7 @@ public ImmediateConventionScope(ConventionSet conventionSet, ConventionDispatche
_propertyListConventionContext = new ConventionContext>(dispatcher);
_stringConventionContext = new ConventionContext(dispatcher);
_fieldInfoConventionContext = new ConventionContext(dispatcher);
+ _elementTypeConventionContext = new ConventionContext(dispatcher);
_boolConventionContext = new ConventionContext(dispatcher);
_boolListConventionContext = new ConventionContext?>(dispatcher);
}
@@ -133,7 +135,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 +161,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 +192,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 +251,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 +287,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 +323,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 +351,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 +358,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 +397,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 +425,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 +432,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 +465,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 +521,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 +554,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 +581,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 +588,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 +624,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 +685,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 +720,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 +753,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 +786,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 +818,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 +878,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 +915,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 +946,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 +983,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 +1043,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 +1085,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 +1126,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 +1161,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 +1215,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 +1266,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 +1320,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 +1354,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 +1403,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 +1434,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 +1469,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 +1503,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 +1538,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 +1548,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 +1606,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
}
@@ -1542,6 +1615,38 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB
return _fieldInfoConventionContext.Result;
}
+ public override IElementType? OnPropertyElementTypeChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IElementType? newElementType,
+ IElementType? oldElementType)
+ {
+ if (!propertyBuilder.Metadata.IsInModel
+ || !propertyBuilder.Metadata.DeclaringType.IsInModel)
+ {
+ return null;
+ }
+#if DEBUG
+ var initialValue = propertyBuilder.Metadata.GetElementType();
+#endif
+ _elementTypeConventionContext.ResetState(newElementType);
+ foreach (var propertyConvention in _conventionSet.PropertyElementTypeChangedConventions)
+ {
+ propertyConvention.ProcessPropertyElementTypeChanged(
+ propertyBuilder, newElementType, oldElementType, _elementTypeConventionContext);
+ if (_elementTypeConventionContext.ShouldStopProcessing())
+ {
+ return _elementTypeConventionContext.Result;
+ }
+#if DEBUG
+ Check.DebugAssert(
+ initialValue == propertyBuilder.Metadata.GetElementType(),
+ $"Convention {propertyConvention.GetType().Name} changed value without terminating");
+#endif
+ }
+
+ return _elementTypeConventionContext.Result;
+ }
+
public override IConventionAnnotation? OnPropertyAnnotationChanged(
IConventionPropertyBuilder propertyBuilder,
string name,
@@ -1569,7 +1674,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 +1685,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..1f4f806de38 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
@@ -680,6 +689,18 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder
FieldInfo? oldFieldInfo)
=> _scope.OnPropertyFieldChanged(propertyBuilder, newFieldInfo, oldFieldInfo);
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual IElementType? OnPropertyElementTypeChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IElementType? newElementType,
+ IElementType? oldElementType)
+ => _scope.OnPropertyElementTypeChanged(propertyBuilder, newElementType, oldElementType);
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -704,6 +725,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..e04f01282dc 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,17 @@ 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 propertyBuilder = entityTypeBuilder.Property(propertyInfo);
+ if (mapping?.ElementTypeMapping != null)
+ {
+ propertyBuilder?.PrimitiveCollection();
+ }
}
}
}
diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
index f158cbe1bca..ca406251699 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);
@@ -58,6 +58,15 @@ protected virtual RuntimeModel Create(IModel model)
CreateAnnotations(
property, runtimeProperty, static (convention, annotations, source, target, runtime) =>
convention.ProcessPropertyAnnotations(annotations, source, target, runtime));
+
+ var elementType = property.GetElementType();
+ if (elementType != null)
+ {
+ var runtimeElementType = Create(runtimeProperty, elementType);
+ CreateAnnotations(
+ elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
+ convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
+ }
}
foreach (var serviceProperty in entityType.GetDeclaredServiceProperties())
@@ -389,6 +398,20 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
property.GetJsonValueReaderWriter(),
property.GetTypeMapping());
+ private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element)
+ => runtimeProperty.SetElementType(
+ element.ClrType,
+ element.IsNullable,
+ element.GetMaxLength(),
+ element.IsUnicode(),
+ element.GetPrecision(),
+ element.GetScale(),
+ element.GetProviderClrType(),
+ element.GetValueConverter(),
+ element.GetValueComparer(),
+ element.GetJsonValueReaderWriter(),
+ element.GetTypeMapping());
+
///
/// Updates the property annotations that will be set on the read-only object.
///
@@ -414,6 +437,31 @@ protected virtual void ProcessPropertyAnnotations(
}
}
+ ///
+ /// Updates the element type annotations that will be set on the read-only object.
+ ///
+ /// The annotations to be processed.
+ /// The source element type.
+ /// The target element type that will contain the annotations.
+ /// Indicates whether the given annotations are runtime annotations.
+ protected virtual void ProcessElementTypeAnnotations(
+ Dictionary annotations,
+ IElementType element,
+ RuntimeElementType runtimeElement,
+ bool runtime)
+ {
+ if (!runtime)
+ {
+ foreach (var (key, _) in annotations)
+ {
+ if (CoreAnnotationNames.AllNames.Contains(key))
+ {
+ annotations.Remove(key);
+ }
+ }
+ }
+ }
+
private static RuntimeServiceProperty Create(IServiceProperty property, RuntimeEntityType runtimeEntityType)
=> runtimeEntityType.AddServiceProperty(
property.Name,
@@ -450,18 +498,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;
@@ -472,6 +520,15 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeE
CreateAnnotations(
property, runtimeProperty, static (convention, annotations, source, target, runtime) =>
convention.ProcessPropertyAnnotations(annotations, source, target, runtime));
+
+ var elementType = property.GetElementType();
+ if (elementType != null)
+ {
+ var runtimeElementType = Create(runtimeProperty, elementType);
+ CreateAnnotations(
+ elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
+ convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
+ }
}
foreach (var property in complexType.GetComplexProperties())
@@ -488,18 +545,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;
@@ -510,6 +567,15 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeC
CreateAnnotations(
property, runtimeProperty, static (convention, annotations, source, target, runtime) =>
convention.ProcessPropertyAnnotations(annotations, source, target, runtime));
+
+ var elementType = property.GetElementType();
+ if (elementType != null)
+ {
+ var runtimeElementType = Create(runtimeProperty, elementType);
+ CreateAnnotations(
+ elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
+ convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
+ }
}
foreach (var property in complexType.GetComplexProperties())
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..03608def33f 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.
+ ///
+ /// If , then this is a collection of primitive elements.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configuration for the elements.
+ IElementType? IsPrimitiveCollection(bool primitiveCollection, 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..e4e6637257d
--- /dev/null
+++ b/src/EFCore/Metadata/IElementType.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;
+
+///
+/// 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;
+ }
+}
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..0750186a025 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.
+ ///
+ /// If , then this is a collection of primitive elements.
+ void IsPrimitiveCollection(bool primitiveCollection);
+
+ ///
+ 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..708ea5f2e46 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..f4a75a470a4
--- /dev/null
+++ b/src/EFCore/Metadata/Internal/ElementType.cs
@@ -0,0 +1,971 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Storage.Json;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public 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()
+ => _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 override bool IsReadOnly
+ => CollectionProperty.IsReadOnly;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public 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]
+ ?? TypeMapping?.Comparer)?.ToNullableComparer(ClrType);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual 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()
+ => JsonValueReaderWriter.CreateFromType((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType])
+ ?? TypeMapping?.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 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]
+ 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..a05183517b2
--- /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 element, InternalModelBuilder modelBuilder)
+ : base(element, 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? IsRequired(bool? required, ConfigurationSource configurationSource)
+ {
+ if (configurationSource != ConfigurationSource.Explicit
+ && !CanSetIsRequired(required, configurationSource))
+ {
+ return null;
+ }
+
+ Metadata.SetIsNullable(!required, configurationSource);
+
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource)
+ => ((configurationSource.HasValue
+ && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource()))
+ || (Metadata.IsNullable == !required))
+ && (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? HasMaxLength(int? maxLength, ConfigurationSource configurationSource)
+ {
+ if (CanSetMaxLength(maxLength, configurationSource))
+ {
+ Metadata.SetMaxLength(maxLength, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource())
+ || Metadata.GetMaxLength() == maxLength;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasPrecision(int? precision, ConfigurationSource configurationSource)
+ {
+ if (CanSetPrecision(precision, configurationSource))
+ {
+ Metadata.SetPrecision(precision, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetPrecisionConfigurationSource())
+ || Metadata.GetPrecision() == precision;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasScale(int? scale, ConfigurationSource configurationSource)
+ {
+ if (CanSetScale(scale, configurationSource))
+ {
+ Metadata.SetScale(scale, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetScaleConfigurationSource())
+ || Metadata.GetScale() == scale;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? IsUnicode(bool? unicode, ConfigurationSource configurationSource)
+ {
+ if (CanSetIsUnicode(unicode, configurationSource))
+ {
+ Metadata.SetIsUnicode(unicode, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetIsUnicodeConfigurationSource())
+ || Metadata.IsUnicode() == unicode;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasConversion(ValueConverter? converter, ConfigurationSource configurationSource)
+ {
+ if (CanSetConversion(converter, configurationSource))
+ {
+ Metadata.SetProviderClrType(null, configurationSource);
+ Metadata.SetValueConverter(converter, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetConversion(
+ ValueConverter? converter,
+ ConfigurationSource? configurationSource)
+ => (configurationSource == ConfigurationSource.Explicit
+ || (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource())
+ && Metadata.CheckValueConverter(converter) == null)
+ || (Metadata[CoreAnnotationNames.ValueConverterType] == null
+ && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter))
+ && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource());
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasConversion(Type? providerClrType, ConfigurationSource configurationSource)
+ {
+ if (CanSetConversion(providerClrType, configurationSource))
+ {
+ Metadata.SetValueConverter((ValueConverter?)null, configurationSource);
+ Metadata.SetProviderClrType(providerClrType, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? configurationSource)
+ => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource())
+ || Metadata.GetProviderClrType() == providerClrType)
+ && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource());
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasConverter(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? converterType,
+ ConfigurationSource configurationSource)
+ {
+ if (CanSetConverter(converterType, configurationSource))
+ {
+ Metadata.SetProviderClrType(null, configurationSource);
+ Metadata.SetValueConverter(converterType, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetConverter(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? converterType,
+ ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource())
+ || (Metadata[CoreAnnotationNames.ValueConverter] == null
+ && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasTypeMapping(
+ CoreTypeMapping? typeMapping,
+ ConfigurationSource configurationSource)
+ {
+ if (CanSetTypeMapping(typeMapping, configurationSource))
+ {
+ Metadata.SetTypeMapping(typeMapping, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetTypeMappingConfigurationSource())
+ || Metadata.TypeMapping == typeMapping;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasValueComparer(
+ ValueComparer? comparer,
+ ConfigurationSource configurationSource)
+ {
+ if (CanSetValueComparer(comparer, configurationSource))
+ {
+ Metadata.SetValueComparer(comparer, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSource? configurationSource)
+ {
+ if (configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource()))
+ {
+ var errorString = Metadata.CheckValueComparer(comparer);
+ if (errorString != null)
+ {
+ if (configurationSource == ConfigurationSource.Explicit)
+ {
+ throw new InvalidOperationException(errorString);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ return Metadata[CoreAnnotationNames.ValueComparerType] == null
+ && Metadata[CoreAnnotationNames.ValueComparer] == comparer;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalElementTypeBuilder? HasValueComparer(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? comparerType,
+ ConfigurationSource configurationSource)
+ {
+ if (CanSetValueComparer(comparerType, configurationSource))
+ {
+ Metadata.SetValueComparer(comparerType, configurationSource);
+
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetValueComparerConfigurationSource())
+ || (Metadata[CoreAnnotationNames.ValueComparer] == null
+ && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ 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.IsRequired(bool? required, bool fromDataAnnotation)
+ => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasMaxLength(int? maxLength, bool fromDataAnnotation)
+ => HasMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.IsUnicode(bool? unicode, bool fromDataAnnotation)
+ => IsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasPrecision(int? precision, bool fromDataAnnotation)
+ => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasScale(int? scale, bool fromDataAnnotation)
+ => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasConversion(ValueConverter? converter, bool fromDataAnnotation)
+ => HasConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasConverter(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? converterType,
+ bool fromDataAnnotation)
+ => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasConversion(Type? providerClrType, bool fromDataAnnotation)
+ => HasConversion(
+ providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool IConventionElementTypeBuilder.CanSetConversion(Type? providerClrType, bool fromDataAnnotation)
+ => CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasTypeMapping(
+ CoreTypeMapping? typeMapping,
+ bool fromDataAnnotation)
+ => HasTypeMapping(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.HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation)
+ => HasValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool 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.HasValueComparer(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? comparerType,
+ bool fromDataAnnotation)
+ => HasValueComparer(
+ comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool IConventionElementTypeBuilder.CanSetValueComparer(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+ Type? comparerType,
+ bool fromDataAnnotation)
+ => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+}
diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
index 505884263cc..e7c35712ca5 100644
--- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
@@ -4317,12 +4317,11 @@ public virtual bool CanSetServiceOnlyConstructorBinding(
discriminatorProperty = null;
}
- return (InternalPropertyBuilder?)Metadata.GetRootType().Builder.Property(
- type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType,
- name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName,
- typeConfigurationSource: type != null ? configurationSource : null,
- configurationSource)
- ?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention);
+ return Metadata.GetRootType().Builder.Property(
+ type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType,
+ name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName,
+ typeConfigurationSource: type != null ? configurationSource : null,
+ configurationSource)?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention);
}
private DiscriminatorBuilder? DiscriminatorBuilder(
diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
index 882374d2ac4..f2badfd4d61 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.IsPrimitiveCollection(false, 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? PrimitiveCollection(ConfigurationSource configurationSource)
+ {
+ if (CanSetPrimitiveCollection(configurationSource))
+ {
+ Metadata.IsPrimitiveCollection(true, configurationSource);
+ Metadata.SetValueConverter((Type?)null, 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 CanSetPrimitiveCollection(ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetElementTypeConfigurationSource());
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -868,6 +904,38 @@ public virtual bool CanSetProviderValueComparer(
newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value);
}
+ var element = Metadata.GetElementType();
+ var oldElementTypeConfigurationSource = Metadata.GetElementTypeConfigurationSource();
+ if (element != null
+ && oldElementTypeConfigurationSource.HasValue
+ && newPropertyBuilder.CanSetPrimitiveCollection(oldElementTypeConfigurationSource))
+ {
+ newPropertyBuilder.PrimitiveCollection(oldElementTypeConfigurationSource.Value);
+ var elementType = (ElementType?)Metadata.GetElementType();
+ if (elementType != null)
+ {
+ newPropertyBuilder.PrimitiveCollection(oldElementTypeConfigurationSource.Value);
+ var newElementTypeBuilder = new InternalElementTypeBuilder(elementType, ModelBuilder);
+ newElementTypeBuilder.IsRequired(
+ !elementType.IsNullable, elementType.GetIsNullableConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.IsUnicode(
+ elementType.IsUnicode(), elementType.GetIsUnicodeConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasConverter(
+ elementType.GetValueConverter()?.GetType(),
+ elementType.GetValueConverterConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasPrecision(
+ elementType.GetPrecision(), elementType.GetPrecisionConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasScale(
+ elementType.GetScale(), elementType.GetScaleConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasMaxLength(
+ elementType.GetMaxLength(), elementType.GetMaxLengthConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasTypeMapping(
+ elementType.TypeMapping, elementType.GetTypeMappingConfigurationSource() ?? ConfigurationSource.Convention);
+ newElementTypeBuilder.HasValueComparer(
+ elementType.GetValueComparer(), elementType.GetValueComparerConfigurationSource() ?? ConfigurationSource.Convention);
+ }
+ }
+
return newPropertyBuilder;
}
@@ -902,10 +970,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 +987,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 +1004,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 +1094,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 +1105,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 +1146,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 +1267,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 +1300,8 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func
IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGeneratorFactory(
- [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType,
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)]
+ Type? valueGeneratorFactoryType,
bool fromDataAnnotation)
=> HasValueGeneratorFactory(
valueGeneratorFactoryType,
@@ -1224,7 +1314,8 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func
bool IConventionPropertyBuilder.CanSetValueGeneratorFactory(
- [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType,
+ [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)]
+ Type? valueGeneratorFactoryType,
bool fromDataAnnotation)
=> CanSetValueGeneratorFactory(
valueGeneratorFactoryType,
@@ -1255,7 +1346,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 +1358,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 +1414,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 +1426,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 +1456,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 +1468,34 @@ 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.
+ ///
+ IConventionElementTypeBuilder? IConventionPropertyBuilder.PrimitiveCollection(bool fromDataAnnotation)
+ {
+ var propertyBuilder = PrimitiveCollection(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ return propertyBuilder == null
+ ? null
+ : new InternalElementTypeBuilder((ElementType)propertyBuilder.Metadata.GetElementType()!, propertyBuilder.ModelBuilder);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetPrimitiveCollection(bool fromDataAnnotation = false)
+ => (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
+ .Overrides(Metadata.GetElementTypeConfigurationSource());
}
diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs
index 8dd00df5d6b..f0a7bf2c629 100644
--- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs
@@ -559,6 +559,7 @@ public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties(
property.GetIdentifyingMemberInfo(),
typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null,
configurationSource);
+
if (builder == null)
{
return null;
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..9df40658827 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -998,7 +998,7 @@ public virtual CoreTypeMapping? TypeMapping
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual ValueComparer? GetValueComparer()
- => (GetValueComparer(null) ?? TypeMapping?.Comparer).ToNullableComparer(this);
+ => (GetValueComparer(null) ?? TypeMapping?.Comparer).ToNullableComparer(ClrType);
private ValueComparer? GetValueComparer(HashSet? checkedProperties)
{
@@ -1043,7 +1043,7 @@ public virtual CoreTypeMapping? TypeMapping
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual ValueComparer? GetKeyValueComparer()
- => (GetValueComparer(null) ?? TypeMapping?.KeyComparer).ToNullableComparer(this);
+ => (GetValueComparer(null) ?? TypeMapping?.KeyComparer).ToNullableComparer(ClrType);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1166,34 +1166,7 @@ public virtual CoreTypeMapping? TypeMapping
/// 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;
- }
- }
+ => JsonValueReaderWriter.CreateFromType((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1226,6 +1199,69 @@ 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? IsPrimitiveCollection(
+ bool primitiveCollection,
+ ConfigurationSource configurationSource)
+ {
+ var existingElementType = GetElementType();
+ if (existingElementType == null
+ && primitiveCollection)
+ {
+ var elementClrType = ClrType.TryGetElementType(typeof(IEnumerable<>));
+ if (elementClrType == null)
+ {
+ throw new InvalidOperationException(CoreStrings.NotCollection(ClrType.ShortDisplayName(), Name));
+ }
+ var elementType = new ElementType(elementClrType, this, configurationSource);
+ SetAnnotation(CoreAnnotationNames.ElementType, elementType, configurationSource);
+ OnElementTypeSet(elementType, null);
+ return elementType;
+ }
+
+ if (existingElementType != null && !primitiveCollection)
+ {
+ ((ElementType)existingElementType).SetRemovedFromModel();
+ RemoveAnnotation(CoreAnnotationNames.ElementType);
+ OnElementTypeSet(null, existingElementType);
+ return null;
+ }
+
+ return existingElementType;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual IElementType? OnElementTypeSet(IElementType? newElementType, IElementType? oldElementType)
+ => DeclaringType.Model.ConventionDispatcher.OnPropertyElementTypeChanged(Builder, newElementType, oldElementType);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual ConfigurationSource? GetElementTypeConfigurationSource()
+ => FindAnnotation(CoreAnnotationNames.ElementType)?.GetConfigurationSource();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -1971,6 +2007,18 @@ 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.IsPrimitiveCollection(bool primitiveCollection, bool fromDataAnnotation)
+ => IsPrimitiveCollection(
+ primitiveCollection,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your 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.IsPrimitiveCollection(bool primitiveCollection)
+ => IsPrimitiveCollection(primitiveCollection, ConfigurationSource.Explicit);
}
diff --git a/src/EFCore/Metadata/RuntimeElementType.cs b/src/EFCore/Metadata/RuntimeElementType.cs
new file mode 100644
index 00000000000..80ba1155301
--- /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..6bdcfcc03cc 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;
@@ -109,6 +109,55 @@ public RuntimeProperty(
_jsonValueReaderWriter = jsonValueReaderWriter;
}
+ ///
+ /// Sets the element type for this property.
+ ///
+ /// The type of value the property will hold.
+ /// A value indicating whether this property can contain .
+ /// The maximum length of data that is allowed in this property.
+ /// A value indicating whether or not the property can persist Unicode characters.
+ /// The precision of data that is allowed in this property.
+ /// The scale of data that is allowed in this property.
+ ///
+ /// The type that the property value will be converted to before being sent to the database provider.
+ ///
+ /// The custom set for this property.
+ /// The for this property.
+ /// The for this property.
+ /// The for this property.
+ /// The newly created property.
+ public virtual RuntimeElementType SetElementType(
+ Type clrType,
+ bool nullable = false,
+ int? maxLength = null,
+ bool? unicode = null,
+ int? precision = null,
+ int? scale = null,
+ Type? providerPropertyType = null,
+ ValueConverter? valueConverter = null,
+ ValueComparer? valueComparer = null,
+ JsonValueReaderWriter? jsonValueReaderWriter = null,
+ CoreTypeMapping? typeMapping = null)
+ {
+ var elementType = new RuntimeElementType(
+ clrType,
+ this,
+ nullable,
+ maxLength,
+ unicode,
+ precision,
+ scale,
+ providerPropertyType,
+ valueConverter,
+ valueComparer,
+ jsonValueReaderWriter,
+ typeMapping);
+
+ SetAnnotation(CoreAnnotationNames.ElementType, elementType);
+
+ return elementType;
+ }
+
///
/// Gets the type of value that this property-like object holds.
///
@@ -180,11 +229,11 @@ public virtual CoreTypeMapping TypeMapping
private ValueComparer GetValueComparer()
=> (GetValueComparer(null) ?? TypeMapping.Comparer)
- .ToNullableComparer(this)!;
+ .ToNullableComparer(ClrType)!;
private ValueComparer GetKeyValueComparer()
=> (GetKeyValueComparer(null) ?? TypeMapping.KeyComparer)
- .ToNullableComparer(this)!;
+ .ToNullableComparer(ClrType)!;
private ValueComparer? GetValueComparer(HashSet? checkedProperties)
{
@@ -214,7 +263,7 @@ private ValueComparer GetKeyValueComparer()
private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties)
{
- if ( _keyValueComparer != null)
+ if (_keyValueComparer != null)
{
return _keyValueComparer;
}
@@ -249,6 +298,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 8c194549d2e..b54adbc18a2 100644
--- a/src/EFCore/Metadata/RuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/RuntimeTypeBase.cs
@@ -60,6 +60,7 @@ public RuntimeTypeBase(
_baseType = baseType;
baseType._directlyDerivedTypes.Add(this);
}
+
_changeTrackingStrategy = changeTrackingStrategy;
_indexerPropertyInfo = indexerPropertyInfo;
_isPropertyBag = propertyBag;
@@ -84,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
@@ -288,7 +291,7 @@ private IEnumerable GetDerivedProperties()
}
///
- /// Gets the properties with the given name on this type, base types or derived types.
+ /// Gets the properties with the given name on this type, base types or derived types.
///
/// Type properties.
public virtual IEnumerable FindPropertiesInHierarchy(string propertyName)
@@ -325,7 +328,8 @@ protected virtual IEnumerable GetProperties()
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- protected virtual SortedDictionary Properties => _properties;
+ protected virtual SortedDictionary Properties
+ => _properties;
///
[DebuggerStepThrough]
@@ -433,7 +437,7 @@ public virtual IEnumerable GetComplexProperties()
: _complexProperties.Values;
///
- /// Gets the complex properties with the given name on this type, base types or derived types.
+ /// Gets the complex properties with the given name on this type, base types or derived types.
///
/// Type complex properties.
public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName)
@@ -448,7 +452,7 @@ private IEnumerable FindDerivedComplexProperties(string
return _directlyDerivedTypes.Count == 0
? Enumerable.Empty()
: (IEnumerable)GetDerivedTypes()
- .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null);
+ .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null);
}
///
@@ -491,9 +495,7 @@ private IEnumerable FindDerivedComplexProperties(string
///
[EntityFrameworkInternal]
public virtual void SetOriginalValuesFactory(Func factory)
- {
- _originalValuesFactory = factory;
- }
+ => _originalValuesFactory = factory;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -503,9 +505,7 @@ public virtual void SetOriginalValuesFactory(Func fac
///
[EntityFrameworkInternal]
public virtual void SetStoreGeneratedValuesFactory(Func factory)
- {
- _storeGeneratedValuesFactory = factory;
- }
+ => _storeGeneratedValuesFactory = factory;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -515,9 +515,7 @@ public virtual void SetStoreGeneratedValuesFactory(Func factory)
///
[EntityFrameworkInternal]
public virtual void SetTemporaryValuesFactory(Func factory)
- {
- _temporaryValuesFactory = factory;
- }
+ => _temporaryValuesFactory = factory;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -527,9 +525,7 @@ public virtual void SetTemporaryValuesFactory(Func fa
///
[EntityFrameworkInternal]
public virtual void SetShadowValuesFactory(Func factory)
- {
- _shadowValuesFactory = factory;
- }
+ => _shadowValuesFactory = factory;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -537,12 +533,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 337ccf8e321..70ddeda2d8d 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 type mapping used is incompatible with a compiled model. The mapping type must have a 'public static readonly {typeMapping} {typeMapping}.Default' property.
///
@@ -634,6 +650,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.
///
@@ -2065,6 +2089,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("NotCollection", 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 0454c7f73d2..e44b43b1e73 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 type mapping used is incompatible with a compiled model. The mapping type must have a 'public static readonly {typeMapping} {typeMapping}.Default' property.
@@ -348,6 +354,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.
@@ -1206,6 +1215,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 9cb01a0cffe..85d32f3c09d 100644
--- a/src/EFCore/Storage/CoreTypeMapping.cs
+++ b/src/EFCore/Storage/CoreTypeMapping.cs
@@ -110,8 +110,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,
@@ -137,39 +137,6 @@ public CoreTypeMappingParameters WithComposedConverter(
JsonValueReaderWriter, converter)!
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel)));
}
-
- ///
- /// 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;
@@ -296,11 +263,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);
///
/// Clones the type mapping to update any parameter if needed.
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/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs
index c9b54861e5e..15dc20343eb 100644
--- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs
+++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs
@@ -64,7 +64,7 @@ internal JsonValueReaderWriter()
public virtual object FromJsonString(string json, object? existingObject = null)
{
var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)));
- return FromJson(ref readerManager);
+ return FromJson(ref readerManager, existingObject);
}
///
@@ -84,4 +84,38 @@ public virtual string ToJsonString(object value)
return Encoding.UTF8.GetString(buffer);
}
+
+ ///
+ /// Creates a instance of the given type, using the Instance
+ /// property to get th singleton instance if possible.
+ ///
+ /// The type, which must inherit from .
+ /// The reader/writer instance./
+ /// if the type does not represent a
+ /// that can be instantiated.
+ public static JsonValueReaderWriter? CreateFromType(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;
+ }
+
}
diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs
index 32cef9c53b6..7407af2d4b5 100644
--- a/src/EFCore/Storage/TypeMappingInfo.cs
+++ b/src/EFCore/Storage/TypeMappingInfo.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.Json;
namespace Microsoft.EntityFrameworkCore.Storage;
@@ -23,6 +24,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 = JsonValueReaderWriter.CreateFromType((Type?)elementType[CoreAnnotationNames.JsonValueReaderWriterType]);
+ }
+
///
/// Creates a new instance of .
///
@@ -70,19 +152,19 @@ public TypeMappingInfo(
if (fallbackPrecision == null)
{
- var precisionFromProperty = principal.GetPrecision();
- if (precisionFromProperty != null)
+ var precision = principal.GetPrecision();
+ if (precision != null)
{
- fallbackPrecision = precisionFromProperty;
+ fallbackPrecision = precision;
}
}
if (fallbackScale == null)
{
- var scaleFromProperty = principal.GetScale();
- if (scaleFromProperty != null)
+ var scale = principal.GetScale();
+ if (scale != null)
{
- fallbackScale = scaleFromProperty;
+ fallbackScale = scale;
}
}
@@ -106,7 +188,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 +271,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 +282,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 +318,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..cd06b5e562e 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,73 +88,79 @@ 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 (mappingInfo, providerClrType, customConverter, elementMapping) = k;
- if (mapping == null)
+ var sourceType = mappingInfo.ClrType;
+ CoreTypeMapping? mapping = null;
+
+ if (elementMapping == null
+ || customConverter != null)
{
- var sourceType = info.ClrType;
- if (sourceType != null)
+ mapping = providerClrType == null
+ || providerClrType == mappingInfo.ClrType
+ ? self.FindMapping(mappingInfo)
+ : 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, providerClrType))
{
- foreach (var secondConverterInfo in self.Dependencies
- .ValueConverterSelector
- .Select(providerType))
- {
- mapping = self.FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
+ var mappingInfoUsed = mappingInfo.WithConverter(converterInfo);
+ mapping = self.FindMapping(mappingInfoUsed);
- if (mapping != null)
+ if (mapping == null
+ && providerClrType != null)
+ {
+ foreach (var secondConverterInfo in self.Dependencies
+ .ValueConverterSelector
+ .Select(providerClrType))
{
- 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: mappingInfo.JsonValueReaderWriter);
+ break;
+ }
}
- }
- if (mapping == null)
- {
- mapping = self.TryFindCollectionMapping(info, sourceType, providerType);
+ mapping ??= self.TryFindCollectionMapping(mappingInfo, sourceType, providerClrType, elementMapping);
}
}
}
+ else if (sourceType != null)
+ {
+ mapping = self.TryFindCollectionMapping(mappingInfo, sourceType, providerClrType, elementMapping);
+ }
if (mapping != null
- && converter != null)
+ && customConverter != null)
{
mapping = mapping.Clone(
- converter,
- info.ElementTypeMapping,
- jsonValueReaderWriter: info.JsonValueReaderWriter);
+ customConverter,
+ jsonValueReaderWriter: mappingInfo.JsonValueReaderWriter);
}
return mapping;
@@ -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 4b6f787bff2..2bc79663a0c 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..4891eb90128 100644
--- a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs
@@ -5,212 +5,115 @@
namespace Microsoft.EntityFrameworkCore.Cosmos;
-public class JsonTypesCosmosTest : JsonTypesTestBase
+public class JsonTypesCosmosTest : JsonTypesTestBase
{
- public JsonTypesCosmosTest(JsonTypesCosmosFixture fixture, ITestOutputHelper testOutputHelper)
- : base(fixture)
- {
- }
-
public override void Can_read_write_point()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_point());
+ => Assert.Throws(() => base.Can_read_write_point());
+
+ public override void Can_read_write_point_with_Z()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z());
+
+ public override void Can_read_write_point_with_M()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_M());
+
+ public override void Can_read_write_point_with_Z_and_M()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z_and_M());
public override void Can_read_write_line_string()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_line_string());
+ => Assert.Throws(() => base.Can_read_write_line_string());
public override void Can_read_write_multi_line_string()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_multi_line_string());
+ => Assert.Throws(() => base.Can_read_write_multi_line_string());
public override void Can_read_write_polygon()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_polygon());
+ => Assert.Throws(() => base.Can_read_write_polygon());
public override void Can_read_write_polygon_typed_as_geometry()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_polygon_typed_as_geometry());
+ => Assert.Throws(() => base.Can_read_write_polygon_typed_as_geometry());
public override void Can_read_write_point_as_GeoJson()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_point_as_GeoJson());
+ => Assert.Throws(() => base.Can_read_write_point_as_GeoJson());
+
+ public override void Can_read_write_point_with_Z_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z_as_GeoJson());
+
+ public override void Can_read_write_point_with_M_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_M_as_GeoJson());
+
+ public override void Can_read_write_point_with_Z_and_M_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z_and_M_as_GeoJson());
public override void Can_read_write_line_string_as_GeoJson()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_line_string_as_GeoJson());
+ => Assert.Throws(() => base.Can_read_write_line_string_as_GeoJson());
public override void Can_read_write_multi_line_string_as_GeoJson()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_multi_line_string_as_GeoJson());
+ => Assert.Throws(() => base.Can_read_write_multi_line_string_as_GeoJson());
public override void Can_read_write_polygon_as_GeoJson()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_polygon_as_GeoJson());
+ => Assert.Throws(() => base.Can_read_write_polygon_as_GeoJson());
public override void Can_read_write_polygon_typed_as_geometry_as_GeoJson()
// No built-in JSON support for spatial types in the Cosmos provider
- => Assert.Throws(() => base.Can_read_write_polygon_typed_as_geometry_as_GeoJson());
-
- public override void Can_read_write_collection_of_sbyte_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_sbyte_JSON_values());
-
- public override void Can_read_write_collection_of_short_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_short_JSON_values());
-
- public override void Can_read_write_collection_of_int_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_int_JSON_values());
-
- public override void Can_read_write_collection_of_long_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_long_JSON_values());
-
- public override void Can_read_write_collection_of_byte_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_byte_JSON_values());
-
- public override void Can_read_write_collection_of_uint_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_uint_JSON_values());
-
- public override void Can_read_write_collection_of_float_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_float_JSON_values());
-
- public override void Can_read_write_collection_of_double_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_double_JSON_values());
-
- public override void Can_read_write_collection_of_decimal_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_decimal_JSON_values());
-
- public override void Can_read_write_collection_of_DateOnly_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_DateOnly_JSON_values());
-
- public override void Can_read_write_collection_of_TimeOnly_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_TimeOnly_JSON_values());
-
- public override void Can_read_write_collection_of_DateTime_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_DateTime_JSON_values());
+ => Assert.Throws(() => base.Can_read_write_polygon_typed_as_geometry_as_GeoJson());
- public override void Can_read_write_collection_of_DateTimeOffset_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_DateTimeOffset_JSON_values());
-
- 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());
-
- public override void Can_read_write_collection_of_string_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_string_JSON_values());
-
- public override void Can_read_write_collection_of_binary_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_binary_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_sbyte_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_sbyte_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_short_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_short_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_int_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_int_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_long_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_long_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_byte_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_byte_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_uint_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_uint_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_float_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_float_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_double_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_double_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_decimal_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_decimal_JSON_values());
-
- public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_DateOnly_JSON_values());
+ public override void Can_read_write_nullable_point()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point());
- public override void Can_read_write_collection_of_nullable_TimeOnly_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_TimeOnly_JSON_values());
+ public override void Can_read_write_nullable_line_string()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_line_string());
- public override void Can_read_write_collection_of_nullable_DateTime_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_DateTime_JSON_values());
+ public override void Can_read_write_nullable_multi_line_string()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_multi_line_string());
- public override void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values());
+ public override void Can_read_write_nullable_polygon()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_polygon());
- public override void Can_read_write_collection_of_nullable_TimeSpan_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_TimeSpan_JSON_values());
+ public override void Can_read_write_nullable_point_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_point_as_GeoJson());
- public override void Can_read_write_collection_of_nullable_bool_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_bool_JSON_values());
+ public override void Can_read_write_nullable_line_string_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_line_string_as_GeoJson());
- public override void Can_read_write_collection_of_nullable_char_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_char_JSON_values());
+ public override void Can_read_write_nullable_multi_line_string_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_multi_line_string_as_GeoJson());
- public override void Can_read_write_collection_of_nullable_GUID_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_string_JSON_values());
+ public override void Can_read_write_nullable_polygon_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_polygon_as_GeoJson());
- public override void Can_read_write_collection_of_nullable_string_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_string_JSON_values());
+ public override void Can_read_write_polygon_typed_as_nullable_geometry()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_polygon_typed_as_nullable_geometry());
- public override void Can_read_write_collection_of_nullable_binary_JSON_values()
- // Cosmos currently uses a different mechanism for primitive collections
- => Assert.Throws(() => base.Can_read_write_collection_of_nullable_binary_JSON_values());
+ public override void Can_read_write_polygon_typed_as_nullable_geometry_as_GeoJson()
+ // No built-in JSON support for spatial types in the Cosmos provider
+ => Assert.Throws(() => base.Can_read_write_polygon_typed_as_nullable_geometry_as_GeoJson());
- public class JsonTypesCosmosFixture : JsonTypesFixtureBase
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
- protected override ITestStoreFactory TestStoreFactory
- => CosmosTestStoreFactory.Instance;
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- modelBuilder.Ignore();
- modelBuilder.Ignore();
- }
+ var store = CosmosTestStore.GetOrCreate(nameof(JsonTypesCosmosTest));
+ base.OnConfiguring(optionsBuilder.UseCosmos(store.ConnectionUri, store.AuthToken, store.Name));
}
}
diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
index 5085c43a96c..4b04a27d661 100644
--- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
+++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
@@ -164,7 +164,7 @@ public virtual void No_id_property_created_if_another_property_mapped_to_id_in_p
var model = modelBuilder.FinalizeModel();
- var entity = model.FindEntityType(typeof(Customer));
+ var entity = model.FindEntityType(typeof(Customer))!;
Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
@@ -243,6 +243,90 @@ public virtual void No_alternate_key_is_created_if_id_is_partition_key()
Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
}
+ public override void Primitive_collections_can_be_made_concurrency_tokens()
+ => Assert.Equal(
+ CosmosStrings.NonETagConcurrencyToken(nameof(CollectionQuarks), "Charm"),
+ Assert.Throws(
+ () => base.Primitive_collections_can_be_made_concurrency_tokens()).Message);
+
+ [ConditionalFact]
+ public virtual void Primitive_collections_key_is_added_to_the_keys()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders)
+ .HasPartitionKey(b => b.Notes)
+ .PrimitiveCollection(b => b.Notes);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer))!;
+
+ Assert.Equal(
+ new[] { nameof(Customer.Id), nameof(Customer.Notes) },
+ entity.FindPrimaryKey()!.Properties.Select(p => p.Name));
+ Assert.Equal(
+ new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.Notes) },
+ entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name));
+
+ var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)!;
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.NotNull(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void No_id_property_created_if_another_primitive_collection_mapped_to_id()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .PrimitiveCollection(c => c.Notes)
+ .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName);
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer))!;
+
+ Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
+ Assert.Single(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+
+ var idProperty = entity.GetDeclaredProperties()
+ .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.Null(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void No_id_property_created_if_another_primitive_collection_to_id_in_pk()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .PrimitiveCollection(c => c.Notes)
+ .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName);
+ modelBuilder.Entity()
+ .Ignore(c => c.Details)
+ .Ignore(c => c.Orders)
+ .HasKey(c => c.Notes);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer))!;
+
+ Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
+ Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+
+ var idProperty = entity.GetDeclaredProperties()
+ .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.Null(idProperty.GetValueGeneratorFactory());
+ }
+
protected override TestModelBuilder CreateModelBuilder(Action configure = null)
=> CreateTestModelBuilder(CosmosTestHelpers.Instance, configure);
}
diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs
index aa54bcc4f9d..a8274349adc 100644
--- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs
+++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs
@@ -58,4 +58,21 @@ public static ModelBuilderTest.TestPropertyBuilder ToJsonProperty ToJsonProperty(
+ this ModelBuilderTest.TestPrimitiveCollectionBuilder builder,
+ string name)
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.ToJsonProperty(name);
+ break;
+ case IInfrastructure nonGenericBuilder:
+ nonGenericBuilder.Instance.ToJsonProperty(name);
+ break;
+ }
+
+ return builder;
+ }
}
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.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs
index 7a04e6d82f6..b5364a02350 100644
--- a/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs
+++ b/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs
@@ -5,17 +5,24 @@
namespace Microsoft.EntityFrameworkCore;
-public class JsonTypesInMemoryTest : JsonTypesTestBase
+public class JsonTypesInMemoryTest : JsonTypesTestBase
{
- public JsonTypesInMemoryTest(JsonTypesInMemoryFixture fixture, ITestOutputHelper testOutputHelper)
- : base(fixture)
- {
- }
-
public override void Can_read_write_point()
// No built-in JSON support for spatial types in the in-memory provider
=> Assert.Throws(() => base.Can_read_write_point());
+ public override void Can_read_write_point_with_M()
+ // No built-in JSON support for spatial types in the in-memory provider
+ => Assert.Throws(() => base.Can_read_write_point_with_M());
+
+ public override void Can_read_write_point_with_Z()
+ // No built-in JSON support for spatial types in the in-memory provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z());
+
+ public override void Can_read_write_point_with_Z_and_M()
+ // No built-in JSON support for spatial types in the in-memory provider
+ => Assert.Throws(() => base.Can_read_write_point_with_Z_and_M());
+
public override void Can_read_write_line_string()
// No built-in JSON support for spatial types in the in-memory provider
=> Assert.Throws(() => base.Can_read_write_line_string());
@@ -32,9 +39,6 @@ public override void Can_read_write_polygon_typed_as_geometry()
// No built-in JSON support for spatial types in the in-memory provider
=> Assert.Throws(() => base.Can_read_write_polygon_typed_as_geometry());
- public class JsonTypesInMemoryFixture : JsonTypesFixtureBase
- {
- protected override ITestStoreFactory TestStoreFactory
- => InMemoryTestStoreFactory.Instance;
- }
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => base.OnConfiguring(optionsBuilder.UseInMemoryDatabase("X"));
}
diff --git a/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs
new file mode 100644
index 00000000000..92b4e05db8d
--- /dev/null
+++ b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore;
+
+public class JsonTypesRelationalTestBase : JsonTypesTestBase
+{
+ [ConditionalTheory]
+ [InlineData(null)]
+ public virtual void Can_read_write_collection_of_fixed_length_string_JSON_values(object? storeType)
+ => Can_read_and_write_JSON_collection_value>(
+ b => b.ElementType().IsFixedLength().HasMaxLength(32),
+ nameof(StringCollectionType.String),
+ new List
+ {
+ "MinValue",
+ "Value",
+ "MaxValue"
+ },
+ """{"Prop":["MinValue","Value","MaxValue"]}""",
+ facets: new Dictionary
+ {
+ { RelationalAnnotationNames.IsFixedLength, true },
+ { RelationalAnnotationNames.StoreType, storeType },
+ { CoreAnnotationNames.MaxLength, 32 }
+ });
+
+ [ConditionalTheory]
+ [InlineData(null)]
+ public virtual void Can_read_write_collection_of_ASCII_string_JSON_values(object? storeType)
+ => Can_read_and_write_JSON_collection_value>(
+ b => b.ElementType().IsUnicode(false),
+ nameof(StringCollectionType.String),
+ new List
+ {
+ "MinValue",
+ "Value",
+ "MaxValue"
+ },
+ """{"Prop":["MinValue","Value","MaxValue"]}""",
+ facets: new Dictionary
+ {
+ { RelationalAnnotationNames.StoreType, storeType },
+ { CoreAnnotationNames.Unicode, false }
+ });
+
+ protected override void AssertElementFacets(IElementType element, Dictionary? facets)
+ {
+ base.AssertElementFacets(element, facets);
+
+ Assert.Same(element.FindTypeMapping(), element.FindRelationalTypeMapping());
+ Assert.Equal(FacetValue(RelationalAnnotationNames.IsFixedLength), element.IsFixedLength());
+
+ var expectedStoreType = FacetValue(RelationalAnnotationNames.StoreType)
+ ?? element.FindRelationalTypeMapping()!.StoreType;
+
+ Assert.Equal(expectedStoreType, element.GetStoreType());
+
+ object? FacetValue(string facetName)
+ => facets?.TryGetValue(facetName, out var facet) == true ? facet : null;
+ }
+}
diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs
index 784209f9d19..360cd51c519 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs
@@ -296,14 +296,12 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives
{
var query = context.Entities.Where(x => x.Reference.IntArray[0] == 1);
- if (async)
- {
- await Assert.ThrowsAsync(() => query.ToListAsync());
- }
- else
- {
- Assert.Throws(() => query.ToList());
- }
+ var result = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ Assert.Equal(1, result.Count);
+ Assert.Equal(1, result[0].Reference.IntArray[0]);
}
}
@@ -318,14 +316,12 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives
{
var query = context.Entities.Where(x => x.Reference.ListOfString[1] == "Bar");
- if (async)
- {
- await Assert.ThrowsAsync(() => query.ToListAsync());
- }
- else
- {
- Assert.Throws(() => query.ToList());
- }
+ var result = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ Assert.Equal(1, result.Count);
+ Assert.Equal("Bar", result[0].Reference.ListOfString[1]);
}
}
@@ -338,17 +334,18 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives
using (var context = contextFactory.CreateContext())
{
- var query = context.Entities.Where(x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1
- || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar");
+ var query = context.Entities.Where(
+ x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1
+ || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar")
+ .OrderBy(e => e.Id);
- if (async)
- {
- await Assert.ThrowsAsync(() => query.ToListAsync());
- }
- else
- {
- Assert.Throws(() => query.ToList());
- }
+ var result = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ Assert.Equal(1, result.Count);
+ Assert.Equal(1, result[0].Reference.IntArray[0]);
+ Assert.Equal("Bar", result[0].Reference.ListOfString[1]);
}
}
diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
index 19a84b92361..a4a9e4007d1 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
@@ -144,6 +144,142 @@ public static ModelBuilderTest.TestPropertyBuilder HasJsonPropertyNam
return builder;
}
+ public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasColumnName(
+ this ModelBuilderTest.TestPrimitiveCollectionBuilder builder,
+ string? name)
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasColumnName(name);
+ break;
+ case IInfrastructure nonGenericBuilder:
+ nonGenericBuilder.Instance.HasColumnName(name);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasColumnType(
+ this ModelBuilderTest.TestPrimitiveCollectionBuilder builder,
+ string typeName)
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasColumnType(typeName);
+ break;
+ case IInfrastructure nonGenericBuilder:
+ nonGenericBuilder.Instance.HasColumnType(typeName);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasDefaultValueSql(
+ this ModelBuilderTest.TestPrimitiveCollectionBuilder builder,
+ string sql)
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasDefaultValueSql(sql);
+ break;
+ case IInfrastructure nonGenericBuilder:
+ nonGenericBuilder.Instance.HasDefaultValueSql(sql);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasComputedColumnSql(
+ this ModelBuilderTest.TestPrimitiveCollectionBuilder builder,
+ string sql)
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasComputedColumnSql(sql);
+ break;
+ case IInfrastructure nonGenericBuilder:
+ nonGenericBuilder.Instance.HasComputedColumnSql(sql);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasDefaultValue