diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs index b1ef2a7ce2d..24efef0d0fc 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs @@ -883,88 +883,6 @@ public static bool CanSetDefaultTimeToLive( bool fromDataAnnotation = false) => entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.DefaultTimeToLive, seconds, fromDataAnnotation); - /// - /// Configures a default language to use for full-text search at container scope. - /// - /// - /// See Modeling entity types and relationships, and - /// Accessing Azure Cosmos DB with EF Core for more information and examples. - /// - /// The builder for the entity type being configured. - /// The default language. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder HasDefaultFullTextLanguage( - this EntityTypeBuilder entityTypeBuilder, - string? language) - { - entityTypeBuilder.Metadata.SetDefaultFullTextSearchLanguage(language); - - return entityTypeBuilder; - } - - /// - /// Configures a default language to use for full-text search at container scope. - /// - /// - /// See Modeling entity types and relationships, and - /// Accessing Azure Cosmos DB with EF Core for more information and examples. - /// - /// The builder for the entity type being configured. - /// The default language. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder HasDefaultFullTextLanguage( - this EntityTypeBuilder entityTypeBuilder, - string? language) - where TEntity : class - => (EntityTypeBuilder)HasDefaultFullTextLanguage((EntityTypeBuilder)entityTypeBuilder, language); - - /// - /// Configures a default language to use for full-text search at container scope. - /// - /// - /// See Modeling entity types and relationships, and - /// Accessing Azure Cosmos DB with EF Core for more information and examples. - /// - /// The builder for the entity type being configured. - /// The default language. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - public static IConventionEntityTypeBuilder? HasDefaultFullTextLanguage( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? language, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetDefaultFullTextLanguage(language, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetDefaultFullTextSearchLanguage(language, fromDataAnnotation); - - return entityTypeBuilder; - } - - /// - /// Returns a value indicating whether the default full-text language can be set - /// from the current configuration source - /// - /// - /// See Modeling entity types and relationships, and - /// Accessing Azure Cosmos DB with EF Core for more information and examples. - /// - /// The builder for the entity type being configured. - /// The default language. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetDefaultFullTextLanguage( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? language, - bool fromDataAnnotation = false) - => entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage, language, fromDataAnnotation); - /// /// Configures the manual provisioned throughput offering. /// diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs index 0ef110abb91..02f86c605e1 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs @@ -586,48 +586,4 @@ public static void SetThroughput(this IMutableEntityType entityType, int? throug public static ConfigurationSource? GetThroughputConfigurationSource(this IConventionEntityType entityType) => entityType.FindAnnotation(CosmosAnnotationNames.Throughput) ?.GetConfigurationSource(); - - /// - /// Returns the default language for the full-text search at container scope. - /// - /// The entity type. - /// The default language for the full-text search. - public static string? GetDefaultFullTextSearchLanguage(this IReadOnlyEntityType entityType) - => entityType.BaseType != null - ? entityType.GetRootType().GetDefaultFullTextSearchLanguage() - : (string?)entityType[CosmosAnnotationNames.DefaultFullTextSearchLanguage]; - - /// - /// Sets the default language for the full-text search at container scope. - /// - /// The entity type. - /// The default language for the full-text search. - public static void SetDefaultFullTextSearchLanguage(this IMutableEntityType entityType, string? language) - => entityType.SetOrRemoveAnnotation( - CosmosAnnotationNames.DefaultFullTextSearchLanguage, - language); - - /// - /// Sets the default language for the full-text search at container scope. - /// - /// The entity type. - /// The default language for the full-text search. - /// Indicates whether the configuration was specified using a data annotation. - public static string? SetDefaultFullTextSearchLanguage( - this IConventionEntityType entityType, - string? language, - bool fromDataAnnotation = false) - => (string?)entityType.SetOrRemoveAnnotation( - CosmosAnnotationNames.DefaultFullTextSearchLanguage, - language, - fromDataAnnotation)?.Value; - - /// - /// Gets the for the default full-text search language at container scope. - /// - /// The entity type to find configuration source for. - /// The for the default full-text search language. - public static ConfigurationSource? GetDefaultFullTextSearchLanguageConfigurationSource(this IConventionEntityType entityType) - => entityType.FindAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage) - ?.GetConfigurationSource(); } diff --git a/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs index d81309fbdf9..3edbb250a1a 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs @@ -25,10 +25,14 @@ public static class CosmosIndexBuilderExtensions /// /// The builder for the index being configured. /// The type of vector index to create. + /// The value indicating whether the index is configured as a vector index. /// A builder to further configure the index. - public static IndexBuilder IsVectorIndex(this IndexBuilder indexBuilder, VectorIndexType? indexType) + public static IndexBuilder IsVectorIndex( + this IndexBuilder indexBuilder, + VectorIndexType? indexType, + bool vectorIndex = true) { - indexBuilder.Metadata.SetVectorIndexType(indexType); + indexBuilder.Metadata.SetVectorIndexType(indexType, vectorIndex); return indexBuilder; } @@ -43,10 +47,12 @@ public static IndexBuilder IsVectorIndex(this IndexBuilder indexBuilder, VectorI /// /// The builder for the index being configured. /// The type of vector index to create. + /// The value indicating whether the index is configured as a vector index. /// A builder to further configure the index. public static IndexBuilder IsVectorIndex( this IndexBuilder indexBuilder, - VectorIndexType? indexType) + VectorIndexType? indexType, + bool vectorIndex = true) => (IndexBuilder)IsVectorIndex((IndexBuilder)indexBuilder, indexType); /// @@ -104,11 +110,11 @@ public static bool CanSetVectorIndexType( /// Accessing Azure Cosmos DB with EF Core for more information and examples. /// /// The builder for the index being configured. - /// The value indicating whether the index is configured for Full-text search. + /// The value indicating whether the index is configured for Full-text search. /// A builder to further configure the index. - public static IndexBuilder IsFullTextIndex(this IndexBuilder indexBuilder, bool? value = true) + public static IndexBuilder IsFullTextIndex(this IndexBuilder indexBuilder, bool fullTextIndex = true) { - indexBuilder.Metadata.SetIsFullTextIndex(value); + indexBuilder.Metadata.SetIsFullTextIndex(fullTextIndex); return indexBuilder; } @@ -122,12 +128,12 @@ public static IndexBuilder IsFullTextIndex(this IndexBuilder indexBuilder, bool? /// Accessing Azure Cosmos DB with EF Core for more information and examples. /// /// The builder for the index being configured. - /// The value indicating whether the index is configured for Full-text search. + /// The value indicating whether the index is configured for Full-text search. /// A builder to further configure the index. public static IndexBuilder IsFullTextIndex( this IndexBuilder indexBuilder, - bool? value = true) - => (IndexBuilder)IsFullTextIndex((IndexBuilder)indexBuilder, value); + bool fullTextIndex = true) + => (IndexBuilder)IsFullTextIndex((IndexBuilder)indexBuilder, fullTextIndex); /// /// Configures the index as a full-text index. @@ -138,7 +144,7 @@ public static IndexBuilder IsFullTextIndex( /// Accessing Azure Cosmos DB with EF Core for more information and examples. /// /// The builder for the index being configured. - /// The value indicating whether the index is configured for Full-text search. + /// The value indicating whether the index is configured for Full-text search. /// Indicates whether the configuration was specified using a data annotation. /// /// The same builder instance if the configuration was applied, @@ -146,12 +152,12 @@ public static IndexBuilder IsFullTextIndex( /// public static IConventionIndexBuilder? IsFullTextIndex( this IConventionIndexBuilder indexBuilder, - bool? value, + bool? fullTextIndex, bool fromDataAnnotation = false) { if (indexBuilder.CanSetIsFullTextIndex(fromDataAnnotation)) { - indexBuilder.Metadata.SetIsFullTextIndex(value, fromDataAnnotation); + indexBuilder.Metadata.SetIsFullTextIndex(fullTextIndex, fromDataAnnotation); return indexBuilder; } @@ -166,12 +172,12 @@ public static IndexBuilder IsFullTextIndex( /// Accessing Azure Cosmos DB with EF Core for more information and examples. /// /// The builder for the index being configured. - /// The value indicating whether the index is configured for Full-text search. + /// The value indicating whether the index is configured for Full-text search. /// Indicates whether the configuration was specified using a data annotation. /// if the index can be configured as a Full-text index. public static bool CanSetIsFullTextIndex( this IConventionIndexBuilder indexBuilder, - bool? value, + bool? fullTextIndex, bool fromDataAnnotation = false) - => indexBuilder.CanSetAnnotation(CosmosAnnotationNames.FullTextIndex, value, fromDataAnnotation); + => indexBuilder.CanSetAnnotation(CosmosAnnotationNames.FullTextIndex, fullTextIndex, fromDataAnnotation); } diff --git a/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs index 5aabcc0f8b6..078262d9f69 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs @@ -32,20 +32,32 @@ public static class CosmosIndexExtensions /// /// The index. /// The index type to use. - public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType? indexType) - => index.SetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType); + /// The value indicating whether the index is configured as a vector index. + public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType? indexType, bool? vectorIndex) + { + if (vectorIndex == true) + { + index.SetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType); + } + else + { + index.RemoveAnnotation(CosmosAnnotationNames.VectorIndexType); + } + } /// /// Sets the vector index type to use, such as "flat", "diskANN", or "quantizedFlat". /// See Vector Search in Azure Cosmos DB for NoSQL for more information. /// - /// The index type to use. /// The index. + /// The index type to use. + /// The value indicating whether the index is configured as a vector index. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. public static string? SetVectorIndexType( this IConventionIndex index, VectorIndexType? indexType, + bool? vectorIndex, bool fromDataAnnotation = false) => (string?)index.SetAnnotation( CosmosAnnotationNames.VectorIndexType, @@ -76,25 +88,25 @@ public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType? /// See Full-text search in Azure Cosmos DB for NoSQL for more information. /// /// The index. - /// The value indicating whether the index is configured for full-text search. - public static void SetIsFullTextIndex(this IMutableIndex index, bool? value) - => index.SetAnnotation(CosmosAnnotationNames.FullTextIndex, value); + /// The value indicating whether the index is configured for full-text search. + public static void SetIsFullTextIndex(this IMutableIndex index, bool? fullTextIndex) + => index.SetAnnotation(CosmosAnnotationNames.FullTextIndex, fullTextIndex); /// /// Configures the index for full-text search. /// See Full-text search in Azure Cosmos DB for NoSQL for more information. /// /// The index. - /// The value indicating whether the index is configured for full-text search. + /// The value indicating whether the index is configured for full-text search. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. public static string? SetIsFullTextIndex( this IConventionIndex index, - bool? value, + bool? fullTextIndex, bool fromDataAnnotation = false) => (string?)index.SetAnnotation( CosmosAnnotationNames.FullTextIndex, - value, + fullTextIndex, fromDataAnnotation)?.Value; /// diff --git a/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs index fa04b61a020..d5b25616597 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs @@ -257,4 +257,70 @@ public static bool CanSetThroughput( ? existingThroughput?.Throughput == throughput : existingThroughput?.AutoscaleMaxThroughput == throughput; } + + /// + /// Configures a default language to use for full-text search at database scope. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The model builder. + /// The default language. + /// The same builder instance so that multiple calls can be chained. + public static ModelBuilder HasDefaultFullTextLanguage( + this ModelBuilder modelBuilder, + string? language) + { + modelBuilder.Model.SetDefaultFullTextSearchLanguage(language); + + return modelBuilder; + } + + /// + /// Configures a default language to use for full-text search at database scope. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The model builder. + /// The default language. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionModelBuilder? HasDefaultFullTextLanguage( + this IConventionModelBuilder modelBuilder, + string? language, + bool fromDataAnnotation = false) + { + if (!modelBuilder.CanSetDefaultFullTextLanguage(language, fromDataAnnotation)) + { + return null; + } + + modelBuilder.Metadata.SetDefaultFullTextSearchLanguage(language, fromDataAnnotation); + + return modelBuilder; + } + + /// + /// Returns a value indicating whether the default full-text language can be set + /// from the current configuration source + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The model builder. + /// The default language. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetDefaultFullTextLanguage( + this IConventionModelBuilder modelBuilder, + string? language, + bool fromDataAnnotation = false) + => modelBuilder.CanSetAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage, language, fromDataAnnotation); } diff --git a/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs index 93c56dbdaba..be6f8f79366 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs @@ -197,4 +197,46 @@ public static void SetThroughput(this IMutableModel model, int? throughput, bool public static ConfigurationSource? GetThroughputConfigurationSource(this IConventionModel model) => model.FindAnnotation(CosmosAnnotationNames.Throughput) ?.GetConfigurationSource(); + + /// + /// Returns the default language for the full-text search at database scope. + /// + /// The model. + /// The default language for the full-text search. + public static string? GetDefaultFullTextSearchLanguage(this IReadOnlyModel model) + => (string?)model[CosmosAnnotationNames.DefaultFullTextSearchLanguage]; + + /// + /// Sets the default language for the full-text search at database scope. + /// + /// The model. + /// The default language for the full-text search. + public static void SetDefaultFullTextSearchLanguage(this IMutableModel model, string? language) + => model.SetOrRemoveAnnotation( + CosmosAnnotationNames.DefaultFullTextSearchLanguage, + language); + + /// + /// Sets the default language for the full-text search at database scope. + /// + /// The model. + /// The default language for the full-text search. + /// Indicates whether the configuration was specified using a data annotation. + public static string? SetDefaultFullTextSearchLanguage( + this IConventionModel model, + string? language, + bool fromDataAnnotation = false) + => (string?)model.SetOrRemoveAnnotation( + CosmosAnnotationNames.DefaultFullTextSearchLanguage, + language, + fromDataAnnotation)?.Value; + + /// + /// Gets the for the default full-text search language at database scope. + /// + /// The model type to find configuration source for. + /// The for the default full-text search language. + public static ConfigurationSource? GetDefaultFullTextSearchLanguageConfigurationSource(this IConventionModel model) + => model.FindAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage) + ?.GetConfigurationSource(); } diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs index 1f6e0f477fb..8fd7565bc71 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs @@ -169,11 +169,10 @@ protected virtual void ValidateSharedContainerCompatibility( IDiagnosticsLogger logger) { var discriminatorValues = new Dictionary(); - List partitionKeyStoreNames = new(); + List partitionKeyStoreNames = []; int? analyticalTtl = null; int? defaultTtl = null; ThroughputProperties? throughput = null; - string? defaultFullTextSearchLanguage = null; IEntityType? firstEntityType = null; bool? isDiscriminatorMappingComplete = null; @@ -338,27 +337,6 @@ protected virtual void ValidateSharedContainerCompatibility( CosmosStrings.ThroughputTypeMismatch(manualType.DisplayName(), autoscaleType.DisplayName(), container)); } } - - var currentFullTextSearchDefaultLanguage = entityType.GetDefaultFullTextSearchLanguage(); - if (currentFullTextSearchDefaultLanguage != null) - { - if (defaultFullTextSearchLanguage == null) - { - defaultFullTextSearchLanguage = currentFullTextSearchDefaultLanguage; - } - else if (defaultFullTextSearchLanguage != currentFullTextSearchDefaultLanguage) - { - var conflictingEntityType = mappedTypes.First(et => et.GetDefaultFullTextSearchLanguage() != null); - - throw new InvalidOperationException( - CosmosStrings.FullTextSearchDefaultLanguageMismatch( - defaultFullTextSearchLanguage, - conflictingEntityType.DisplayName(), - entityType.DisplayName(), - currentFullTextSearchDefaultLanguage, - container)); - } - } } } diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs index 425c3db1266..ff9b790d385 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs @@ -187,14 +187,6 @@ public static string FullTextSearchConfiguredForUnsupportedPropertyType(object? GetString("FullTextSearchConfiguredForUnsupportedPropertyType", nameof(entityType), nameof(property), nameof(clrType)), entityType, property, clrType); - /// - /// The default full-text search language was configured to '{defaultLanguage1}' on '{entityType1}', but on '{entityType2}' it was configured to '{defaultLanguage2}'. All entity types mapped to the same container '{container}' must be configured with the same default full-text search language. - /// - public static string FullTextSearchDefaultLanguageMismatch(object? defaultLanguage1, object? entityType1, object? entityType2, object? defaultLanguage2, object? container) - => string.Format( - GetString("FullTextSearchDefaultLanguageMismatch", nameof(defaultLanguage1), nameof(entityType1), nameof(entityType2), nameof(defaultLanguage2), nameof(container)), - defaultLanguage1, entityType1, entityType2, defaultLanguage2, container); - /// /// 'HasShadowId' was called on a non-root entity type '{entityType}'. JSON 'id' configuration can only be made on the document root. /// diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx index 6fa5a8ecbed..b42289a5704 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx @@ -183,9 +183,6 @@ Property '{entityType}.{property}' was configured for full-text search, but has type '{clrType}'; only string properties can be configured for full-text search. - - The default full-text search language was configured to '{defaultLanguage1}' on '{entityType1}', but on '{entityType2}' it was configured to '{defaultLanguage2}'. All entity types mapped to the same container '{container}' must be configured with the same default full-text search language. - 'HasShadowId' was called on a non-root entity type '{entityType}'. JSON 'id' configuration can only be made on the document root. diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs index 4dc784ec67a..8d475d7268d 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs @@ -117,6 +117,7 @@ private static IEnumerable GetContainersToCreate(IModel mod mappedTypes.Add(entityType); } + var defaultFullTextLanguage = model.GetDefaultFullTextSearchLanguage(); foreach (var (containerName, mappedTypes) in containers) { IReadOnlyList partitionKeyStoreNames = Array.Empty(); @@ -125,7 +126,6 @@ private static IEnumerable GetContainersToCreate(IModel mod ThroughputProperties? throughput = null; var indexes = new List(); var vectors = new List<(IProperty Property, CosmosVectorType VectorType)>(); - string? defaultFullTextLanguage = null; var fullTextProperties = new List<(IProperty Property, string? Language)>(); foreach (var entityType in mappedTypes) @@ -138,7 +138,6 @@ private static IEnumerable GetContainersToCreate(IModel mod analyticalTtl ??= entityType.GetAnalyticalStoreTimeToLive(); defaultTtl ??= entityType.GetDefaultTimeToLive(); throughput ??= entityType.GetThroughput(); - defaultFullTextLanguage ??= entityType.GetDefaultFullTextSearchLanguage(); ProcessEntityType(entityType, indexes, vectors, fullTextProperties); } diff --git a/src/EFCore.Relational/Extensions/RelationalComplexCollectionBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexCollectionBuilderExtensions.cs index bcd09203a6d..97196af09aa 100644 --- a/src/EFCore.Relational/Extensions/RelationalComplexCollectionBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalComplexCollectionBuilderExtensions.cs @@ -38,7 +38,7 @@ public static ComplexCollectionBuilder ToJson( public static ComplexCollectionBuilder ToJson( this ComplexCollectionBuilder complexCollectionBuilder, string? jsonColumnName = null) - where TComplex : class + where TComplex : notnull => (ComplexCollectionBuilder)ToJson((ComplexCollectionBuilder)complexCollectionBuilder, jsonColumnName); /// diff --git a/src/EFCore.Relational/Extensions/RelationalComplexCollectionTypePropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexCollectionTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..136edb2463e --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalComplexCollectionTypePropertyBuilderExtensions.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalComplexCollectionTypePropertyBuilderExtensions +{ + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionTypePropertyBuilder HasJsonPropertyName( + this ComplexCollectionTypePropertyBuilder propertyBuilder, + string? name) + { + Check.NullButNotEmpty(name); + + propertyBuilder.Metadata.SetJsonPropertyName(name); + + return propertyBuilder; + } + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexCollectionTypePropertyBuilder HasJsonPropertyName( + this ComplexCollectionTypePropertyBuilder propertyBuilder, + string? name) + => (ComplexCollectionTypePropertyBuilder)HasJsonPropertyName((ComplexCollectionTypePropertyBuilder)propertyBuilder, name); +} diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index ea8979aeb5e..0177582274d 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -2097,7 +2097,7 @@ public static void SetJsonPropertyName(this IMutableProperty property, string? n : (string?)property[RelationalAnnotationNames.DefaultConstraintName] ?? (ShouldHaveDefaultConstraintName(property) && StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) is StoreObjectIdentifier table - ? property.GenerateDefaultConstraintName(table) + ? property.GetDefaultDefaultConstraintName(table) : null); /// @@ -2110,7 +2110,7 @@ public static void SetJsonPropertyName(this IMutableProperty property, string? n ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) : (string?)property[RelationalAnnotationNames.DefaultConstraintName] ?? (ShouldHaveDefaultConstraintName(property) - ? property.GenerateDefaultConstraintName(storeObject) + ? property.GetDefaultDefaultConstraintName(storeObject) : null); private static bool ShouldHaveDefaultConstraintName(IReadOnlyProperty property) @@ -2123,7 +2123,7 @@ private static bool ShouldHaveDefaultConstraintName(IReadOnlyProperty property) /// /// The property. /// The store object identifier to generate the name for. - public static string GenerateDefaultConstraintName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + public static string GetDefaultDefaultConstraintName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { var candidate = $"DF_{storeObject.Name}_{property.GetColumnName(storeObject)}"; diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs index 24fbda9033a..05e17e4a1b0 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs @@ -277,7 +277,7 @@ public override IEnumerable For(IColumn column, bool designTime) // (we already checked that in the finalize model convention step) yield return new Annotation( RelationalAnnotationNames.DefaultConstraintName, - mappedProperty.GenerateDefaultConstraintName(table)); + mappedProperty.GetDefaultDefaultConstraintName(table)); } } } diff --git a/src/EFCore/ChangeTracking/Internal/InternalComplexEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalComplexEntry.cs index b460e303363..5b8be17c366 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalComplexEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalComplexEntry.cs @@ -273,10 +273,7 @@ public override void AcceptChanges() /// public string GetPropertyPath(IReadOnlyProperty property) { - Check.DebugAssert(property.DeclaringType == StructuralType - || property.DeclaringType.ContainingType == StructuralType - || StructuralType.ClrType == typeof(object), // For testing - "Property " + property.Name + " not contained under " + StructuralType.Name); + StructuralType.CheckContains(property); return GetPropertyPath() + "." + GetShortNameChain(property.DeclaringType) + property.Name; } diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index 2e42fcd12a6..f4bb13c6d81 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -164,7 +164,7 @@ protected virtual Expression CreateSnapshotExpression( { Expression.Assign( structuralTypeVariable, - propertyBases[0]!.DeclaringType switch + (IRuntimeTypeBase)propertyBases[0]!.DeclaringType switch { IComplexType declaringComplexType when declaringComplexType.ComplexProperty.IsCollection => PropertyAccessorsFactory.CreateComplexCollectionElementAccess( @@ -175,7 +175,7 @@ IComplexType declaringComplexType when declaringComplexType.ComplexProperty.IsCo indicesExpression, fromDeclaringType: false, fromEntity: true), - { ContainingType: IComplexType collectionComplexType } + { ContainingEntryType: IComplexType collectionComplexType } => PropertyAccessorsFactory.CreateComplexCollectionElementAccess( collectionComplexType.ComplexProperty, Expression.Convert( diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs index 71be133c83e..e27b4011792 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder.cs @@ -119,7 +119,7 @@ public virtual ComplexCollectionBuilder IsRequired(bool required = true) /// /// The name of the property to be configured. /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder Property(string propertyName) + public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName) => new( TypeBuilder.Property( Check.NotEmpty(propertyName), @@ -139,7 +139,7 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName) /// The type of the property to be configured. /// The name of the property to be configured. /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder Property(string propertyName) + public virtual ComplexCollectionTypePropertyBuilder Property(string propertyName) => new( TypeBuilder.Property( typeof(TProperty), @@ -159,7 +159,7 @@ public virtual ComplexTypePropertyBuilder Property(string /// The type of the property to be configured. /// The name of the property to be configured. /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder Property(Type propertyType, string propertyName) + public virtual ComplexCollectionTypePropertyBuilder Property(Type propertyType, string propertyName) => new( TypeBuilder.Property( Check.NotNull(propertyType), @@ -237,7 +237,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type pr /// The type of the property to be configured. /// The name of the property to be configured. /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder IndexerProperty + public virtual ComplexCollectionTypePropertyBuilder IndexerProperty <[DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] TProperty>(string propertyName) => new( TypeBuilder.IndexerProperty( @@ -256,7 +256,7 @@ public virtual ComplexTypePropertyBuilder IndexerProperty /// The type of the property to be configured. /// The name of the property to be configured. /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder IndexerProperty( + public virtual ComplexCollectionTypePropertyBuilder IndexerProperty( [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, string propertyName) { @@ -586,7 +586,7 @@ public virtual ComplexCollectionBuilder ComplexCollection /// The name of the property to be configured. /// An action that performs configuration of the property. - /// An object that can be used to configure the property. + /// The same builder instance so that multiple configuration calls can be chained. public virtual ComplexCollectionBuilder ComplexCollection(string propertyName, Action buildAction) { Check.NotNull(buildAction); @@ -728,7 +728,7 @@ public virtual ComplexCollectionBuilder ComplexCollection( /// The type of the property to be configured. /// The name of the property to be configured. /// An action that performs configuration of the property. - /// An object that can be used to configure the property. + /// The same builder instance so that multiple configuration calls can be chained. public virtual ComplexCollectionBuilder ComplexCollection(Type propertyType, string propertyName, Action buildAction) { Check.NotNull(buildAction); diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs index b5c81a7fcc3..a2929ae24f9 100644 --- a/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs @@ -72,7 +72,7 @@ public ComplexCollectionBuilder(IMutableComplexProperty complexProperty) /// blog => blog.Url). /// /// An object that can be used to configure the property. - public virtual ComplexTypePropertyBuilder Property(Expression> propertyExpression) + public virtual ComplexCollectionTypePropertyBuilder Property(Expression> propertyExpression) => new( TypeBuilder.Property( Check.NotNull(propertyExpression).GetMemberAccess(), ConfigurationSource.Explicit)! diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder.cs new file mode 100644 index 00000000000..6a27c5dbb2d --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder.cs @@ -0,0 +1,548 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a on a complex collection type. +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexCollectionTypePropertyBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexCollectionTypePropertyBuilder(IMutableProperty property) + { + Check.NotNull(property); + + Builder = ((Property)property).Builder; + } + + /// + /// The internal builder being used to configure the property. + /// + IConventionPropertyBuilder IInfrastructure.Instance + => Builder; + + private InternalPropertyBuilder Builder { get; } + + /// + /// The property being configured. + /// + public virtual IMutableProperty Metadata + => Builder.Metadata; + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder IsRequired(bool required = true) + { + Builder.IsRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public virtual ComplexCollectionTypePropertyBuilder HasSentinel(object? sentinel) + { + Builder.HasSentinel(sentinel, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder IsUnicode(bool unicode = true) + { + Builder.IsUnicode(unicode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + { + Builder.HasValueGenerator(typeof(TGenerator), ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType) + { + Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => HasValueGeneratorFactory(typeof(TFactory)); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType) + { + Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasField(string fieldName) + { + Check.NotEmpty(fieldName); + + Builder.HasField(fieldName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion>() + => HasConversion(typeof(TConversion)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? conversionType) + { + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion(converter, null, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion>( + ValueComparer? valueComparer) + => HasConversion(typeof(TConversion), valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// 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 ComplexCollectionTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion(typeof(TConversion), valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + ValueComparer? valueComparer) + => HasConversion(conversionType, valueComparer, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + { + Check.NotNull(conversionType); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => HasConversion(converter, valueComparer, null); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + { + Builder.HasConversion(converter, ConfigurationSource.Explicit); + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer>() + where TComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer), typeof(TProviderComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) + => HasConversion(conversionType, comparerType, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerComparerType) + { + Check.NotNull(conversionType); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparerType, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder`.cs new file mode 100644 index 00000000000..de6fd0abe56 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexCollectionTypePropertyBuilder`.cs @@ -0,0 +1,515 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a on a complex collection type. +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexCollectionTypePropertyBuilder : ComplexCollectionTypePropertyBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexCollectionTypePropertyBuilder(IMutableProperty property) + : base(property) + { + } + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasAnnotation(string annotation, object? value) + => (ComplexCollectionTypePropertyBuilder)base.HasAnnotation(annotation, value); + + /// + /// Configures whether this property must have a value assigned or whether null is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder IsRequired(bool required = true) + => (ComplexCollectionTypePropertyBuilder)base.IsRequired(required); + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public new virtual ComplexCollectionTypePropertyBuilder HasSentinel(object? sentinel) + => (ComplexCollectionTypePropertyBuilder)base.HasSentinel(sentinel); + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public virtual ComplexCollectionTypePropertyBuilder HasSentinel(TProperty? sentinel) + => (ComplexCollectionTypePropertyBuilder)base.HasSentinel(sentinel); + + /// + /// Configures the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder IsUnicode(bool unicode = true) + => (ComplexCollectionTypePropertyBuilder)base.IsUnicode(unicode); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + => (ComplexCollectionTypePropertyBuilder)base.HasValueGenerator(); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting null does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? valueGeneratorType) + => (ComplexCollectionTypePropertyBuilder)base.HasValueGenerator(valueGeneratorType); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => (ComplexCollectionTypePropertyBuilder)base.HasValueGeneratorFactory(); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] + Type? valueGeneratorFactoryType) + => (ComplexCollectionTypePropertyBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType); + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasField(string fieldName) + => (ComplexCollectionTypePropertyBuilder)base.HasField(fieldName); + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexCollectionTypePropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerClrType) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(providerClrType); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression), + Check.NotNull(convertFromProviderExpression))); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion((ValueConverter?)converter); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(converter); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + ValueComparer? valueComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(conversionType, valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(conversionType, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression), + Check.NotNull(convertFromProviderExpression)), + valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression), + Check.NotNull(convertFromProviderExpression)), + valueComparer, + providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// 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 ComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => HasConversion((ValueConverter?)converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion((ValueConverter?)converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer>() + where TComparer : ValueComparer + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(conversionType, comparerType); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexCollectionTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type? providerComparerType) + => (ComplexCollectionTypePropertyBuilder)base.HasConversion(conversionType, comparerType, providerComparerType); +} diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 7e056624bdb..2e20cb235ee 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -37,12 +37,6 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable new IConventionEntityType ContainingEntityType => (IConventionEntityType)this; - /// - /// Gets this entity type or the closest collection property in the complex property chain. - /// - new IConventionTypeBase ContainingType - => this; - /// /// Gets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. /// diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index 9c17325656e..69efecea0e5 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -32,12 +32,6 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable new IMutableEntityType ContainingEntityType => (IMutableEntityType)this; - /// - /// Gets this entity type or the closest collection property in the complex property chain. - /// - new IMutableTypeBase ContainingType - => this; - /// /// Gets or sets the base type of this type. Returns if this is not a derived type in an inheritance /// hierarchy. diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index f5d04a545d7..86fc3734eee 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -25,12 +25,6 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable IReadOnlyEntityType ContainingEntityType => (IReadOnlyEntityType)this; - /// - /// Gets this entity type or the closest collection property in the complex property chain. - /// - IReadOnlyTypeBase ContainingType - => this; - /// /// Gets the base type of this type. Returns if this is not a /// derived type in an inheritance hierarchy. diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index b037a700f94..903d909e292 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -22,12 +22,6 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable new IEntityType ContainingEntityType => (IEntityType)this; - /// - /// Gets the containing entity type or the element type of the closest complex collection property in the complex property chain from the containing entity type. - /// - new ITypeBase ContainingType - => this; - /// /// Gets the base type of this type. Returns if this is not a derived type in an inheritance /// hierarchy. diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs index b3929de7122..e380d2693c6 100644 --- a/src/EFCore/Metadata/Internal/ComplexType.cs +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -104,10 +104,10 @@ public virtual EntityType ContainingEntityType /// any release. You should only use it directly in your 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 TypeBase ContainingType + public virtual TypeBase ContainingEntryType => ComplexProperty.DeclaringType switch { - ComplexType declaringComplexType when !declaringComplexType.ComplexProperty.IsCollection => declaringComplexType.ContainingType, + ComplexType declaringComplexType when !declaringComplexType.ComplexProperty.IsCollection => declaringComplexType.ContainingEntryType, _ => ComplexProperty.DeclaringType }; @@ -657,46 +657,10 @@ IEntityType ITypeBase.ContainingEntityType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IReadOnlyTypeBase IReadOnlyTypeBase.ContainingType + IRuntimeTypeBase IRuntimeTypeBase.ContainingEntryType { [DebuggerStepThrough] - get => ContainingType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IMutableTypeBase IMutableTypeBase.ContainingType - { - [DebuggerStepThrough] - get => ContainingType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionTypeBase IConventionTypeBase.ContainingType - { - [DebuggerStepThrough] - get => ContainingType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - ITypeBase ITypeBase.ContainingType - { - [DebuggerStepThrough] - get => ContainingType; + get => ContainingEntryType; } /// diff --git a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs index e40fd042c1b..a7411e199bc 100644 --- a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs @@ -21,6 +21,18 @@ public interface IRuntimeTypeBase : ITypeBase /// PropertyCounts CalculateCounts(); + /// + /// Gets this entity type or the closest collection complex type in the complex property chain. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IRuntimeTypeBase ContainingEntryType + => this; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs index c2aef88f3fb..80abde1b829 100644 --- a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs @@ -43,9 +43,10 @@ public static T CheckContains(this IReadOnlyTypeBase structuralType, T proper Check.NotNull(property); return !property.DeclaringType.IsAssignableFrom(structuralType) - && (!property.DeclaringType.ContainingType.IsAssignableFrom(structuralType) + && (!((IRuntimeTypeBase)property.DeclaringType).ContainingEntryType.IsAssignableFrom(structuralType) || (property.DeclaringType is IComplexType complexType && complexType.ComplexProperty.IsCollection)) + && structuralType.ClrType != typeof(object) // For testing ? throw new InvalidOperationException( CoreStrings.PropertyDoesNotBelong(property.Name, property.DeclaringType.DisplayName(), structuralType.DisplayName())) : property; diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index f14b6d72d00..dd71a31f3fc 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -54,9 +54,9 @@ public RuntimeComplexType( RuntimeComplexType declaringComplexType => declaringComplexType.ContainingEntityType, _ => throw new NotImplementedException() }; - ContainingType = ComplexProperty.DeclaringType switch + ContainingEntryType = ComplexProperty.DeclaringType switch { - RuntimeComplexType declaringComplexType when !declaringComplexType.ComplexProperty.IsCollection => declaringComplexType.ContainingType, + RuntimeComplexType declaringComplexType when !declaringComplexType.ComplexProperty.IsCollection => declaringComplexType.ContainingEntryType, _ => ComplexProperty.DeclaringType }; } @@ -72,10 +72,9 @@ public RuntimeComplexType( /// public virtual RuntimeComplexProperty ComplexProperty { get; } - private RuntimeEntityType ContainingEntityType { get; } - private RuntimeTypeBase ContainingType { get; } + private RuntimeTypeBase ContainingEntryType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -278,22 +277,10 @@ IEntityType ITypeBase.ContainingEntityType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IReadOnlyTypeBase IReadOnlyTypeBase.ContainingType - { - [DebuggerStepThrough] - get => ContainingType; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - ITypeBase ITypeBase.ContainingType + IRuntimeTypeBase IRuntimeTypeBase.ContainingEntryType { [DebuggerStepThrough] - get => ContainingType; + get => ContainingEntryType; } /// diff --git a/test/EFCore.Cosmos.FunctionalTests/AddHocVectorSearchCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/AdHocVectorSearchCosmosTest.cs similarity index 96% rename from test/EFCore.Cosmos.FunctionalTests/AddHocVectorSearchCosmosTest.cs rename to test/EFCore.Cosmos.FunctionalTests/AdHocVectorSearchCosmosTest.cs index 0bbfcb49a35..4cbb276cc93 100644 --- a/test/EFCore.Cosmos.FunctionalTests/AddHocVectorSearchCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/AdHocVectorSearchCosmosTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; [CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)] -public class AddHocVectorSearchCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture +public class AdHocVectorSearchCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocVectorSearchTests"; diff --git a/test/EFCore.Cosmos.FunctionalTests/AddHocFullTextSearchCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/AddHocFullTextSearchCosmosTest.cs index 93f36b59633..b8c9f8e1a53 100644 --- a/test/EFCore.Cosmos.FunctionalTests/AddHocFullTextSearchCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/AddHocFullTextSearchCosmosTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; [CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)] -public class AddHocFullTextSearchCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture +public class AdHocFullTextSearchCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocFullTextSearchTests"; @@ -162,34 +162,22 @@ public class Entity } protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(b => - { - b.ToContainer("Entities").HasDefaultFullTextLanguage("xx-YY"); - b.HasPartitionKey(x => x.PartitionKey); - b.Property(x => x.Name).EnableFullTextSearch(); - b.HasIndex(x => x.Name).IsFullTextIndex(); - }); + { + modelBuilder.HasDefaultFullTextLanguage("xx-YY"); + modelBuilder.Entity(b => + { + b.ToContainer("Entities"); + b.HasPartitionKey(x => x.PartitionKey); + b.Property(x => x.Name).EnableFullTextSearch(); + b.HasIndex(x => x.Name).IsFullTextIndex(); + }); + } } #endregion #region DefaultFullTextSearchLanguageMismatch - [ConditionalFact] - public async Task Set_different_full_text_search_default_language_for_the_same_container() - { - var message = (await Assert.ThrowsAsync( - () => InitializeAsync())).Message; - - Assert.Equal( - CosmosStrings.FullTextSearchDefaultLanguageMismatch( - "pl-PL", - nameof(ContextDefaultFullTextSearchLanguageMismatch.Entity1), - nameof(ContextDefaultFullTextSearchLanguageMismatch.Entity2), - "en-US", - "Entities"), message); - } - protected class ContextDefaultFullTextSearchLanguageMismatch(DbContextOptions options) : DbContext(options) { public DbSet Entities1 { get; set; } = null!; @@ -213,9 +201,10 @@ public class Entity2 protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.HasDefaultFullTextLanguage("pl-PL"); modelBuilder.Entity(b => { - b.ToContainer("Entities").HasDefaultFullTextLanguage("pl-PL"); + b.ToContainer("Entities"); b.HasPartitionKey(x => x.PartitionKey); b.Property(x => x.Name).EnableFullTextSearch(); b.HasIndex(x => x.Name).IsFullTextIndex(); @@ -223,7 +212,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(b => { - b.ToContainer("Entities").HasDefaultFullTextLanguage("en-US"); + b.ToContainer("Entities"); b.HasPartitionKey(x => x.PartitionKey); b.Property(x => x.Name).EnableFullTextSearch(); b.HasIndex(x => x.Name).IsFullTextIndex(); @@ -276,6 +265,7 @@ public class Entity3 protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.HasDefaultFullTextLanguage("xx-YY"); modelBuilder.Entity(b => { b.ToContainer("Entities"); @@ -286,7 +276,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(b => { - b.ToContainer("Entities").HasDefaultFullTextLanguage("xx-YY"); + b.ToContainer("Entities"); b.HasPartitionKey(x => x.PartitionKey); b.Property(x => x.Name).EnableFullTextSearch(); b.HasIndex(x => x.Name).IsFullTextIndex(); @@ -328,13 +318,16 @@ public class Entity } protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(b => { - b.ToContainer("Entities").HasDefaultFullTextLanguage("xx-YY"); - b.HasPartitionKey(x => x.PartitionKey); - b.Property(x => x.Name).EnableFullTextSearch(); - b.HasIndex(x => x.Name).IsFullTextIndex(); - }); + modelBuilder.HasDefaultFullTextLanguage("xx-YY"); + modelBuilder.Entity(b => + { + b.ToContainer("Entities"); + b.HasPartitionKey(x => x.PartitionKey); + b.Property(x => x.Name).EnableFullTextSearch(); + b.HasIndex(x => x.Name).IsFullTextIndex(); + }); + } } #endregion @@ -363,13 +356,16 @@ public class Entity } protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(b => { - b.ToContainer("Entities").HasDefaultFullTextLanguage("en-US"); - b.HasPartitionKey(x => x.PartitionKey); - b.Property(x => x.Name).EnableFullTextSearch("xx-YY"); - b.HasIndex(x => x.Name).IsFullTextIndex(); - }); + modelBuilder.HasDefaultFullTextLanguage("en-US"); + modelBuilder.Entity(b => + { + b.ToContainer("Entities"); + b.HasPartitionKey(x => x.PartitionKey); + b.Property(x => x.Name).EnableFullTextSearch("xx-YY"); + b.HasIndex(x => x.Name).IsFullTextIndex(); + }); + } } #endregion diff --git a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs index 4eca9446a7b..b3f236038e5 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -843,11 +843,6 @@ public override void Can_set_complex_property_annotation() Notes (List) Element type: string Required", complexCollection.ToDebugString(), ignoreLineEndingDifferences: true); } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] - public override void Properties_can_be_set_to_generate_values_on_Add() - { - } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] public override void Access_mode_can_be_overridden_at_entity_and_property_levels() { @@ -863,11 +858,6 @@ public override void Can_set_property_annotation() { } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] - public override void Properties_can_set_row_version() - { - } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] public override void Can_set_property_annotation_by_type() { @@ -883,11 +873,6 @@ public override void Value_converter_configured_on_non_nullable_type_is_applied( { } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] - public override void Can_set_precision_and_scale_for_properties() - { - } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] public override void Properties_can_be_ignored_by_type() { @@ -958,16 +943,6 @@ public override void Can_ignore_shadow_properties_when_they_have_been_added_expl { } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] - public override void Can_set_max_length_for_properties() - { - } - - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] - public override void Properties_can_be_made_concurrency_tokens() - { - } - [ConditionalFact(Skip = "Issue #31253: Complex type collections are not supported in Cosmos")] public override void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() { diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index b088d2a608c..1aec9dff2ef 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -435,6 +435,7 @@ await database.Value.GetCosmosDBSqlContainers().CreateOrUpdateAsync( mappedTypes.Add(entityType); } + var fullTextDefaultLanguage = model.GetDefaultFullTextSearchLanguage(); foreach (var (containerName, mappedTypes) in containers) { IReadOnlyList partitionKeyStoreNames = Array.Empty(); @@ -443,7 +444,6 @@ await database.Value.GetCosmosDBSqlContainers().CreateOrUpdateAsync( ThroughputProperties? throughput = null; var indexes = new List(); var vectors = new List<(IProperty Property, CosmosVectorType VectorType)>(); - string? fullTextDefaultLanguage = null; var fullTextProperties = new List<(IProperty Property, string? Language)>(); foreach (var entityType in mappedTypes) @@ -456,7 +456,6 @@ await database.Value.GetCosmosDBSqlContainers().CreateOrUpdateAsync( analyticalTtl ??= entityType.GetAnalyticalStoreTimeToLive(); defaultTtl ??= entityType.GetDefaultTimeToLive(); throughput ??= entityType.GetThroughput(); - fullTextDefaultLanguage ??= entityType.GetDefaultFullTextSearchLanguage(); ProcessEntityType(entityType, indexes, vectors, fullTextProperties); } diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs index 94e409387ac..d47f4281dc8 100644 --- a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs @@ -93,10 +93,7 @@ public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) fieldInfo: typeof(CompiledModelTestBase.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, nullable: true, - maxLength: 64, unicode: false, - precision: 3, - scale: 2, sentinel: ""); details.SetGetter( string (CompiledModelTestBase.PrincipalDerived> entity, IReadOnlyList indices) => ((PrincipalDerivedUnsafeAccessors>.ManyOwned(entity) == null ? throw new InvalidOperationException(CoreStrings.ComplexCollectionNotInitialized("PrincipalDerived", "ManyOwned")) : PrincipalDerivedUnsafeAccessors>.ManyOwned(entity))[indices[0]] == null ? default(string) : (PrincipalDerivedUnsafeAccessors>.ManyOwned(entity) == null ? throw new InvalidOperationException(CoreStrings.ComplexCollectionNotInitialized("PrincipalDerived", "ManyOwned")) : PrincipalDerivedUnsafeAccessors>.ManyOwned(entity))[indices[0]].Details), diff --git a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 813061231ef..c0feb06aa53 100644 --- a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -1646,6 +1646,23 @@ public static ModelBuilderTest.TestComplexTypePropertyBuilder HasJson return builder; } + public static ModelBuilderTest.TestComplexCollectionTypePropertyBuilder HasJsonPropertyName( + this ModelBuilderTest.TestComplexCollectionTypePropertyBuilder builder, + string? name) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasJsonPropertyName(name); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasJsonPropertyName(name); + break; + } + + return builder; + } + public static ModelBuilderTest.TestComplexTypePrimitiveCollectionBuilder HasJsonPropertyName( this ModelBuilderTest.TestComplexTypePrimitiveCollectionBuilder builder, string? name) diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs index 5e1203ad118..f5782e88439 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs @@ -365,51 +365,6 @@ public virtual void Properties_specified_by_string_are_shadow_properties_unless_ Assert.NotEqual(complexType.FindProperty("Gluon").GetShadowIndex(), complexType.FindProperty("Photon").GetShadowIndex()); } - [ConditionalFact] - public virtual void Properties_can_be_made_concurrency_tokens() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder - .Ignore() - .Ignore() - .Entity() - .Ignore(e => e.Quarks) - .ComplexCollection( - e => e.QuarksCollection, - 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 complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - - Assert.False(complexType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); - Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); - Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); - Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); - Assert.False(complexType.FindProperty("Strange").IsConcurrencyToken); - Assert.True(complexType.FindProperty("Top").IsConcurrencyToken); - Assert.False(complexType.FindProperty("Bottom").IsConcurrencyToken); - - Assert.Equal(-1, complexType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); - Assert.Equal(2, complexType.FindProperty("Up").GetOriginalValueIndex()); - Assert.Equal(-1, complexType.FindProperty("Down").GetOriginalValueIndex()); - Assert.Equal(0, complexType.FindProperty("Charm").GetOriginalValueIndex()); - Assert.Equal(-1, complexType.FindProperty("Strange").GetOriginalValueIndex()); - Assert.Equal(1, complexType.FindProperty("Top").GetOriginalValueIndex()); - Assert.Equal(-1, complexType.FindProperty("Bottom").GetOriginalValueIndex()); - - Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, complexType.GetChangeTrackingStrategy()); - } - [ConditionalFact] public virtual void Properties_can_have_access_mode_set() { @@ -922,103 +877,6 @@ protected virtual void Throws_for_incompatible_type() .ComplexCollection, Customer>(nameof(ComplexProperties.Customer))).Message); } - [ConditionalFact] - public virtual void Properties_can_be_set_to_generate_values_on_Add() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder - .Ignore() - .Ignore() - .Entity() - .ComplexCollection( - e => e.QuarksCollection, - b => - { - 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 complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - Assert.Equal(ValueGenerated.Never, complexType.FindProperty(Customer.IdProperty.Name).ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); - Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); - Assert.Equal(ValueGenerated.OnUpdateSometimes, complexType.FindProperty("Charm").ValueGenerated); - Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Strange").ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Top").ValueGenerated); - Assert.Equal(ValueGenerated.OnUpdate, complexType.FindProperty("Bottom").ValueGenerated); - } - - [ConditionalFact] - public virtual void Properties_can_set_row_version() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder - .Ignore() - .Ignore() - .Entity() - .ComplexCollection( - e => e.QuarksCollection, - b => - { - b.Property(e => e.Up).IsRowVersion(); - b.Property(e => e.Down).ValueGeneratedNever(); - b.Property("Charm").IsRowVersion(); - }); - - var model = modelBuilder.FinalizeModel(); - - var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - - Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); - Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Charm").ValueGenerated); - - Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); - Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); - Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); - } - - [ConditionalFact] - public virtual void Can_set_max_length_for_properties() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder - .Ignore() - .Ignore() - .Entity() - .ComplexCollection( - e => e.QuarksCollection, - 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 complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - - Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); - Assert.Equal(100, complexType.FindProperty("Down").GetMaxLength()); - Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); - Assert.Equal(-1, complexType.FindProperty("Strange").GetMaxLength()); - Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); - Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); - } - [ConditionalFact] public virtual void Can_set_max_length_for_property_type() { @@ -1160,46 +1018,6 @@ public virtual void Can_set_unbounded_max_length_for_property_type() Assert.Equal(-1, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] - public virtual void Can_set_precision_and_scale_for_properties() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder - .Ignore() - .Ignore() - .Entity() - .ComplexCollection( - e => e.QuarksCollection, - 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 complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - - Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetPrecision()); - Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetScale()); - Assert.Equal(1, complexType.FindProperty("Up").GetPrecision()); - Assert.Equal(0, complexType.FindProperty("Up").GetScale()); - Assert.Equal(100, complexType.FindProperty("Down").GetPrecision()); - Assert.Equal(10, complexType.FindProperty("Down").GetScale()); - Assert.Equal(1, complexType.FindProperty("Charm").GetPrecision()); - Assert.Equal(0, complexType.FindProperty("Charm").GetScale()); - Assert.Equal(100, complexType.FindProperty("Strange").GetPrecision()); - Assert.Equal(10, complexType.FindProperty("Strange").GetScale()); - Assert.Equal(1, complexType.FindProperty("Top").GetPrecision()); - Assert.Equal(0, complexType.FindProperty("Top").GetScale()); - Assert.Equal(100, complexType.FindProperty("Bottom").GetPrecision()); - Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); - } - [ConditionalFact] public virtual void Can_set_precision_and_scale_for_property_type() { @@ -1427,15 +1245,8 @@ public virtual void PropertyBuilder_methods_can_be_chained() .Property(e => e.Up) .IsRequired() .HasAnnotation("A", "V") - .IsConcurrencyToken() - .ValueGeneratedNever() - .ValueGeneratedOnAdd() - .ValueGeneratedOnAddOrUpdate() - .ValueGeneratedOnUpdate() .IsUnicode() - .HasMaxLength(100) .HasSentinel(1) - .HasPrecision(10, 1) .HasValueGenerator() .HasValueGenerator(typeof(CustomValueGenerator)) .HasValueGeneratorFactory() diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs index 332ec7fcbcd..b3ddcf9b4a2 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs @@ -589,6 +589,9 @@ protected virtual GenericTestComplexPropertyBuilder Wrap(ComplexPropertyBu protected virtual GenericTestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) => new(propertyBuilder); + protected virtual GenericTestComplexCollectionTypePropertyBuilder Wrap(ComplexCollectionTypePropertyBuilder propertyBuilder) + => new(propertyBuilder); + protected virtual GenericTestComplexTypePrimitiveCollectionBuilder Wrap( ComplexTypePrimitiveCollectionBuilder propertyBuilder) => new(propertyBuilder); @@ -599,12 +602,12 @@ public override TestComplexCollectionBuilder HasPropertyAnnotation(str public override TestComplexCollectionBuilder HasTypeAnnotation(string annotation, object? value) => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); - public override TestComplexTypePropertyBuilder Property( + public override TestComplexCollectionTypePropertyBuilder Property( Expression> propertyExpression) where TProperty : default => Wrap(PropertyBuilder.Property(propertyExpression)); - public override TestComplexTypePropertyBuilder Property(string propertyName) + public override TestComplexCollectionTypePropertyBuilder Property(string propertyName) => Wrap(PropertyBuilder.Property(propertyName)); public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection( @@ -615,7 +618,7 @@ public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCo public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) => Wrap(PropertyBuilder.PrimitiveCollection(propertyName)); - public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + public override TestComplexCollectionTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); public override TestComplexPropertyBuilder ComplexProperty(string propertyName) @@ -1141,6 +1144,125 @@ ComplexTypePropertyBuilder IInfrastructure PropertyBuilder; } + protected class GenericTestComplexCollectionTypePropertyBuilder(ComplexCollectionTypePropertyBuilder propertyBuilder) : + TestComplexCollectionTypePropertyBuilder, + IInfrastructure> + { + protected ComplexCollectionTypePropertyBuilder PropertyBuilder { get; } = propertyBuilder; + + public override IMutableProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexCollectionTypePropertyBuilder Wrap(ComplexCollectionTypePropertyBuilder propertyBuilder) + => new GenericTestComplexCollectionTypePropertyBuilder(propertyBuilder); + + public override TestComplexCollectionTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexCollectionTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexCollectionTypePropertyBuilder HasSentinel(TProperty? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexCollectionTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexCollectionTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexCollectionTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexCollectionTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexCollectionTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer, + providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexCollectionTypePropertyBuilder IInfrastructure>.Instance + => PropertyBuilder; + } + protected class GenericTestComplexTypePrimitiveCollectionBuilder( ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) : TestComplexTypePrimitiveCollectionBuilder, diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs index b4e5b0d82af..a724926f9df 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs @@ -662,6 +662,9 @@ protected virtual NonGenericTestComplexPropertyBuilder Wrap(ComplexPropert protected virtual NonGenericTestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) => new(propertyBuilder); + protected virtual NonGenericTestComplexCollectionTypePropertyBuilder Wrap(ComplexCollectionTypePropertyBuilder propertyBuilder) + => new(propertyBuilder); + protected virtual NonGenericTestComplexTypePrimitiveCollectionBuilder Wrap( ComplexTypePrimitiveCollectionBuilder propertyBuilder) => new(propertyBuilder); @@ -672,14 +675,14 @@ public override TestComplexCollectionBuilder HasPropertyAnnotation(str public override TestComplexCollectionBuilder HasTypeAnnotation(string annotation, object? value) => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); - public override TestComplexTypePropertyBuilder Property( + public override TestComplexCollectionTypePropertyBuilder Property( Expression> propertyExpression) { var memberInfo = propertyExpression.GetMemberAccess(); return Wrap(PropertyBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); } - public override TestComplexTypePropertyBuilder Property(string propertyName) + public override TestComplexCollectionTypePropertyBuilder Property(string propertyName) => Wrap(PropertyBuilder.Property(propertyName)); public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection( @@ -692,7 +695,7 @@ public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCo public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) => Wrap(PropertyBuilder.PrimitiveCollection(propertyName)); - public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + public override TestComplexCollectionTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); public override TestComplexPropertyBuilder ComplexProperty(string propertyName) @@ -1233,6 +1236,128 @@ ComplexTypePropertyBuilder IInfrastructure.Instance => PropertyBuilder; } + protected class NonGenericTestComplexCollectionTypePropertyBuilder(ComplexCollectionTypePropertyBuilder propertyBuilder) : + TestComplexCollectionTypePropertyBuilder, + IInfrastructure + { + protected ComplexCollectionTypePropertyBuilder PropertyBuilder { get; } = propertyBuilder; + + public override IMutableProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexCollectionTypePropertyBuilder Wrap(ComplexCollectionTypePropertyBuilder propertyBuilder) + => new NonGenericTestComplexCollectionTypePropertyBuilder(propertyBuilder); + + public override TestComplexCollectionTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexCollectionTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexCollectionTypePropertyBuilder HasSentinel(TProperty? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexCollectionTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexCollectionTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexCollectionTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexCollectionTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexCollectionTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter( + convertToProviderExpression, + convertFromProviderExpression))); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter( + convertToProviderExpression, + convertFromProviderExpression), + valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter( + convertToProviderExpression, + convertFromProviderExpression), + valueComparer, + providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexCollectionTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexCollectionTypePropertyBuilder IInfrastructure.Instance + => PropertyBuilder; + } + protected class NonGenericTestComplexTypePrimitiveCollectionBuilder( ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) : TestComplexTypePrimitiveCollectionBuilder, diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs index 5ca120eb6fd..3d5d9fbb92e 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs @@ -487,17 +487,17 @@ public abstract class TestComplexCollectionBuilder public abstract TestComplexCollectionBuilder HasTypeAnnotation(string annotation, object? value); public abstract TestComplexCollectionBuilder HasPropertyAnnotation(string annotation, object? value); - public abstract TestComplexTypePropertyBuilder Property( + public abstract TestComplexCollectionTypePropertyBuilder Property( Expression> propertyExpression); - public abstract TestComplexTypePropertyBuilder Property(string propertyName); + public abstract TestComplexCollectionTypePropertyBuilder Property(string propertyName); public abstract TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection( Expression> propertyExpression); public abstract TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName); - public abstract TestComplexTypePropertyBuilder IndexerProperty(string propertyName); + public abstract TestComplexCollectionTypePropertyBuilder IndexerProperty(string propertyName); public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName) where TProperty : notnull; @@ -865,6 +865,76 @@ public abstract TestComplexTypePropertyBuilder HasConversion + { + public abstract IMutableProperty Metadata { get; } + public abstract TestComplexCollectionTypePropertyBuilder HasAnnotation(string annotation, object? value); + public abstract TestComplexCollectionTypePropertyBuilder IsRequired(bool isRequired = true); + public abstract TestComplexCollectionTypePropertyBuilder HasSentinel(TProperty? sentinel); + public abstract TestComplexCollectionTypePropertyBuilder IsUnicode(bool unicode = true); + + public abstract TestComplexCollectionTypePropertyBuilder HasValueGenerator() + where TGenerator : ValueGenerator; + + public abstract TestComplexCollectionTypePropertyBuilder HasValueGenerator(Type valueGeneratorType); + + public abstract TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory() + where TFactory : ValueGeneratorFactory; + + public abstract TestComplexCollectionTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType); + + public abstract TestComplexCollectionTypePropertyBuilder HasField(string fieldName); + public abstract TestComplexCollectionTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion(); + public abstract TestComplexCollectionTypePropertyBuilder HasConversion(ValueComparer? valueComparer); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter converter); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter); + public abstract TestComplexCollectionTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion() + where TComparer : ValueComparer; + + public abstract TestComplexCollectionTypePropertyBuilder HasConversion() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer; + } + public abstract class TestComplexTypePrimitiveCollectionBuilder { public abstract IMutableProperty Metadata { get; } diff --git a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs index 869ff2acc69..6ea94fdc497 100644 --- a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs +++ b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs @@ -1242,8 +1242,6 @@ protected virtual void BuildComplexTypesModel(ModelBuilder modelBuilder) .HasField("_details") .HasSentinel("") .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction) - .HasMaxLength(64) - .HasPrecision(3, 2) .HasAnnotation("foo", "bar"); eb.Ignore(e => e.Context); eb.ComplexProperty( @@ -1357,9 +1355,6 @@ protected virtual void AssertComplexTypes(IModel model) Assert.Equal("_details", collectionDetails.FieldInfo.Name); Assert.True(collectionDetails.IsNullable); Assert.False(collectionDetails.IsUnicode()); - Assert.Equal(64, collectionDetails.GetMaxLength()); - Assert.Equal(3, collectionDetails.GetPrecision()); - Assert.Equal(2, collectionDetails.GetScale()); Assert.Equal("", collectionDetails.Sentinel); Assert.Equal(PropertyAccessMode.FieldDuringConstruction, collectionDetails.GetPropertyAccessMode()); Assert.Null(collectionDetails.GetValueConverter()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs index d45fd1cc224..ffff37251cc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs @@ -73,7 +73,7 @@ private IRelationalModel CreateRelationalModel() microsoftEntityFrameworkCoreScaffoldingCompiledModelTestBasePrincipalBaseTableBase.Columns.Add("FlagsEnum2", flagsEnum2ColumnBase); var idColumnBase = new ColumnBase("Id", "bigint", microsoftEntityFrameworkCoreScaffoldingCompiledModelTestBasePrincipalBaseTableBase); microsoftEntityFrameworkCoreScaffoldingCompiledModelTestBasePrincipalBaseTableBase.Columns.Add("Id", idColumnBase); - var manyOwned_DetailsColumnBase = new ColumnBase("ManyOwned_Details", "varchar(64)", microsoftEntityFrameworkCoreScaffoldingCompiledModelTestBasePrincipalBaseTableBase) + var manyOwned_DetailsColumnBase = new ColumnBase("ManyOwned_Details", "varchar(max)", microsoftEntityFrameworkCoreScaffoldingCompiledModelTestBasePrincipalBaseTableBase) { IsNullable = true }; @@ -405,7 +405,7 @@ private IRelationalModel CreateRelationalModel() var flagsEnum2Column = new Column("FlagsEnum2", "int", principalBaseTable); principalBaseTable.Columns.Add("FlagsEnum2", flagsEnum2Column); flagsEnum2Column.Accessors = ColumnAccessorsFactory.CreateGeneric(flagsEnum2Column); - var manyOwned_DetailsColumn = new Column("ManyOwned_Details", "varchar(64)", principalBaseTable) + var manyOwned_DetailsColumn = new Column("ManyOwned_Details", "varchar(max)", principalBaseTable) { IsNullable = true }; diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs index b5d92b86f2f..4f099dcd066 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs @@ -94,10 +94,7 @@ public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) fieldInfo: typeof(CompiledModelTestBase.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, nullable: true, - maxLength: 64, unicode: false, - precision: 3, - scale: 2, sentinel: ""); details.SetGetter( string (CompiledModelTestBase.PrincipalDerived> entity, IReadOnlyList indices) => ((PrincipalDerivedUnsafeAccessors>.ManyOwned(entity) == null ? throw new InvalidOperationException(CoreStrings.ComplexCollectionNotInitialized("PrincipalDerived", "ManyOwned")) : PrincipalDerivedUnsafeAccessors>.ManyOwned(entity))[indices[0]] == null ? default(string) : (PrincipalDerivedUnsafeAccessors>.ManyOwned(entity) == null ? throw new InvalidOperationException(CoreStrings.ComplexCollectionNotInitialized("PrincipalDerived", "ManyOwned")) : PrincipalDerivedUnsafeAccessors>.ManyOwned(entity))[indices[0]].Details), @@ -161,10 +158,8 @@ public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) int (string v) => ((object)v).GetHashCode(), string (string v) => v), mappingInfo: new RelationalTypeMappingInfo( - storeTypeName: "varchar(64)", - size: 64, - precision: 3, - scale: 2)); + storeTypeName: "varchar(max)"), + storeTypePostfix: StoreTypePostfix.None); details.AddAnnotation("foo", "bar"); details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); diff --git a/test/EFCore.Sqlite.FunctionalTests/ModelBuilding/SqliteTestModelBuilderExtensions.cs b/test/EFCore.Sqlite.FunctionalTests/ModelBuilding/SqliteTestModelBuilderExtensions.cs new file mode 100644 index 00000000000..e69de29bb2d