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 ValueGeneratedOnUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnUpdate()); + + public override TestPrimitiveCollectionBuilder HasValueGenerator() + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator()); + + public override TestPrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestPrimitiveCollectionBuilder HasValueGenerator( + Func factory) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(factory)); + + public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory() + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); + + public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestPrimitiveCollectionBuilder HasField(string fieldName) + => Wrap(PrimitiveCollectionBuilder.HasField(fieldName)); + + public override TestPrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PrimitiveCollectionBuilder.UsePropertyAccessMode(propertyAccessMode)); + + PrimitiveCollectionBuilder IInfrastructure>.Instance + => PrimitiveCollectionBuilder; + } + protected class GenericTestComplexTypePropertyBuilder : TestComplexTypePropertyBuilder, IInfrastructure> { diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 95491e3608b..062e09cf790 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -17,6 +17,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new NonGenericTestModelBuilder(testHelpers, configure); } + public class NonGenericPrimitiveProperties : PrimitiveCollectionsTestBase + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new NonGenericTestModelBuilder(testHelpers, configure); + } + public class NonGenericComplexType : ComplexTypeTestBase { protected override TestModelBuilder CreateTestModelBuilder( @@ -223,6 +231,9 @@ protected virtual NonGenericTestEntityTypeBuilder Wrap(EntityTypeBuilde protected virtual TestPropertyBuilder Wrap(PropertyBuilder propertyBuilder) => new NonGenericTestPropertyBuilder(propertyBuilder); + protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder propertyBuilder) + => new NonGenericTestPrimitiveCollectionBuilder(propertyBuilder); + public override TestEntityTypeBuilder HasAnnotation(string annotation, object? value) => Wrap(EntityTypeBuilder.HasAnnotation(annotation, value)); @@ -259,6 +270,16 @@ public override TestPropertyBuilder Property(Expression Property(string propertyName) => Wrap(EntityTypeBuilder.Property(propertyName)); + public override TestPrimitiveCollectionBuilder PrimitiveCollection( + Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(EntityTypeBuilder.PrimitiveCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestPrimitiveCollectionBuilder PrimitiveCollection(string propertyName) + => Wrap(EntityTypeBuilder.PrimitiveCollection(propertyName)); + public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); @@ -845,6 +866,77 @@ PropertyBuilder IInfrastructure.Instance => PropertyBuilder; } + protected class NonGenericTestPrimitiveCollectionBuilder : TestPrimitiveCollectionBuilder, IInfrastructure + { + public NonGenericTestPrimitiveCollectionBuilder(PrimitiveCollectionBuilder primitiveCollectionBuilder) + { + PrimitiveCollectionBuilder = primitiveCollectionBuilder; + } + + private PrimitiveCollectionBuilder PrimitiveCollectionBuilder { get; } + + public override IMutableProperty Metadata + => PrimitiveCollectionBuilder.Metadata; + + protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder primitiveCollectionBuilder) + => new NonGenericTestPrimitiveCollectionBuilder(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 ValueGeneratedOnUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnUpdate()); + + public override TestPrimitiveCollectionBuilder HasValueGenerator() + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator()); + + public override TestPrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestPrimitiveCollectionBuilder HasValueGenerator( + Func factory) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(factory)); + + public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory() + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); + + public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestPrimitiveCollectionBuilder HasField(string fieldName) + => Wrap(PrimitiveCollectionBuilder.HasField(fieldName)); + + public override TestPrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PrimitiveCollectionBuilder.UsePropertyAccessMode(propertyAccessMode)); + + PrimitiveCollectionBuilder IInfrastructure.Instance + => PrimitiveCollectionBuilder; + } + protected class NonGenericTestComplexTypePropertyBuilder : TestComplexTypePropertyBuilder, IInfrastructure { diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 7a6a14f6e3b..66f8d43c79c 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -201,6 +201,12 @@ public abstract TestPropertyBuilder Property( Expression> propertyExpression); public abstract TestPropertyBuilder Property(string propertyName); + + public abstract TestPrimitiveCollectionBuilder PrimitiveCollection( + Expression> propertyExpression); + + public abstract TestPrimitiveCollectionBuilder PrimitiveCollection(string propertyName); + public abstract TestPropertyBuilder IndexerProperty(string propertyName); public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); @@ -515,6 +521,38 @@ public abstract TestPropertyBuilder HasConversion + { + public abstract IMutableProperty Metadata { get; } + public abstract TestPrimitiveCollectionBuilder HasAnnotation(string annotation, object? value); + public abstract TestPrimitiveCollectionBuilder IsRequired(bool isRequired = true); + public abstract TestPrimitiveCollectionBuilder HasMaxLength(int maxLength); + public abstract TestPrimitiveCollectionBuilder HasSentinel(object? sentinel); + public abstract TestPrimitiveCollectionBuilder IsUnicode(bool unicode = true); + public abstract TestPrimitiveCollectionBuilder IsConcurrencyToken(bool isConcurrencyToken = true); + + public abstract TestPrimitiveCollectionBuilder ValueGeneratedNever(); + public abstract TestPrimitiveCollectionBuilder ValueGeneratedOnAdd(); + public abstract TestPrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate(); + public abstract TestPrimitiveCollectionBuilder ValueGeneratedOnUpdate(); + + public abstract TestPrimitiveCollectionBuilder HasValueGenerator() + where TGenerator : ValueGenerator; + + public abstract TestPrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType); + + public abstract TestPrimitiveCollectionBuilder HasValueGenerator( + Func factory); + + public abstract TestPrimitiveCollectionBuilder HasValueGeneratorFactory() + where TFactory : ValueGeneratorFactory; + + public abstract TestPrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType); + + public abstract TestPrimitiveCollectionBuilder HasField(string fieldName); + public abstract TestPrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + } + public abstract class TestComplexTypePropertyBuilder { public abstract IMutableProperty Metadata { get; } diff --git a/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs b/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs new file mode 100644 index 00000000000..e03b48b9cc2 --- /dev/null +++ b/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs @@ -0,0 +1,1884 @@ +// 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 System.Collections.ObjectModel; +using System.ComponentModel; +using System.Dynamic; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestModels; +using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; + +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public abstract partial class ModelBuilderTest +{ + public abstract class PrimitiveCollectionsTestBase : ModelBuilderTestBase + { + [ConditionalFact] + public virtual void Can_set_property_annotation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .PrimitiveCollection(c => c.Notes).HasAnnotation("foo", "bar"); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_when_no_clr_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .PrimitiveCollection>(nameof(Customer.Notes)).HasAnnotation("foo", "bar"); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); + + modelBuilder.Ignore(); + var propertyBuilder = modelBuilder + .Entity() + .PrimitiveCollection(c => c.Notes); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property>("Charm"); + b.Property?>("Strange"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.True(entityType.FindProperty("Charm")!.IsNullable); // Because we can't detect the non-nullable reference type + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Ignore(e => e.Up); + b.Ignore(e => e.Down); + b.Ignore("Charm"); + b.Ignore("Strange"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + Assert.Contains(nameof(CollectionQuarks.Id), entityType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(CollectionQuarks.Up), entityType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(CollectionQuarks.Down), entityType.GetProperties().Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void Can_override_navigations_as_properties() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + modelBuilder.Entity(); + + var customer = model.FindEntityType(typeof(Customer))!; + Assert.NotNull(customer.FindNavigation(nameof(Customer.Orders))); + + modelBuilder.Entity().PrimitiveCollection(c => c.Orders); + + Assert.Null(customer.FindNavigation(nameof(Customer.Orders))); + Assert.NotNull(customer.FindProperty(nameof(Customer.Orders))); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Up).IsRequired(); + b.Property(e => e.Down).IsRequired(); + b.Property>("Charm").IsRequired(); + b.Property?>("Strange").IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.False(entityType.FindProperty("Down")!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.IsNullable); + Assert.False(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Up).IsRequired(false); + b.Property(e => e.Down).IsRequired(false); + b.Property>("Charm").IsRequired(false); + b.Property?>("Strange").IsRequired(false); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.True(entityType.FindProperty("Up")!.IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.True(entityType.FindProperty("Charm")!.IsNullable); + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Key_properties_cannot_be_made_optional() + => Assert.Equal( + CoreStrings.KeyPropertyCannotBeNullable(nameof(CollectionQuarks.Down), nameof(CollectionQuarks), "{'" + nameof(CollectionQuarks.Down) + "'}"), + Assert.Throws( + () => + CreateModelBuilder().Entity( + b => + { + b.HasAlternateKey( + e => new { e.Down }); + b.Property(e => e.Down).IsRequired(false); + })).Message); + + [ConditionalFact] + public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Property>("Up"); + b.Property?>("Down"); + b.Property>("Charm"); + b.Property?>("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsShadowProperty()); + Assert.False(entityType.FindProperty("Down")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Charm")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Strange")!.IsShadowProperty()); + + Assert.Equal(-1, entityType.FindProperty("Up")!.GetShadowIndex()); + Assert.Equal(-1, entityType.FindProperty("Down")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Charm")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Strange")!.GetShadowIndex()); + } + + // [ConditionalFact] + // public virtual void Properties_can_be_made_concurrency_tokens() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).IsConcurrencyToken(); + // b.Property(e => e.Down).IsConcurrencyToken(false); + // b.Property("Charm").IsConcurrencyToken(); + // b.Property("Strange").IsConcurrencyToken(false); + // b.Property("Top").IsConcurrencyToken(); + // b.Property("Bottom").IsConcurrencyToken(false); + // b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); + // + // Assert.False(entityType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); + // Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); + // Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); + // Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); + // Assert.False(entityType.FindProperty("Strange").IsConcurrencyToken); + // Assert.True(entityType.FindProperty("Top").IsConcurrencyToken); + // Assert.False(entityType.FindProperty("Bottom").IsConcurrencyToken); + // + // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); + // Assert.Equal(3, entityType.FindProperty("Up").GetOriginalValueIndex()); + // Assert.Equal(-1, entityType.FindProperty("Down").GetOriginalValueIndex()); + // Assert.Equal(1, entityType.FindProperty("Charm").GetOriginalValueIndex()); + // Assert.Equal(-1, entityType.FindProperty("Strange").GetOriginalValueIndex()); + // Assert.Equal(2, entityType.FindProperty("Top").GetOriginalValueIndex()); + // Assert.Equal(-1, entityType.FindProperty("Bottom").GetOriginalValueIndex()); + // + // Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, entityType.GetChangeTrackingStrategy()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_access_mode_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up); + // b.Property(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); + // b.Property("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); + // b.Property("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + // + // Assert.Equal(PropertyAccessMode.PreferField, entityType.FindProperty("Up").GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.Field, entityType.FindProperty("Down").GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.Property, entityType.FindProperty("Charm").GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, entityType.FindProperty("Strange").GetPropertyAccessMode()); + // } + // + // [ConditionalFact] + // public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field); + // + // modelBuilder.Entity( + // b => + // { + // b.HasKey(e => e.Id1); + // }); + // modelBuilder.Ignore(); + // + // modelBuilder.Entity( + // b => + // { + // b.UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + // b.Property(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + // b.Property(e => e.Down).HasField("_forDown"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); + // + // var hobsType = (IReadOnlyEntityType)model.FindEntityType(typeof(Hob)); + // Assert.Equal(PropertyAccessMode.Field, hobsType.GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.Field, hobsType.FindProperty("Id1").GetPropertyAccessMode()); + // + // var quarksType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); + // Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_provider_type_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up); + // b.Property(e => e.Down).HasConversion(); + // b.Property("Charm").HasConversion>(); + // b.Property("Strange").HasConversion( + // new CustomValueComparer(), new CustomValueComparer()); + // b.Property("Strange").HasConversion(null); + // b.Property("Top").HasConversion(new CustomValueComparer()); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + // + // var up = entityType.FindProperty("Up"); + // Assert.Null(up.GetProviderClrType()); + // Assert.IsType>(up.GetValueComparer()); + // + // var down = entityType.FindProperty("Down"); + // Assert.Same(typeof(byte[]), down.GetProviderClrType()); + // Assert.IsType>(down.GetValueComparer()); + // Assert.IsType>(down.GetProviderValueComparer()); + // + // var charm = entityType.FindProperty("Charm"); + // Assert.Same(typeof(long), charm.GetProviderClrType()); + // Assert.IsType>(charm.GetValueComparer()); + // Assert.IsType>(charm.GetProviderValueComparer()); + // + // var strange = entityType.FindProperty("Strange"); + // Assert.Null(strange.GetProviderClrType()); + // Assert.IsType>(strange.GetValueComparer()); + // Assert.IsType>(strange.GetProviderValueComparer()); + // + // var top = entityType.FindProperty("Top"); + // Assert.Same(typeof(string), top.GetProviderClrType()); + // Assert.IsType>(top.GetValueComparer()); + // Assert.IsType>(top.GetProviderValueComparer()); + // } + // + // [ConditionalFact] + // public virtual 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"); + // }); + // + // 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()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_non_generic_value_converter_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // ValueConverter stringConverter = new StringToBytesConverter(Encoding.UTF8); + // ValueConverter intConverter = new CastingConverter(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up); + // b.Property(e => e.Down).HasConversion(stringConverter); + // b.Property("Charm").HasConversion(intConverter, null, new CustomValueComparer()); + // b.Property("Strange").HasConversion(stringConverter); + // b.Property("Strange").HasConversion(null); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + // + // Assert.Null(entityType.FindProperty("Up").GetValueConverter()); + // + // var down = entityType.FindProperty("Down"); + // Assert.Same(stringConverter, down.GetValueConverter()); + // Assert.IsType>(down.GetValueComparer()); + // Assert.IsType>(down.GetProviderValueComparer()); + // + // var charm = entityType.FindProperty("Charm"); + // Assert.Same(intConverter, charm.GetValueConverter()); + // Assert.IsType>(charm.GetValueComparer()); + // Assert.IsType>(charm.GetProviderValueComparer()); + // + // Assert.Null(entityType.FindProperty("Strange").GetValueConverter()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_custom_type_value_converter_type_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasConversion>(); + // b.Property(e => e.Down) + // .HasConversion, CustomValueComparer>(); + // b.Property("Charm").HasConversion, CustomValueComparer>(); + // b.Property("Strange").HasConversion>(); + // b.Property("Strange").HasConversion(null, null); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + // + // var up = entityType.FindProperty("Up"); + // Assert.Equal(typeof(int), up.GetProviderClrType()); + // Assert.Null(up.GetValueConverter()); + // Assert.IsType>(up.GetValueComparer()); + // Assert.IsType>(up.GetProviderValueComparer()); + // + // var down = entityType.FindProperty("Down"); + // Assert.IsType(down.GetValueConverter()); + // Assert.IsType>(down.GetValueComparer()); + // Assert.IsType>(down.GetProviderValueComparer()); + // + // var charm = entityType.FindProperty("Charm"); + // Assert.IsType>(charm.GetValueConverter()); + // Assert.IsType>(charm.GetValueComparer()); + // Assert.IsType>(charm.GetProviderValueComparer()); + // + // var strange = entityType.FindProperty("Strange"); + // Assert.Null(strange.GetValueConverter()); + // Assert.IsType>(strange.GetValueComparer()); + // Assert.IsType>(strange.GetProviderValueComparer()); + // } + // + // private class UTF8StringToBytesConverter : StringToBytesConverter + // { + // public UTF8StringToBytesConverter() + // : base(Encoding.UTF8) + // { + // } + // } + // + // private class CustomValueComparer : ValueComparer + // { + // public CustomValueComparer() + // : base(false) + // { + // } + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_value_converter_set_inline() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up); + // b.Property(e => e.Down).HasConversion(v => int.Parse(v), v => v.ToString()); + // b.Property("Charm").HasConversion(v => (long)v, v => (int)v, new CustomValueComparer()); + // b.Property("Strange").HasConversion( + // v => (double)v, v => (float)v, new CustomValueComparer(), new CustomValueComparer()); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // var up = entityType.FindProperty("Up"); + // Assert.Null(up.GetProviderClrType()); + // Assert.Null(up.GetValueConverter()); + // Assert.IsType>(up.GetValueComparer()); + // Assert.IsType>(up.GetProviderValueComparer()); + // + // var down = entityType.FindProperty("Down"); + // Assert.IsType>(down.GetValueConverter()); + // Assert.IsType>(down.GetValueComparer()); + // Assert.IsType>(down.GetProviderValueComparer()); + // + // var charm = entityType.FindProperty("Charm"); + // Assert.IsType>(charm.GetValueConverter()); + // Assert.IsType>(charm.GetValueComparer()); + // Assert.IsType>(charm.GetProviderValueComparer()); + // + // var strange = entityType.FindProperty("Strange"); + // Assert.IsType>(strange.GetValueConverter()); + // Assert.IsType>(strange.GetValueComparer()); + // Assert.IsType>(strange.GetProviderValueComparer()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_value_converter_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up); + // b.Property(e => e.Down).HasConversion( + // new ValueConverter(v => int.Parse(v), v => v.ToString())); + // b.Property("Charm").HasConversion( + // new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); + // b.Property("Strange").HasConversion( + // new ValueConverter(v => v, v => (float)v), new CustomValueComparer(), + // new CustomValueComparer()); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // var up = entityType.FindProperty("Up"); + // Assert.Null(up.GetProviderClrType()); + // Assert.Null(up.GetValueConverter()); + // Assert.IsType>(up.GetValueComparer()); + // Assert.IsType>(up.GetProviderValueComparer()); + // + // var down = entityType.FindProperty("Down"); + // Assert.IsType>(down.GetValueConverter()); + // Assert.IsType>(down.GetValueComparer()); + // Assert.IsType>(down.GetProviderValueComparer()); + // + // var charm = entityType.FindProperty("Charm"); + // Assert.IsType>(charm.GetValueConverter()); + // Assert.IsType>(charm.GetValueComparer()); + // Assert.IsType>(charm.GetProviderValueComparer()); + // + // var strange = entityType.FindProperty("Strange"); + // Assert.IsType>(strange.GetValueConverter()); + // Assert.IsType>(strange.GetValueComparer()); + // Assert.IsType>(strange.GetProviderValueComparer()); + // } + // + // [ConditionalFact] + // public virtual void IEnumerable_properties_with_value_converter_set_are_not_discovered_as_navigations() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.ExpandoObject).HasConversion( + // v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)); + // + // var comparer = new ValueComparer( + // (v1, v2) => v1.SequenceEqual(v2), + // v => v.GetHashCode()); + // + // b.Property(e => e.ExpandoObject).Metadata.SetValueComparer(comparer); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); + // Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueConverter()); + // Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueComparer()); + // } + // + // private static ExpandoObject DeserializeExpandoObject(string value) + // { + // dynamic obj = new ExpandoObject(); + // obj.Value = value; + // + // return obj; + // } + // + // private class ExpandoObjectConverter : ValueConverter + // { + // public ExpandoObjectConverter() + // : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) + // { + // } + // } + // + // private class ExpandoObjectComparer : ValueComparer + // { + // public ExpandoObjectComparer() + // : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + // { + // } + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_value_converter_configured_by_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties(typeof(IWrapped<>)).AreUnicode(false); + // c.Properties().HaveMaxLength(20); + // c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); + // }); + // + // modelBuilder.Entity(); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); + // var wrappedProperty = entityType.FindProperty(nameof(WrappedStringEntity.WrappedString)); + // Assert.False(wrappedProperty.IsUnicode()); + // Assert.Equal(20, wrappedProperty.GetMaxLength()); + // Assert.IsType(wrappedProperty.GetValueConverter()); + // Assert.IsType>(wrappedProperty.GetValueComparer()); + // } + // + // [ConditionalFact] + // public virtual void Value_converter_configured_on_non_nullable_type_is_applied() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveConversion, CustomValueComparer>(); + // }); + // + // modelBuilder.Entity( + // b => + // { + // b.Property("Wierd"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // var id = entityType.FindProperty("Id"); + // Assert.IsType>(id.GetValueConverter()); + // Assert.IsType>(id.GetValueComparer()); + // + // var wierd = entityType.FindProperty("Wierd"); + // Assert.IsType>(wierd.GetValueConverter()); + // Assert.IsType>(wierd.GetValueComparer()); + // } + // + // [ConditionalFact] + // public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveConversion, CustomValueComparer>(); + // c.Properties() + // .HaveConversion, CustomValueComparer, CustomValueComparer>(); + // }); + // + // modelBuilder.Entity( + // b => + // { + // b.Property("Wierd"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // var id = entityType.FindProperty("Id"); + // Assert.IsType>(id.GetValueConverter()); + // Assert.IsType>(id.GetValueComparer()); + // Assert.IsType>(id.GetProviderValueComparer()); + // + // var wierd = entityType.FindProperty("Wierd"); + // Assert.IsType>(wierd.GetValueConverter()); + // Assert.IsType>(wierd.GetValueComparer()); + // Assert.IsType>(wierd.GetProviderValueComparer()); + // } + // + // [ConditionalFact] + // public virtual void Value_converter_configured_on_base_type_is_not_applied() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); + // }); + // + // modelBuilder.Entity(); + // + // Assert.Equal( + // CoreStrings.PropertyNotMapped( + // nameof(WrappedString), + // nameof(WrappedStringEntity), + // nameof(WrappedStringEntity.WrappedString)), + // Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + // } + // + // private class WrappedStringToStringConverter : ValueConverter + // { + // public WrappedStringToStringConverter() + // : base(v => v.Value, v => new WrappedString { Value = v }) + // { + // } + // } + // + // [ConditionalFact] + // public virtual void Throws_for_conflicting_base_configurations_by_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties(); + // c.IgnoreAny>(); + // }); + // + // Assert.Equal( + // CoreStrings.TypeConfigurationConflict( + // nameof(WrappedString), "Property", + // "IWrapped", "Ignored"), + // Assert.Throws(() => modelBuilder.Entity()).Message); + // } + // + // [ConditionalFact] + // public virtual void Value_converter_type_is_checked() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // Assert.Equal( + // CoreStrings.ConverterPropertyMismatch("string", "Quarks", "Up", "int"), + // Assert.Throws( + // () => b.Property(e => e.Up).HasConversion( + // new StringToBytesConverter(Encoding.UTF8))).Message); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // Assert.Null(entityType.FindProperty("Up").GetValueConverter()); + // } + // + // [ConditionalFact] + // public virtual void Properties_can_have_field_set() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property("Up").HasField("_forUp"); + // b.Property(e => e.Down).HasField("_forDown"); + // b.Property("_forWierd").HasField("_forWierd"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Equal("_forUp", entityType.FindProperty("Up").GetFieldName()); + // Assert.Equal("_forDown", entityType.FindProperty("Down").GetFieldName()); + // Assert.Equal("_forWierd", entityType.FindProperty("_forWierd").GetFieldName()); + // } + // + // [ConditionalFact] + // public virtual void HasField_throws_if_field_is_not_found() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // Assert.Equal( + // CoreStrings.MissingBackingField("_notFound", nameof(Quarks.Down), nameof(Quarks)), + // Assert.Throws(() => b.Property(e => e.Down).HasField("_notFound")).Message); + // }); + // } + // + // [ConditionalFact] + // public virtual void HasField_throws_if_field_is_wrong_type() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // Assert.Equal( + // CoreStrings.BadBackingFieldType("_forUp", "int", nameof(Quarks), nameof(Quarks.Down), "string"), + // Assert.Throws(() => b.Property(e => e.Down).HasField("_forUp")).Message); + // }); + // } + // + // [ConditionalFact] + // public virtual 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.OnAdd, 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 Properties_can_set_row_version() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.HasKey(e => e.Id); + // b.Property(e => e.Up).IsRowVersion(); + // b.Property(e => e.Down).ValueGeneratedNever(); + // b.Property("Charm").IsRowVersion(); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); + // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); + // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Charm").ValueGenerated); + // + // Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); + // Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); + // Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); + // } + // + // [ConditionalFact] + // public virtual void Can_set_max_length_for_properties() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasMaxLength(0); + // b.Property(e => e.Down).HasMaxLength(100); + // b.Property("Charm").HasMaxLength(0); + // b.Property("Strange").HasMaxLength(-1); + // b.Property("Top").HasMaxLength(0); + // b.Property("Bottom").HasMaxLength(100); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); + // Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); + // Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); + // Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_max_length_for_property_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveMaxLength(0); + // c.Properties().HaveMaxLength(100); + // }); + // + // 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(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); + // Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); + // Assert.Equal(100, entityType.FindProperty("Strange").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); + // Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_sentinel_for_properties() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasSentinel(1); + // b.Property(e => e.Down).HasSentinel("100"); + // b.Property("Charm").HasSentinel(-1); + // b.Property("Strange").HasSentinel("-1"); + // b.Property("Top").HasSentinel(77); + // b.Property("Bottom").HasSentinel("100"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks))!; + // + // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + // Assert.Equal(1, entityType.FindProperty("Up")!.Sentinel); + // Assert.Equal("100", entityType.FindProperty("Down")!.Sentinel); + // Assert.Equal(-1, entityType.FindProperty("Charm")!.Sentinel); + // Assert.Equal("-1", entityType.FindProperty("Strange")!.Sentinel); + // Assert.Equal(77, entityType.FindProperty("Top")!.Sentinel); + // Assert.Equal("100", entityType.FindProperty("Bottom")!.Sentinel); + // } + // + // [ConditionalFact] + // public virtual void Can_set_sentinel_for_property_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveSentinel(-1); + // c.Properties().HaveSentinel("100"); + // }); + // + // 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(-1, entityType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + // Assert.Equal(-1, entityType.FindProperty("Up")!.Sentinel); + // Assert.Equal("100", entityType.FindProperty("Down")!.Sentinel); + // Assert.Equal(-1, entityType.FindProperty("Charm")!.Sentinel); + // Assert.Equal("100", entityType.FindProperty("Strange")!.Sentinel); + // Assert.Equal(-1, entityType.FindProperty("Top")!.Sentinel); + // Assert.Equal("100", entityType.FindProperty("Bottom")!.Sentinel); + // } + // + // [ConditionalFact] + // public virtual void Can_set_unbounded_max_length_for_property_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HaveMaxLength(0); + // c.Properties().HaveMaxLength(-1); + // }); + // + // 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(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); + // Assert.Equal(-1, entityType.FindProperty("Down").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); + // Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); + // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); + // Assert.Equal(-1, entityType.FindProperty("Bottom").GetMaxLength()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_precision_and_scale_for_properties() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasPrecision(1, 0); + // b.Property(e => e.Down).HasPrecision(100, 10); + // b.Property("Charm").HasPrecision(1, 0); + // b.Property("Strange").HasPrecision(100, 10); + // b.Property("Top").HasPrecision(1, 0); + // b.Property("Bottom").HasPrecision(100, 10); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetScale()); + // Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Up").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Down").GetScale()); + // Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); + // Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Top").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_precision_and_scale_for_property_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().HavePrecision(1, 0); + // c.Properties().HavePrecision(100, 10); + // }); + // + // 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(1, entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetScale()); + // Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Up").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Down").GetScale()); + // Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); + // Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); + // Assert.Equal(0, entityType.FindProperty("Top").GetScale()); + // Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); + // Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_custom_value_generator_for_properties() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasValueGenerator(); + // b.Property(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); + // b.Property("Charm").HasValueGenerator((_, __) => new CustomValueGenerator()); + // b.Property("Strange").HasValueGenerator(); + // b.Property("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); + // b.Property("Bottom").HasValueGeneratorFactory(); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); + // Assert.IsType(entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); + // Assert.IsType(entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); + // Assert.IsType(entityType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); + // Assert.IsType(entityType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); + // Assert.IsType(entityType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); + // Assert.IsType(entityType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); + // } + // + // private class CustomValueGenerator : ValueGenerator + // { + // public override int Next(EntityEntry entry) + // => throw new NotImplementedException(); + // + // public override bool GeneratesTemporaryValues + // => false; + // } + // + // private class CustomValueGeneratorFactory : ValueGeneratorFactory + // { + // public override ValueGenerator Create(IProperty property, ITypeBase entityType) + // => new CustomValueGenerator(); + // } + // + // [ConditionalFact] + // public virtual void Throws_for_bad_value_generator_type() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // Assert.Equal( + // CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), + // Assert.Throws(() => b.Property(e => e.Down).HasValueGenerator(typeof(Random))).Message); + // }); + // } + // + // [ConditionalFact] + // public virtual void Throws_for_value_generator_that_cannot_be_constructed() + // { + // var modelBuilder = CreateModelBuilder(); + // var model = modelBuilder.Model; + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).HasValueGenerator(); + // b.Property(e => e.Down).HasValueGenerator(); + // }); + // + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Equal( + // CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), + // Assert.Throws( + // () => entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)).Message); + // + // Assert.Equal( + // CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), + // Assert.Throws( + // () => entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)).Message); + // } + // + // private class BadCustomValueGenerator1 : CustomValueGenerator + // { + // public BadCustomValueGenerator1(string foo) + // { + // } + // } + // + // private abstract class BadCustomValueGenerator2 : CustomValueGenerator + // { + // } + // + // protected class StringCollectionEntity + // { + // public ICollection Property { get; set; } + // } + // + // [ConditionalFact] + // public virtual void Object_cannot_be_configured_as_property() + // => Assert.Equal( + // CoreStrings.UnconfigurableType("Dictionary", "Property", "SharedTypeEntityType", "object"), + // Assert.Throws(() => CreateModelBuilder(c => c.Properties())).Message); + // + // [ConditionalFact] + // public virtual void Property_bag_cannot_be_configured_as_property() + // { + // Assert.Equal( + // CoreStrings.UnconfigurableType( + // "Dictionary", "Property", "SharedTypeEntityType", "Dictionary"), + // Assert.Throws(() => CreateModelBuilder(c => c.Properties>())) + // .Message); + // + // Assert.Equal( + // CoreStrings.UnconfigurableType( + // "Dictionary", "Property", "SharedTypeEntityType", "IDictionary"), + // Assert.Throws(() => CreateModelBuilder(c => c.Properties>())) + // .Message); + // } + // + // [ConditionalFact] + // protected virtual void Mapping_ignores_ignored_array() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().Ignore(e => e.One); + // + // var model = modelBuilder.FinalizeModel(); + // + // Assert.Null(model.FindEntityType(typeof(OneDee)).FindProperty("One")); + // } + // + // [ConditionalFact] + // protected virtual void Mapping_ignores_ignored_two_dimensional_array() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().Ignore(e => e.Two); + // + // var model = modelBuilder.FinalizeModel(); + // + // Assert.Null(model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); + // } + // + // [ConditionalFact] + // protected virtual void Mapping_throws_for_non_ignored_three_dimensional_array() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity(); + // + // Assert.Equal( + // CoreStrings.PropertyNotAdded( + // typeof(ThreeDee).ShortDisplayName(), "Three", typeof(int[,,]).ShortDisplayName()), + // Assert.Throws(modelBuilder.FinalizeModel).Message); + // } + // + // [ConditionalFact] + // protected virtual void Mapping_ignores_ignored_three_dimensional_array() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().Ignore(e => e.Three); + // + // var model = modelBuilder.FinalizeModel(); + // + // Assert.Null(model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); + // } + // + // protected class OneDee + // { + // public int Id { get; set; } + // + // public int[] One { get; set; } + // } + // + // protected class TwoDee + // { + // public int Id { get; set; } + // + // public int[,] Two { get; set; } + // } + // + // protected class ThreeDee + // { + // public int Id { get; set; } + // + // public int[,,] Three { get; set; } + // } + // + // [ConditionalFact] + // public virtual void Private_property_is_not_discovered_by_convention() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Ignore(); + // modelBuilder.Entity(); + // + // var model = modelBuilder.FinalizeModel(); + // + // Assert.Empty( + // model.FindEntityType(typeof(Gamma)).GetProperties() + // .Where(p => p.Name == "PrivateProperty")); + // } + // + // [ConditionalFact] + // protected virtual void Throws_for_int_keyed_dictionary() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity(); + // + // Assert.Equal( + // CoreStrings.NavigationNotAdded( + // nameof(IntDict), nameof(IntDict.Notes), typeof(Dictionary).ShortDisplayName()), + // Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + // } + // + // protected class IntDict + // { + // public int Id { get; set; } + // public Dictionary Notes { get; set; } + // } + // + // [ConditionalFact] + // public virtual void Can_set_unicode_for_properties() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.Property(e => e.Up).IsUnicode(); + // b.Property(e => e.Down).IsUnicode(false); + // b.Property("Charm").IsUnicode(); + // b.Property("Strange").IsUnicode(false); + // b.Property("Top").IsUnicode(); + // b.Property("Bottom").IsUnicode(false); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Quarks)); + // + // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + // Assert.True(entityType.FindProperty("Up").IsUnicode()); + // Assert.False(entityType.FindProperty("Down").IsUnicode()); + // Assert.True(entityType.FindProperty("Charm").IsUnicode()); + // Assert.False(entityType.FindProperty("Strange").IsUnicode()); + // Assert.True(entityType.FindProperty("Top").IsUnicode()); + // Assert.False(entityType.FindProperty("Bottom").IsUnicode()); + // } + // + // [ConditionalFact] + // public virtual void Can_set_unicode_for_property_type() + // { + // var modelBuilder = CreateModelBuilder( + // c => + // { + // c.Properties().AreUnicode(); + // c.Properties().AreUnicode(false); + // }); + // + // 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.True(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + // Assert.True(entityType.FindProperty("Up").IsUnicode()); + // Assert.False(entityType.FindProperty("Down").IsUnicode()); + // Assert.True(entityType.FindProperty("Charm").IsUnicode()); + // Assert.False(entityType.FindProperty("Strange").IsUnicode()); + // Assert.True(entityType.FindProperty("Top").IsUnicode()); + // Assert.False(entityType.FindProperty("Bottom").IsUnicode()); + // } + // + // [ConditionalFact] + // public virtual void PropertyBuilder_methods_can_be_chained() + // => CreateModelBuilder() + // .Entity() + // .Property(e => e.Up) + // .IsRequired() + // .HasAnnotation("A", "V") + // .IsConcurrencyToken() + // .ValueGeneratedNever() + // .ValueGeneratedOnAdd() + // .ValueGeneratedOnAddOrUpdate() + // .ValueGeneratedOnUpdate() + // .IsUnicode() + // .HasMaxLength(100) + // .HasSentinel(null) + // .HasPrecision(10, 1) + // .HasValueGenerator() + // .HasValueGenerator(typeof(CustomValueGenerator)) + // .HasValueGeneratorFactory() + // .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) + // .HasValueGenerator((_, __) => null) + // .IsRequired(); + // + // [ConditionalFact] + // public virtual void Can_add_index() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Ignore(); + // modelBuilder + // .Entity() + // .HasIndex(ix => ix.Name); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Customer)); + // + // var index = entityType.GetIndexes().Single(); + // Assert.Equal(Customer.NameProperty.Name, index.Properties.Single().Name); + // } + // + // [ConditionalFact] + // public virtual void Can_add_index_when_no_clr_property() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Ignore(); + // modelBuilder + // .Entity( + // b => + // { + // b.Property("Index"); + // b.HasIndex("Index"); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = model.FindEntityType(typeof(Customer)); + // + // var index = entityType.GetIndexes().Single(); + // Assert.Equal("Index", index.Properties.Single().Name); + // } + // + // [ConditionalFact] + // public virtual void Can_add_multiple_indexes() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Ignore(); + // var entityBuilder = modelBuilder.Entity(); + // entityBuilder.HasIndex(ix => ix.Id).IsUnique(); + // entityBuilder.HasIndex(ix => ix.Name).HasAnnotation("A1", "V1"); + // entityBuilder.HasIndex(ix => ix.Id, "Named"); + // entityBuilder.HasIndex(ix => ix.Id, "Descending").IsDescending(); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(Customer)); + // var idProperty = entityType.FindProperty(nameof(Customer.Id)); + // var nameProperty = entityType.FindProperty(nameof(Customer.Name)); + // + // Assert.Equal(4, entityType.GetIndexes().Count()); + // var firstIndex = entityType.FindIndex(idProperty); + // Assert.True(firstIndex.IsUnique); + // var secondIndex = entityType.FindIndex(nameProperty); + // Assert.False(secondIndex.IsUnique); + // Assert.Equal("V1", secondIndex["A1"]); + // var namedIndex = entityType.FindIndex("Named"); + // Assert.False(namedIndex.IsUnique); + // var descendingIndex = entityType.FindIndex("Descending"); + // Assert.Equal(Array.Empty(), descendingIndex.IsDescending); + // } + // + // [ConditionalFact] + // public virtual void Can_add_contained_indexes() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Ignore(); + // var entityBuilder = modelBuilder.Entity(); + // var firstIndexBuilder = entityBuilder.HasIndex( + // ix => new { ix.Id, ix.AlternateKey }).IsUnique(); + // var secondIndexBuilder = entityBuilder.HasIndex( + // ix => new { ix.Id }); + // + // var model = modelBuilder.FinalizeModel(); + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Customer)); + // + // Assert.Equal(2, entityType.GetIndexes().Count()); + // Assert.True(firstIndexBuilder.Metadata.IsUnique); + // Assert.False(secondIndexBuilder.Metadata.IsUnique); + // } + // + // [ConditionalFact] + // public virtual void Can_set_primary_key_by_convention_for_user_specified_shadow_property() + // { + // var modelBuilder = CreateModelBuilder(); + // var model = modelBuilder.Model; + // + // var entityBuilder = modelBuilder.Entity(); + // + // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(EntityWithoutId)); + // + // Assert.Null(entityType.FindPrimaryKey()); + // + // entityBuilder.Property("Id"); + // + // Assert.NotNull(entityType.FindPrimaryKey()); + // AssertEqual(new[] { "Id" }, entityType.FindPrimaryKey().Properties.Select(p => p.Name)); + // } + // + // [ConditionalFact] + // public virtual void Can_ignore_explicit_interface_implementation_property() + // { + // var modelBuilder = CreateModelBuilder(); + // modelBuilder.Entity().HasNoKey().Ignore(e => ((IEntityBase)e).Target); + // + // Assert.DoesNotContain( + // nameof(IEntityBase.Target), + // modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); + // + // modelBuilder.Entity().Property(e => ((IEntityBase)e).Target); + // + // Assert.Contains( + // nameof(IEntityBase.Target), + // modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); + // } + // + // [ConditionalFact] + // public virtual void Can_set_key_on_an_entity_with_fields() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().HasKey(e => e.Id); + // + // var model = modelBuilder.FinalizeModel(); + // var entity = model.FindEntityType(typeof(EntityWithFields)); + // var primaryKey = entity.FindPrimaryKey(); + // Assert.NotNull(primaryKey); + // var property = Assert.Single(primaryKey.Properties); + // Assert.Equal(nameof(EntityWithFields.Id), property.Name); + // Assert.Null(property.PropertyInfo); + // Assert.NotNull(property.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_set_composite_key_on_an_entity_with_fields() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); + // + // var model = modelBuilder.FinalizeModel(); + // var entity = model.FindEntityType(typeof(EntityWithFields)); + // var primaryKeyProperties = entity.FindPrimaryKey().Properties; + // Assert.Equal(2, primaryKeyProperties.Count); + // var first = primaryKeyProperties[0]; + // var second = primaryKeyProperties[1]; + // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + // Assert.Null(first.PropertyInfo); + // Assert.NotNull(first.FieldInfo); + // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + // Assert.Null(second.PropertyInfo); + // Assert.NotNull(second.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_set_alternate_key_on_an_entity_with_fields() + // { + // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + // + // modelBuilder.Entity().HasAlternateKey(e => e.CompanyId); + // + // var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + // var properties = entity.GetProperties(); + // Assert.Single(properties); + // var property = properties.Single(); + // Assert.Equal(nameof(EntityWithFields.CompanyId), property.Name); + // Assert.Null(property.PropertyInfo); + // Assert.NotNull(property.FieldInfo); + // var keys = entity.GetKeys(); + // var key = Assert.Single(keys); + // Assert.Equal(properties, key.Properties); + // } + // + // [ConditionalFact] + // public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() + // { + // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + // + // modelBuilder.Entity().HasAlternateKey(e => new { e.TenantId, e.CompanyId }); + // + // var keys = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetKeys(); + // Assert.Single(keys); + // var properties = keys.Single().Properties; + // Assert.Equal(2, properties.Count); + // var first = properties[0]; + // var second = properties[1]; + // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + // Assert.Null(first.PropertyInfo); + // Assert.NotNull(first.FieldInfo); + // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + // Assert.Null(second.PropertyInfo); + // Assert.NotNull(second.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_call_Property_on_an_entity_with_fields() + // { + // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + // + // modelBuilder.Entity().Property(e => e.Id); + // + // var model = modelBuilder.FinalizeModel(); + // var properties = model.FindEntityType(typeof(EntityWithFields)).GetProperties(); + // var property = Assert.Single(properties); + // Assert.Equal(nameof(EntityWithFields.Id), property.Name); + // Assert.Null(property.PropertyInfo); + // Assert.NotNull(property.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_set_index_on_an_entity_with_fields() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().HasNoKey().HasIndex(e => e.CompanyId); + // + // var model = modelBuilder.FinalizeModel(); + // var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + // var index = Assert.Single(indexes); + // var property = Assert.Single(index.Properties); + // Assert.Null(property.PropertyInfo); + // Assert.NotNull(property.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_set_composite_index_on_an_entity_with_fields() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity().HasNoKey().HasIndex(e => new { e.TenantId, e.CompanyId }); + // + // var model = modelBuilder.FinalizeModel(); + // var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + // var index = Assert.Single(indexes); + // Assert.Equal(2, index.Properties.Count); + // var properties = index.Properties; + // var first = properties[0]; + // var second = properties[1]; + // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + // Assert.Null(first.PropertyInfo); + // Assert.NotNull(first.FieldInfo); + // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + // Assert.Null(second.PropertyInfo); + // Assert.NotNull(second.FieldInfo); + // } + // + // [ConditionalFact] + // public virtual void Can_ignore_a_field_on_an_entity_with_fields() + // { + // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + // + // modelBuilder.Entity() + // .Ignore(e => e.CompanyId) + // .HasKey(e => e.Id); + // + // var model = modelBuilder.FinalizeModel(); + // var entity = model.FindEntityType(typeof(EntityWithFields)); + // var property = Assert.Single(entity.GetProperties()); + // Assert.Equal(nameof(EntityWithFields.Id), property.Name); + // } + // + // [ConditionalFact] + // public virtual void Can_ignore_a_field_on_a_keyless_entity_with_fields() + // { + // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + // + // modelBuilder.Entity() + // .HasNoKey() + // .Ignore(e => e.FirstName) + // .Property(e => e.LastName); + // + // var model = modelBuilder.FinalizeModel(); + // var entity = model.FindEntityType(typeof(KeylessEntityWithFields)); + // var property = Assert.Single(entity.GetProperties()); + // Assert.Equal(nameof(KeylessEntityWithFields.LastName), property.Name); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_objects() + // { + // var modelBuilder = CreateModelBuilder(); + // var model = modelBuilder.Model; + // modelBuilder.Ignore(); + // modelBuilder.Entity( + // c => + // { + // c.HasData( + // new Beta { Id = -1, Name = " -1" }); + // var customers = new List { new() { Id = -2 } }; + // c.HasData(customers); + // }); + // + // var finalModel = modelBuilder.FinalizeModel(); + // + // var customer = finalModel.FindEntityType(typeof(Beta)); + // var data = customer.GetSeedData(); + // Assert.Equal(2, data.Count()); + // Assert.Equal(-1, data.First()[nameof(Beta.Id)]); + // Assert.Equal(" -1", data.First()[nameof(Beta.Name)]); + // Assert.Equal(-2, data.Last()[nameof(Beta.Id)]); + // + // var _ = finalModel.ToDebugString(); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_anonymous_objects() + // { + // var modelBuilder = CreateModelBuilder(); + // modelBuilder.Ignore(); + // modelBuilder.Entity( + // c => + // { + // c.HasData( + // new { Id = -1 }); + // var customers = new List { new { Id = -2 } }; + // c.HasData(customers); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var customer = model.FindEntityType(typeof(Beta)); + // var data = customer.GetSeedData(); + // Assert.Equal(2, data.Count()); + // Assert.Equal(-1, data.First().Values.Single()); + // Assert.Equal(-2, data.Last().Values.Single()); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_objects_indexed_property() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.IndexerProperty("Required"); + // b.IndexerProperty("Optional"); + // var d = new IndexedClass { Id = -1 }; + // d["Required"] = 2; + // b.HasData(d); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(IndexedClass)); + // var data = Assert.Single(entityType.GetSeedData()); + // Assert.Equal(-1, data["Id"]); + // Assert.Equal(2, data["Required"]); + // Assert.Null(data["Optional"]); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_anonymous_objects_indexed_property() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity( + // b => + // { + // b.IndexerProperty("Required"); + // b.IndexerProperty("Optional"); + // b.HasData(new { Id = -1, Required = 2 }); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(IndexedClass)); + // var data = Assert.Single(entityType.GetSeedData()); + // Assert.Equal(-1, data["Id"]); + // Assert.Equal(2, data["Required"]); + // Assert.False(data.ContainsKey("Optional")); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_objects_indexed_property_dictionary() + // { + // var modelBuilder = CreateModelBuilder(); + // modelBuilder.Entity( + // b => + // { + // b.IndexerProperty("Required"); + // b.IndexerProperty("Optional"); + // var d = new IndexedClassByDictionary { Id = -1 }; + // d["Required"] = 2; + // b.HasData(d); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); + // var data = Assert.Single(entityType.GetSeedData()); + // Assert.Equal(-1, data["Id"]); + // Assert.Equal(2, data["Required"]); + // Assert.Null(data["Optional"]); + // } + // + // [ConditionalFact] + // public virtual void Can_add_seed_data_anonymous_objects_indexed_property_dictionary() + // { + // var modelBuilder = CreateModelBuilder(); + // modelBuilder.Entity( + // b => + // { + // b.IndexerProperty("Required"); + // b.IndexerProperty("Optional"); + // b.HasData(new { Id = -1, Required = 2 }); + // }); + // + // var model = modelBuilder.FinalizeModel(); + // + // var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); + // var data = Assert.Single(entityType.GetSeedData()); + // Assert.Equal(-1, data["Id"]); + // Assert.Equal(2, data["Required"]); + // Assert.False(data.ContainsKey("Optional")); + // } + // + // [ConditionalFact] //Issue#12617 + // [UseCulture("de-DE")] + // public virtual void EntityType_name_is_stored_culture_invariantly() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity(); + // modelBuilder.Entity(); + // + // var model = modelBuilder.FinalizeModel(); + // + // Assert.Equal(2, model.GetEntityTypes().Count()); + // Assert.Equal(2, model.FindEntityType(typeof(Entityss)).GetNavigations().Count()); + // } + // + // protected class Entityß + // { + // public int Id { get; set; } + // } + // + // protected class Entityss + // { + // public int Id { get; set; } + // public Entityß Navigationß { get; set; } + // public Entityß Navigationss { get; set; } + // } + // + // [ConditionalFact] + // public virtual void Can_add_shared_type_entity_type() + // { + // var modelBuilder = CreateModelBuilder(); + // modelBuilder.SharedTypeEntity>( + // "Shared1", b => + // { + // b.IndexerProperty("Key"); + // b.Property("Keys"); + // b.Property("Values"); + // b.Property("Count"); + // b.HasKey("Key"); + // }); + // + // modelBuilder.SharedTypeEntity>("Shared2", b => b.IndexerProperty("Id")); + // + // Assert.Equal( + // CoreStrings.ClashingSharedType(typeof(Dictionary).ShortDisplayName()), + // Assert.Throws(() => modelBuilder.Entity>()).Message); + // + // var model = modelBuilder.FinalizeModel(); + // Assert.Equal(2, model.GetEntityTypes().Count()); + // + // var shared1 = model.FindEntityType("Shared1"); + // Assert.NotNull(shared1); + // Assert.True(shared1.HasSharedClrType); + // Assert.Null(shared1.FindProperty("Id")); + // Assert.Equal(typeof(int), shared1.FindProperty("Keys").ClrType); + // Assert.Equal(typeof(byte[]), shared1.FindProperty("Values").ClrType); + // Assert.Equal(typeof(string), shared1.FindProperty("Count").ClrType); + // + // var shared2 = model.FindEntityType("Shared2"); + // Assert.NotNull(shared2); + // Assert.True(shared2.HasSharedClrType); + // Assert.NotNull(shared2.FindProperty("Id")); + // + // var indexer = shared1.FindIndexerPropertyInfo(); + // Assert.True(model.IsIndexerMethod(indexer.GetMethod)); + // Assert.True(model.IsIndexerMethod(indexer.SetMethod)); + // Assert.Same(indexer, shared2.FindIndexerPropertyInfo()); + // } + // + // [ConditionalFact] + // public virtual void Cannot_add_shared_type_when_non_shared_exists() + // { + // var modelBuilder = CreateModelBuilder(); + // + // modelBuilder.Entity(); + // + // Assert.Equal( + // CoreStrings.ClashingNonSharedType("Shared1", nameof(Customer)), + // Assert.Throws(() => modelBuilder.SharedTypeEntity("Shared1")).Message); + // } + } +} diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 5bb1f728954..af6cbca250d 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -97,6 +98,8 @@ protected class Customer public IEnumerable? Orders { get; set; } + public List? Notes { get; set; } + [NotMapped] public ICollection? SomeOrders { get; set; } @@ -296,6 +299,34 @@ public string? Down set => _forDown = value; } +#pragma warning disable 67 + public event PropertyChangingEventHandler? PropertyChanging; + public event PropertyChangedEventHandler? PropertyChanged; +#pragma warning restore 67 + } + + // INotify interfaces not really implemented; just marking the classes to test metadata construction + protected class CollectionQuarks : INotifyPropertyChanging, INotifyPropertyChanged + { + private ObservableCollection _forUp = null!; + private ObservableCollection? _forDown; + + public int Id { get; set; } + + // ReSharper disable once ConvertToAutoProperty + public ObservableCollection Up + { + get => _forUp; + set => _forUp = value; + } + + // ReSharper disable once ConvertToAutoProperty + public ObservableCollection? Down + { + get => _forDown; + set => _forDown = value; + } + #pragma warning disable 67 public event PropertyChangingEventHandler? PropertyChanging; public event PropertyChangedEventHandler? PropertyChanged;