diff --git a/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs
index 0f8352068b1..b9ee3cd2bdd 100644
--- a/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalElementTypeBuilderExtensions.cs
@@ -20,7 +20,7 @@ public static class RelationalElementTypeBuilderExtensions
/// 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 ElementsHaveStoreType(
+ public static ElementTypeBuilder HasStoreType(
this ElementTypeBuilder elementTypeBuilder,
string? typeName)
{
@@ -41,7 +41,7 @@ public static ElementTypeBuilder ElementsHaveStoreType(
/// 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? ElementsHaveStoreType(
+ public static IConventionElementTypeBuilder? HasStoreType(
this IConventionElementTypeBuilder elementTypeBuilder,
string? typeName,
bool fromDataAnnotation = false)
@@ -80,7 +80,7 @@ public static bool CanSetStoreType(
/// The builder for the elements being configured.
/// A value indicating whether the elements are constrained to fixed length values.
/// The same builder instance so that multiple configuration calls can be chained.
- public static ElementTypeBuilder ElementsAreFixedLength(
+ public static ElementTypeBuilder IsFixedLength(
this ElementTypeBuilder elementTypeBuilder,
bool fixedLength = true)
{
@@ -99,7 +99,7 @@ public static ElementTypeBuilder ElementsAreFixedLength(
/// A value indicating whether the elements are constrained to fixed length values.
/// Indicates whether the configuration was specified using a data annotation.
/// The same builder instance if the configuration was applied, otherwise.
- public static IConventionElementTypeBuilder? ElementsAreFixedLength(
+ public static IConventionElementTypeBuilder? IsFixedLength(
this IConventionElementTypeBuilder elementTypeBuilder,
bool? fixedLength,
bool fromDataAnnotation = false)
diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
index dd0e93deb25..4afabd1b6cd 100644
--- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
+++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
@@ -60,7 +60,7 @@ public RelationalTypeMappingInfo(
_coreTypeMappingInfo = new TypeMappingInfo(elementType, fallbackUnicode, fallbackSize, fallbackPrecision, fallbackScale);
fallbackFixedLength ??= elementType.IsFixedLength();
- storeTypeName ??= elementType.GetStoreType();
+ storeTypeName ??= (string?)elementType[RelationalAnnotationNames.StoreType];
var customConverter = elementType.GetValueConverter();
var mappingHints = customConverter?.MappingHints;
diff --git a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs
index bcc7cbaa812..a11e92e4632 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 hasValueMethod = property.ClrType.GetMethod("get_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 hasValueMethod = clrType.GetMethod("get_HasValue")!;
var v1HasValue = Expression.Parameter(typeof(bool), "v1HasValue");
var v2HasValue = Expression.Parameter(typeof(bool), "v2HasValue");
return (ValueComparer)Activator.CreateInstance(
- typeof(ValueComparer<>).MakeGenericType(property.ClrType),
+ typeof(ValueComparer<>).MakeGenericType(clrType),
Expression.Lambda(
Expression.Block(
typeof(bool),
@@ -66,8 +66,8 @@ public static class ValueComparerExtensions
Expression.Call(newSnapshotParam, hasValueMethod),
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/ComplexTypePrimitiveCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs
index 87d3aa8ce02..12badbff9e8 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs
@@ -109,32 +109,6 @@ public virtual ComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentine
return this;
}
- ///
- /// Configures the precision and scale of the property.
- ///
- /// The precision of the property.
- /// The scale of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual ComplexTypePrimitiveCollectionBuilder HasPrecision(int precision, int scale)
- {
- Builder.HasPrecision(precision, ConfigurationSource.Explicit);
- Builder.HasScale(scale, ConfigurationSource.Explicit);
-
- return this;
- }
-
- ///
- /// Configures the precision of the property.
- ///
- /// The precision of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual ComplexTypePrimitiveCollectionBuilder HasPrecision(int precision)
- {
- Builder.HasPrecision(precision, ConfigurationSource.Explicit);
-
- return this;
- }
-
///
/// Configures whether the property as capable of persisting unicode characters.
/// Can only be set on properties.
@@ -148,23 +122,6 @@ public virtual ComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = tr
return this;
}
- ///
- /// Configures the property as and
- /// .
- ///
- ///
- /// Database providers can choose to interpret this in different way, but it is commonly used
- /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection.
- ///
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual ComplexTypePrimitiveCollectionBuilder IsRowVersion()
- {
- Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit);
- Builder.IsConcurrencyToken(true, ConfigurationSource.Explicit);
-
- return this;
- }
-
///
/// Configures the that will generate values for this property.
///
@@ -433,7 +390,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder HasField(string fieldName)
}
///
- /// Configures the type of primitive value stored by the collection.
+ /// Configures this property as a collection containing elements of a primitive type.
///
/// A builder to configure the collection element type.
public virtual ElementTypeBuilder ElementType()
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs
index 3b48559d009..46563dc1360 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs
@@ -73,23 +73,6 @@ public ComplexTypePrimitiveCollectionBuilder(IMutableProperty property)
public new virtual ComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel)
=> (ComplexTypePrimitiveCollectionBuilder)base.HasSentinel(sentinel);
- ///
- /// Configures the precision and scale of the property.
- ///
- /// The precision of the property.
- /// The scale of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual ComplexTypePrimitiveCollectionBuilder HasPrecision(int precision, int scale)
- => (ComplexTypePrimitiveCollectionBuilder)base.HasPrecision(precision, scale);
-
- ///
- /// Configures the precision of the property.
- ///
- /// The precision of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual ComplexTypePrimitiveCollectionBuilder HasPrecision(int precision)
- => (ComplexTypePrimitiveCollectionBuilder)base.HasPrecision(precision);
-
///
/// Configures the property as capable of persisting unicode characters.
/// Can only be set on properties.
@@ -99,18 +82,6 @@ public ComplexTypePrimitiveCollectionBuilder(IMutableProperty property)
public new virtual ComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true)
=> (ComplexTypePrimitiveCollectionBuilder)base.IsUnicode(unicode);
- ///
- /// Configures the property as and
- /// .
- ///
- ///
- /// Database providers can choose to interpret this in different way, but it is commonly used
- /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection.
- ///
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual ComplexTypePrimitiveCollectionBuilder IsRowVersion()
- => (ComplexTypePrimitiveCollectionBuilder)base.IsRowVersion();
-
///
/// Configures the that will generate values for this property.
///
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
index 041e73d9777..b6c57e0effb 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs
@@ -432,29 +432,6 @@ public virtual ComplexTypePropertyBuilder HasField(string fieldName)
return this;
}
- ///
- /// Configures the type of primitive value stored by the collection.
- ///
- /// A builder to configure the collection element type.
- public virtual ElementTypeBuilder ElementType()
- {
- Builder.HasElementType(ConfigurationSource.Explicit);
-
- return new ElementTypeBuilder((IMutableElementType)Builder.Metadata.GetElementType()!);
- }
-
- ///
- /// Configures this property as a collection containing elements of a primitive type.
- ///
- /// An action that performs configuration of the collection element type.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual ComplexTypePropertyBuilder ElementType(Action builderAction)
- {
- builderAction(ElementType());
-
- return this;
- }
-
///
/// Sets the to use for this property.
///
diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
index a7a001608fc..7f0e24503b5 100644
--- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs
@@ -331,14 +331,6 @@ public ComplexTypePropertyBuilder(IMutableProperty property)
public new virtual ComplexTypePropertyBuilder HasField(string fieldName)
=> (ComplexTypePropertyBuilder)base.HasField(fieldName);
- ///
- /// Configures this property as a collection containing elements of a primitive type.
- ///
- /// An action that performs configuration of the collection element type.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual ComplexTypePropertyBuilder ElementType(Action builderAction)
- => (ComplexTypePropertyBuilder)base.ElementType(builderAction);
-
///
/// Sets the to use for this property.
///
diff --git a/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs
index 98081c16d1e..96aaab63982 100644
--- a/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs
+++ b/src/EFCore/Metadata/Builders/ElementTypeBuilder.cs
@@ -8,7 +8,7 @@
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
///
-/// Provides a simple API for configuring a .
+/// Provides a simple API for configuring the of a primitive collection.
///
///
///
@@ -37,7 +37,7 @@ public ElementTypeBuilder(IMutableElementType elementType)
}
///
- /// The internal builder being used to configure the elements of the collection.
+ /// The internal builder being used to configure the element type.
///
IConventionElementTypeBuilder IInfrastructure.Instance
=> Builder;
@@ -45,19 +45,19 @@ IConventionElementTypeBuilder IInfrastructure.Ins
private InternalElementTypeBuilder Builder { get; }
///
- /// The elements of the collection being configured.
+ /// The element type being configured.
///
public virtual IMutableElementType Metadata
=> Builder.Metadata;
///
- /// Adds or updates an annotation on the elements of the collection. If an annotation with the key specified in
+ /// 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 ElementsHaveAnnotation(string annotation, object? value)
+ public virtual ElementTypeBuilder HasAnnotation(string annotation, object? value)
{
Check.NotEmpty(annotation, nameof(annotation));
@@ -73,9 +73,9 @@ public virtual ElementTypeBuilder ElementsHaveAnnotation(string annotation, obje
///
/// A value indicating whether elements of the collection must not be .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsAreRequired(bool required = true)
+ public virtual ElementTypeBuilder IsRequired(bool required = true)
{
- Builder.ElementsAreRequired(required, ConfigurationSource.Explicit);
+ Builder.IsRequired(required, ConfigurationSource.Explicit);
return this;
}
@@ -88,9 +88,9 @@ public virtual ElementTypeBuilder ElementsAreRequired(bool required = true)
/// collection have no maximum length.
///
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveMaxLength(int maxLength)
+ public virtual ElementTypeBuilder HasMaxLength(int maxLength)
{
- Builder.ElementsHaveMaxLength(maxLength, ConfigurationSource.Explicit);
+ Builder.HasMaxLength(maxLength, ConfigurationSource.Explicit);
return this;
}
@@ -101,10 +101,10 @@ public virtual ElementTypeBuilder ElementsHaveMaxLength(int maxLength)
/// The precision of elements of the collection.
/// The scale of elements of the collection.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHavePrecision(int precision, int scale)
+ public virtual ElementTypeBuilder HasPrecision(int precision, int scale)
{
- Builder.ElementsHavePrecision(precision, ConfigurationSource.Explicit);
- Builder.ElementsHaveScale(scale, ConfigurationSource.Explicit);
+ Builder.HasPrecision(precision, ConfigurationSource.Explicit);
+ Builder.HasScale(scale, ConfigurationSource.Explicit);
return this;
}
@@ -114,9 +114,9 @@ public virtual ElementTypeBuilder ElementsHavePrecision(int precision, int scale
///
/// The precision of elements of the collection.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHavePrecision(int precision)
+ public virtual ElementTypeBuilder HasPrecision(int precision)
{
- Builder.ElementsHavePrecision(precision, ConfigurationSource.Explicit);
+ Builder.HasPrecision(precision, ConfigurationSource.Explicit);
return this;
}
@@ -126,9 +126,9 @@ public virtual ElementTypeBuilder ElementsHavePrecision(int precision)
///
/// A value indicating whether elements of the collection can contain unicode characters.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsAreUnicode(bool unicode = true)
+ public virtual ElementTypeBuilder IsUnicode(bool unicode = true)
{
- Builder.ElementsAreUnicode(unicode, ConfigurationSource.Explicit);
+ Builder.IsUnicode(unicode, ConfigurationSource.Explicit);
return this;
}
@@ -139,9 +139,9 @@ public virtual ElementTypeBuilder ElementsAreUnicode(bool unicode = true)
///
/// The type to convert to and from or a type that inherits from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion<
+ public virtual ElementTypeBuilder HasConversion<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>()
- => ElementsHaveConversion(typeof(TConversion));
+ => HasConversion(typeof(TConversion));
///
/// Configures elements of the collection so that their values are converted before writing to the database and converted back
@@ -149,17 +149,17 @@ public virtual ElementTypeBuilder ElementsHaveConversion<
///
/// The type to convert to and from or a type that inherits from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion(
+ public virtual ElementTypeBuilder HasConversion(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? conversionType)
{
if (typeof(ValueConverter).IsAssignableFrom(conversionType))
{
- Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
}
else
{
- Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
}
return this;
@@ -171,8 +171,8 @@ public virtual ElementTypeBuilder ElementsHaveConversion(
///
/// The converter to use.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? converter)
- => ElementsHaveConversion(converter, null);
+ public virtual ElementTypeBuilder HasConversion(ValueConverter? converter)
+ => HasConversion(converter, null);
///
/// Configures elements of the collection so that their values are converted before
@@ -181,11 +181,11 @@ public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? convert
/// The comparer to use for values before conversion.
/// The type to convert to and from or a type that inherits from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion<
+ public virtual ElementTypeBuilder HasConversion<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
TConversion>(
ValueComparer? valueComparer)
- => ElementsHaveConversion(typeof(TConversion), valueComparer);
+ => HasConversion(typeof(TConversion), valueComparer);
///
/// Configures elements of the collection so that their values are converted before
@@ -194,7 +194,7 @@ public virtual ElementTypeBuilder ElementsHaveConversion<
/// The type to convert to and from or a type that inherits from .
/// The comparer to use for values before conversion.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion(
+ public virtual ElementTypeBuilder HasConversion(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type conversionType,
ValueComparer? valueComparer)
@@ -203,14 +203,14 @@ public virtual ElementTypeBuilder ElementsHaveConversion(
if (typeof(ValueConverter).IsAssignableFrom(conversionType))
{
- Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
}
else
{
- Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
}
- Builder.ElementsHaveValueComparer(valueComparer, ConfigurationSource.Explicit);
+ Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);
return this;
}
@@ -222,10 +222,10 @@ public virtual ElementTypeBuilder ElementsHaveConversion(
/// The converter to use.
/// The comparer to use for values before conversion.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? converter, ValueComparer? valueComparer)
+ public virtual ElementTypeBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer)
{
- Builder.ElementsHaveConversion(converter, ConfigurationSource.Explicit);
- Builder.ElementsHaveValueComparer(valueComparer, ConfigurationSource.Explicit);
+ Builder.HasConversion(converter, ConfigurationSource.Explicit);
+ Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);
return this;
}
@@ -237,13 +237,13 @@ public virtual ElementTypeBuilder ElementsHaveConversion(ValueConverter? convert
/// The type to convert to and from or a type that inherits from .
/// A type that inherits from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion<
+ public virtual ElementTypeBuilder HasConversion<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
TConversion,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
TComparer>()
where TComparer : ValueComparer
- => ElementsHaveConversion(typeof(TConversion), typeof(TComparer));
+ => HasConversion(typeof(TConversion), typeof(TComparer));
///
/// Configures elements of the collection so that their values are converted before
@@ -252,7 +252,7 @@ public virtual ElementTypeBuilder ElementsHaveConversion<
/// The type to convert to and from or a type that inherits from .
/// A type that inherits from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual ElementTypeBuilder ElementsHaveConversion(
+ public virtual ElementTypeBuilder HasConversion(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type conversionType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
@@ -262,14 +262,14 @@ public virtual ElementTypeBuilder ElementsHaveConversion(
if (typeof(ValueConverter).IsAssignableFrom(conversionType))
{
- Builder.ElementsHaveConverter(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConverter(conversionType, ConfigurationSource.Explicit);
}
else
{
- Builder.ElementsHaveConversion(conversionType, ConfigurationSource.Explicit);
+ Builder.HasConversion(conversionType, ConfigurationSource.Explicit);
}
- Builder.ElementsHaveValueComparer(comparerType, ConfigurationSource.Explicit);
+ Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit);
return this;
}
diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
index b1be2f91c3e..a96293d07b2 100644
--- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
@@ -153,8 +153,8 @@ public virtual PropertyBuilder Property(Expression
- /// Returns an object that can be used to configure a property of the entity type.
- /// If the specified property is not already part of the model, it will be added.
+ /// 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 (
diff --git a/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs
index ffdc0adc9cf..0538d82a83c 100644
--- a/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs
+++ b/src/EFCore/Metadata/Builders/IConventionElementTypeBuilder.cs
@@ -7,7 +7,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
///
///
-/// Provides a simple API surface for configuring an from conventions.
+/// 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
@@ -20,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
{
///
- /// Gets the collection elements being configured.
+ /// Gets the element type being configured.
///
new IConventionElementType Metadata { get; }
@@ -74,7 +75,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the requiredness was configured,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsAreRequired(bool? required, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? IsRequired(bool? required, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether this element requiredness can be configured from the current configuration source.
@@ -98,7 +99,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveMaxLength(int? maxLength, bool fromDataAnnotation = false);
+ 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.
@@ -117,7 +118,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsAreUnicode(bool? unicode, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? IsUnicode(bool? unicode, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the elements can be configured as capable of persisting unicode characters
@@ -137,7 +138,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsHavePrecision(int? precision, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasPrecision(int? precision, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the precision of elements can be set from the current configuration source.
@@ -156,7 +157,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveScale(int? scale, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasScale(int? scale, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the scale of elements can be set from the current configuration source.
@@ -176,7 +177,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveConversion(ValueConverter? converter, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasConversion(ValueConverter? converter, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the can be configured for the elements
@@ -199,7 +200,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
/// The same builder instance if the configuration was applied,
/// otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveConversion(Type? providerClrType, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasConversion(Type? providerClrType, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the given type to convert values to and from
@@ -224,7 +225,7 @@ public interface IConventionElementTypeBuilder : IConventionAnnotatableBuilder
///
/// The same builder instance if the configuration was applied, or otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveConverter(
+ IConventionElementTypeBuilder? HasConverter(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? converterType,
bool fromDataAnnotation = false);
@@ -253,7 +254,7 @@ bool CanSetConverter(
///
/// The same builder instance if the configuration was applied, or otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasTypeMapping(CoreTypeMapping? typeMapping, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the given
@@ -274,7 +275,7 @@ bool CanSetConverter(
///
/// The same builder instance if the configuration was applied, or otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasValueComparer(ValueComparer? comparer, bool fromDataAnnotation = false);
///
/// Returns a value indicating whether the given
@@ -297,7 +298,7 @@ bool CanSetConverter(
///
/// The same builder instance if the configuration was applied, or otherwise.
///
- IConventionElementTypeBuilder? ElementsHaveValueComparer(
+ IConventionElementTypeBuilder? HasValueComparer(
[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 5ce9def04d3..0dac98f9345 100644
--- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
@@ -545,7 +545,7 @@ bool CanSetProviderValueComparer(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionPropertyBuilder? HasElementType(IElementType? elementType, bool fromDataAnnotation = false);
+ IConventionElementTypeBuilder? HasElementType(bool fromDataAnnotation = false);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -553,5 +553,13 @@ bool CanSetProviderValueComparer(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- bool CanSetElementType(IElementType? elementType, bool fromDataAnnotation = false);
+ IConventionPropertyBuilder? HasElementType(Action builderAction, bool fromDataAnnotation = false);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ bool CanSetElementType(bool fromDataAnnotation = false);
}
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
index 7df938efa1f..a52324adfa2 100644
--- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
+++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
@@ -192,7 +192,8 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName)
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata);
///
- /// Returns an object that can be used to configure a property of the owned entity type.
+ /// 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.
///
///
@@ -211,7 +212,8 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyNam
.HasElementType(ConfigurationSource.Explicit)!.Metadata));
///
- /// Returns an object that can be used to configure a property of the owned entity type.
+ /// 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.
///
///
@@ -233,7 +235,8 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection
- /// Returns an object that can be used to configure a property of the owned entity type.
+ /// 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.
///
///
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
index 9168293a31a..1d5bb55bf54 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)!.HasElementType(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
index 38552423792..a0149951521 100644
--- a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs
+++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs
@@ -109,32 +109,6 @@ public virtual PrimitiveCollectionBuilder HasSentinel(object? sentinel)
return this;
}
- ///
- /// Configures the precision and scale of the property.
- ///
- /// The precision of the property.
- /// The scale of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual PrimitiveCollectionBuilder HasPrecision(int precision, int scale)
- {
- Builder.HasPrecision(precision, ConfigurationSource.Explicit);
- Builder.HasScale(scale, ConfigurationSource.Explicit);
-
- return this;
- }
-
- ///
- /// Configures the precision of the property.
- ///
- /// The precision of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual PrimitiveCollectionBuilder HasPrecision(int precision)
- {
- Builder.HasPrecision(precision, ConfigurationSource.Explicit);
-
- return this;
- }
-
///
/// Configures whether the property as capable of persisting unicode characters.
/// Can only be set on properties.
@@ -148,23 +122,6 @@ public virtual PrimitiveCollectionBuilder IsUnicode(bool unicode = true)
return this;
}
- ///
- /// Configures the property as and
- /// .
- ///
- ///
- /// Database providers can choose to interpret this in different way, but it is commonly used
- /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection.
- ///
- /// The same builder instance so that multiple configuration calls can be chained.
- public virtual PrimitiveCollectionBuilder IsRowVersion()
- {
- Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit);
- Builder.IsConcurrencyToken(true, ConfigurationSource.Explicit);
-
- return this;
- }
-
///
/// Configures the that will generate values for this property.
///
@@ -435,7 +392,7 @@ public virtual PrimitiveCollectionBuilder HasField(string fieldName)
}
///
- /// Configures the type of primitive value stored by the collection.
+ /// Configures this property as a collection containing elements of a primitive type.
///
/// A builder to configure the collection element type.
public virtual ElementTypeBuilder ElementType()
diff --git a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs
index e21c579e9d4..e71c6e96c13 100644
--- a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs
@@ -73,23 +73,6 @@ public PrimitiveCollectionBuilder(IMutableProperty property)
public new virtual PrimitiveCollectionBuilder HasSentinel(object? sentinel)
=> (PrimitiveCollectionBuilder)base.HasSentinel(sentinel);
- ///
- /// Configures the precision and scale of the property.
- ///
- /// The precision of the property.
- /// The scale of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual PrimitiveCollectionBuilder HasPrecision(int precision, int scale)
- => (PrimitiveCollectionBuilder)base.HasPrecision(precision, scale);
-
- ///
- /// Configures the precision of the property.
- ///
- /// The precision of the property.
- /// The same builder instance so that multiple configuration calls can be chained.
- public new virtual PrimitiveCollectionBuilder HasPrecision(int precision)
- => (PrimitiveCollectionBuilder)base.HasPrecision(precision);
-
///
/// Configures the property as capable of persisting unicode characters.
/// Can only be set on properties.
diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
index 1b873e28933..542a48ba673 100644
--- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
+++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
@@ -91,18 +91,10 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
continue;
}
- if (mapping?.ElementTypeMapping == null)
+ var propertyBuilder = entityTypeBuilder.Property(propertyInfo);
+ if (mapping?.ElementTypeMapping != null)
{
- entityTypeBuilder.Property(propertyInfo);
- }
- else
- {
- var propertyBuilder = entityTypeBuilder.Property(propertyInfo);
- propertyBuilder?.HasElementType(
- new ElementType(
- mapping.ElementTypeMapping.ClrType,
- (Property)propertyBuilder.Metadata,
- ConfigurationSource.Convention));
+ propertyBuilder?.HasElementType();
}
}
}
diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
index 4dbf58eb5ee..88dae8d8cc7 100644
--- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
+++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
@@ -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())
@@ -363,8 +372,7 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
property.GetKeyValueComparer(),
property.GetProviderValueComparer(),
property.GetJsonValueReaderWriter(),
- property.GetTypeMapping(),
- property.GetElementType())
+ property.GetTypeMapping())
: ((RuntimeComplexType)runtimeType).AddProperty(
property.Name,
property.ClrType,
@@ -388,8 +396,21 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
property.GetKeyValueComparer(),
property.GetProviderValueComparer(),
property.GetJsonValueReaderWriter(),
- property.GetTypeMapping(),
- property.GetElementType());
+ property.GetTypeMapping());
+
+ private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element)
+ => runtimeProperty.AddElementType(
+ 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.
@@ -416,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,
@@ -474,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())
@@ -512,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/IElementType.cs b/src/EFCore/Metadata/IElementType.cs
index 117e602d9c2..e4e6637257d 100644
--- a/src/EFCore/Metadata/IElementType.cs
+++ b/src/EFCore/Metadata/IElementType.cs
@@ -19,10 +19,4 @@ public interface IElementType : IReadOnlyElementType, IAnnotatable
[DebuggerStepThrough]
get => (IProperty)((IReadOnlyElementType)this).CollectionProperty;
}
-
- ///
- /// Gets the for elements of the collection.
- ///
- /// The comparer.
- new ValueComparer GetValueComparer();
}
diff --git a/src/EFCore/Metadata/Internal/ElementType.cs b/src/EFCore/Metadata/Internal/ElementType.cs
index 0e25d107d57..8d397fa885d 100644
--- a/src/EFCore/Metadata/Internal/ElementType.cs
+++ b/src/EFCore/Metadata/Internal/ElementType.cs
@@ -2,6 +2,7 @@
// 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;
@@ -131,6 +132,15 @@ public virtual void SetRemovedFromModel()
[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
@@ -556,7 +566,8 @@ public virtual CoreTypeMapping? TypeMapping
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual ValueComparer? GetValueComparer()
- => (ValueComparer?)this[CoreAnnotationNames.ValueComparer];
+ => ((ValueComparer?)this[CoreAnnotationNames.ValueComparer]
+ ?? TypeMapping?.Comparer)?.ToNullableComparer(ClrType);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -591,7 +602,8 @@ public virtual CoreTypeMapping? TypeMapping
///
public virtual JsonValueReaderWriter? GetJsonValueReaderWriter()
{
- return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]);
+ return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType])
+ ?? TypeMapping?.JsonValueReaderWriter;
static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType)
{
@@ -960,16 +972,6 @@ void IMutableElementType.SetValueComparer(
comparerType,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- [DebuggerStepThrough]
- ValueComparer IElementType.GetValueComparer()
- => GetValueComparer()!;
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs
index 6ee2c4d7829..a05183517b2 100644
--- a/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalElementTypeBuilder.cs
@@ -19,8 +19,8 @@ public class InternalElementTypeBuilder : AnnotatableBuilder
- public InternalElementTypeBuilder(ElementType property, InternalModelBuilder modelBuilder)
- : base(property, modelBuilder)
+ public InternalElementTypeBuilder(ElementType element, InternalModelBuilder modelBuilder)
+ : base(element, modelBuilder)
{
}
@@ -39,7 +39,7 @@ protected virtual IConventionElementTypeBuilder This
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsAreRequired(bool? required, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? IsRequired(bool? required, ConfigurationSource configurationSource)
{
if (configurationSource != ConfigurationSource.Explicit
&& !CanSetIsRequired(required, configurationSource))
@@ -71,7 +71,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveMaxLength(int? maxLength, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? HasMaxLength(int? maxLength, ConfigurationSource configurationSource)
{
if (CanSetMaxLength(maxLength, configurationSource))
{
@@ -99,7 +99,7 @@ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configu
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHavePrecision(int? precision, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? HasPrecision(int? precision, ConfigurationSource configurationSource)
{
if (CanSetPrecision(precision, configurationSource))
{
@@ -127,7 +127,7 @@ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configu
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveScale(int? scale, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? HasScale(int? scale, ConfigurationSource configurationSource)
{
if (CanSetScale(scale, configurationSource))
{
@@ -155,7 +155,7 @@ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSo
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsAreUnicode(bool? unicode, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? IsUnicode(bool? unicode, ConfigurationSource configurationSource)
{
if (CanSetIsUnicode(unicode, configurationSource))
{
@@ -183,7 +183,7 @@ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configur
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveConversion(ValueConverter? converter, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? HasConversion(ValueConverter? converter, ConfigurationSource configurationSource)
{
if (CanSetConversion(converter, configurationSource))
{
@@ -218,7 +218,7 @@ public virtual bool CanSetConversion(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveConversion(Type? providerClrType, ConfigurationSource configurationSource)
+ public virtual InternalElementTypeBuilder? HasConversion(Type? providerClrType, ConfigurationSource configurationSource)
{
if (CanSetConversion(providerClrType, configurationSource))
{
@@ -248,7 +248,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource?
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveConverter(
+ public virtual InternalElementTypeBuilder? HasConverter(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? converterType,
ConfigurationSource configurationSource)
@@ -284,7 +284,7 @@ public virtual bool CanSetConverter(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveTypeMapping(
+ public virtual InternalElementTypeBuilder? HasTypeMapping(
CoreTypeMapping? typeMapping,
ConfigurationSource configurationSource)
{
@@ -314,7 +314,7 @@ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, Configuratio
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveValueComparer(
+ public virtual InternalElementTypeBuilder? HasValueComparer(
ValueComparer? comparer,
ConfigurationSource configurationSource)
{
@@ -362,7 +362,7 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual InternalElementTypeBuilder? ElementsHaveValueComparer(
+ public virtual InternalElementTypeBuilder? HasValueComparer(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? comparerType,
ConfigurationSource configurationSource)
@@ -448,8 +448,8 @@ IConventionElementType IConventionElementTypeBuilder.Metadata
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsAreRequired(bool? required, bool fromDataAnnotation)
- => ElementsAreRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -466,8 +466,8 @@ bool IConventionElementTypeBuilder.CanSetIsRequired(bool? required, bool fromDat
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveMaxLength(int? maxLength, bool fromDataAnnotation)
- => ElementsHaveMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -484,8 +484,8 @@ bool IConventionElementTypeBuilder.CanSetMaxLength(int? maxLength, bool fromData
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsAreUnicode(bool? unicode, bool fromDataAnnotation)
- => ElementsAreUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -502,8 +502,8 @@ bool IConventionElementTypeBuilder.CanSetIsUnicode(bool? unicode, bool fromDataA
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHavePrecision(int? precision, bool fromDataAnnotation)
- => ElementsHavePrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -520,8 +520,8 @@ bool IConventionElementTypeBuilder.CanSetPrecision(int? precision, bool fromData
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveScale(int? scale, bool fromDataAnnotation)
- => ElementsHaveScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -538,8 +538,8 @@ bool IConventionElementTypeBuilder.CanSetScale(int? scale, bool fromDataAnnotati
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConversion(ValueConverter? converter, bool fromDataAnnotation)
- => ElementsHaveConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -556,11 +556,11 @@ bool IConventionElementTypeBuilder.CanSetConversion(ValueConverter? converter, b
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConverter(
+ IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasConverter(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? converterType,
bool fromDataAnnotation)
- => ElementsHaveConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -580,8 +580,8 @@ bool IConventionElementTypeBuilder.CanSetConverter(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveConversion(Type? providerClrType, bool fromDataAnnotation)
- => ElementsHaveConversion(
+ IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasConversion(Type? providerClrType, bool fromDataAnnotation)
+ => HasConversion(
providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
@@ -594,10 +594,10 @@ bool IConventionElementTypeBuilder.CanSetConversion(Type? providerClrType, bool
=> CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveTypeMapping(
+ IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasTypeMapping(
CoreTypeMapping? typeMapping,
bool fromDataAnnotation)
- => ElementsHaveTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ => HasTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
bool IConventionElementTypeBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation)
@@ -609,8 +609,8 @@ bool IConventionElementTypeBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveValueComparer(ValueComparer? comparer, bool fromDataAnnotation)
- => ElementsHaveValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ 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
@@ -627,11 +627,11 @@ bool IConventionElementTypeBuilder.CanSetValueComparer(ValueComparer? comparer,
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionElementTypeBuilder? IConventionElementTypeBuilder.ElementsHaveValueComparer(
+ IConventionElementTypeBuilder? IConventionElementTypeBuilder.HasValueComparer(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type? comparerType,
bool fromDataAnnotation)
- => ElementsHaveValueComparer(
+ => HasValueComparer(
comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
index 3e00037bb67..e7c35712ca5 100644
--- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
@@ -4317,27 +4317,11 @@ public virtual bool CanSetServiceOnlyConstructorBinding(
discriminatorProperty = null;
}
- var builder = Metadata.GetRootType().Builder.Property(
+ return Metadata.GetRootType().Builder.Property(
type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType,
name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName,
typeConfigurationSource: type != null ? configurationSource : null,
- configurationSource);
-
- CopyElementType(builder, discriminatorProperty, configurationSource);
-
- return builder?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention);
- }
-
- private static void CopyElementType(
- InternalPropertyBuilder? builder,
- IReadOnlyProperty? discriminatorProperty,
- ConfigurationSource configurationSource)
- {
- var elementType = discriminatorProperty?.GetElementType();
- if (elementType != null)
- {
- builder?.HasElementType(elementType, configurationSource);
- }
+ configurationSource)?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention);
}
private DiscriminatorBuilder? DiscriminatorBuilder(
@@ -4355,8 +4339,6 @@ private static void CopyElementType(
discriminatorPropertyBuilder = rootTypeBuilder.Property(
discriminatorProperty.ClrType, discriminatorProperty.Name, null, ConfigurationSource.Convention)!;
- CopyElementType(discriminatorPropertyBuilder, discriminatorProperty, configurationSource);
-
RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource);
rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, configurationSource);
diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
index f20df1f7b3c..f5645a8df71 100644
--- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
@@ -935,6 +935,13 @@ public virtual bool CanSetElementType(IElementType? elementType, ConfigurationSo
newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value);
}
+ var oldElementTypeConfigurationSource = Metadata.GetElementTypeConfigurationSource();
+ if (oldElementTypeConfigurationSource.HasValue
+ && newPropertyBuilder.CanSetElementType(Metadata.GetElementType(), oldElementTypeConfigurationSource))
+ {
+ newPropertyBuilder.HasElementType(Metadata.GetElementType(), oldElementTypeConfigurationSource.Value);
+ }
+
return newPropertyBuilder;
}
@@ -1479,8 +1486,34 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IConventionPropertyBuilder? IConventionPropertyBuilder.HasElementType(IElementType? elementType, bool fromDataAnnotation)
- => HasElementType(elementType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ IConventionElementTypeBuilder? IConventionPropertyBuilder.HasElementType(bool fromDataAnnotation)
+ {
+ var propertyBuilder = HasElementType(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.
+ ///
+ IConventionPropertyBuilder? IConventionPropertyBuilder.HasElementType(
+ Action builderAction, bool fromDataAnnotation)
+ {
+ var elementBuilder = ((IConventionPropertyBuilder)this).HasElementType(fromDataAnnotation);
+
+ if (elementBuilder != null)
+ {
+ builderAction.Invoke(elementBuilder);
+ return this;
+ }
+
+ return null;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1488,6 +1521,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual bool CanSetElementType(IElementType? elementType, bool fromDataAnnotation = false)
- => CanSetElementType(elementType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ public virtual bool CanSetElementType(bool fromDataAnnotation = false)
+ => (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
+ .Overrides(Metadata.GetElementTypeConfigurationSource());
}
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index 7134dc97e92..6dc7ac592f0 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
diff --git a/src/EFCore/Metadata/RuntimeElementType.cs b/src/EFCore/Metadata/RuntimeElementType.cs
index 39ed68634b2..80ba1155301 100644
--- a/src/EFCore/Metadata/RuntimeElementType.cs
+++ b/src/EFCore/Metadata/RuntimeElementType.cs
@@ -16,7 +16,7 @@ public class RuntimeElementType : AnnotatableBase, IElementType
{
private readonly bool _isNullable;
private readonly ValueConverter? _valueConverter;
- private readonly ValueComparer _valueComparer;
+ private readonly ValueComparer? _valueComparer;
private readonly JsonValueReaderWriter? _jsonValueReaderWriter;
private readonly CoreTypeMapping? _typeMapping;
@@ -37,7 +37,7 @@ public RuntimeElementType(
int? scale,
Type? providerClrType,
ValueConverter? valueConverter,
- ValueComparer valueComparer,
+ ValueComparer? valueComparer,
JsonValueReaderWriter? jsonValueReaderWriter,
CoreTypeMapping? typeMapping)
{
@@ -148,7 +148,7 @@ public virtual bool IsNullable
///
/// The comparer, or if none has been set.
[DebuggerStepThrough]
- public virtual ValueComparer GetValueComparer()
+ public virtual ValueComparer? GetValueComparer()
=> _valueComparer;
///
diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs
index ba7a63c9afd..7593981ab60 100644
--- a/src/EFCore/Metadata/RuntimeProperty.cs
+++ b/src/EFCore/Metadata/RuntimeProperty.cs
@@ -63,8 +63,7 @@ public RuntimeProperty(
ValueComparer? keyValueComparer,
ValueComparer? providerValueComparer,
JsonValueReaderWriter? jsonValueReaderWriter,
- CoreTypeMapping? typeMapping,
- IElementType? element)
+ CoreTypeMapping? typeMapping)
: base(name, propertyInfo, fieldInfo, propertyAccessMode)
{
DeclaringType = declaringType;
@@ -108,24 +107,55 @@ public RuntimeProperty(
_keyValueComparer = keyValueComparer ?? valueComparer;
_providerValueComparer = providerValueComparer;
_jsonValueReaderWriter = jsonValueReaderWriter;
+ }
- if (element != null)
- {
- SetAnnotation(
- CoreAnnotationNames.ElementType, new RuntimeElementType(
- element.ClrType,
- this,
- element.IsNullable,
- element.GetMaxLength(),
- element.IsUnicode(),
- element.GetPrecision(),
- element.GetScale(),
- element.GetProviderClrType(),
- element.GetValueConverter(),
- element.GetValueComparer(),
- element.GetJsonValueReaderWriter(),
- element.FindTypeMapping()));
- }
+ ///
+ /// 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 AddElementType(
+ 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;
}
///
@@ -199,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)
{
diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs
index 49252391d30..e8f1c3e4ab1 100644
--- a/src/EFCore/Metadata/RuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/RuntimeTypeBase.cs
@@ -175,10 +175,6 @@ protected virtual IEnumerable GetDerivedTypes()
/// The to use for the provider values for this property.
/// The for this property.
/// The for this property.
- ///
- /// The for this property, or if the property is not a
- /// collection.
- ///
/// The newly created property.
public virtual RuntimeProperty AddProperty(
string name,
@@ -203,8 +199,7 @@ public virtual RuntimeProperty AddProperty(
ValueComparer? keyValueComparer = null,
ValueComparer? providerValueComparer = null,
JsonValueReaderWriter? jsonValueReaderWriter = null,
- CoreTypeMapping? typeMapping = null,
- IElementType? elementType = null)
+ CoreTypeMapping? typeMapping = null)
{
var property = new RuntimeProperty(
name,
@@ -230,8 +225,7 @@ public virtual RuntimeProperty AddProperty(
keyValueComparer,
providerValueComparer,
jsonValueReaderWriter,
- typeMapping,
- elementType);
+ typeMapping);
_properties.Add(property.Name, property);
diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs
index e5cc2c7186c..50da68c13d1 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;
@@ -101,7 +102,10 @@ public TypeMappingInfo(
ClrType = (customConverter?.ProviderClrType ?? elementType.ClrType).UnwrapNullableType();
Scale = fallbackScale ?? mappingHints?.Scale;
Precision = fallbackPrecision ?? mappingHints?.Precision;
- JsonValueReaderWriter = elementType.GetJsonValueReaderWriter();
+
+ JsonValueReaderWriter = elementType[CoreAnnotationNames.JsonValueReaderWriterType] != null
+ ? elementType.GetJsonValueReaderWriter()
+ : null;
}
///
diff --git a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs
index 01f638b03d7..4891eb90128 100644
--- a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs
@@ -111,150 +111,9 @@ public override void Can_read_write_polygon_typed_as_nullable_geometry_as_GeoJso
// 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 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());
-
- 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_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_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_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_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_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_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_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_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_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_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());
-
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- => base.OnConfiguring(optionsBuilder.UseCosmos("localhost", "_", "_"));
+ {
+ 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..a96d25e4e9c 100644
--- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
+++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
@@ -247,6 +247,238 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(CosmosTestHelpers.Instance, configure);
}
+ public class CosmosGenericPrimitiveCollections : GenericPrimitiveCollections
+ {
+ // public override void Properties_can_be_made_concurrency_tokens()
+ // => Assert.Equal(
+ // CosmosStrings.NonETagConcurrencyToken(nameof(Quarks), "Charm"),
+ // Assert.Throws(
+ // () => base.Properties_can_be_made_concurrency_tokens()).Message);
+ //
+ // public override void Properties_can_have_provider_type_set_for_type()
+ // {
+ // var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion());
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.Property(e => e.Up);
+ // b.Property(e => e.Down);
+ // b.Property("Charm");
+ // b.Property("Strange");
+ // b.Property("__id").HasConversion(null);
+ // });
+ //
+ // var model = modelBuilder.FinalizeModel();
+ // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks));
+ //
+ // Assert.Null(entityType.FindProperty("Up").GetProviderClrType());
+ // Assert.Same(typeof(byte[]), entityType.FindProperty("Down").GetProviderClrType());
+ // Assert.Null(entityType.FindProperty("Charm").GetProviderClrType());
+ // Assert.Same(typeof(byte[]), entityType.FindProperty("Strange").GetProviderClrType());
+ // }
+ //
+ // public override void Properties_can_be_set_to_generate_values_on_Add()
+ // {
+ // var modelBuilder = CreateModelBuilder();
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.HasKey(e => e.Id);
+ // b.Property(e => e.Up).ValueGeneratedOnAddOrUpdate();
+ // b.Property(e => e.Down).ValueGeneratedNever();
+ // b.Property("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes;
+ // b.Property("Strange").ValueGeneratedNever();
+ // b.Property("Top").ValueGeneratedOnAddOrUpdate();
+ // b.Property("Bottom").ValueGeneratedOnUpdate();
+ // });
+ //
+ // var model = modelBuilder.FinalizeModel();
+ // var entityType = model.FindEntityType(typeof(Quarks));
+ // Assert.Equal(ValueGenerated.Never, entityType.FindProperty(Customer.IdProperty.Name).ValueGenerated);
+ // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated);
+ // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated);
+ // Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm").ValueGenerated);
+ // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange").ValueGenerated);
+ // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top").ValueGenerated);
+ // Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom").ValueGenerated);
+ // }
+
+ [ConditionalFact]
+ public virtual void Partition_key_is_added_to_the_keys()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders)
+ .HasPartitionKey(b => b.AlternateKey)
+ .Property(b => b.AlternateKey).HasConversion();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Equal(
+ new[] { nameof(Customer.Id), nameof(Customer.AlternateKey) },
+ entity.FindPrimaryKey().Properties.Select(p => p.Name));
+ Assert.Equal(
+ new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) },
+ entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name));
+
+ var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.NotNull(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void Partition_key_is_added_to_the_alternate_key_if_primary_key_contains_id()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName);
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders)
+ .HasPartitionKey(b => b.AlternateKey)
+ .Property(b => b.AlternateKey).HasConversion();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Equal(
+ new[] { StoreKeyConvention.DefaultIdPropertyName },
+ entity.FindPrimaryKey().Properties.Select(p => p.Name));
+ Assert.Equal(
+ new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) },
+ entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name));
+ }
+
+ [ConditionalFact]
+ public virtual void No_id_property_created_if_another_property_mapped_to_id()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .Property(c => c.Name)
+ .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName);
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
+ Assert.Single(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+
+ var idProperty = entity.GetDeclaredProperties()
+ .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.NotNull(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void No_id_property_created_if_another_property_mapped_to_id_in_pk()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity()
+ .Property(c => c.Name)
+ .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName);
+ modelBuilder.Entity()
+ .Ignore(c => c.Details)
+ .Ignore(c => c.Orders)
+ .HasKey(c => c.Name);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
+ Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+
+ var idProperty = entity.GetDeclaredProperties()
+ .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.Null(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void No_alternate_key_is_created_if_primary_key_contains_id()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName);
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders);
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Equal(
+ new[] { StoreKeyConvention.DefaultIdPropertyName },
+ entity.FindPrimaryKey().Properties.Select(p => p.Name));
+ Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+
+ var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName);
+ Assert.Single(idProperty.GetContainingKeys());
+ Assert.Null(idProperty.GetValueGeneratorFactory());
+ }
+
+ [ConditionalFact]
+ public virtual void No_alternate_key_is_created_if_primary_key_contains_id_and_partition_key()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName);
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders)
+ .HasPartitionKey(b => b.AlternateKey)
+ .Property(b => b.AlternateKey).HasConversion();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Equal(
+ new[] { nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName },
+ entity.FindPrimaryKey().Properties.Select(p => p.Name));
+ Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+ }
+
+ [ConditionalFact]
+ public virtual void No_alternate_key_is_created_if_id_is_partition_key()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey));
+ modelBuilder.Entity()
+ .Ignore(b => b.Details)
+ .Ignore(b => b.Orders)
+ .HasPartitionKey(b => b.AlternateKey)
+ .Property(b => b.AlternateKey).HasConversion().ToJsonProperty("id");
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Customer));
+
+ Assert.Equal(
+ new[] { nameof(Customer.AlternateKey) },
+ entity.FindPrimaryKey().Properties.Select(p => p.Name));
+ Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey()));
+ }
+
+ protected override TestModelBuilder CreateModelBuilder(Action configure = null)
+ => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure);
+ }
+
public class CosmosGenericComplexType : GenericComplexType
{
public override void Properties_can_have_provider_type_set_for_type()
diff --git a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs
index 291cb87beb0..9243696b8a8 100644
--- a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs
+++ b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs
@@ -15,6 +15,12 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure);
}
+ public class InMemoryGenericPrimitiveCollections : GenericPrimitiveCollections
+ {
+ protected override TestModelBuilder CreateModelBuilder(Action configure = null)
+ => CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure);
+ }
+
public class InMemoryGenericComplexTypeTestBase : GenericComplexType
{
protected override TestModelBuilder CreateModelBuilder(Action configure = null)
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.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
index a888a5810ae..183441de376 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
@@ -10,6 +10,335 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding;
public class RelationalModelBuilderTest : ModelBuilderTest
{
+ public abstract class RelationalPrimitiveCollectionsTestBase : PrimitiveCollectionsTestBase
+ {
+ [ConditionalFact]
+ public virtual void Can_use_table_splitting()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.HasDefaultSchema("dbo");
+
+ modelBuilder.Entity().SplitToTable(
+ "OrderDetails", s =>
+ {
+ s.ExcludeFromMigrations();
+ var propertyBuilder = s.Property(o => o.CustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("id");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.False(entity.IsTableExcludedFromMigrations());
+ Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo")));
+ Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "dbo")));
+ Assert.Same(
+ entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "dbo")));
+
+ var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
+ Assert.Equal("CustomerId", customerId.GetColumnName());
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo")));
+ Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "dbo")));
+ Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "dbo")));
+ }
+
+ [ConditionalFact]
+ public virtual void Can_use_table_splitting_with_schema()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity().ToTable("Order", "dbo")
+ .SplitToTable(
+ "OrderDetails", "sch", s =>
+ s.ExcludeFromMigrations()
+ .Property(o => o.CustomerId).HasColumnName("id"));
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.False(entity.IsTableExcludedFromMigrations());
+ Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo")));
+ Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch")));
+ Assert.Same(
+ entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch")));
+ Assert.Equal(
+ RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
+ Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order")))
+ .Message);
+
+ var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
+ Assert.Equal("CustomerId", customerId.GetColumnName());
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo")));
+ Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "sch")));
+ Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "sch")));
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order")));
+ }
+
+ [ConditionalFact]
+ public virtual void Can_use_view_splitting()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity().ToView("Order")
+ .SplitToView(
+ "OrderDetails", s =>
+ {
+ var propertyBuilder = s.Property(o => o.CustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("id");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails")));
+
+ var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
+ Assert.Equal("CustomerId", customerId.GetColumnName());
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order")));
+ Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails")));
+ Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails")));
+ }
+
+ [ConditionalFact]
+ public virtual void Can_use_view_splitting_with_schema()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity().ToView("Order", "dbo")
+ .SplitToView(
+ "OrderDetails", "sch", s =>
+ s.Property(o => o.CustomerId).HasColumnName("id"));
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.Same(
+ entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch")));
+ Assert.Equal(
+ RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
+ Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order")))
+ .Message);
+
+ var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
+ Assert.Equal("CustomerId", customerId.GetColumnName());
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order", "dbo")));
+ Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails", "sch")));
+ Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch")));
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order")));
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_return_and_parameter_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedParameter()
+ .HasRowsAffectedReturnValue()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_return_and_result_column_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedResultColumn()
+ .HasRowsAffectedReturnValue()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedReturnValue()
+ .HasRowsAffectedParameter()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedReturnValue()
+ .HasRowsAffectedResultColumn()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedParameter()
+ .HasRowsAffectedResultColumn()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Duplicate_sproc_rows_affected_result_column_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var sproc = modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedResultColumn()).Metadata.GetUpdateStoredProcedure()!;
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"),
+ Assert.Throws(() => sproc.AddRowsAffectedResultColumn())
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Conflicting_sproc_rows_affected_parameter_and_result_column_throw()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedResultColumn()
+ .HasRowsAffectedParameter()))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Duplicate_sproc_rows_affected_parameter_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var sproc = modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(
+ s => s.HasRowsAffectedParameter()).Metadata.GetUpdateStoredProcedure()!;
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"),
+ Assert.Throws(() => sproc.AddRowsAffectedParameter())
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Duplicate_sproc_parameter_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var sproc = modelBuilder.Entity()
+ .InsertUsingStoredProcedure(
+ s => s.HasParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!;
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateParameter("Id", "BookLabel_Insert"),
+ Assert.Throws(() => sproc.AddParameter("Id"))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Duplicate_sproc_original_value_parameter_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var sproc = modelBuilder.Entity()
+ .InsertUsingStoredProcedure(
+ s => s.HasOriginalValueParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!;
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateOriginalValueParameter("Id", "BookLabel_Insert"),
+ Assert.Throws(() => sproc.AddOriginalValueParameter("Id"))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Duplicate_sproc_result_column_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var sproc = modelBuilder.Entity()
+ .InsertUsingStoredProcedure(
+ s => s.HasResultColumn(b => b.Id)).Metadata.GetInsertStoredProcedure()!;
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureDuplicateResultColumn("Id", "BookLabel_Insert"),
+ Assert.Throws(() => sproc.AddResultColumn("Id"))
+ .Message);
+ }
+
+ [ConditionalFact]
+ public virtual void Configuring_direction_on_RowsAffectedParameter_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ var param = modelBuilder.Entity()
+ .InsertUsingStoredProcedure(
+ s => s.HasRowsAffectedParameter()).Metadata.GetInsertStoredProcedure()!.Parameters.Single();
+
+ Assert.Equal(
+ RelationalStrings.StoredProcedureParameterInvalidConfiguration("Direction", "RowsAffected", "BookLabel_Insert"),
+ Assert.Throws(() => param.Direction = ParameterDirection.Input)
+ .Message);
+ }
+ }
+
public abstract class RelationalNonRelationshipTestBase : NonRelationshipTestBase
{
[ConditionalFact]
diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs
index e0c6cbb387c..719a863d7ad 100644
--- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs
+++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs
@@ -616,6 +616,7 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method
var parameterIndex = method.IsStatic ? 1 : 0;
var parameters = method.GetParameters();
if (parameters.Length > parameterIndex
+ && !parameters[parameterIndex].ParameterType.FullName!.Contains("Builder")
&& parameters[parameterIndex].ParameterType != canSetMethod.GetParameters()[parameterIndex].ParameterType)
{
return $"{declaringType.Name}.{canSetMethod.Name}({Format(canSetMethod.GetParameters())})"
diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs
index 19e3daa8fda..1f371592339 100644
--- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs
+++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs
@@ -3,11 +3,13 @@
#nullable enable
+using System.Collections;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Net;
using System.Net.NetworkInformation;
using System.Text.Json;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.Json;
using NetTopologySuite;
using NetTopologySuite.Geometries;
@@ -3253,8 +3255,8 @@ public virtual void Can_read_write_collection_of_int_with_converter_JSON_values(
b => b.ElementType(
b =>
{
- b.ElementsHaveConversion();
- b.ElementsAreRequired();
+ b.HasConversion();
+ b.IsRequired();
}),
nameof(DddIdCollectionType.DddId),
new List
@@ -3264,7 +3266,7 @@ public virtual void Can_read_write_collection_of_int_with_converter_JSON_values(
new() { Id = int.MaxValue }
},
"""{"Prop":[-2147483648,0,2147483647]}""",
- mappedCollection: true);
+ facets: new Dictionary { { CoreAnnotationNames.ValueConverter, typeof(DddIdConverter) } });
protected class DddIdCollectionType
{
@@ -3274,7 +3276,7 @@ protected class DddIdCollectionType
[ConditionalFact]
public virtual void Can_read_write_collection_of_nullable_int_with_converter_JSON_values()
=> Can_read_and_write_JSON_collection_value>(
- b => b.ElementType().ElementsHaveConversion(),
+ b => b.ElementType().HasConversion(),
nameof(NullableDddIdCollectionType.DddId),
new List
{
@@ -3285,7 +3287,7 @@ public virtual void Can_read_write_collection_of_nullable_int_with_converter_JSO
new() { Id = int.MaxValue }
},
"""{"Prop":[null,-2147483648,null,0,2147483647]}""",
- mappedCollection: true);
+ facets: new Dictionary { { CoreAnnotationNames.ValueConverter, typeof(DddIdConverter) } });
protected class NullableDddIdCollectionType
{
@@ -3298,20 +3300,48 @@ public virtual void Can_read_write_binary_as_collection()
_ => { },
nameof(BinaryAsJsonType.BinaryAsJson),
new byte[] { 77, 78, 79, 80 },
- """{"Prop":[77,78,79,80]}""",
- mappedCollection: true);
+ """{"Prop":[77,78,79,80]}""");
protected class BinaryAsJsonType
{
public byte[] BinaryAsJson { get; set; } = null!;
}
+ [ConditionalFact]
+ public virtual void Can_read_write_collection_of_decimal_with_precision_and_scale_JSON_values()
+ => Can_read_and_write_JSON_collection_value>(
+ b => b.ElementType().HasPrecision(12, 6),
+ nameof(DecimalCollectionType.Decimal),
+ new List
+ {
+ decimal.MinValue,
+ 0,
+ decimal.MaxValue
+ },
+ """{"Prop":[-79228162514264337593543950335,0,79228162514264337593543950335]}""",
+ facets: new Dictionary { { CoreAnnotationNames.Precision, 12 }, { CoreAnnotationNames.Scale, 6 } });
+
+ [ConditionalFact]
+ public virtual void Can_read_write_collection_of_Guid_converted_to_bytes_JSON_values()
+ => Can_read_and_write_JSON_collection_value>(
+ b => b.ElementType().HasConversion(),
+ nameof(GuidCollectionType.Guid),
+ new List
+ {
+ new(),
+ new("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD"),
+ Guid.Parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
+ },
+ """{"Prop":["AAAAAAAAAAAAAAAAAAAAAA==","LyREjD+OIEqL6JjHwarevQ==","/////////////////////w=="]}""",
+ facets: new Dictionary { { CoreAnnotationNames.ProviderClrType, typeof(byte[]) } });
+
protected virtual void Can_read_and_write_JSON_value(
string propertyName,
TModel value,
string json,
bool mappedCollection = false,
- object? existingObject = null)
+ object? existingObject = null,
+ Dictionary? facets = null)
where TEntity : class
{
if (mappedCollection)
@@ -3323,7 +3353,8 @@ protected virtual void Can_read_and_write_JSON_value(
value,
json,
mappedCollection,
- existingObject);
+ existingObject,
+ facets);
}
else
{
@@ -3334,7 +3365,8 @@ protected virtual void Can_read_and_write_JSON_value(
value,
json,
mappedCollection,
- existingObject);
+ existingObject,
+ facets);
}
}
@@ -3343,8 +3375,8 @@ protected virtual void Can_read_and_write_JSON_property_value(
string propertyName,
TModel value,
string json,
- bool mappedCollection = false,
- object? existingObject = null)
+ object? existingObject = null,
+ Dictionary? facets = null)
where TEntity : class
=> Can_read_and_write_JSON_value(
b => buildProperty(b.Entity().HasNoKey().Property(propertyName)),
@@ -3352,16 +3384,17 @@ protected virtual void Can_read_and_write_JSON_property_value(
propertyName,
value,
json,
- mappedCollection,
- existingObject);
+ mappedCollection: false,
+ existingObject,
+ facets);
protected virtual void Can_read_and_write_JSON_collection_value(
Action buildCollection,
string propertyName,
TModel value,
string json,
- bool mappedCollection = false,
- object? existingObject = null)
+ object? existingObject = null,
+ Dictionary? facets = null)
where TEntity : class
=> Can_read_and_write_JSON_value(
b => buildCollection(b.Entity().HasNoKey().PrimitiveCollection(propertyName)),
@@ -3369,8 +3402,9 @@ protected virtual void Can_read_and_write_JSON_collection_value
propertyName,
value,
json,
- mappedCollection,
- existingObject);
+ mappedCollection: true,
+ existingObject,
+ facets);
protected virtual void Can_read_and_write_JSON_value(
Action buildModel,
@@ -3379,7 +3413,8 @@ protected virtual void Can_read_and_write_JSON_value(
TModel value,
string json,
bool mappedCollection = false,
- object? existingObject = null)
+ object? existingObject = null,
+ Dictionary? facets = null)
where TEntity : class
{
using var context = new SingleTypeDbContext(OnConfiguring, buildModel, configureConventions);
@@ -3391,6 +3426,63 @@ protected virtual void Can_read_and_write_JSON_value(
var jsonReaderWriter = property.GetJsonValueReaderWriter()
?? property.GetTypeMapping().JsonValueReaderWriter!;
+ var actual = ToJsonPropertyString(jsonReaderWriter, value);
+ Assert.Equal(json, actual);
+
+ var fromJson = FromJsonPropertyString(jsonReaderWriter, actual, existingObject);
+ if (existingObject != null)
+ {
+ Assert.Same(fromJson, existingObject);
+ }
+ Assert.Equal(value, fromJson);
+
+ var element = property.GetElementType();
+ if (mappedCollection)
+ {
+ Assert.NotNull(element);
+
+ Assert.Equal(typeof(TModel).GetSequenceType(), element.ClrType);
+ Assert.Same(property, element.CollectionProperty);
+ Assert.Equal(json.Contains("null", StringComparison.Ordinal) || !element.ClrType.IsValueType, element.IsNullable);
+ Assert.Null(element.FindTypeMapping()!.ElementTypeMapping);
+
+ var comparer = element.GetValueComparer()!;
+ var elementReaderWriter = element.GetJsonValueReaderWriter()!;
+ foreach (var item in (IEnumerable)value!)
+ {
+ Assert.True(comparer.Equals(item, comparer.Snapshot(item)));
+ Assert.True(
+ comparer.Equals(
+ item, FromJsonPropertyString(
+ elementReaderWriter, ToJsonPropertyString(elementReaderWriter, item))));
+ }
+
+ AssertElementFacets(element, facets);
+ }
+ else
+ {
+ Assert.Null(element);
+ }
+ }
+
+ protected virtual void AssertElementFacets(IElementType element, Dictionary? facets)
+ {
+ Assert.Equal(FacetValue(CoreAnnotationNames.Precision), element.GetPrecision());
+ Assert.Equal(FacetValue(CoreAnnotationNames.Scale), element.GetScale());
+ Assert.Equal(FacetValue(CoreAnnotationNames.MaxLength), element.GetMaxLength());
+ Assert.Equal(FacetValue(CoreAnnotationNames.ProviderClrType), element.GetProviderClrType());
+ Assert.Equal(FacetValue(CoreAnnotationNames.Unicode), element.IsUnicode());
+ Assert.Equal(FacetValue(CoreAnnotationNames.ValueConverter), element.GetValueConverter()?.GetType());
+
+ object? FacetValue(string facetName)
+ => facets?.TryGetValue(facetName, out var facet) == true ? facet : null;
+ }
+
+ protected string ToJsonPropertyString(JsonValueReaderWriter jsonReaderWriter, object? value)
+ {
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream);
+
writer.WriteStartObject();
writer.WritePropertyName("Prop");
if (value == null)
@@ -3407,39 +3499,20 @@ protected virtual void Can_read_and_write_JSON_value(
var buffer = stream.ToArray();
- var actual = Encoding.UTF8.GetString(buffer);
-
- Assert.Equal(json, actual);
+ return Encoding.UTF8.GetString(buffer);
+ }
+ protected object? FromJsonPropertyString(JsonValueReaderWriter jsonReaderWriter, string value, object? existingValue = null)
+ {
+ var buffer = Encoding.UTF8.GetBytes(value);
var readerManager = new Utf8JsonReaderManager(new JsonReaderData(buffer));
readerManager.MoveNext();
readerManager.MoveNext();
readerManager.MoveNext();
- if (readerManager.CurrentReader.TokenType == JsonTokenType.Null)
- {
- Assert.Null(value);
- }
- else
- {
- var fromJson = jsonReaderWriter.FromJson(ref readerManager, existingObject);
- if (existingObject != null)
- {
- Assert.Same(fromJson, existingObject);
- }
-
- Assert.Equal(value, fromJson);
- }
-
- var element = property.GetElementType();
- if (mappedCollection)
- {
- Assert.NotNull(element);
- }
- else
- {
- Assert.Null(element);
- }
+ return readerManager.CurrentReader.TokenType == JsonTokenType.Null
+ ? null
+ : jsonReaderWriter.FromJson(ref readerManager, existingValue);
}
protected readonly struct DddId
diff --git a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs
index 674b5c5eea5..6966ab238d1 100644
--- a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs
@@ -5,7 +5,7 @@
namespace Microsoft.EntityFrameworkCore;
-public class JsonTypesSqlServerTest : JsonTypesTestBase
+public class JsonTypesSqlServerTest : JsonTypesRelationalTestBase
{
public override void Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json)
{
@@ -54,6 +54,12 @@ public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_value
"""{"Prop":[0,null,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server
mappedCollection: true);
+ public override void Can_read_write_collection_of_fixed_length_string_JSON_values(object? storeType)
+ => base.Can_read_write_collection_of_fixed_length_string_JSON_values("nchar(32)");
+
+ public override void Can_read_write_collection_of_ASCII_string_JSON_values(object? storeType)
+ => base.Can_read_write_collection_of_ASCII_string_JSON_values("varchar(max)");
+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> base.OnConfiguring(optionsBuilder.UseSqlServer(b => b.UseNetTopologySuite()));
}
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
index f801e094818..962f98d824c 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
@@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder(
=> new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure);
}
+ public class SqlServerGenericPrimitiveCollections : SqlServerPrimitiveCollections
+ {
+ protected override TestModelBuilder CreateTestModelBuilder(
+ TestHelpers testHelpers,
+ Action? configure)
+ => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure);
+ }
+
public class SqlServerGenericComplexType: SqlServerComplexType
{
protected override TestModelBuilder CreateTestModelBuilder(
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs
index 37d8f51cea6..830238132cf 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs
@@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder(
=> new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure);
}
+ public class SqlServerNonGenericPrimitiveCollections : SqlServerPrimitiveCollections
+ {
+ protected override TestModelBuilder CreateTestModelBuilder(
+ TestHelpers testHelpers,
+ Action? configure)
+ => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure);
+ }
+
public class SqlServerNonGenericComplexType : SqlServerComplexType
{
protected override TestModelBuilder CreateTestModelBuilder(
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
index 4a5b9142fa1..e47214cc889 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
@@ -198,6 +198,197 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure);
}
+ public abstract class SqlServerPrimitiveCollections : RelationalPrimitiveCollectionsTestBase
+ {
+ [ConditionalFact]
+ public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties()
+ {
+ var modelBuilder = CreateModelBuilder();
+ var entityTypeBuilder = modelBuilder
+ .Entity();
+ var indexBuilder = entityTypeBuilder
+ .HasIndex(ix => ix.Name)
+ .IsUnique();
+
+ var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!;
+ var index = entityType.GetIndexes().Single();
+ Assert.Equal("[Name] IS NOT NULL", index.GetFilter());
+
+ indexBuilder.IsUnique(false);
+
+ Assert.Null(index.GetFilter());
+
+ indexBuilder.IsUnique();
+
+ Assert.Equal("[Name] IS NOT NULL", index.GetFilter());
+
+ indexBuilder.IsClustered();
+
+ Assert.Null(index.GetFilter());
+
+ indexBuilder.IsClustered(false);
+
+ Assert.Equal("[Name] IS NOT NULL", index.GetFilter());
+
+ entityTypeBuilder.Property(e => e.Name).IsRequired();
+
+ Assert.Null(index.GetFilter());
+
+ entityTypeBuilder.Property(e => e.Name).IsRequired(false);
+
+ Assert.Equal("[Name] IS NOT NULL", index.GetFilter());
+
+ entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName");
+
+ Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter());
+
+ entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName");
+
+ Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter());
+
+ entityTypeBuilder.Property(e => e.Name).HasColumnName(null);
+
+ Assert.Equal("[Name] IS NOT NULL", index.GetFilter());
+
+ indexBuilder.HasFilter("Foo");
+
+ Assert.Equal("Foo", index.GetFilter());
+
+ indexBuilder.HasFilter(null);
+
+ Assert.Null(index.GetFilter());
+ }
+
+ [ConditionalFact]
+ public void Indexes_can_have_same_name_across_tables()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity()
+ .HasIndex(e => e.Id, "Ix_Id")
+ .IsUnique();
+ modelBuilder.Entity()
+ .HasIndex(e => e.CustomerId, "Ix_Id")
+ .IsUnique();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single();
+ Assert.Equal("Ix_Id", customerIndex.Name);
+ Assert.Equal("Ix_Id", customerIndex.GetDatabaseName());
+ Assert.Equal(
+ "Ix_Id", customerIndex.GetDatabaseName(
+ StoreObjectIdentifier.Table("Customer")));
+
+ var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single();
+ Assert.Equal("Ix_Id", detailsIndex.Name);
+ Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName());
+ Assert.Equal(
+ "Ix_Id", detailsIndex.GetDatabaseName(
+ StoreObjectIdentifier.Table("CustomerDetails")));
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_store_type_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(
+ c =>
+ {
+ c.Properties().HaveColumnType("smallint");
+ c.Properties().HaveColumnType("nchar(max)");
+ c.Properties(typeof(Nullable<>)).HavePrecision(2);
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks))!;
+
+ Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType());
+ Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType());
+ Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType());
+ var charm = entityType.FindProperty("Charm")!;
+ Assert.Equal("smallint", charm.GetColumnType());
+ Assert.Null(charm.GetPrecision());
+ Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType());
+ var top = entityType.FindProperty("Top")!;
+ Assert.Equal("smallint", top.GetColumnType());
+ Assert.Equal(2, top.GetPrecision());
+ Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType());
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_fixed_length_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(
+ c =>
+ {
+ c.Properties().AreFixedLength(false);
+ c.Properties().AreFixedLength();
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks))!;
+
+ Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength());
+ Assert.False(entityType.FindProperty("Up")!.IsFixedLength());
+ Assert.True(entityType.FindProperty("Down")!.IsFixedLength());
+ Assert.False(entityType.FindProperty("Charm")!.IsFixedLength());
+ Assert.True(entityType.FindProperty("Strange")!.IsFixedLength());
+ Assert.False(entityType.FindProperty("Top")!.IsFixedLength());
+ Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength());
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_collation_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(
+ c =>
+ {
+ c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS");
+ c.Properties().UseCollation("Latin1_General_BIN");
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks))!;
+
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation());
+ }
+
+ protected override TestModelBuilder CreateModelBuilder(Action? configure = null)
+ => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure);
+ }
+
public abstract class SqlServerComplexType : RelationalComplexTypeTestBase
{
protected override TestModelBuilder CreateModelBuilder(Action? configure = null)
@@ -1257,7 +1448,7 @@ public virtual void Temporal_table_with_history_table_configuration()
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(5, entity.GetProperties().Count());
+ Assert.Equal(6, entity.GetProperties().Count());
Assert.Equal("HistoryTable", entity.GetHistoryTableName());
Assert.Equal("historySchema", entity.GetHistoryTableSchema());
@@ -1306,7 +1497,7 @@ public virtual void Temporal_table_with_changed_configuration()
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(5, entity.GetProperties().Count());
+ Assert.Equal(6, entity.GetProperties().Count());
Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName());
Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema());
@@ -1355,7 +1546,7 @@ public virtual void Temporal_table_with_period_column_names_changed_configuratio
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(5, entity.GetProperties().Count());
+ Assert.Equal(6, entity.GetProperties().Count());
Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName());
Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema());
@@ -1405,7 +1596,7 @@ public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(5, entity.GetProperties().Count());
+ Assert.Equal(6, entity.GetProperties().Count());
Assert.Equal("HistoryTable", entity.GetHistoryTableName());
@@ -1453,7 +1644,7 @@ public virtual void
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(7, entity.GetProperties().Count());
+ Assert.Equal(8, entity.GetProperties().Count());
Assert.Equal("HistoryTable", entity.GetHistoryTableName());
@@ -1496,7 +1687,7 @@ public virtual void Switching_from_temporal_to_non_temporal_default_settings()
Assert.False(entity.IsTemporal());
Assert.Null(entity.GetPeriodStartPropertyName());
Assert.Null(entity.GetPeriodEndPropertyName());
- Assert.Equal(3, entity.GetProperties().Count());
+ Assert.Equal(4, entity.GetProperties().Count());
}
[ConditionalFact]
diff --git a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs
index 5a9ec63186a..edcc2558762 100644
--- a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs
@@ -6,7 +6,7 @@
namespace Microsoft.EntityFrameworkCore;
[SpatialiteRequired]
-public class JsonTypesSqliteTest : JsonTypesTestBase
+public class JsonTypesSqliteTest : JsonTypesRelationalTestBase
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> base.OnConfiguring(optionsBuilder.UseSqlite(b => b.UseNetTopologySuite()));
diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs
index 2c564c70a19..a21673f32b1 100644
--- a/test/EFCore.Tests/ApiConsistencyTest.cs
+++ b/test/EFCore.Tests/ApiConsistencyTest.cs
@@ -44,8 +44,11 @@ protected override void Initialize()
typeof(DiscriminatorBuilder<>),
typeof(EntityTypeBuilder),
typeof(EntityTypeBuilder<>),
+ typeof(ElementTypeBuilder),
typeof(ComplexPropertyBuilder),
typeof(ComplexPropertyBuilder<>),
+ typeof(ComplexTypePrimitiveCollectionBuilder),
+ typeof(ComplexTypePrimitiveCollectionBuilder<>),
typeof(IndexBuilder),
typeof(IndexBuilder<>),
typeof(TriggerBuilder),
@@ -62,6 +65,8 @@ protected override void Initialize()
typeof(OwnershipBuilder<,>),
typeof(PropertyBuilder),
typeof(PropertyBuilder<>),
+ typeof(PrimitiveCollectionBuilder),
+ typeof(PrimitiveCollectionBuilder<>),
typeof(ComplexTypePropertyBuilder),
typeof(ComplexTypePropertyBuilder<>),
typeof(ReferenceCollectionBuilder),
diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs
index 6b6b3a53572..1fe44b00639 100644
--- a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs
@@ -31,14 +31,14 @@ public virtual void Can_set_complex_property_annotation()
Assert.Equal("bar", complexProperty.ComplexType["foo"]);
Assert.Equal("bar2", complexProperty["foo2"]);
Assert.Equal(typeof(Customer).Name, complexProperty.Name);
- Assert.Equal("""
-Customer (Customer)
+ Assert.Equal(
+@"Customer (Customer)
ComplexType: ComplexProperties.Customer#Customer
- Properties:
+ Properties: " + @"
AlternateKey (Guid) Required
Id (int) Required
Name (string)
-""", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true);
+ Notes (List)", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true);
}
[ConditionalFact]
@@ -1617,6 +1617,7 @@ protected virtual void Mapping_throws_for_empty_complex_types()
modelBuilder
.Entity()
.ComplexProperty(e => e.Customer)
+ .Ignore(c => c.Notes)
.Ignore(c => c.Name)
.Ignore(c => c.Id)
.Ignore(c => c.AlternateKey);
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
index 823b04165ea..aa1f164da51 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
@@ -23,6 +23,14 @@ protected override TestModelBuilder CreateTestModelBuilder(
=> new GenericTypeTestModelBuilder(testHelpers, configure);
}
+ public class GenericPrimitiveCollectionsTest : PrimitiveCollectionsTestBase
+ {
+ protected override TestModelBuilder CreateTestModelBuilder(
+ TestHelpers testHelpers,
+ Action? configure)
+ => new GenericTypeTestModelBuilder(testHelpers, configure);
+ }
+
private class GenericTypeTestModelBuilder : TestModelBuilder
{
public GenericTypeTestModelBuilder(TestHelpers testHelpers, Action? configure)
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
index 43db9739093..b20d933034c 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
@@ -68,6 +68,14 @@ public virtual void Changing_propertyInfo_updates_Property()
}
}
+ public class GenericPrimitiveCollections : PrimitiveCollectionsTestBase
+ {
+ protected override TestModelBuilder CreateTestModelBuilder(
+ TestHelpers testHelpers,
+ Action? configure)
+ => new GenericTestModelBuilder(testHelpers, configure);
+ }
+
public class GenericComplexType : ComplexTypeTestBase
{
protected override TestModelBuilder CreateTestModelBuilder(
@@ -199,6 +207,9 @@ protected virtual TestEntityTypeBuilder Wrap(EntityTypeBuilder
protected virtual TestPropertyBuilder Wrap(PropertyBuilder propertyBuilder)
=> new GenericTestPropertyBuilder(propertyBuilder);
+ protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder propertyBuilder)
+ => new GenericTestPrimitiveCollectionBuilder(propertyBuilder);
+
public override TestEntityTypeBuilder HasAnnotation(string annotation, object? value)
=> Wrap(EntityTypeBuilder.HasAnnotation(annotation, value));
@@ -230,6 +241,13 @@ public override TestPropertyBuilder Property(Expression Property(string propertyName)
=> Wrap(EntityTypeBuilder.Property(propertyName));
+ public override TestPrimitiveCollectionBuilder PrimitiveCollection(Expression> propertyExpression)
+ where TProperty : default
+ => Wrap(EntityTypeBuilder.PrimitiveCollection(propertyExpression));
+
+ public override TestPrimitiveCollectionBuilder PrimitiveCollection(string propertyName)
+ => Wrap(EntityTypeBuilder.PrimitiveCollection(propertyName));
+
public override TestPropertyBuilder IndexerProperty(string propertyName)
=> Wrap(EntityTypeBuilder.IndexerProperty(propertyName));
@@ -761,6 +779,78 @@ PropertyBuilder IInfrastructure>.Instance
=> PropertyBuilder;
}
+ protected class GenericTestPrimitiveCollectionBuilder
+ : TestPrimitiveCollectionBuilder, IInfrastructure>
+ {
+ public GenericTestPrimitiveCollectionBuilder(PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ {
+ PrimitiveCollectionBuilder = primitiveCollectionBuilder;
+ }
+
+ protected PrimitiveCollectionBuilder PrimitiveCollectionBuilder { get; }
+
+ public override IMutableProperty Metadata
+ => PrimitiveCollectionBuilder.Metadata;
+
+ protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder primitiveCollectionBuilder)
+ => new GenericTestPrimitiveCollectionBuilder(primitiveCollectionBuilder);
+
+ public override TestPrimitiveCollectionBuilder HasAnnotation(string annotation, object? value)
+ => Wrap(PrimitiveCollectionBuilder.HasAnnotation(annotation, value));
+
+ public override TestPrimitiveCollectionBuilder IsRequired(bool isRequired = true)
+ => Wrap(PrimitiveCollectionBuilder.IsRequired(isRequired));
+
+ public override TestPrimitiveCollectionBuilder HasMaxLength(int maxLength)
+ => Wrap(PrimitiveCollectionBuilder.HasMaxLength(maxLength));
+
+ public override TestPrimitiveCollectionBuilder HasSentinel(object? sentinel)
+ => Wrap(PrimitiveCollectionBuilder.HasSentinel(sentinel));
+
+ public override TestPrimitiveCollectionBuilder IsUnicode(bool unicode = true)
+ => Wrap(PrimitiveCollectionBuilder.IsUnicode(unicode));
+
+ public override TestPrimitiveCollectionBuilder IsConcurrencyToken(bool isConcurrencyToken = true)
+ => Wrap(PrimitiveCollectionBuilder.IsConcurrencyToken(isConcurrencyToken));
+
+ public override TestPrimitiveCollectionBuilder ValueGeneratedNever()
+ => Wrap(PrimitiveCollectionBuilder.ValueGeneratedNever());
+
+ public override TestPrimitiveCollectionBuilder ValueGeneratedOnAdd()
+ => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAdd());
+
+ public override TestPrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate()
+ => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAddOrUpdate());
+
+ public override TestPrimitiveCollectionBuilder