From 478059e03777543de80c3a82cc0d561e9a4d94b1 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 4 Aug 2023 16:14:34 +0100 Subject: [PATCH] More tests and refactoring of how ElementTypes are created. --- ...mosPrimitiveCollectionBuilderExtensions.cs | 91 + ...ypePrimitiveCollectionBuilderExtensions.cs | 518 +++++ ...nalComplexTypePropertyBuilderExtensions.cs | 2 - ...nalPrimitiveCollectionBuilderExtensions.cs | 518 +++++ ...ypePrimitiveCollectionBuilderExtensions.cs | 53 + ...verPrimitiveCollectionBuilderExtensions.cs | 53 + ...itePrimitiveCollectionBuilderExtensions.cs | 46 + .../Internal/CurrentValueComparerFactory.cs | 8 +- .../Builders/ComplexPropertyBuilder.cs | 6 +- .../Builders/ComplexPropertyBuilder`.cs | 14 + .../ComplexTypePrimitiveCollectionBuilder.cs | 41 +- .../ComplexTypePrimitiveCollectionBuilder`.cs | 27 +- .../Builders/ComplexTypePropertyBuilder.cs | 31 - .../Builders/ComplexTypePropertyBuilder`.cs | 25 - .../Metadata/Builders/EntityTypeBuilder.cs | 6 +- .../Metadata/Builders/EntityTypeBuilder`.cs | 2 +- .../Builders/IConventionPropertyBuilder.cs | 12 +- .../Builders/OwnedNavigationBuilder.cs | 6 +- .../Builders/OwnedNavigationBuilder`.cs | 2 +- .../Builders/PrimitiveCollectionBuilder.cs | 41 +- .../Builders/PrimitiveCollectionBuilder`.cs | 27 +- .../PropertyDiscoveryConvention.cs | 2 +- .../Conventions/RuntimeModelConvention.cs | 2 +- src/EFCore/Metadata/IConventionProperty.cs | 4 +- src/EFCore/Metadata/IMutableProperty.cs | 4 +- src/EFCore/Metadata/Internal/ElementType.cs | 29 +- .../Internal/InternalPropertyBuilder.cs | 98 +- .../Internal/InternalTypeBaseBuilder.cs | 8 - src/EFCore/Metadata/Internal/Property.cs | 72 +- src/EFCore/Metadata/RuntimeProperty.cs | 2 +- src/EFCore/Properties/CoreStrings.Designer.cs | 2 +- .../Storage/Json/JsonValueReaderWriter.cs | 34 + src/EFCore/Storage/TypeMappingInfo.cs | 5 +- .../CosmosModelBuilderGenericTest.cs | 192 +- .../CosmosTestModelBuilderExtensions.cs | 17 + .../InMemoryModelBuilderGenericTest.cs | 6 - .../Query/JsonQueryAdHocTestBase.cs | 49 +- .../RelationalModelBuilderTest.cs | 329 --- .../RelationalTestModelBuilderExtensions.cs | 136 ++ .../ApiConsistencyTestBase.cs | 9 +- .../SqlServerApiConsistencyTest.cs | 1 + .../SqlServerEndToEndTest.cs | 1 + .../SqlServerModelBuilderGenericTest.cs | 8 - .../SqlServerModelBuilderNonGenericTest.cs | 8 - .../SqlServerModelBuilderTestBase.cs | 190 +- .../SqliteApiConsistencyTest.cs | 3 +- .../Conventions/ConventionDispatcherTest.cs | 25 +- .../ModelBuilding/ComplexTypeTestBase.cs | 539 ++++- ...ModelBuilderGenericRelationshipTypeTest.cs | 8 - .../ModelBuilding/ModelBuilderGenericTest.cs | 100 +- .../ModelBuilderNonGenericTest.cs | 102 +- .../ModelBuilding/ModelBuilderTestBase.cs | 94 +- .../ModelBuilding/NonRelationshipTestBase.cs | 1662 ++++++++++++--- .../PrimitiveCollectionsTestBase.cs | 1894 +---------------- test/EFCore.Tests/ModelBuilding/TestModel.cs | 37 +- 55 files changed, 3853 insertions(+), 3348 deletions(-) create mode 100644 src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs create mode 100644 src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs create mode 100644 src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs create mode 100644 src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs create mode 100644 src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs create mode 100644 src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs diff --git a/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..d06f96a391c --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosPrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Cosmos-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Azure Cosmos DB with EF Core for more information and examples. +/// +public static class CosmosPrimitiveCollectionBuilderExtensions +{ + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// + /// If an empty string is supplied, the property will not be persisted. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder ToJsonProperty( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string name) + { + Check.NotNull(name, nameof(name)); + + primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the property. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder ToJsonProperty( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string name) + => (PrimitiveCollectionBuilder)ToJsonProperty((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name); + + /// + /// Configures this property to be the etag concurrency token. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder IsETagConcurrency(this PrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder + .IsConcurrencyToken() + .ToJsonProperty("_etag") + .ValueGeneratedOnAddOrUpdate(); + + return primitiveCollectionBuilder; + } + + /// + /// Configures this property to be the etag concurrency token. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder IsETagConcurrency( + this PrimitiveCollectionBuilder primitiveCollectionBuilder) + => (PrimitiveCollectionBuilder)IsETagConcurrency((PrimitiveCollectionBuilder)primitiveCollectionBuilder); +} diff --git a/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..048a41845e0 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,518 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalComplexTypePrimitiveCollectionBuilderExtensions +{ + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnName( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + primitiveCollectionBuilder.Metadata.SetColumnName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnName( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + => (ComplexTypePrimitiveCollectionBuilder)HasColumnName((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, name); + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnOrder(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, int? order) + { + primitiveCollectionBuilder.Metadata.SetColumnOrder(order); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnOrder(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, int? order) + => (ComplexTypePrimitiveCollectionBuilder)HasColumnOrder((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, order); + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnType( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? typeName) + { + Check.NullButNotEmpty(typeName, nameof(typeName)); + + primitiveCollectionBuilder.Metadata.SetColumnType(typeName); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasColumnType( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? typeName) + => (ComplexTypePrimitiveCollectionBuilder)HasColumnType((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, typeName); + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder IsFixedLength( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + bool fixedLength = true) + { + primitiveCollectionBuilder.Metadata.SetIsFixedLength(fixedLength); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder IsFixedLength( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + bool fixedLength = true) + => (ComplexTypePrimitiveCollectionBuilder)IsFixedLength((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, fixedLength); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetDefaultValueSql(string.Empty); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + primitiveCollectionBuilder.Metadata.SetDefaultValueSql(sql); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValueSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValueSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValueSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sql); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetComputedColumnSql(string.Empty); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => HasComputedColumnSql(primitiveCollectionBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql, + bool? stored) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + primitiveCollectionBuilder.Metadata.SetComputedColumnSql(sql); + + if (stored != null) + { + primitiveCollectionBuilder.Metadata.SetIsStored(stored); + } + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + => (ComplexTypePrimitiveCollectionBuilder)HasComputedColumnSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => HasComputedColumnSql(primitiveCollectionBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComputedColumnSql( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql, + bool? stored) + => (ComplexTypePrimitiveCollectionBuilder)HasComputedColumnSql((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sql, stored); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetDefaultValue(DBNull.Value); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + object? value) + { + primitiveCollectionBuilder.Metadata.SetDefaultValue(value); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValue((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasDefaultValue( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + object? value) + => (ComplexTypePrimitiveCollectionBuilder)HasDefaultValue((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, value); + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComment( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? comment) + { + primitiveCollectionBuilder.Metadata.SetComment(comment); + + return primitiveCollectionBuilder; + } + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasComment( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? comment) + => (ComplexTypePrimitiveCollectionBuilder)HasComment((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, comment); + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder UseCollation(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, string? collation) + { + Check.NullButNotEmpty(collation, nameof(collation)); + + primitiveCollectionBuilder.Metadata.SetCollation(collation); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder UseCollation( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? collation) + => (ComplexTypePrimitiveCollectionBuilder)UseCollation((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, collation); + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasJsonPropertyName( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePrimitiveCollectionBuilder HasJsonPropertyName( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + => (ComplexTypePrimitiveCollectionBuilder)HasJsonPropertyName((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, name); +} diff --git a/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs index e2fe7925290..6b28a917b6c 100644 --- a/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Metadata.Internal; - // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore; diff --git a/src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..10980241fc8 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalPrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,518 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalPrimitiveCollectionBuilderExtensions +{ + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnName( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + primitiveCollectionBuilder.Metadata.SetColumnName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnName( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + => (PrimitiveCollectionBuilder)HasColumnName((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name); + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnOrder(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int? order) + { + primitiveCollectionBuilder.Metadata.SetColumnOrder(order); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnOrder(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int? order) + => (PrimitiveCollectionBuilder)HasColumnOrder((PrimitiveCollectionBuilder)primitiveCollectionBuilder, order); + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnType( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? typeName) + { + Check.NullButNotEmpty(typeName, nameof(typeName)); + + primitiveCollectionBuilder.Metadata.SetColumnType(typeName); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasColumnType( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? typeName) + => (PrimitiveCollectionBuilder)HasColumnType((PrimitiveCollectionBuilder)primitiveCollectionBuilder, typeName); + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static PrimitiveCollectionBuilder IsFixedLength( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + bool fixedLength = true) + { + primitiveCollectionBuilder.Metadata.SetIsFixedLength(fixedLength); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static PrimitiveCollectionBuilder IsFixedLength( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + bool fixedLength = true) + => (PrimitiveCollectionBuilder)IsFixedLength((PrimitiveCollectionBuilder)primitiveCollectionBuilder, fixedLength); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValueSql(this PrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetDefaultValueSql(string.Empty); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValueSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + primitiveCollectionBuilder.Metadata.SetDefaultValueSql(sql); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValueSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder) + => (PrimitiveCollectionBuilder)HasDefaultValueSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValueSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => (PrimitiveCollectionBuilder)HasDefaultValueSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sql); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql(this PrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetComputedColumnSql(string.Empty); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => HasComputedColumnSql(primitiveCollectionBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql, + bool? stored) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + primitiveCollectionBuilder.Metadata.SetComputedColumnSql(sql); + + if (stored != null) + { + primitiveCollectionBuilder.Metadata.SetIsStored(stored); + } + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder) + => (PrimitiveCollectionBuilder)HasComputedColumnSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql) + => HasComputedColumnSql(primitiveCollectionBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComputedColumnSql( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? sql, + bool? stored) + => (PrimitiveCollectionBuilder)HasComputedColumnSql((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sql, stored); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValue(this PrimitiveCollectionBuilder primitiveCollectionBuilder) + { + primitiveCollectionBuilder.Metadata.SetDefaultValue(DBNull.Value); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValue( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + object? value) + { + primitiveCollectionBuilder.Metadata.SetDefaultValue(value); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValue( + this PrimitiveCollectionBuilder primitiveCollectionBuilder) + => (PrimitiveCollectionBuilder)HasDefaultValue((PrimitiveCollectionBuilder)primitiveCollectionBuilder); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasDefaultValue( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + object? value) + => (PrimitiveCollectionBuilder)HasDefaultValue((PrimitiveCollectionBuilder)primitiveCollectionBuilder, value); + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComment( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? comment) + { + primitiveCollectionBuilder.Metadata.SetComment(comment); + + return primitiveCollectionBuilder; + } + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasComment( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? comment) + => (PrimitiveCollectionBuilder)HasComment((PrimitiveCollectionBuilder)primitiveCollectionBuilder, comment); + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder UseCollation(this PrimitiveCollectionBuilder primitiveCollectionBuilder, string? collation) + { + Check.NullButNotEmpty(collation, nameof(collation)); + + primitiveCollectionBuilder.Metadata.SetCollation(collation); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder UseCollation( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? collation) + => (PrimitiveCollectionBuilder)UseCollation((PrimitiveCollectionBuilder)primitiveCollectionBuilder, collation); + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasJsonPropertyName( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + primitiveCollectionBuilder.Metadata.SetJsonPropertyName(name); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasJsonPropertyName( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + string? name) + => (PrimitiveCollectionBuilder)HasJsonPropertyName((PrimitiveCollectionBuilder)primitiveCollectionBuilder, name); +} diff --git a/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..afd29bd2087 --- /dev/null +++ b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// SQL Server specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing SQL Server and SQL Azure databases with EF Core +/// for more information and examples. +/// +public static class SqlServerComplexTypePrimitiveCollectionBuilderExtensions +{ + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static ComplexTypePrimitiveCollectionBuilder IsSparse(this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, bool sparse = true) + { + primitiveCollectionBuilder.Metadata.SetIsSparse(sparse); + + return primitiveCollectionBuilder; + } + + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static ComplexTypePrimitiveCollectionBuilder IsSparse( + this ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder, + bool sparse = true) + => (ComplexTypePrimitiveCollectionBuilder)IsSparse((ComplexTypePrimitiveCollectionBuilder)primitiveCollectionBuilder, sparse); +} diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..17dd2d26dfe --- /dev/null +++ b/src/EFCore.SqlServer/Extensions/SqlServerPrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// SQL Server specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing SQL Server and SQL Azure databases with EF Core +/// for more information and examples. +/// +public static class SqlServerPrimitiveCollectionBuilderExtensions +{ + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static PrimitiveCollectionBuilder IsSparse(this PrimitiveCollectionBuilder primitiveCollectionBuilder, bool sparse = true) + { + primitiveCollectionBuilder.Metadata.SetIsSparse(sparse); + + return primitiveCollectionBuilder; + } + + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static PrimitiveCollectionBuilder IsSparse( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + bool sparse = true) + => (PrimitiveCollectionBuilder)IsSparse((PrimitiveCollectionBuilder)primitiveCollectionBuilder, sparse); +} diff --git a/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs new file mode 100644 index 00000000000..0a675c038ff --- /dev/null +++ b/src/EFCore.Sqlite.Core/Extensions/SqlitePrimitiveCollectionBuilderExtensions.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +/// +/// SQLite-specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing SQLite databases with EF Core for more information and examples. +/// +public static class SqlitePrimitiveCollectionBuilderExtensions +{ + /// + /// Configures the SRID of the column that the property maps to when targeting SQLite. + /// + /// + /// See Spatial data, and + /// Accessing SQLite databases with EF Core for more information and examples. + /// + /// The builder for the property being configured. + /// The SRID. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasSrid(this PrimitiveCollectionBuilder primitiveCollectionBuilder, int srid) + { + primitiveCollectionBuilder.Metadata.SetSrid(srid); + + return primitiveCollectionBuilder; + } + + /// + /// Configures the SRID of the column that the property maps to when targeting SQLite. + /// + /// + /// See Spatial data, and + /// Accessing SQLite databases with EF Core for more information and examples. + /// + /// The builder for the property being configured. + /// The SRID. + /// The same builder instance so that multiple calls can be chained. + public static PrimitiveCollectionBuilder HasSrid( + this PrimitiveCollectionBuilder primitiveCollectionBuilder, + int srid) + => (PrimitiveCollectionBuilder)HasSrid((PrimitiveCollectionBuilder)primitiveCollectionBuilder, srid); +} diff --git a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs index 5df96374e28..cf00bc77dec 100644 --- a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs @@ -49,10 +49,14 @@ public virtual IComparer Create(IPropertyBase propertyBase) var nonNullableProviderType = providerType.UnwrapNullableType(); if (IsGenericComparable(providerType, nonNullableProviderType)) { + var elementType = property.GetElementType(); + var modelBaseType = elementType != null + ? typeof(IEnumerable<>).MakeGenericType(elementType.ClrType) + : modelType; var comparerType = modelType.IsClass - ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType) + ? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) : modelType == converter.ModelClrType - ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType) + ? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType) : typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType( nonNullableModelType, providerType); diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index d69938caa9a..b4c43eb787d 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -217,7 +217,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string => new( TypeBuilder.Property( Check.NotEmpty(propertyName, nameof(propertyName)), - ConfigurationSource.Explicit)!.HasElementType(ConfigurationSource.Explicit)!.Metadata); + ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the complex type. @@ -238,7 +238,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect TypeBuilder.Property( typeof(TProperty), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the complex type. @@ -259,7 +259,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type pr TypeBuilder.Property( Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the complex type. diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index 01c9b8066de..bb1380a8afc 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -100,6 +100,20 @@ public virtual ComplexTypePropertyBuilder Property(Express Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)! .Metadata); + /// + /// Returns an object that can be used to configure a primitive collection property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Expression> propertyExpression) + => new(TypeBuilder.Property( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)! + .Metadata); + /// /// Configures a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. diff --git a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs index 12badbff9e8..d03b58f7b21 100644 --- a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder.cs @@ -185,37 +185,6 @@ public virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator( return this; } - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the complex type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator(Func factory) - { - Check.NotNull(factory, nameof(factory)); - - Builder.HasValueGenerator(factory, ConfigurationSource.Explicit); - - return this; - } - /// /// Configures the for creating a /// to use to generate values for this property. @@ -390,18 +359,14 @@ public virtual ComplexTypePrimitiveCollectionBuilder HasField(string fieldName) } /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// A builder to configure the collection element type. public virtual ElementTypeBuilder ElementType() - { - Builder.HasElementType(ConfigurationSource.Explicit); - - return new ElementTypeBuilder((IMutableElementType)Builder.Metadata.GetElementType()!); - } + => new((IMutableElementType)Builder.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata.GetElementType()!); /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// An action that performs configuration of the collection element type. /// The same builder instance so that multiple configuration calls can be chained. diff --git a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs index 46563dc1360..1ef05f1a311 100644 --- a/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexTypePrimitiveCollectionBuilder`.cs @@ -137,31 +137,6 @@ public ComplexTypePrimitiveCollectionBuilder(IMutableProperty property) [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGenerator(valueGeneratorType); - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the complex type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public new virtual ComplexTypePrimitiveCollectionBuilder HasValueGenerator(Func factory) - => (ComplexTypePrimitiveCollectionBuilder)base.HasValueGenerator(factory); - /// /// Configures the for creating a /// to use to generate values for this property. @@ -303,7 +278,7 @@ public ComplexTypePrimitiveCollectionBuilder(IMutableProperty property) => (ComplexTypePrimitiveCollectionBuilder)base.HasField(fieldName); /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// An action that performs configuration of the collection element type. /// The same builder instance so that multiple configuration calls can be chained. diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs index b6c57e0effb..47e6af20fda 100644 --- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs @@ -228,37 +228,6 @@ public virtual ComplexTypePropertyBuilder HasValueGenerator( return this; } - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the complex type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) - { - Check.NotNull(factory, nameof(factory)); - - Builder.HasValueGenerator(factory, ConfigurationSource.Explicit); - - return this; - } - /// /// Configures the for creating a /// to use to generate values for this property. diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs index 7f0e24503b5..318c1966d9c 100644 --- a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs @@ -166,31 +166,6 @@ public ComplexTypePropertyBuilder(IMutableProperty property) [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) => (ComplexTypePropertyBuilder)base.HasValueGenerator(valueGeneratorType); - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the complex type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public new virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) - => (ComplexTypePropertyBuilder)base.HasValueGenerator(factory); - /// /// Configures the for creating a /// to use to generate values for this property. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 70d2c70ab51..506da585a50 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -185,7 +185,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyNam => new( Builder.Property( Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -207,7 +207,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection /// Returns an object that can be used to configure a property of the entity type where that property represents @@ -229,7 +229,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, Builder.Property( Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the entity type. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index a96293d07b2..b7913f6b3de 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -165,7 +165,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection> propertyExpression) => new( Builder.Property(Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), - ConfigurationSource.Explicit)!.HasElementType(ConfigurationSource.Explicit)!.Metadata); + ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Configures a complex property of the entity type. diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 0dac98f9345..09771a49187 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -545,7 +545,7 @@ bool CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionElementTypeBuilder? HasElementType(bool fromDataAnnotation = false); + IConventionElementTypeBuilder? PrimitiveCollection(bool fromDataAnnotation = false); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -553,13 +553,5 @@ bool CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? HasElementType(Action builderAction, bool fromDataAnnotation = false); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool CanSetElementType(bool fromDataAnnotation = false); + bool CanSetPrimitiveCollection(bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs index a52324adfa2..58e65f710da 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs @@ -209,7 +209,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(string propertyNam DependentEntityType.Builder.Property( Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata)); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata)); /// /// Returns an object that can be used to configure a property of the owned type where that property represents @@ -232,7 +232,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection /// Returns an object that can be used to configure a property of the owned type where that property represents @@ -254,7 +254,7 @@ public virtual PrimitiveCollectionBuilder PrimitiveCollection(Type propertyType, DependentEntityType.Builder.Property( Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)! - .HasElementType(ConfigurationSource.Explicit)!.Metadata); + .PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata); /// /// Returns an object that can be used to configure a property of the entity type. diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index 1d5bb55bf54..84b9c7dff7e 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -118,7 +118,7 @@ public virtual PropertyBuilder PrimitiveCollection( () => new PropertyBuilder( DependentEntityType.Builder.Property( Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), - ConfigurationSource.Explicit)!.HasElementType(ConfigurationSource.Explicit)!.Metadata)); + ConfigurationSource.Explicit)!.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata)); /// /// Returns an object that can be used to configure an existing navigation property diff --git a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs index a0149951521..4241964f374 100644 --- a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder.cs @@ -186,37 +186,6 @@ public virtual PrimitiveCollectionBuilder HasValueGenerator( return this; } - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the entity type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public virtual PrimitiveCollectionBuilder HasValueGenerator(Func factory) - { - Check.NotNull(factory, nameof(factory)); - - Builder.HasValueGenerator(factory, ConfigurationSource.Explicit); - - return this; - } - /// /// Configures the for creating a /// to use to generate values for this property. @@ -392,18 +361,14 @@ public virtual PrimitiveCollectionBuilder HasField(string fieldName) } /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// A builder to configure the collection element type. public virtual ElementTypeBuilder ElementType() - { - Builder.HasElementType(ConfigurationSource.Explicit); - - return new ElementTypeBuilder((IMutableElementType)Builder.Metadata.GetElementType()!); - } + => new((IMutableElementType)Builder.PrimitiveCollection(ConfigurationSource.Explicit)!.Metadata.GetElementType()!); /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// An action that performs configuration of the collection element type. /// The same builder instance so that multiple configuration calls can be chained. diff --git a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs index e71c6e96c13..c9547adf2ab 100644 --- a/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/PrimitiveCollectionBuilder`.cs @@ -138,31 +138,6 @@ public PrimitiveCollectionBuilder(IMutableProperty property) Type? valueGeneratorType) => (PrimitiveCollectionBuilder)base.HasValueGenerator(valueGeneratorType); - /// - /// Configures a factory for creating a to use to generate values - /// for this property. - /// - /// - /// - /// Values are generated when the entity is added to the context using, for example, - /// . Values are generated only when the property is assigned - /// the CLR default value ( for string, 0 for int, - /// Guid.Empty for Guid, etc.). - /// - /// - /// This factory will be invoked once to create a single instance of the value generator, and - /// this will be used to generate values for this property in all instances of the entity type. - /// - /// - /// This method is intended for use with custom value generation. Value generation for common cases is - /// usually handled automatically by the database provider. - /// - /// - /// A delegate that will be used to create value generator instances. - /// The same builder instance so that multiple configuration calls can be chained. - public new virtual PrimitiveCollectionBuilder HasValueGenerator(Func factory) - => (PrimitiveCollectionBuilder)base.HasValueGenerator(factory); - /// /// Configures the for creating a /// to use to generate values for this property. @@ -305,7 +280,7 @@ public PrimitiveCollectionBuilder(IMutableProperty property) => (PrimitiveCollectionBuilder)base.HasField(fieldName); /// - /// Configures this property as a collection containing elements of a primitive type. + /// Configures the elements of this collection. /// /// An action that performs configuration of the collection element type. /// The same builder instance so that multiple configuration calls can be chained. diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index 542a48ba673..e04f01282dc 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -94,7 +94,7 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder) var propertyBuilder = entityTypeBuilder.Property(propertyInfo); if (mapping?.ElementTypeMapping != null) { - propertyBuilder?.HasElementType(); + propertyBuilder?.PrimitiveCollection(); } } } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 88dae8d8cc7..ca406251699 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -399,7 +399,7 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim property.GetTypeMapping()); private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element) - => runtimeProperty.AddElementType( + => runtimeProperty.SetElementType( element.ClrType, element.IsNullable, element.GetMaxLength(), diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index d2a9bf40283..03608def33f 100644 --- a/src/EFCore/Metadata/IConventionProperty.cs +++ b/src/EFCore/Metadata/IConventionProperty.cs @@ -463,10 +463,10 @@ bool IsImplicitlyCreated() /// /// Sets the configuration for elements of the primitive collection represented by this property. /// - /// The element configuration to use. + /// If , then this is a collection of primitive elements. /// Indicates whether the configuration was specified using a data annotation. /// The configuration for the elements. - IElementType? SetElementType(IElementType? element, bool fromDataAnnotation = false); + IElementType? IsPrimitiveCollection(bool primitiveCollection, bool fromDataAnnotation = false); /// /// Returns the configuration source for . diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index 26d482fa7e8..0750186a025 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -276,8 +276,8 @@ void SetProviderValueComparer( /// /// Sets the configuration for elements of the primitive collection represented by this property. /// - /// The element configuration to use. - void SetElementType(IElementType? elementType); + /// If , then this is a collection of primitive elements. + void IsPrimitiveCollection(bool primitiveCollection); /// bool IReadOnlyProperty.IsNullable diff --git a/src/EFCore/Metadata/Internal/ElementType.cs b/src/EFCore/Metadata/Internal/ElementType.cs index 8d397fa885d..f4a75a470a4 100644 --- a/src/EFCore/Metadata/Internal/ElementType.cs +++ b/src/EFCore/Metadata/Internal/ElementType.cs @@ -601,36 +601,9 @@ public virtual CoreTypeMapping? TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() - { - return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]) + => JsonValueReaderWriter.CreateFromType((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]) ?? TypeMapping?.JsonValueReaderWriter; - static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType) - { - if (readerWriterType != null) - { - var instanceProperty = readerWriterType.GetAnyProperty("Instance"); - try - { - return instanceProperty != null - && instanceProperty.IsStatic() - && instanceProperty.GetMethod?.IsPublic == true - && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) - ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) - : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateJsonValueReaderWriter( - readerWriterType.ShortDisplayName()), e); - } - } - - return null; - } - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index f5645a8df71..f2badfd4d61 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -564,7 +564,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? { if (CanSetConverter(converterType, configurationSource)) { - Metadata.SetElementType(null, configurationSource); + Metadata.IsPrimitiveCollection(false, configurationSource); Metadata.SetProviderClrType(null, configurationSource); Metadata.SetValueConverter(converterType, configurationSource); @@ -776,37 +776,12 @@ public virtual bool CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder? HasElementType(ConfigurationSource configurationSource) + public virtual InternalPropertyBuilder? PrimitiveCollection(ConfigurationSource configurationSource) { - var element = (ElementType?)Metadata.GetElementType(); - - if (element == null) - { - var elementType = Metadata.ClrType.TryGetElementType(typeof(IEnumerable<>)); - if (elementType == null) - { - throw new InvalidOperationException(CoreStrings.NotCollection(Metadata.ClrType.ShortDisplayName(), Metadata.Name)); - } - - element = new ElementType(elementType, Metadata, configurationSource); - } - - return HasElementType(element, configurationSource); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? HasElementType(IElementType? elementType, ConfigurationSource configurationSource) - { - if (CanSetElementType(elementType, configurationSource)) + if (CanSetPrimitiveCollection(configurationSource)) { + Metadata.IsPrimitiveCollection(true, configurationSource); Metadata.SetValueConverter((Type?)null, configurationSource); - Metadata.SetElementType(elementType, configurationSource); - return this; } @@ -819,7 +794,7 @@ public virtual bool CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetElementType(IElementType? elementType, ConfigurationSource? configurationSource) + public virtual bool CanSetPrimitiveCollection(ConfigurationSource? configurationSource) => configurationSource.Overrides(Metadata.GetElementTypeConfigurationSource()); /// @@ -859,12 +834,6 @@ public virtual bool CanSetElementType(IElementType? elementType, ConfigurationSo Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) : typeBaseBuilder.Property(identifyingMemberInfo, configurationSource); - var elementType = Metadata.GetElementType(); - if (elementType != null) - { - newPropertyBuilder?.HasElementType(elementType, configurationSource); - } - if (newPropertyBuilder is null) { return null; @@ -935,11 +904,36 @@ public virtual bool CanSetElementType(IElementType? elementType, ConfigurationSo newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value); } + var element = Metadata.GetElementType(); var oldElementTypeConfigurationSource = Metadata.GetElementTypeConfigurationSource(); - if (oldElementTypeConfigurationSource.HasValue - && newPropertyBuilder.CanSetElementType(Metadata.GetElementType(), oldElementTypeConfigurationSource)) + if (element != null + && oldElementTypeConfigurationSource.HasValue + && newPropertyBuilder.CanSetPrimitiveCollection(oldElementTypeConfigurationSource)) { - newPropertyBuilder.HasElementType(Metadata.GetElementType(), oldElementTypeConfigurationSource.Value); + newPropertyBuilder.PrimitiveCollection(oldElementTypeConfigurationSource.Value); + var elementType = (ElementType?)Metadata.GetElementType(); + if (elementType != null) + { + newPropertyBuilder.PrimitiveCollection(oldElementTypeConfigurationSource.Value); + var newElementTypeBuilder = new InternalElementTypeBuilder(elementType, ModelBuilder); + newElementTypeBuilder.IsRequired( + !elementType.IsNullable, elementType.GetIsNullableConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.IsUnicode( + elementType.IsUnicode(), elementType.GetIsUnicodeConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasConverter( + elementType.GetValueConverter()?.GetType(), + elementType.GetValueConverterConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasPrecision( + elementType.GetPrecision(), elementType.GetPrecisionConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasScale( + elementType.GetScale(), elementType.GetScaleConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasMaxLength( + elementType.GetMaxLength(), elementType.GetMaxLengthConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasTypeMapping( + elementType.TypeMapping, elementType.GetTypeMappingConfigurationSource() ?? ConfigurationSource.Convention); + newElementTypeBuilder.HasValueComparer( + elementType.GetValueComparer(), elementType.GetValueComparerConfigurationSource() ?? ConfigurationSource.Convention); + } } return newPropertyBuilder; @@ -1486,9 +1480,9 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionElementTypeBuilder? IConventionPropertyBuilder.HasElementType(bool fromDataAnnotation) + IConventionElementTypeBuilder? IConventionPropertyBuilder.PrimitiveCollection(bool fromDataAnnotation) { - var propertyBuilder = HasElementType(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + var propertyBuilder = PrimitiveCollection(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); return propertyBuilder == null ? null @@ -1501,27 +1495,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasElementType( - Action builderAction, bool fromDataAnnotation) - { - var elementBuilder = ((IConventionPropertyBuilder)this).HasElementType(fromDataAnnotation); - - if (elementBuilder != null) - { - builderAction.Invoke(elementBuilder); - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetElementType(bool fromDataAnnotation = false) + public virtual bool CanSetPrimitiveCollection(bool fromDataAnnotation = false) => (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) .Overrides(Metadata.GetElementTypeConfigurationSource()); } diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index 358b0fded42..4723fb708da 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -551,9 +551,6 @@ public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties( { var property = properties[i]; var typeConfigurationSource = property.GetTypeConfigurationSource(); - var oldConfigurationSource = property.GetConfigurationSource(); - var elementType = property.GetElementType(); - var builder = property.IsInModel && property.DeclaringType.IsAssignableFrom(Metadata) ? property.Builder : Property( @@ -568,11 +565,6 @@ public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties( return null; } - if (elementType != null) - { - builder.HasElementType(elementType, configurationSource ?? oldConfigurationSource); - } - actualProperties[i] = builder.Metadata; } diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 6dc7ac592f0..9df40658827 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -1166,34 +1166,7 @@ public virtual CoreTypeMapping? TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() - { - return TryCreateReader((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]); - - static JsonValueReaderWriter? TryCreateReader(Type? readerWriterType) - { - if (readerWriterType != null) - { - var instanceProperty = readerWriterType.GetAnyProperty("Instance"); - try - { - return instanceProperty != null - && instanceProperty.IsStatic() - && instanceProperty.GetMethod?.IsPublic == true - && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) - ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) - : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); - } - catch (Exception e) - { - throw new InvalidOperationException( - CoreStrings.CannotCreateJsonValueReaderWriter( - readerWriterType.ShortDisplayName()), e); - } - } - - return null; - } - } + => JsonValueReaderWriter.CreateFromType((Type?)this[CoreAnnotationNames.JsonValueReaderWriterType]); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1241,19 +1214,34 @@ public virtual CoreTypeMapping? TypeMapping /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IElementType? SetElementType( - IElementType? elementType, + public virtual IElementType? IsPrimitiveCollection( + bool primitiveCollection, ConfigurationSource configurationSource) { - var oldElementType = GetElementType(); - if (!ReferenceEquals(oldElementType, elementType)) + var existingElementType = GetElementType(); + if (existingElementType == null + && primitiveCollection) + { + var elementClrType = ClrType.TryGetElementType(typeof(IEnumerable<>)); + if (elementClrType == null) + { + throw new InvalidOperationException(CoreStrings.NotCollection(ClrType.ShortDisplayName(), Name)); + } + var elementType = new ElementType(elementClrType, this, configurationSource); + SetAnnotation(CoreAnnotationNames.ElementType, elementType, configurationSource); + OnElementTypeSet(elementType, null); + return elementType; + } + + if (existingElementType != null && !primitiveCollection) { - SetOrRemoveAnnotation(CoreAnnotationNames.ElementType, elementType, configurationSource); - ((ElementType?)oldElementType)?.SetRemovedFromModel(); - OnElementTypeSet(elementType, oldElementType); + ((ElementType)existingElementType).SetRemovedFromModel(); + RemoveAnnotation(CoreAnnotationNames.ElementType); + OnElementTypeSet(null, existingElementType); + return null; } - return elementType; + return existingElementType; } /// @@ -2019,11 +2007,9 @@ void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IElementType? IConventionProperty.SetElementType( - IElementType? elementType, - bool fromDataAnnotation) - => SetElementType( - elementType, + IElementType? IConventionProperty.IsPrimitiveCollection(bool primitiveCollection, bool fromDataAnnotation) + => IsPrimitiveCollection( + primitiveCollection, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -2033,6 +2019,6 @@ void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - void IMutableProperty.SetElementType(IElementType? elementType) - => SetElementType(elementType, ConfigurationSource.Explicit); + void IMutableProperty.IsPrimitiveCollection(bool primitiveCollection) + => IsPrimitiveCollection(primitiveCollection, ConfigurationSource.Explicit); } diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index 7593981ab60..6bdcfcc03cc 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -126,7 +126,7 @@ public RuntimeProperty( /// The for this property. /// The for this property. /// The newly created property. - public virtual RuntimeElementType AddElementType( + public virtual RuntimeElementType SetElementType( Type clrType, bool nullable = false, int? maxLength = null, diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index de5ddaa2eaf..9d4d56013dc 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2078,7 +2078,7 @@ public static string NotAnEFService(object? service) /// public static string NotCollection(object? entityType, object? property) => string.Format( - GetString(nameof(entityType), nameof(property)), + GetString("NotCollection", nameof(entityType), nameof(property)), entityType, property); /// diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index 0cb805e8c3f..15dc20343eb 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -84,4 +84,38 @@ public virtual string ToJsonString(object value) return Encoding.UTF8.GetString(buffer); } + + /// + /// Creates a instance of the given type, using the Instance + /// property to get th singleton instance if possible. + /// + /// The type, which must inherit from . + /// The reader/writer instance./ + /// if the type does not represent a + /// that can be instantiated. + public static JsonValueReaderWriter? CreateFromType(Type? readerWriterType) + { + if (readerWriterType != null) + { + var instanceProperty = readerWriterType.GetAnyProperty("Instance"); + try + { + return instanceProperty != null + && instanceProperty.IsStatic() + && instanceProperty.GetMethod?.IsPublic == true + && readerWriterType.IsAssignableFrom(instanceProperty.PropertyType) + ? (JsonValueReaderWriter?)instanceProperty.GetValue(null) + : (JsonValueReaderWriter?)Activator.CreateInstance(readerWriterType); + } + catch (Exception e) + { + throw new InvalidOperationException( + CoreStrings.CannotCreateJsonValueReaderWriter( + readerWriterType.ShortDisplayName()), e); + } + } + + return null; + } + } diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index 50da68c13d1..7407af2d4b5 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -102,10 +102,7 @@ public TypeMappingInfo( ClrType = (customConverter?.ProviderClrType ?? elementType.ClrType).UnwrapNullableType(); Scale = fallbackScale ?? mappingHints?.Scale; Precision = fallbackPrecision ?? mappingHints?.Precision; - - JsonValueReaderWriter = elementType[CoreAnnotationNames.JsonValueReaderWriterType] != null - ? elementType.GetJsonValueReaderWriter() - : null; + JsonValueReaderWriter = JsonValueReaderWriter.CreateFromType((Type?)elementType[CoreAnnotationNames.JsonValueReaderWriterType]); } /// diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index a96d25e4e9c..4b04a27d661 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -164,7 +164,7 @@ public virtual void No_id_property_created_if_another_property_mapped_to_id_in_p var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); @@ -243,126 +243,46 @@ public virtual void No_alternate_key_is_created_if_id_is_partition_key() Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); } - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); - } - - public class CosmosGenericPrimitiveCollections : GenericPrimitiveCollections - { - // public override void Properties_can_be_made_concurrency_tokens() - // => Assert.Equal( - // CosmosStrings.NonETagConcurrencyToken(nameof(Quarks), "Charm"), - // Assert.Throws( - // () => base.Properties_can_be_made_concurrency_tokens()).Message); - // - // public override void Properties_can_have_provider_type_set_for_type() - // { - // var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down); - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("__id").HasConversion(null); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty("Up").GetProviderClrType()); - // Assert.Same(typeof(byte[]), entityType.FindProperty("Down").GetProviderClrType()); - // Assert.Null(entityType.FindProperty("Charm").GetProviderClrType()); - // Assert.Same(typeof(byte[]), entityType.FindProperty("Strange").GetProviderClrType()); - // } - // - // public override void Properties_can_be_set_to_generate_values_on_Add() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.HasKey(e => e.Id); - // b.Property(e => e.Up).ValueGeneratedOnAddOrUpdate(); - // b.Property(e => e.Down).ValueGeneratedNever(); - // b.Property("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; - // b.Property("Strange").ValueGeneratedNever(); - // b.Property("Top").ValueGeneratedOnAddOrUpdate(); - // b.Property("Bottom").ValueGeneratedOnUpdate(); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty(Customer.IdProperty.Name).ValueGenerated); - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); - // Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm").ValueGenerated); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange").ValueGenerated); - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top").ValueGenerated); - // Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom").ValueGenerated); - // } + public override void Primitive_collections_can_be_made_concurrency_tokens() + => Assert.Equal( + CosmosStrings.NonETagConcurrencyToken(nameof(CollectionQuarks), "Charm"), + Assert.Throws( + () => base.Primitive_collections_can_be_made_concurrency_tokens()).Message); [ConditionalFact] - public virtual void Partition_key_is_added_to_the_keys() + public virtual void Primitive_collections_key_is_added_to_the_keys() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .Ignore(b => b.Details) .Ignore(b => b.Orders) - .HasPartitionKey(b => b.AlternateKey) - .Property(b => b.AlternateKey).HasConversion(); + .HasPartitionKey(b => b.Notes) + .PrimitiveCollection(b => b.Notes); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Equal( - new[] { nameof(Customer.Id), nameof(Customer.AlternateKey) }, - entity.FindPrimaryKey().Properties.Select(p => p.Name)); + new[] { nameof(Customer.Id), nameof(Customer.Notes) }, + entity.FindPrimaryKey()!.Properties.Select(p => p.Name)); Assert.Equal( - new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, + new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.Notes) }, entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); - var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); + var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)!; Assert.Single(idProperty.GetContainingKeys()); Assert.NotNull(idProperty.GetValueGeneratorFactory()); } [ConditionalFact] - public virtual void Partition_key_is_added_to_the_alternate_key_if_primary_key_contains_id() + public virtual void No_id_property_created_if_another_primitive_collection_mapped_to_id() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); modelBuilder.Entity() - .Ignore(b => b.Details) - .Ignore(b => b.Orders) - .HasPartitionKey(b => b.AlternateKey) - .Property(b => b.AlternateKey).HasConversion(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - - Assert.Equal( - new[] { StoreKeyConvention.DefaultIdPropertyName }, - entity.FindPrimaryKey().Properties.Select(p => p.Name)); - Assert.Equal( - new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, - entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); - } - - [ConditionalFact] - public virtual void No_id_property_created_if_another_property_mapped_to_id() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity() - .Property(c => c.Name) + .PrimitiveCollection(c => c.Notes) .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); modelBuilder.Entity() .Ignore(b => b.Details) @@ -370,7 +290,7 @@ public virtual void No_id_property_created_if_another_property_mapped_to_id() var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); Assert.Single(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); @@ -378,25 +298,25 @@ public virtual void No_id_property_created_if_another_property_mapped_to_id() var idProperty = entity.GetDeclaredProperties() .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName); Assert.Single(idProperty.GetContainingKeys()); - Assert.NotNull(idProperty.GetValueGeneratorFactory()); + Assert.Null(idProperty.GetValueGeneratorFactory()); } [ConditionalFact] - public virtual void No_id_property_created_if_another_property_mapped_to_id_in_pk() + public virtual void No_id_property_created_if_another_primitive_collection_to_id_in_pk() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() - .Property(c => c.Name) + .PrimitiveCollection(c => c.Notes) .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); modelBuilder.Entity() .Ignore(c => c.Details) .Ignore(c => c.Orders) - .HasKey(c => c.Name); + .HasKey(c => c.Notes); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); @@ -407,74 +327,6 @@ public virtual void No_id_property_created_if_another_property_mapped_to_id_in_p Assert.Null(idProperty.GetValueGeneratorFactory()); } - [ConditionalFact] - public virtual void No_alternate_key_is_created_if_primary_key_contains_id() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); - modelBuilder.Entity() - .Ignore(b => b.Details) - .Ignore(b => b.Orders); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - - Assert.Equal( - new[] { StoreKeyConvention.DefaultIdPropertyName }, - entity.FindPrimaryKey().Properties.Select(p => p.Name)); - Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); - - var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); - Assert.Single(idProperty.GetContainingKeys()); - Assert.Null(idProperty.GetValueGeneratorFactory()); - } - - [ConditionalFact] - public virtual void No_alternate_key_is_created_if_primary_key_contains_id_and_partition_key() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName); - modelBuilder.Entity() - .Ignore(b => b.Details) - .Ignore(b => b.Orders) - .HasPartitionKey(b => b.AlternateKey) - .Property(b => b.AlternateKey).HasConversion(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - - Assert.Equal( - new[] { nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName }, - entity.FindPrimaryKey().Properties.Select(p => p.Name)); - Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); - } - - [ConditionalFact] - public virtual void No_alternate_key_is_created_if_id_is_partition_key() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey)); - modelBuilder.Entity() - .Ignore(b => b.Details) - .Ignore(b => b.Orders) - .HasPartitionKey(b => b.AlternateKey) - .Property(b => b.AlternateKey).HasConversion().ToJsonProperty("id"); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - - Assert.Equal( - new[] { nameof(Customer.AlternateKey) }, - entity.FindPrimaryKey().Properties.Select(p => p.Name)); - Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); - } - protected override TestModelBuilder CreateModelBuilder(Action configure = null) => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); } diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs index aa54bcc4f9d..a8274349adc 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosTestModelBuilderExtensions.cs @@ -58,4 +58,21 @@ public static ModelBuilderTest.TestPropertyBuilder ToJsonProperty ToJsonProperty( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string name) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToJsonProperty(name); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToJsonProperty(name); + break; + } + + return builder; + } } diff --git a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs index 9243696b8a8..291cb87beb0 100644 --- a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs +++ b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs @@ -15,12 +15,6 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); } - public class InMemoryGenericPrimitiveCollections : GenericPrimitiveCollections - { - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); - } - public class InMemoryGenericComplexTypeTestBase : GenericComplexType { protected override TestModelBuilder CreateModelBuilder(Action configure = null) diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs index 784209f9d19..360cd51c519 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs @@ -296,14 +296,12 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives { var query = context.Entities.Where(x => x.Reference.IntArray[0] == 1); - if (async) - { - await Assert.ThrowsAsync(() => query.ToListAsync()); - } - else - { - Assert.Throws(() => query.ToList()); - } + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Equal(1, result.Count); + Assert.Equal(1, result[0].Reference.IntArray[0]); } } @@ -318,14 +316,12 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives { var query = context.Entities.Where(x => x.Reference.ListOfString[1] == "Bar"); - if (async) - { - await Assert.ThrowsAsync(() => query.ToListAsync()); - } - else - { - Assert.Throws(() => query.ToList()); - } + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Equal(1, result.Count); + Assert.Equal("Bar", result[0].Reference.ListOfString[1]); } } @@ -338,17 +334,18 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives using (var context = contextFactory.CreateContext()) { - var query = context.Entities.Where(x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1 - || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar"); + var query = context.Entities.Where( + x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1 + || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar") + .OrderBy(e => e.Id); - if (async) - { - await Assert.ThrowsAsync(() => query.ToListAsync()); - } - else - { - Assert.Throws(() => query.ToList()); - } + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Equal(1, result.Count); + Assert.Equal(1, result[0].Reference.IntArray[0]); + Assert.Equal("Bar", result[0].Reference.ListOfString[1]); } } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 183441de376..a888a5810ae 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -10,335 +10,6 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding; public class RelationalModelBuilderTest : ModelBuilderTest { - public abstract class RelationalPrimitiveCollectionsTestBase : PrimitiveCollectionsTestBase - { - [ConditionalFact] - public virtual void Can_use_table_splitting() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.HasDefaultSchema("dbo"); - - modelBuilder.Entity().SplitToTable( - "OrderDetails", s => - { - s.ExcludeFromMigrations(); - var propertyBuilder = s.Property(o => o.CustomerId); - var columnBuilder = propertyBuilder.HasColumnName("id"); - if (columnBuilder is IInfrastructure> genericBuilder) - { - Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); - Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); - } - else - { - var nonGenericBuilder = (IInfrastructure)columnBuilder; - Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); - Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); - } - }); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Order))!; - - Assert.False(entity.IsTableExcludedFromMigrations()); - Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); - Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); - Assert.Same( - entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); - - var customerId = entity.FindProperty(nameof(Order.CustomerId))!; - Assert.Equal("CustomerId", customerId.GetColumnName()); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); - Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); - Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); - } - - [ConditionalFact] - public virtual void Can_use_table_splitting_with_schema() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().ToTable("Order", "dbo") - .SplitToTable( - "OrderDetails", "sch", s => - s.ExcludeFromMigrations() - .Property(o => o.CustomerId).HasColumnName("id")); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Order))!; - - Assert.False(entity.IsTableExcludedFromMigrations()); - Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); - Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch"))); - Assert.Same( - entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch"))); - Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), - Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))) - .Message); - - var customerId = entity.FindProperty(nameof(Order.CustomerId))!; - Assert.Equal("CustomerId", customerId.GetColumnName()); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); - Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "sch"))); - Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "sch"))); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order"))); - } - - [ConditionalFact] - public virtual void Can_use_view_splitting() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().ToView("Order") - .SplitToView( - "OrderDetails", s => - { - var propertyBuilder = s.Property(o => o.CustomerId); - var columnBuilder = propertyBuilder.HasColumnName("id"); - if (columnBuilder is IInfrastructure> genericBuilder) - { - Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); - Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); - } - else - { - var nonGenericBuilder = (IInfrastructure)columnBuilder; - Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); - Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); - } - }); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Order))!; - - Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails"))); - - var customerId = entity.FindProperty(nameof(Order.CustomerId))!; - Assert.Equal("CustomerId", customerId.GetColumnName()); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); - Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails"))); - Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails"))); - } - - [ConditionalFact] - public virtual void Can_use_view_splitting_with_schema() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().ToView("Order", "dbo") - .SplitToView( - "OrderDetails", "sch", s => - s.Property(o => o.CustomerId).HasColumnName("id")); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - var model = modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Order))!; - - Assert.Same( - entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch"))); - Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), - Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order"))) - .Message); - - var customerId = entity.FindProperty(nameof(Order.CustomerId))!; - Assert.Equal("CustomerId", customerId.GetColumnName()); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order", "dbo"))); - Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails", "sch"))); - Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch"))); - Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_return_and_parameter_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedParameter() - .HasRowsAffectedReturnValue())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_return_and_result_column_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedResultColumn() - .HasRowsAffectedReturnValue())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedReturnValue() - .HasRowsAffectedParameter())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedReturnValue() - .HasRowsAffectedResultColumn())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedParameter() - .HasRowsAffectedResultColumn())) - .Message); - } - - [ConditionalFact] - public virtual void Duplicate_sproc_rows_affected_result_column_throws() - { - var modelBuilder = CreateModelBuilder(); - - var sproc = modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedResultColumn()).Metadata.GetUpdateStoredProcedure()!; - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), - Assert.Throws(() => sproc.AddRowsAffectedResultColumn()) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_parameter_and_result_column_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedResultColumn() - .HasRowsAffectedParameter())) - .Message); - } - - [ConditionalFact] - public virtual void Duplicate_sproc_rows_affected_parameter_throws() - { - var modelBuilder = CreateModelBuilder(); - - var sproc = modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedParameter()).Metadata.GetUpdateStoredProcedure()!; - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), - Assert.Throws(() => sproc.AddRowsAffectedParameter()) - .Message); - } - - [ConditionalFact] - public virtual void Duplicate_sproc_parameter_throws() - { - var modelBuilder = CreateModelBuilder(); - - var sproc = modelBuilder.Entity() - .InsertUsingStoredProcedure( - s => s.HasParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateParameter("Id", "BookLabel_Insert"), - Assert.Throws(() => sproc.AddParameter("Id")) - .Message); - } - - [ConditionalFact] - public virtual void Duplicate_sproc_original_value_parameter_throws() - { - var modelBuilder = CreateModelBuilder(); - - var sproc = modelBuilder.Entity() - .InsertUsingStoredProcedure( - s => s.HasOriginalValueParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateOriginalValueParameter("Id", "BookLabel_Insert"), - Assert.Throws(() => sproc.AddOriginalValueParameter("Id")) - .Message); - } - - [ConditionalFact] - public virtual void Duplicate_sproc_result_column_throws() - { - var modelBuilder = CreateModelBuilder(); - - var sproc = modelBuilder.Entity() - .InsertUsingStoredProcedure( - s => s.HasResultColumn(b => b.Id)).Metadata.GetInsertStoredProcedure()!; - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateResultColumn("Id", "BookLabel_Insert"), - Assert.Throws(() => sproc.AddResultColumn("Id")) - .Message); - } - - [ConditionalFact] - public virtual void Configuring_direction_on_RowsAffectedParameter_throws() - { - var modelBuilder = CreateModelBuilder(); - - var param = modelBuilder.Entity() - .InsertUsingStoredProcedure( - s => s.HasRowsAffectedParameter()).Metadata.GetInsertStoredProcedure()!.Parameters.Single(); - - Assert.Equal( - RelationalStrings.StoredProcedureParameterInvalidConfiguration("Direction", "RowsAffected", "BookLabel_Insert"), - Assert.Throws(() => param.Direction = ParameterDirection.Input) - .Message); - } - } - public abstract class RelationalNonRelationshipTestBase : NonRelationshipTestBase { [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 19a84b92361..a4a9e4007d1 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -144,6 +144,142 @@ public static ModelBuilderTest.TestPropertyBuilder HasJsonPropertyNam return builder; } + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasColumnName( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string? name) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasColumnName(name); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasColumnName(name); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasColumnType( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string typeName) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasColumnType(typeName); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasColumnType(typeName); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasDefaultValueSql( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string sql) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasDefaultValueSql(sql); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasDefaultValueSql(sql); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasComputedColumnSql( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string sql) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasComputedColumnSql(sql); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasComputedColumnSql(sql); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasDefaultValue( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + object value) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasDefaultValue(value); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasDefaultValue(value); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder IsFixedLength( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + bool fixedLength = true) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.IsFixedLength(fixedLength); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.IsFixedLength(fixedLength); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder UseCollation( + this ModelBuilderTest.TestPrimitiveCollectionBuilder builder, + string? collation) + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.UseCollation(collation); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.UseCollation(collation); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestPrimitiveCollectionBuilder HasJsonPropertyName( + this ModelBuilderTest.TestPrimitiveCollectionBuilder 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.TestEntityTypeBuilder UseTpcMappingStrategy( this ModelBuilderTest.TestEntityTypeBuilder builder) where TEntity : class diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 719a863d7ad..72f944bc6f2 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -616,7 +616,6 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method var parameterIndex = method.IsStatic ? 1 : 0; var parameters = method.GetParameters(); if (parameters.Length > parameterIndex - && !parameters[parameterIndex].ParameterType.FullName!.Contains("Builder") && parameters[parameterIndex].ParameterType != canSetMethod.GetParameters()[parameterIndex].ParameterType) { return $"{declaringType.Name}.{canSetMethod.Name}({Format(canSetMethod.GetParameters())})" @@ -1264,7 +1263,9 @@ protected ApiConsistencyFixtureBase() { typeof(OwnedEntityTypeBuilder), typeof(OwnedEntityTypeBuilder<>) }, { typeof(OwnershipBuilder), typeof(OwnershipBuilder<,>) }, { typeof(PropertyBuilder), typeof(PropertyBuilder<>) }, + { typeof(PrimitiveCollectionBuilder), typeof(PrimitiveCollectionBuilder<>) }, { typeof(ComplexTypePropertyBuilder), typeof(ComplexTypePropertyBuilder<>) }, + { typeof(ComplexTypePrimitiveCollectionBuilder), typeof(ComplexTypePrimitiveCollectionBuilder<>) }, { typeof(ReferenceCollectionBuilder), typeof(ReferenceCollectionBuilder<,>) }, { typeof(ReferenceNavigationBuilder), typeof(ReferenceNavigationBuilder<,>) }, { typeof(ReferenceReferenceBuilder), typeof(ReferenceReferenceBuilder<,>) }, @@ -1395,6 +1396,12 @@ protected ApiConsistencyFixtureBase() typeof(IConventionPropertyBase), typeof(IConventionPropertyBaseBuilder<>), typeof(IPropertyBase)) + }, + { + typeof(IReadOnlyElementType), (typeof(IMutableElementType), + typeof(IConventionElementType), + typeof(IConventionElementTypeBuilder), + typeof(IElementType)) } }; diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index 1ecaf5677d3..1cef8dab1f9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -29,6 +29,7 @@ public class SqlServerApiConsistencyFixture : ApiConsistencyFixtureBase typeof(SqlServerKeyBuilderExtensions), typeof(SqlServerModelBuilderExtensions), typeof(SqlServerPropertyBuilderExtensions), + typeof(SqlServerPrimitiveCollectionBuilderExtensions), typeof(SqlServerEntityTypeBuilderExtensions), typeof(SqlServerServiceCollectionExtensions), typeof(SqlServerDbFunctionsExtensions), diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs index d820f27c429..467a1ac6725 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; // ReSharper disable StringStartsWithIsCultureSpecific // ReSharper disable VirtualMemberCallInConstructor diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 962f98d824c..f801e094818 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -16,14 +16,6 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class SqlServerGenericPrimitiveCollections : SqlServerPrimitiveCollections - { - protected override TestModelBuilder CreateTestModelBuilder( - TestHelpers testHelpers, - Action? configure) - => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); - } - public class SqlServerGenericComplexType: SqlServerComplexType { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs index 830238132cf..37d8f51cea6 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -16,14 +16,6 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); } - public class SqlServerNonGenericPrimitiveCollections : SqlServerPrimitiveCollections - { - protected override TestModelBuilder CreateTestModelBuilder( - TestHelpers testHelpers, - Action? configure) - => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); - } - public class SqlServerNonGenericComplexType : SqlServerComplexType { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index e47214cc889..82c1a10d146 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -3,6 +3,8 @@ #nullable enable +using System.Collections.ObjectModel; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public class SqlServerModelBuilderTestBase : RelationalModelBuilderTest @@ -194,195 +196,71 @@ public virtual void Can_set_collation_for_property_type() Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); } - protected override TestModelBuilder CreateModelBuilder(Action? configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - } - - public abstract class SqlServerPrimitiveCollections : RelationalPrimitiveCollectionsTestBase - { [ConditionalFact] - public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() + public virtual void Can_set_store_type_for_primitive_collection() { var modelBuilder = CreateModelBuilder(); - var entityTypeBuilder = modelBuilder - .Entity(); - var indexBuilder = entityTypeBuilder - .HasIndex(ix => ix.Name) - .IsUnique(); - - var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; - var index = entityType.GetIndexes().Single(); - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsUnique(false); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsUnique(); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsClustered(); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsClustered(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(); - - Assert.Null(index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); - - Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); - - Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName(null); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.HasFilter("Foo"); - - Assert.Equal("Foo", index.GetFilter()); - - indexBuilder.HasFilter(null); - - Assert.Null(index.GetFilter()); - } - - [ConditionalFact] - public void Indexes_can_have_same_name_across_tables() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .HasIndex(e => e.Id, "Ix_Id") - .IsUnique(); - modelBuilder.Entity() - .HasIndex(e => e.CustomerId, "Ix_Id") - .IsUnique(); - - var model = modelBuilder.FinalizeModel(); - - var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); - Assert.Equal("Ix_Id", customerIndex.Name); - Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); - Assert.Equal( - "Ix_Id", customerIndex.GetDatabaseName( - StoreObjectIdentifier.Table("Customer"))); - - var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); - Assert.Equal("Ix_Id", detailsIndex.Name); - Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); - Assert.Equal( - "Ix_Id", detailsIndex.GetDatabaseName( - StoreObjectIdentifier.Table("CustomerDetails"))); - } - - [ConditionalFact] - public virtual void Can_set_store_type_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().HaveColumnType("smallint"); - c.Properties().HaveColumnType("nchar(max)"); - c.Properties(typeof(Nullable<>)).HavePrecision(2); - }); - - modelBuilder.Entity( + modelBuilder.Entity( b => { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); + b.PrimitiveCollection(e => e.Up).HasColumnType("national character varying(255)"); + b.PrimitiveCollection(e => e.Down).HasColumnType("nchar(10)"); + b.PrimitiveCollection("Charm").HasColumnType("nvarchar(25)"); + b.PrimitiveCollection("Strange").HasColumnType("text"); + b.PrimitiveCollection>("Top").HasColumnType("char(100)");; + b.PrimitiveCollection?>("Bottom").HasColumnType("varchar(max)");; }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; - Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); - Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); - Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); - var charm = entityType.FindProperty("Charm")!; - Assert.Equal("smallint", charm.GetColumnType()); - Assert.Null(charm.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); - var top = entityType.FindProperty("Top")!; - Assert.Equal("smallint", top.GetColumnType()); - Assert.Equal(2, top.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + Assert.Equal("int", entityType.FindProperty(nameof(CollectionQuarks.Id))!.GetColumnType()); + Assert.Equal("national character varying(255)", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(10)", entityType.FindProperty("Down")!.GetColumnType()); + Assert.Equal("nvarchar(25)", entityType.FindProperty("Charm")!.GetColumnType()); + Assert.Equal("text", entityType.FindProperty("Strange")!.GetColumnType()); + Assert.Equal("char(100)", entityType.FindProperty("Top")!.GetColumnType()); + Assert.Equal("varchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); } [ConditionalFact] - public virtual void Can_set_fixed_length_for_property_type() + public virtual void Can_set_fixed_length_for_primitive_collection() { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().AreFixedLength(false); - c.Properties().AreFixedLength(); - }); - - modelBuilder.Entity( + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( b => { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); + b.PrimitiveCollection(e => e.Up).IsFixedLength(false); + b.PrimitiveCollection(e => e.Down).IsFixedLength(); + b.PrimitiveCollection("Charm").IsFixedLength(true); }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; - Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); - Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); - Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); - Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); - Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Charm")!.IsFixedLength()); } [ConditionalFact] - public virtual void Can_set_collation_for_property_type() + public virtual void Can_set_collation_for_primitive_collection() { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); - c.Properties().UseCollation("Latin1_General_BIN"); - }); - - modelBuilder.Entity( + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( b => { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); + b.PrimitiveCollection(e => e.Up).UseCollation("Latin1_General_CS_AS_KS_WS"); + b.PrimitiveCollection(e => e.Down).UseCollation("Latin1_General_BIN"); + b.PrimitiveCollection("Charm").UseCollation("Latin1_General_CI_AI"); }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); + Assert.Equal("Latin1_General_CI_AI", entityType.FindProperty("Charm")!.GetCollation()); } protected override TestModelBuilder CreateModelBuilder(Action? configure = null) diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs index f9f6ce90fcf..91498ec7965 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs @@ -25,7 +25,8 @@ public class SqliteApiConsistencyFixture : ApiConsistencyFixtureBase typeof(SqliteServiceCollectionExtensions), typeof(SqliteDbContextOptionsBuilderExtensions), typeof(SqliteDbContextOptionsBuilder), - typeof(SqlitePropertyBuilderExtensions) + typeof(SqlitePropertyBuilderExtensions), + typeof(SqlitePrimitiveCollectionBuilderExtensions) }; public override diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 3456082d5c0..c375bf3d4e7 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -3681,19 +3681,20 @@ public void OnPropertyElementTypeChanged_calls_conventions_in_order(bool useBuil var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention)!; - var propertyBuilder = entityBuilder.Property(Order.OrderIdProperty, ConfigurationSource.Convention)!; + var propertyBuilder = entityBuilder.Property(Order.OrderIdsProperty, ConfigurationSource.Convention)!; var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; - var elementType = new ElementType(typeof(int), propertyBuilder.Metadata, ConfigurationSource.Convention); + ElementType elementType; if (useBuilder) { - Assert.NotNull(propertyBuilder.HasElementType(elementType, ConfigurationSource.Convention)); + Assert.NotNull(propertyBuilder.PrimitiveCollection(ConfigurationSource.Convention)); + elementType = (ElementType)propertyBuilder.Metadata.GetElementType()!; } else { - propertyBuilder.Metadata.SetElementType(elementType, ConfigurationSource.Convention); + elementType = (ElementType)propertyBuilder.Metadata.IsPrimitiveCollection(true, ConfigurationSource.Convention); } if (useScope) @@ -3709,25 +3710,19 @@ public void OnPropertyElementTypeChanged_calls_conventions_in_order(bool useBuil if (useBuilder) { - Assert.NotNull(propertyBuilder.HasElementType(elementType, ConfigurationSource.Convention)); + Assert.NotNull(propertyBuilder.PrimitiveCollection(ConfigurationSource.Convention)); + elementType = (ElementType)propertyBuilder.Metadata.GetElementType()!; } else { - propertyBuilder.Metadata.SetElementType(elementType, ConfigurationSource.Convention); + elementType = (ElementType)propertyBuilder.Metadata.IsPrimitiveCollection(true, ConfigurationSource.Convention); } Assert.Equal(new (object, object)[] { (null, elementType) }, convention1.Calls); Assert.Equal(new (object, object)[] { (null, elementType) }, convention2.Calls); Assert.Empty(convention3.Calls); - if (useBuilder) - { - Assert.NotNull(propertyBuilder.HasElementType(null, ConfigurationSource.Convention)); - } - else - { - propertyBuilder.Metadata.SetElementType(null, ConfigurationSource.Convention); - } + propertyBuilder.Metadata.IsPrimitiveCollection(false, ConfigurationSource.Convention); Assert.Equal(new (object, object)[] { (null, elementType), (elementType, null) }, convention1.Calls); Assert.Equal(new (object, object)[] { (null, elementType), (elementType, null) }, convention2.Calls); @@ -5051,6 +5046,7 @@ public void ProcessComplexTypeMemberIgnored( private class Order { public static readonly PropertyInfo OrderIdProperty = typeof(Order).GetProperty(nameof(OrderId)); + public static readonly PropertyInfo OrderIdsProperty = typeof(Order).GetProperty(nameof(OrderIds)); public static readonly PropertyInfo OrderDetailsProperty = typeof(Order).GetProperty(nameof(OrderDetails)); public static readonly PropertyInfo OtherOrderDetailsProperty = typeof(Order).GetProperty(nameof(OtherOrderDetails)); @@ -5058,6 +5054,7 @@ private class Order public readonly OrderDetails OrderDetailsField = default; public int OrderId { get; set; } + public int[] OrderIds { get; set; } public string Name { get; set; } diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs index 1fe44b00639..b6fca135a71 100644 --- a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.ObjectModel; using System.Dynamic; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -396,22 +397,40 @@ public virtual void Properties_can_have_access_mode_set() .Ignore() .Ignore() .Entity() - .ComplexProperty(e => e.Quarks, + .ComplexProperty( + e => e.Quarks, b => { b.Property(e => e.Up); b.Property(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); b.Property("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); b.Property("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + }) + .ComplexProperty( + e => e.CollectionQuarks, + b => + { + b.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction); + b.UseDefaultPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + b.PrimitiveCollection(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + b.PrimitiveCollection(e => e.Down).HasField("_forDown"); }); var model = modelBuilder.FinalizeModel(); - var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - Assert.Equal(PropertyAccessMode.PreferField, complexType.FindProperty("Up").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Field, complexType.FindProperty("Down").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Property, complexType.FindProperty("Charm").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.FieldDuringConstruction, complexType.FindProperty("Strange").GetPropertyAccessMode()); + var quarksType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties() + .Single(p => p.Name == nameof(Quarks)).ComplexType; + + Assert.Equal(PropertyAccessMode.PreferField, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, quarksType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Charm")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Strange")!.GetPropertyAccessMode()); + + quarksType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties() + .Single(p => p.Name == nameof(CollectionQuarks)).ComplexType; + + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down")!.GetPropertyAccessMode()); } [ConditionalFact] @@ -434,22 +453,33 @@ public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels( b.UseDefaultPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); b.Property(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); b.Property(e => e.Down).HasField("_forDown"); + }) + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction); + b.UseDefaultPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + b.PrimitiveCollection(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + b.PrimitiveCollection(e => e.Down).HasField("_forDown"); }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(ComplexProperties)); + var entityType = model.FindEntityType(typeof(ComplexProperties))!; Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); - var customerType = entityType.FindComplexProperty(nameof(ComplexProperties.Customer)).ComplexType; - Assert.Equal(PropertyAccessMode.Field, customerType.GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Field, customerType.FindProperty("Id").GetPropertyAccessMode()); - - var quarksProperty = entityType.FindComplexProperty(nameof(ComplexProperties.Quarks)); + var quarksProperty = entityType.FindComplexProperty(nameof(ComplexProperties.Quarks))!; var quarksType = quarksProperty.ComplexType; Assert.Equal(PropertyAccessMode.PreferFieldDuringConstruction, quarksProperty.GetPropertyAccessMode()); Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); + + quarksProperty = entityType.FindComplexProperty(nameof(ComplexProperties.CollectionQuarks))!; + quarksType = quarksProperty.ComplexType; + Assert.Equal(PropertyAccessMode.PreferFieldDuringConstruction, quarksProperty.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); } [ConditionalFact] @@ -1282,7 +1312,6 @@ public virtual void Can_set_custom_value_generator_for_properties() { b.Property(e => e.Up).HasValueGenerator(); b.Property(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); - b.Property("Charm").HasValueGenerator((_, __) => new CustomValueGenerator()); b.Property("Strange").HasValueGenerator(); b.Property("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); b.Property("Bottom").HasValueGeneratorFactory(); @@ -1295,7 +1324,6 @@ public virtual void Can_set_custom_value_generator_for_properties() Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); Assert.IsType(complexType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); Assert.IsType(complexType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); - Assert.IsType(complexType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); Assert.IsType(complexType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); Assert.IsType(complexType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); Assert.IsType(complexType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); @@ -1465,7 +1493,6 @@ public virtual void PropertyBuilder_methods_can_be_chained() .HasValueGenerator(typeof(CustomValueGenerator)) .HasValueGeneratorFactory() .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) - .HasValueGenerator((_, __) => null) .IsRequired(); [ConditionalFact] @@ -1481,7 +1508,7 @@ public virtual void Can_call_Property_on_a_field() var model = modelBuilder.FinalizeModel(); var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - Assert.Equal(3, complexType.GetProperties().Count()); + Assert.Equal(6, complexType.GetProperties().Count()); var property = complexType.FindProperty(nameof(EntityWithFields.Id)); Assert.Null(property.PropertyInfo); Assert.NotNull(property.FieldInfo); @@ -1503,7 +1530,7 @@ public virtual void Can_ignore_a_field() var model = modelBuilder.FinalizeModel(); var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); - Assert.Equal(2, complexProperty.ComplexType.GetProperties().Count()); + Assert.Equal(5, complexProperty.ComplexType.GetProperties().Count()); } [ConditionalFact] @@ -1627,5 +1654,479 @@ protected virtual void Mapping_throws_for_empty_complex_types() "ComplexProperties.Customer#Customer"), Assert.Throws(modelBuilder.FinalizeModel).Message); } + + [ConditionalFact] + public virtual void Can_set_primitive_collection_annotation_when_no_clr_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.Customer) + .Ignore(c => c.Details) + .Ignore(c => c.Orders) + .PrimitiveCollection("Ints").HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty("Ints")!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up")!.IsNullable); + Assert.True(complexType.FindProperty("Down")!.IsNullable); + Assert.True(complexType.FindProperty("Charm")!.IsNullable); // Because we can't detect the non-nullable reference type + Assert.True(complexType.FindProperty("Strange")!.IsNullable); + } + + public virtual void Can_ignore_shadow_primitive_collections_when_they_have_been_added_explicitly() + { + var modelBuilder = CreateModelBuilder(); + + var complexPropertyBuilder = modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Customer, b => b.Ignore(c => c.Details).Ignore(c => c.Orders)); + complexPropertyBuilder.PrimitiveCollection("Shadow"); + complexPropertyBuilder.Ignore("Shadow"); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Can_add_shadow_primitive_collections_when_they_have_been_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.Customer, + b => + { + b.Ignore(c => c.Details); + b.Ignore(c => c.Orders); + b.Ignore("Shadow"); + b.PrimitiveCollection("Shadow"); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + Assert.NotNull(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).IsRequired(); + b.PrimitiveCollection(e => e.Down).IsRequired(); + b.PrimitiveCollection>("Charm").IsRequired(); + b.PrimitiveCollection>("Strange").IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up")!.IsNullable); + Assert.False(complexType.FindProperty("Down")!.IsNullable); + Assert.False(complexType.FindProperty("Charm")!.IsNullable); + Assert.False(complexType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).IsRequired(false); + b.PrimitiveCollection(e => e.Down).IsRequired(false); + b.PrimitiveCollection>("Charm").IsRequired(false); + b.PrimitiveCollection>("Strange").IsRequired(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty("Up")!.IsNullable); + Assert.True(complexType.FindProperty("Down")!.IsNullable); + Assert.True(complexType.FindProperty("Charm")!.IsNullable); + Assert.True(complexType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection>("Up"); + b.PrimitiveCollection>("Down"); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up")!.IsShadowProperty()); + Assert.False(complexType.FindProperty("Down")!.IsShadowProperty()); + Assert.True(complexType.FindProperty("Charm")!.IsShadowProperty()); + Assert.True(complexType.FindProperty("Strange")!.IsShadowProperty()); + + Assert.Equal(-1, complexType.FindProperty("Up")!.GetShadowIndex()); + Assert.Equal(-1, complexType.FindProperty("Down")!.GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Charm")!.GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Strange")!.GetShadowIndex()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_concurrency_tokens() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).IsConcurrencyToken(); + b.PrimitiveCollection(e => e.Down).IsConcurrencyToken(false); + b.PrimitiveCollection>("Charm").IsConcurrencyToken(); + b.PrimitiveCollection>("Strange").IsConcurrencyToken(false); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties()!.Single().ComplexType; + + 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.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, complexType.GetChangeTrackingStrategy()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_have_field_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection>("Up").HasField("_forUp"); + b.PrimitiveCollection(e => e.Down).HasField("_forDown"); + b.PrimitiveCollection>("_forWierd").HasField("_forWierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.Equal("_forUp", complexType.FindProperty("Up")!.GetFieldName()); + Assert.Equal("_forDown", complexType.FindProperty("Down")!.GetFieldName()); + Assert.Equal("_forWierd", complexType.FindProperty("_forWierd")!.GetFieldName()); + } + + [ConditionalFact] + public virtual void HasField_for_primitive_collection_throws_if_field_is_not_found() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + Assert.Equal( + CoreStrings.MissingBackingField("_notFound", nameof(CollectionQuarks.Down), "ComplexProperties.CollectionQuarks#CollectionQuarks"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_notFound")).Message); + }); + } + + [ConditionalFact] + public virtual void HasField_for_primitive_collection_throws_if_field_is_wrong_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + Assert.Equal( + CoreStrings.BadBackingFieldType("_forUp", "ObservableCollection", nameof(CollectionQuarks), nameof(CollectionQuarks.Down), "ObservableCollection"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_forUp")).Message); + }); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_set_to_generate_values_on_Add() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).ValueGeneratedOnAddOrUpdate(); + b.PrimitiveCollection(e => e.Down).ValueGeneratedNever(); + b.PrimitiveCollection>("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; + b.PrimitiveCollection>("Strange").ValueGeneratedNever(); + b.PrimitiveCollection>("Top").ValueGeneratedOnAddOrUpdate(); + b.PrimitiveCollection>("Bottom").ValueGeneratedOnUpdate(); + }); + + 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.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 Can_set_max_length_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).HasMaxLength(0); + b.PrimitiveCollection(e => e.Down).HasMaxLength(100); + b.PrimitiveCollection>("Charm").HasMaxLength(0); + b.PrimitiveCollection>("Strange").HasMaxLength(-1); + b.PrimitiveCollection("Top").HasMaxLength(0); + b.PrimitiveCollection("Bottom").HasMaxLength(100); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + 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_sentinel_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).HasSentinel(1); + b.PrimitiveCollection(e => e.Down).HasSentinel("100"); + b.PrimitiveCollection("Charm").HasSentinel(-1); + b.PrimitiveCollection>("Strange").HasSentinel("-1"); + b.PrimitiveCollection("Top").HasSentinel(77); + b.PrimitiveCollection>("Bottom").HasSentinel("100"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(nameof(CollectionQuarks.Id))!.Sentinel); + Assert.Equal(1, complexType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Charm")!.Sentinel); + Assert.Equal("-1", complexType.FindProperty("Strange")!.Sentinel); + Assert.Equal(77, complexType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_custom_value_generator_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).HasValueGenerator(); + b.PrimitiveCollection(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); + b.PrimitiveCollection>("Strange").HasValueGenerator(); + b.PrimitiveCollection("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); + b.PrimitiveCollection>("Bottom").HasValueGeneratorFactory(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.IsType(complexType.FindProperty("Up")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(complexType.FindProperty("Down")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(complexType.FindProperty("Strange")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(complexType.FindProperty("Top")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(complexType.FindProperty("Bottom")!.GetValueGeneratorFactory()!(null!, null!)); + } + + [ConditionalFact] + public virtual void Throws_for_primitive_collection_with_bad_value_generator_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + Assert.Equal( + CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), + Assert.Throws(() => b.Property(e => e.Down).HasValueGenerator(typeof(Random))).Message); + }); + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.CollectionQuarks, + b => + { + b.PrimitiveCollection(e => e.Up).IsUnicode(); + b.PrimitiveCollection(e => e.Down).IsUnicode(false); + b.PrimitiveCollection("Charm").IsUnicode(); + b.PrimitiveCollection>("Strange").IsUnicode(false); + b.PrimitiveCollection("Top").IsUnicode(); + b.PrimitiveCollection>("Bottom").IsUnicode(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty("Up")!.IsUnicode()); + Assert.False(complexType.FindProperty("Down")!.IsUnicode()); + Assert.True(complexType.FindProperty("Charm")!.IsUnicode()); + Assert.False(complexType.FindProperty("Strange")!.IsUnicode()); + Assert.True(complexType.FindProperty("Top")!.IsUnicode()); + Assert.False(complexType.FindProperty("Bottom")!.IsUnicode()); + } + + [ConditionalFact] + public virtual void PrimitiveCollectionBuilder_methods_can_be_chained() + => CreateModelBuilder() + .Entity() + .ComplexProperty(e => e.CollectionQuarks) + .PrimitiveCollection(e => e.Up) + .IsRequired() + .HasAnnotation("A", "V") + .IsConcurrencyToken() + .ValueGeneratedNever() + .ValueGeneratedOnAdd() + .ValueGeneratedOnAddOrUpdate() + .ValueGeneratedOnUpdate() + .IsUnicode() + .HasMaxLength(100) + .HasSentinel(null) + .HasValueGenerator() + .HasValueGenerator(typeof(CustomValueGenerator)) + .HasValueGeneratorFactory() + .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) + .IsRequired(); + + [ConditionalFact] + public virtual void Can_call_PrimitiveCollection_on_a_field() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty(e => e.EntityWithFields).PrimitiveCollection(e => e.CollectionId); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; + Assert.Equal(6, complexType.GetProperties().Count()); + var property = complexType.FindProperty(nameof(EntityWithFields.CollectionId))!; + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } } } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs index aa1f164da51..823b04165ea 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs @@ -23,14 +23,6 @@ protected override TestModelBuilder CreateTestModelBuilder( => new GenericTypeTestModelBuilder(testHelpers, configure); } - public class GenericPrimitiveCollectionsTest : PrimitiveCollectionsTestBase - { - protected override TestModelBuilder CreateTestModelBuilder( - TestHelpers testHelpers, - Action? configure) - => new GenericTypeTestModelBuilder(testHelpers, configure); - } - private class GenericTypeTestModelBuilder : TestModelBuilder { public GenericTypeTestModelBuilder(TestHelpers testHelpers, Action? configure) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index b20d933034c..fd2c78a6b7d 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -68,14 +68,6 @@ public virtual void Changing_propertyInfo_updates_Property() } } - public class GenericPrimitiveCollections : PrimitiveCollectionsTestBase - { - protected override TestModelBuilder CreateTestModelBuilder( - TestHelpers testHelpers, - Action? configure) - => new GenericTestModelBuilder(testHelpers, configure); - } - public class GenericComplexType : ComplexTypeTestBase { protected override TestModelBuilder CreateTestModelBuilder( @@ -507,6 +499,9 @@ protected virtual TestComplexPropertyBuilder Wrap(ComplexPropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) => new GenericTestComplexTypePropertyBuilder(propertyBuilder); + protected virtual TestComplexTypePrimitiveCollectionBuilder Wrap(ComplexTypePrimitiveCollectionBuilder propertyBuilder) + => new GenericTestComplexTypePrimitiveCollectionBuilder(propertyBuilder); + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); @@ -520,6 +515,13 @@ public override TestComplexTypePropertyBuilder Property(Ex public override TestComplexTypePropertyBuilder Property(string propertyName) => Wrap(PropertyBuilder.Property(propertyName)); + public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Expression> propertyExpression) + where TProperty : default + => Wrap(PropertyBuilder.PrimitiveCollection(propertyExpression)); + + public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) + => Wrap(PropertyBuilder.PrimitiveCollection(propertyName)); + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); @@ -792,6 +794,12 @@ public GenericTestPrimitiveCollectionBuilder(PrimitiveCollectionBuilder PrimitiveCollectionBuilder.Metadata; + public override TestElementTypeBuilder ElementType() + => new(PrimitiveCollectionBuilder.ElementType()); + + public override TestPrimitiveCollectionBuilder ElementType(Action builderAction) + => Wrap(PrimitiveCollectionBuilder.ElementType(b => builderAction(new TestElementTypeBuilder(b)))); + protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder primitiveCollectionBuilder) => new GenericTestPrimitiveCollectionBuilder(primitiveCollectionBuilder); @@ -831,10 +839,6 @@ public override TestPrimitiveCollectionBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType) => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); - public override TestPrimitiveCollectionBuilder HasValueGenerator( - Func factory) - => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(factory)); - public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory() => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); @@ -912,10 +916,6 @@ public override TestComplexTypePropertyBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType) => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); - public override TestComplexTypePropertyBuilder HasValueGenerator( - Func factory) - => Wrap(PropertyBuilder.HasValueGenerator(factory)); - public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() => Wrap(PropertyBuilder.HasValueGeneratorFactory()); @@ -1005,6 +1005,74 @@ ComplexTypePropertyBuilder IInfrastructure PropertyBuilder; } + protected class GenericTestComplexTypePrimitiveCollectionBuilder : + TestComplexTypePrimitiveCollectionBuilder, IInfrastructure> + { + public GenericTestComplexTypePrimitiveCollectionBuilder(ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + { + PrimitiveCollectionBuilder = primitiveCollectionBuilder; + } + + protected ComplexTypePrimitiveCollectionBuilder PrimitiveCollectionBuilder { get; } + + public override IMutableProperty Metadata + => PrimitiveCollectionBuilder.Metadata; + + protected virtual TestComplexTypePrimitiveCollectionBuilder Wrap(ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + => new GenericTestComplexTypePrimitiveCollectionBuilder(primitiveCollectionBuilder); + + public override TestComplexTypePrimitiveCollectionBuilder HasAnnotation(string annotation, object? value) + => Wrap(PrimitiveCollectionBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePrimitiveCollectionBuilder IsRequired(bool isRequired = true) + => Wrap(PrimitiveCollectionBuilder.IsRequired(isRequired)); + + public override TestComplexTypePrimitiveCollectionBuilder HasMaxLength(int maxLength) + => Wrap(PrimitiveCollectionBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel) + => Wrap(PrimitiveCollectionBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true) + => Wrap(PrimitiveCollectionBuilder.IsUnicode(unicode)); + + public override TestComplexTypePrimitiveCollectionBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PrimitiveCollectionBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedNever() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedNever()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAdd() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGenerator() + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory() + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePrimitiveCollectionBuilder HasField(string fieldName) + => Wrap(PrimitiveCollectionBuilder.HasField(fieldName)); + + public override TestComplexTypePrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PrimitiveCollectionBuilder.UsePropertyAccessMode(propertyAccessMode)); + + ComplexTypePrimitiveCollectionBuilder IInfrastructure>.Instance + => PrimitiveCollectionBuilder; + } + protected class GenericTestKeyBuilder : TestKeyBuilder, IInfrastructure> { public GenericTestKeyBuilder(KeyBuilder keyBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 062e09cf790..2df65e959cc 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -17,14 +17,6 @@ protected override TestModelBuilder CreateTestModelBuilder( => new NonGenericTestModelBuilder(testHelpers, configure); } - public class NonGenericPrimitiveProperties : PrimitiveCollectionsTestBase - { - protected override TestModelBuilder CreateTestModelBuilder( - TestHelpers testHelpers, - Action? configure) - => new NonGenericTestModelBuilder(testHelpers, configure); - } - public class NonGenericComplexType : ComplexTypeTestBase { protected override TestModelBuilder CreateTestModelBuilder( @@ -584,6 +576,9 @@ protected virtual NonGenericTestComplexPropertyBuilder Wrap(ComplexPropert protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) => new NonGenericTestComplexTypePropertyBuilder(propertyBuilder); + protected virtual TestComplexTypePrimitiveCollectionBuilder Wrap(ComplexTypePrimitiveCollectionBuilder propertyBuilder) + => new NonGenericTestComplexTypePrimitiveCollectionBuilder(propertyBuilder); + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); @@ -599,6 +594,15 @@ public override TestComplexTypePropertyBuilder Property(Ex public override TestComplexTypePropertyBuilder Property(string propertyName) => Wrap(PropertyBuilder.Property(propertyName)); + public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.PrimitiveCollection(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName) + => Wrap(PropertyBuilder.PrimitiveCollection(propertyName)); + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); @@ -878,6 +882,12 @@ public NonGenericTestPrimitiveCollectionBuilder(PrimitiveCollectionBuilder primi public override IMutableProperty Metadata => PrimitiveCollectionBuilder.Metadata; + public override TestElementTypeBuilder ElementType() + => new(PrimitiveCollectionBuilder.ElementType()); + + public override TestPrimitiveCollectionBuilder ElementType(Action builderAction) + => Wrap(PrimitiveCollectionBuilder.ElementType(b => builderAction(new TestElementTypeBuilder(b)))); + protected virtual TestPrimitiveCollectionBuilder Wrap(PrimitiveCollectionBuilder primitiveCollectionBuilder) => new NonGenericTestPrimitiveCollectionBuilder(primitiveCollectionBuilder); @@ -917,10 +927,6 @@ public override TestPrimitiveCollectionBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType) => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); - public override TestPrimitiveCollectionBuilder HasValueGenerator( - Func factory) - => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(factory)); - public override TestPrimitiveCollectionBuilder HasValueGeneratorFactory() => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); @@ -998,10 +1004,6 @@ public override TestComplexTypePropertyBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType) => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); - public override TestComplexTypePropertyBuilder HasValueGenerator( - Func factory) - => Wrap(PropertyBuilder.HasValueGenerator(factory)); - public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() => Wrap(PropertyBuilder.HasValueGeneratorFactory()); @@ -1088,6 +1090,74 @@ ComplexTypePropertyBuilder IInfrastructure.Instance => PropertyBuilder; } + protected class NonGenericTestComplexTypePrimitiveCollectionBuilder : + TestComplexTypePrimitiveCollectionBuilder, IInfrastructure + { + public NonGenericTestComplexTypePrimitiveCollectionBuilder(ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + { + PrimitiveCollectionBuilder = primitiveCollectionBuilder; + } + + private ComplexTypePrimitiveCollectionBuilder PrimitiveCollectionBuilder { get; } + + public override IMutableProperty Metadata + => PrimitiveCollectionBuilder.Metadata; + + protected virtual TestComplexTypePrimitiveCollectionBuilder Wrap(ComplexTypePrimitiveCollectionBuilder primitiveCollectionBuilder) + => new NonGenericTestComplexTypePrimitiveCollectionBuilder(primitiveCollectionBuilder); + + public override TestComplexTypePrimitiveCollectionBuilder HasAnnotation(string annotation, object? value) + => Wrap(PrimitiveCollectionBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePrimitiveCollectionBuilder IsRequired(bool isRequired = true) + => Wrap(PrimitiveCollectionBuilder.IsRequired(isRequired)); + + public override TestComplexTypePrimitiveCollectionBuilder HasMaxLength(int maxLength) + => Wrap(PrimitiveCollectionBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel) + => Wrap(PrimitiveCollectionBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true) + => Wrap(PrimitiveCollectionBuilder.IsUnicode(unicode)); + + public override TestComplexTypePrimitiveCollectionBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PrimitiveCollectionBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedNever() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedNever()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAdd() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdate() + => Wrap(PrimitiveCollectionBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGenerator() + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PrimitiveCollectionBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory() + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PrimitiveCollectionBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePrimitiveCollectionBuilder HasField(string fieldName) + => Wrap(PrimitiveCollectionBuilder.HasField(fieldName)); + + public override TestComplexTypePrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PrimitiveCollectionBuilder.UsePropertyAccessMode(propertyAccessMode)); + + ComplexTypePrimitiveCollectionBuilder IInfrastructure.Instance + => PrimitiveCollectionBuilder; + } + protected class NonGenericTestNavigationBuilder : TestNavigationBuilder { public NonGenericTestNavigationBuilder(NavigationBuilder navigationBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 66f8d43c79c..00196331793 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -4,8 +4,6 @@ #nullable enable using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using static Microsoft.EntityFrameworkCore.ModelBuilding.ModelBuilderTest; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -372,6 +370,12 @@ public abstract TestComplexTypePropertyBuilder Property( Expression> propertyExpression); public abstract TestComplexTypePropertyBuilder Property(string propertyName); + + public abstract TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection( + Expression> propertyExpression); + + public abstract TestComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string propertyName); + public abstract TestComplexTypePropertyBuilder IndexerProperty(string propertyName); public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); @@ -523,6 +527,9 @@ public abstract TestPropertyBuilder HasConversion { + public abstract TestElementTypeBuilder ElementType(); + public abstract TestPrimitiveCollectionBuilder ElementType(Action builderAction); + public abstract IMutableProperty Metadata { get; } public abstract TestPrimitiveCollectionBuilder HasAnnotation(string annotation, object? value); public abstract TestPrimitiveCollectionBuilder IsRequired(bool isRequired = true); @@ -541,9 +548,6 @@ public abstract TestPrimitiveCollectionBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType); - public abstract TestPrimitiveCollectionBuilder HasValueGenerator( - Func factory); - public abstract TestPrimitiveCollectionBuilder HasValueGeneratorFactory() where TFactory : ValueGeneratorFactory; @@ -553,6 +557,60 @@ public abstract TestPrimitiveCollectionBuilder HasValueGeneratorFacto public abstract TestPrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); } + public class TestElementTypeBuilder(ElementTypeBuilder elementTypeBuilder) + { + public virtual ElementTypeBuilder ElementTypeBuilder { get; } = elementTypeBuilder; + + public virtual IMutableElementType Metadata + => ElementTypeBuilder.Metadata; + + protected virtual TestElementTypeBuilder Wrap(ElementTypeBuilder elementTypeBuilder) + => new(elementTypeBuilder); + + public virtual TestElementTypeBuilder HasAnnotation(string annotation, object? value) + => Wrap(ElementTypeBuilder.HasAnnotation(annotation, value)); + + public virtual TestElementTypeBuilder IsRequired(bool required = true) + => Wrap(ElementTypeBuilder.IsRequired(required)); + + public virtual TestElementTypeBuilder HasMaxLength(int maxLength) + => Wrap(ElementTypeBuilder.HasMaxLength(maxLength)); + + public virtual TestElementTypeBuilder HasPrecision(int precision, int scale) + => Wrap(ElementTypeBuilder.HasPrecision(precision, scale)); + + public virtual TestElementTypeBuilder HasPrecision(int precision) + => Wrap(ElementTypeBuilder.HasPrecision(precision)); + + public virtual TestElementTypeBuilder IsUnicode(bool unicode = true) + => Wrap(ElementTypeBuilder.IsUnicode(unicode)); + + public virtual TestElementTypeBuilder HasConversion() + => Wrap(ElementTypeBuilder.HasConversion()); + + public virtual TestElementTypeBuilder HasConversion(Type? conversionType) + => Wrap(ElementTypeBuilder.HasConversion(conversionType)); + + public virtual TestElementTypeBuilder HasConversion(ValueConverter? converter) + => Wrap(ElementTypeBuilder.HasConversion(converter)); + + public virtual TestElementTypeBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(ElementTypeBuilder.HasConversion(valueComparer)); + + public virtual TestElementTypeBuilder HasConversion(Type conversionType, ValueComparer? valueComparer) + => Wrap(ElementTypeBuilder.HasConversion(conversionType, valueComparer)); + + public virtual TestElementTypeBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(ElementTypeBuilder.HasConversion(converter, valueComparer)); + + public virtual TestElementTypeBuilder HasConversion() + where TComparer : ValueComparer + => Wrap(ElementTypeBuilder.HasConversion()); + + public virtual TestElementTypeBuilder HasConversion(Type conversionType, Type? comparerType) + => Wrap(ElementTypeBuilder.HasConversion(conversionType, comparerType)); + } + public abstract class TestComplexTypePropertyBuilder { public abstract IMutableProperty Metadata { get; } @@ -576,9 +634,6 @@ public abstract TestComplexTypePropertyBuilder HasValueGenerator HasValueGenerator(Type valueGeneratorType); - public abstract TestComplexTypePropertyBuilder HasValueGenerator( - Func factory); - public abstract TestComplexTypePropertyBuilder HasValueGeneratorFactory() where TFactory : ValueGeneratorFactory; @@ -636,6 +691,29 @@ public abstract TestComplexTypePropertyBuilder HasConversion + { + public abstract IMutableProperty Metadata { get; } + public abstract TestComplexTypePrimitiveCollectionBuilder HasAnnotation(string annotation, object? value); + public abstract TestComplexTypePrimitiveCollectionBuilder IsRequired(bool isRequired = true); + public abstract TestComplexTypePrimitiveCollectionBuilder HasMaxLength(int maxLength); + public abstract TestComplexTypePrimitiveCollectionBuilder HasSentinel(object? sentinel); + public abstract TestComplexTypePrimitiveCollectionBuilder IsUnicode(bool unicode = true); + public abstract TestComplexTypePrimitiveCollectionBuilder IsConcurrencyToken(bool isConcurrencyToken = true); + public abstract TestComplexTypePrimitiveCollectionBuilder ValueGeneratedNever(); + public abstract TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAdd(); + public abstract TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnAddOrUpdate(); + public abstract TestComplexTypePrimitiveCollectionBuilder ValueGeneratedOnUpdate(); + public abstract TestComplexTypePrimitiveCollectionBuilder HasValueGenerator() + where TGenerator : ValueGenerator; + public abstract TestComplexTypePrimitiveCollectionBuilder HasValueGenerator(Type valueGeneratorType); + public abstract TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory() + where TFactory : ValueGeneratorFactory; + public abstract TestComplexTypePrimitiveCollectionBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType); + public abstract TestComplexTypePrimitiveCollectionBuilder HasField(string fieldName); + public abstract TestComplexTypePrimitiveCollectionBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + } + public abstract class TestNavigationBuilder { public abstract TestNavigationBuilder HasAnnotation(string annotation, object? value); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index c0751dab34e..9413ae7ec64 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System.Collections.ObjectModel; using System.ComponentModel; using System.Dynamic; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -22,7 +25,7 @@ public void Can_set_model_annotation() Assert.NotNull(modelBuilder); var model = modelBuilder.FinalizeModel(); - Assert.Equal("Ro", model.FindAnnotation("Fus").Value); + Assert.Equal("Ro", model.FindAnnotation("Fus")!.Value); } [ConditionalFact] @@ -44,7 +47,7 @@ public virtual void Can_get_entity_builder_for_clr_type() var entityBuilder = modelBuilder.Entity(); Assert.NotNull(entityBuilder); - Assert.Equal(typeof(Customer).FullName, model.FindEntityType(typeof(Customer)).Name); + Assert.Equal(typeof(Customer).FullName, model.FindEntityType(typeof(Customer))!.Name); } [ConditionalFact] @@ -55,10 +58,10 @@ public virtual void Can_set_entity_key_from_clr_property() modelBuilder.Entity().HasKey(b => b.Id); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; - Assert.Equal(1, entity.FindPrimaryKey().Properties.Count); - Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey().Properties.First().Name); + Assert.Equal(1, entity.FindPrimaryKey()!.Properties.Count); + Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey()!.Properties.First().Name); } [ConditionalFact] @@ -71,8 +74,8 @@ public virtual void Entity_key_on_shadow_property_is_discovered_by_convention() var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(Order)); - Assert.Equal("Id", entity.FindPrimaryKey().Properties.Single().Name); + var entity = model.FindEntityType(typeof(Order))!; + Assert.Equal("Id", entity.FindPrimaryKey()!.Properties.Single().Name); } [ConditionalFact] @@ -86,8 +89,8 @@ public virtual void Entity_key_on_secondary_property_is_discovered_by_convention .Ignore(s => s.Id); modelBuilder.FinalizeModel(); - var entity = modelBuilder.Model.FindEntityType(typeof(SelfRef)); - Assert.Equal(nameof(SelfRef.SelfRefId), entity.FindPrimaryKey().Properties.Single().Name); + var entity = modelBuilder.Model.FindEntityType(typeof(SelfRef))!; + Assert.Equal(nameof(SelfRef.SelfRefId), entity.FindPrimaryKey()!.Properties.Single().Name); } [ConditionalFact] @@ -105,10 +108,10 @@ public virtual void Can_set_entity_key_from_property_name_when_no_clr_property() b.HasKey(Customer.IdProperty.Name + 1); }); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; - Assert.Equal(1, entity.FindPrimaryKey().Properties.Count); - Assert.Equal(Customer.IdProperty.Name + 1, entity.FindPrimaryKey().Properties.First().Name); + Assert.Equal(1, entity.FindPrimaryKey()!.Properties.Count); + Assert.Equal(Customer.IdProperty.Name + 1, entity.FindPrimaryKey()!.Properties.First().Name); } [ConditionalFact] @@ -124,10 +127,10 @@ public virtual void Can_set_entity_key_from_clr_property_when_property_ignored_o b.HasKey(e => e.Id); }); - var entity = modelBuilder.Model.FindEntityType(typeof(Customer)); + var entity = modelBuilder.Model.FindEntityType(typeof(Customer))!; - Assert.Equal(1, entity.FindPrimaryKey().Properties.Count); - Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey().Properties.First().Name); + Assert.Equal(1, entity.FindPrimaryKey()!.Properties.Count); + Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey()!.Properties.First().Name); } [ConditionalFact] @@ -141,11 +144,11 @@ public virtual void Can_set_composite_entity_key_from_clr_properties() .HasKey( e => new { e.Id, e.Name }); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; - Assert.Equal(2, entity.FindPrimaryKey().Properties.Count); - Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey().Properties.First().Name); - Assert.Equal(Customer.NameProperty.Name, entity.FindPrimaryKey().Properties.Last().Name); + Assert.Equal(2, entity.FindPrimaryKey()!.Properties.Count); + Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey()!.Properties.First().Name); + Assert.Equal(Customer.NameProperty.Name, entity.FindPrimaryKey()!.Properties.Last().Name); } [ConditionalFact] @@ -163,11 +166,11 @@ public virtual void Can_set_composite_entity_key_from_property_names_when_mixed_ b.HasKey(Customer.IdProperty.Name, Customer.NameProperty.Name + "Shadow"); }); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; - Assert.Equal(2, entity.FindPrimaryKey().Properties.Count); - Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey().Properties.First().Name); - Assert.Equal(Customer.NameProperty.Name + "Shadow", entity.FindPrimaryKey().Properties.Last().Name); + Assert.Equal(2, entity.FindPrimaryKey()!.Properties.Count); + Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey()!.Properties.First().Name); + Assert.Equal(Customer.NameProperty.Name + "Shadow", entity.FindPrimaryKey()!.Properties.Last().Name); } [ConditionalFact] @@ -184,10 +187,10 @@ public virtual void Can_set_entity_key_with_annotations() keyBuilder.HasAnnotation("A1", "V1") .HasAnnotation("A2", "V2"); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Equal( - new[] { Customer.IdProperty.Name, Customer.NameProperty.Name }, entity.FindPrimaryKey().Properties.Select(p => p.Name)); + new[] { Customer.IdProperty.Name, Customer.NameProperty.Name }, entity.FindPrimaryKey()!.Properties.Select(p => p.Name)); Assert.Equal("V1", keyBuilder.Metadata["A1"]); Assert.Equal("V2", keyBuilder.Metadata["A2"]); } @@ -202,19 +205,19 @@ public virtual void Can_upgrade_candidate_key_to_primary_key() modelBuilder.Ignore(); modelBuilder.Ignore(); - var entity = modelBuilder.Model.FindEntityType(typeof(Customer)); - var key = entity.FindKey(entity.FindProperty(Customer.NameProperty)); + var entity = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var key = entity.FindKey(entity.FindProperty(Customer.NameProperty)!); modelBuilder.Entity().HasKey(b => b.Name); modelBuilder.FinalizeModel(); - var nameProperty = entity.FindPrimaryKey().Properties.Single(); + var nameProperty = entity.FindPrimaryKey()!.Properties.Single(); Assert.Equal(Customer.NameProperty.Name, nameProperty.Name); Assert.False(nameProperty.RequiresValueGenerator()); Assert.Equal(ValueGenerated.Never, nameProperty.ValueGenerated); - var idProperty = (IReadOnlyProperty)entity.FindProperty(Customer.IdProperty); + var idProperty = (IReadOnlyProperty)entity.FindProperty(Customer.IdProperty)!; Assert.Equal(ValueGenerated.Never, idProperty.ValueGenerated); } @@ -226,7 +229,7 @@ public virtual void Can_set_alternate_key_from_clr_property() modelBuilder.Entity().HasAlternateKey(b => b.AlternateKey); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Equal( Customer.AlternateKeyProperty.Name, @@ -246,7 +249,7 @@ public virtual void Can_set_alternate_key_from_property_name_when_no_clr_propert b.HasAlternateKey(Customer.AlternateKeyProperty.Name + 1); }); - var entity = model.FindEntityType(typeof(Customer)); + var entity = model.FindEntityType(typeof(Customer))!; Assert.Equal( Customer.AlternateKeyProperty.Name + 1, @@ -264,7 +267,7 @@ public virtual void Can_set_alternate_key_from_clr_property_when_property_ignore b.HasAlternateKey(e => e.AlternateKey); }); - var entity = modelBuilder.Model.FindEntityType(typeof(Customer)); + var entity = modelBuilder.Model.FindEntityType(typeof(Customer))!; Assert.Equal( Customer.AlternateKeyProperty.Name, @@ -277,8 +280,8 @@ public virtual void Setting_alternate_key_makes_properties_required() var modelBuilder = CreateModelBuilder(); var entityBuilder = modelBuilder.Entity(); - var entity = modelBuilder.Model.FindEntityType(typeof(Customer)); - var alternateKeyProperty = entity.FindProperty(nameof(Customer.Name)); + var entity = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var alternateKeyProperty = entity.FindProperty(nameof(Customer.Name))!; Assert.True(alternateKeyProperty.IsNullable); entityBuilder.HasAlternateKey(e => e.Name); @@ -308,7 +311,7 @@ public virtual void Can_set_property_annotation() .Entity() .Property(c => c.Name).HasAnnotation("foo", "bar"); - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name)); + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; Assert.Equal("bar", property["foo"]); } @@ -323,7 +326,7 @@ public virtual void Can_set_property_annotation_when_no_clr_property() .Entity() .Property(Customer.NameProperty.Name).HasAnnotation("foo", "bar"); - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name)); + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; Assert.Equal("bar", property["foo"]); } @@ -338,7 +341,7 @@ public virtual void Can_set_property_annotation_by_type() .Entity() .Property(c => c.Name); - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name)); + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; Assert.Equal("bar", property["foo"]); } @@ -359,14 +362,14 @@ public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nulla b.Property("Bottom"); }); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks))!; - Assert.False(entityType.FindProperty("Up").IsNullable); - Assert.True(entityType.FindProperty("Down").IsNullable); - Assert.False(entityType.FindProperty("Charm").IsNullable); - Assert.True(entityType.FindProperty("Strange").IsNullable); - Assert.False(entityType.FindProperty("Top").IsNullable); - Assert.True(entityType.FindProperty("Bottom").IsNullable); + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.IsNullable); + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + Assert.False(entityType.FindProperty("Top")!.IsNullable); + Assert.True(entityType.FindProperty("Bottom")!.IsNullable); } [ConditionalFact] @@ -386,7 +389,7 @@ public virtual void Properties_can_be_ignored() b.Ignore("Shadow"); }); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks))!; Assert.Contains(nameof(Quarks.Id), entityType.GetProperties().Select(p => p.Name)); Assert.DoesNotContain(nameof(Quarks.Up), entityType.GetProperties().Select(p => p.Name)); Assert.DoesNotContain(nameof(Quarks.Down), entityType.GetProperties().Select(p => p.Name)); @@ -400,7 +403,7 @@ public virtual void Properties_can_be_ignored_by_type() modelBuilder.Ignore(); modelBuilder.Entity(); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!; Assert.Null(entityType.FindProperty(nameof(Customer.AlternateKey))); } @@ -451,7 +454,7 @@ public virtual void Conventions_can_be_replaced() c => c.Conventions.Replace( s => - new TestDbSetFindingConvention(s.GetService()))); + new TestDbSetFindingConvention(s.GetService()!))); var model = modelBuilder.FinalizeModel(); @@ -530,7 +533,7 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored() var model = modelBuilder.FinalizeModel(); - Assert.NotNull(model.FindEntityType(typeof(Customer)).FindProperty("Shadow")); + Assert.NotNull(model.FindEntityType(typeof(Customer))!.FindProperty("Shadow")); } [ConditionalFact] @@ -540,7 +543,7 @@ public virtual void Can_override_navigations_as_properties() var model = modelBuilder.Model; modelBuilder.Entity(); - var customer = model.FindEntityType(typeof(Customer)); + var customer = model.FindEntityType(typeof(Customer))!; Assert.NotNull(customer.FindNavigation(nameof(Customer.Orders))); modelBuilder.Entity().Property(c => c.Orders); @@ -615,14 +618,14 @@ public virtual void Properties_can_be_made_required() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.False(entityType.FindProperty("Up").IsNullable); - Assert.False(entityType.FindProperty("Down").IsNullable); - Assert.False(entityType.FindProperty("Charm").IsNullable); - Assert.False(entityType.FindProperty("Strange").IsNullable); - Assert.False(entityType.FindProperty("Top").IsNullable); - Assert.False(entityType.FindProperty("Bottom").IsNullable); + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.False(entityType.FindProperty("Down")!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.IsNullable); + Assert.False(entityType.FindProperty("Strange")!.IsNullable); + Assert.False(entityType.FindProperty("Top")!.IsNullable); + Assert.False(entityType.FindProperty("Bottom")!.IsNullable); } [ConditionalFact] @@ -639,11 +642,11 @@ public virtual void Properties_can_be_made_optional() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.True(entityType.FindProperty("Down").IsNullable); - Assert.True(entityType.FindProperty("Strange").IsNullable); - Assert.True(entityType.FindProperty("Bottom").IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + Assert.True(entityType.FindProperty("Bottom")!.IsNullable); } [ConditionalFact] @@ -682,11 +685,11 @@ public virtual void Non_nullable_properties_cannot_be_made_optional() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.False(entityType.FindProperty("Up").IsNullable); - Assert.False(entityType.FindProperty("Charm").IsNullable); - Assert.False(entityType.FindProperty("Top").IsNullable); + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.IsNullable); + Assert.False(entityType.FindProperty("Top")!.IsNullable); } [ConditionalFact] @@ -704,18 +707,18 @@ public virtual void Properties_specified_by_string_are_shadow_properties_unless_ }); var model = modelBuilder.FinalizeModel(); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks))!; - Assert.False(entityType.FindProperty("Up").IsShadowProperty()); - Assert.False(entityType.FindProperty("Down").IsShadowProperty()); - Assert.True(entityType.FindProperty("Gluon").IsShadowProperty()); - Assert.True(entityType.FindProperty("Photon").IsShadowProperty()); + Assert.False(entityType.FindProperty("Up")!.IsShadowProperty()); + Assert.False(entityType.FindProperty("Down")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Gluon")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Photon")!.IsShadowProperty()); - Assert.Equal(-1, entityType.FindProperty("Up").GetShadowIndex()); - Assert.Equal(-1, entityType.FindProperty("Down").GetShadowIndex()); - Assert.NotEqual(-1, entityType.FindProperty("Gluon").GetShadowIndex()); - Assert.NotEqual(-1, entityType.FindProperty("Photon").GetShadowIndex()); - Assert.NotEqual(entityType.FindProperty("Gluon").GetShadowIndex(), entityType.FindProperty("Photon").GetShadowIndex()); + Assert.Equal(-1, entityType.FindProperty("Up")!.GetShadowIndex()); + Assert.Equal(-1, entityType.FindProperty("Down")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Gluon")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Photon")!.GetShadowIndex()); + Assert.NotEqual(entityType.FindProperty("Gluon")!.GetShadowIndex(), entityType.FindProperty("Photon")!.GetShadowIndex()); } [ConditionalFact] @@ -736,23 +739,23 @@ public virtual void Properties_can_be_made_concurrency_tokens() }); var model = modelBuilder.FinalizeModel(); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); - - Assert.False(entityType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); - Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); - Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); - Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); - Assert.False(entityType.FindProperty("Strange").IsConcurrencyToken); - Assert.True(entityType.FindProperty("Top").IsConcurrencyToken); - Assert.False(entityType.FindProperty("Bottom").IsConcurrencyToken); - - Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); - Assert.Equal(3, entityType.FindProperty("Up").GetOriginalValueIndex()); - Assert.Equal(-1, entityType.FindProperty("Down").GetOriginalValueIndex()); - Assert.Equal(1, entityType.FindProperty("Charm").GetOriginalValueIndex()); - Assert.Equal(-1, entityType.FindProperty("Strange").GetOriginalValueIndex()); - Assert.Equal(2, entityType.FindProperty("Top").GetOriginalValueIndex()); - Assert.Equal(-1, entityType.FindProperty("Bottom").GetOriginalValueIndex()); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks))!; + + Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsConcurrencyToken); + Assert.True(entityType.FindProperty("Up")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Down")!.IsConcurrencyToken); + Assert.True(entityType.FindProperty("Charm")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Strange")!.IsConcurrencyToken); + Assert.True(entityType.FindProperty("Top")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Bottom")!.IsConcurrencyToken); + + Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.GetOriginalValueIndex()); + Assert.Equal(3, entityType.FindProperty("Up")!.GetOriginalValueIndex()); + Assert.Equal(-1, entityType.FindProperty("Down")!.GetOriginalValueIndex()); + Assert.Equal(1, entityType.FindProperty("Charm")!.GetOriginalValueIndex()); + Assert.Equal(-1, entityType.FindProperty("Strange")!.GetOriginalValueIndex()); + Assert.Equal(2, entityType.FindProperty("Top")!.GetOriginalValueIndex()); + Assert.Equal(-1, entityType.FindProperty("Bottom")!.GetOriginalValueIndex()); Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, entityType.GetChangeTrackingStrategy()); } @@ -772,12 +775,12 @@ public virtual void Properties_can_have_access_mode_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.Equal(PropertyAccessMode.PreferField, entityType.FindProperty("Up").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Field, entityType.FindProperty("Down").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Property, entityType.FindProperty("Charm").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.FieldDuringConstruction, entityType.FindProperty("Strange").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.PreferField, entityType.FindProperty("Up")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, entityType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, entityType.FindProperty("Charm")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, entityType.FindProperty("Strange")!.GetPropertyAccessMode()); } [ConditionalFact] @@ -805,14 +808,14 @@ public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels( var model = modelBuilder.FinalizeModel(); Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); - var hobsType = (IReadOnlyEntityType)model.FindEntityType(typeof(Hob)); + var hobsType = (IReadOnlyEntityType)model.FindEntityType(typeof(Hob))!; Assert.Equal(PropertyAccessMode.Field, hobsType.GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Field, hobsType.FindProperty("Id1").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, hobsType.FindProperty("Id1")!.GetPropertyAccessMode()); - var quarksType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var quarksType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); - Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); } [ConditionalFact] @@ -833,28 +836,28 @@ public virtual void Properties_can_have_provider_type_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - var up = entityType.FindProperty("Up"); + var up = entityType.FindProperty("Up")!; Assert.Null(up.GetProviderClrType()); Assert.IsType>(up.GetValueComparer()); - var down = entityType.FindProperty("Down"); + var down = entityType.FindProperty("Down")!; Assert.Same(typeof(byte[]), down.GetProviderClrType()); Assert.IsType>(down.GetValueComparer()); Assert.IsType>(down.GetProviderValueComparer()); - var charm = entityType.FindProperty("Charm"); + var charm = entityType.FindProperty("Charm")!; Assert.Same(typeof(long), charm.GetProviderClrType()); Assert.IsType>(charm.GetValueComparer()); Assert.IsType>(charm.GetProviderValueComparer()); - var strange = entityType.FindProperty("Strange"); + var strange = entityType.FindProperty("Strange")!; Assert.Null(strange.GetProviderClrType()); Assert.IsType>(strange.GetValueComparer()); Assert.IsType>(strange.GetProviderValueComparer()); - var top = entityType.FindProperty("Top"); + var top = entityType.FindProperty("Top")!; Assert.Same(typeof(string), top.GetProviderClrType()); Assert.IsType>(top.GetValueComparer()); Assert.IsType>(top.GetProviderValueComparer()); @@ -875,12 +878,12 @@ public virtual void Properties_can_have_provider_type_set_for_type() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty("Up").GetProviderClrType()); - Assert.Same(typeof(byte[]), entityType.FindProperty("Down").GetProviderClrType()); - Assert.Null(entityType.FindProperty("Charm").GetProviderClrType()); - Assert.Same(typeof(byte[]), entityType.FindProperty("Strange").GetProviderClrType()); + Assert.Null(entityType.FindProperty("Up")!.GetProviderClrType()); + Assert.Same(typeof(byte[]), entityType.FindProperty("Down")!.GetProviderClrType()); + Assert.Null(entityType.FindProperty("Charm")!.GetProviderClrType()); + Assert.Same(typeof(byte[]), entityType.FindProperty("Strange")!.GetProviderClrType()); } [ConditionalFact] @@ -902,21 +905,21 @@ public virtual void Properties_can_have_non_generic_value_converter_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty("Up").GetValueConverter()); + Assert.Null(entityType.FindProperty("Up")!.GetValueConverter()); - var down = entityType.FindProperty("Down"); + var down = entityType.FindProperty("Down")!; Assert.Same(stringConverter, down.GetValueConverter()); Assert.IsType>(down.GetValueComparer()); Assert.IsType>(down.GetProviderValueComparer()); - var charm = entityType.FindProperty("Charm"); + var charm = entityType.FindProperty("Charm")!; Assert.Same(intConverter, charm.GetValueConverter()); Assert.IsType>(charm.GetValueComparer()); Assert.IsType>(charm.GetProviderValueComparer()); - Assert.Null(entityType.FindProperty("Strange").GetValueConverter()); + Assert.Null(entityType.FindProperty("Strange")!.GetValueConverter()); } [ConditionalFact] @@ -936,25 +939,25 @@ public virtual void Properties_can_have_custom_type_value_converter_type_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks))!; - var up = entityType.FindProperty("Up"); + var up = entityType.FindProperty("Up")!; Assert.Equal(typeof(int), up.GetProviderClrType()); Assert.Null(up.GetValueConverter()); Assert.IsType>(up.GetValueComparer()); Assert.IsType>(up.GetProviderValueComparer()); - var down = entityType.FindProperty("Down"); + var down = entityType.FindProperty("Down")!; Assert.IsType(down.GetValueConverter()); Assert.IsType>(down.GetValueComparer()); Assert.IsType>(down.GetProviderValueComparer()); - var charm = entityType.FindProperty("Charm"); + var charm = entityType.FindProperty("Charm")!; Assert.IsType>(charm.GetValueConverter()); Assert.IsType>(charm.GetValueComparer()); Assert.IsType>(charm.GetProviderValueComparer()); - var strange = entityType.FindProperty("Strange"); + var strange = entityType.FindProperty("Strange")!; Assert.Null(strange.GetValueConverter()); Assert.IsType>(strange.GetValueComparer()); Assert.IsType>(strange.GetProviderValueComparer()); @@ -985,32 +988,32 @@ public virtual void Properties_can_have_value_converter_set_inline() b => { b.Property(e => e.Up); - b.Property(e => e.Down).HasConversion(v => int.Parse(v), v => v.ToString()); + b.Property(e => e.Down).HasConversion(v => int.Parse(v!), v => v.ToString()); b.Property("Charm").HasConversion(v => (long)v, v => (int)v, new CustomValueComparer()); b.Property("Strange").HasConversion( v => (double)v, v => (float)v, new CustomValueComparer(), new CustomValueComparer()); }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - var up = entityType.FindProperty("Up"); + var up = entityType.FindProperty("Up")!; Assert.Null(up.GetProviderClrType()); Assert.Null(up.GetValueConverter()); Assert.IsType>(up.GetValueComparer()); Assert.IsType>(up.GetProviderValueComparer()); - var down = entityType.FindProperty("Down"); + var down = entityType.FindProperty("Down")!; Assert.IsType>(down.GetValueConverter()); Assert.IsType>(down.GetValueComparer()); Assert.IsType>(down.GetProviderValueComparer()); - var charm = entityType.FindProperty("Charm"); + var charm = entityType.FindProperty("Charm")!; Assert.IsType>(charm.GetValueConverter()); Assert.IsType>(charm.GetValueComparer()); Assert.IsType>(charm.GetProviderValueComparer()); - var strange = entityType.FindProperty("Strange"); + var strange = entityType.FindProperty("Strange")!; Assert.IsType>(strange.GetValueConverter()); Assert.IsType>(strange.GetValueComparer()); Assert.IsType>(strange.GetProviderValueComparer()); @@ -1026,7 +1029,7 @@ public virtual void Properties_can_have_value_converter_set() { b.Property(e => e.Up); b.Property(e => e.Down).HasConversion( - new ValueConverter(v => int.Parse(v), v => v.ToString())); + new ValueConverter(v => int.Parse(v), v => v.ToString())!); b.Property("Charm").HasConversion( new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); b.Property("Strange").HasConversion( @@ -1035,25 +1038,25 @@ public virtual void Properties_can_have_value_converter_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - var up = entityType.FindProperty("Up"); + var up = entityType.FindProperty("Up")!; Assert.Null(up.GetProviderClrType()); Assert.Null(up.GetValueConverter()); Assert.IsType>(up.GetValueComparer()); Assert.IsType>(up.GetProviderValueComparer()); - var down = entityType.FindProperty("Down"); + var down = entityType.FindProperty("Down")!; Assert.IsType>(down.GetValueConverter()); Assert.IsType>(down.GetValueComparer()); Assert.IsType>(down.GetProviderValueComparer()); - var charm = entityType.FindProperty("Charm"); + var charm = entityType.FindProperty("Charm")!; Assert.IsType>(charm.GetValueConverter()); Assert.IsType>(charm.GetValueComparer()); Assert.IsType>(charm.GetProviderValueComparer()); - var strange = entityType.FindProperty("Strange"); + var strange = entityType.FindProperty("Strange")!; Assert.IsType>(strange.GetValueConverter()); Assert.IsType>(strange.GetValueComparer()); Assert.IsType>(strange.GetProviderValueComparer()); @@ -1068,10 +1071,10 @@ public virtual void IEnumerable_properties_with_value_converter_set_are_not_disc b => { b.Property(e => e.ExpandoObject).HasConversion( - v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)); + v => (string)((IDictionary)v!)["Value"], v => DeserializeExpandoObject(v)); var comparer = new ValueComparer( - (v1, v2) => v1.SequenceEqual(v2), + (v1, v2) => v1!.SequenceEqual(v2!), v => v.GetHashCode()); b.Property(e => e.ExpandoObject).Metadata.SetValueComparer(comparer); @@ -1080,8 +1083,8 @@ public virtual void IEnumerable_properties_with_value_converter_set_are_not_disc var model = modelBuilder.FinalizeModel(); var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); - Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueConverter()); - Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueComparer()); + Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject))!.GetValueConverter()); + Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject))!.GetValueComparer()); } private static ExpandoObject DeserializeExpandoObject(string value) @@ -1095,7 +1098,7 @@ private static ExpandoObject DeserializeExpandoObject(string value) private class ExpandoObjectConverter : ValueConverter { public ExpandoObjectConverter() - : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) + : base(v => (string)((IDictionary)v!)["Value"], v => DeserializeExpandoObject(v)) { } } @@ -1103,7 +1106,7 @@ public ExpandoObjectConverter() private class ExpandoObjectComparer : ValueComparer { public ExpandoObjectComparer() - : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + : base((v1, v2) => v1!.SequenceEqual(v2!), v => v.GetHashCode()) { } } @@ -1124,7 +1127,7 @@ public virtual void Properties_can_have_value_converter_configured_by_type() var model = modelBuilder.FinalizeModel(); var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); - var wrappedProperty = entityType.FindProperty(nameof(WrappedStringEntity.WrappedString)); + var wrappedProperty = entityType.FindProperty(nameof(WrappedStringEntity.WrappedString))!; Assert.False(wrappedProperty.IsUnicode()); Assert.Equal(20, wrappedProperty.GetMaxLength()); Assert.IsType(wrappedProperty.GetValueConverter()); @@ -1147,13 +1150,13 @@ public virtual void Value_converter_configured_on_non_nullable_type_is_applied() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - var id = entityType.FindProperty("Id"); + var id = entityType.FindProperty("Id")!; Assert.IsType>(id.GetValueConverter()); Assert.IsType>(id.GetValueComparer()); - var wierd = entityType.FindProperty("Wierd"); + var wierd = entityType.FindProperty("Wierd")!; Assert.IsType>(wierd.GetValueConverter()); Assert.IsType>(wierd.GetValueComparer()); } @@ -1176,14 +1179,14 @@ public virtual void Value_converter_configured_on_nullable_type_overrides_non_nu }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - var id = entityType.FindProperty("Id"); + var id = entityType.FindProperty("Id")!; Assert.IsType>(id.GetValueConverter()); Assert.IsType>(id.GetValueComparer()); Assert.IsType>(id.GetProviderValueComparer()); - var wierd = entityType.FindProperty("Wierd"); + var wierd = entityType.FindProperty("Wierd")!; Assert.IsType>(wierd.GetValueConverter()); Assert.IsType>(wierd.GetValueComparer()); Assert.IsType>(wierd.GetProviderValueComparer()); @@ -1211,7 +1214,7 @@ public virtual void Value_converter_configured_on_base_type_is_not_applied() private class WrappedStringToStringConverter : ValueConverter { public WrappedStringToStringConverter() - : base(v => v.Value, v => new WrappedString { Value = v }) + : base(v => v.Value!, v => new WrappedString { Value = v }) { } } @@ -1249,8 +1252,8 @@ public virtual void Value_converter_type_is_checked() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); - Assert.Null(entityType.FindProperty("Up").GetValueConverter()); + var entityType = model.FindEntityType(typeof(Quarks))!; + Assert.Null(entityType.FindProperty("Up")!.GetValueConverter()); } [ConditionalFact] @@ -1267,11 +1270,11 @@ public virtual void Properties_can_have_field_set() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Equal("_forUp", entityType.FindProperty("Up").GetFieldName()); - Assert.Equal("_forDown", entityType.FindProperty("Down").GetFieldName()); - Assert.Equal("_forWierd", entityType.FindProperty("_forWierd").GetFieldName()); + Assert.Equal("_forUp", entityType.FindProperty("Up")!.GetFieldName()); + Assert.Equal("_forDown", entityType.FindProperty("Down")!.GetFieldName()); + Assert.Equal("_forWierd", entityType.FindProperty("_forWierd")!.GetFieldName()); } [ConditionalFact] @@ -1320,14 +1323,14 @@ public virtual void Properties_can_be_set_to_generate_values_on_Add() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); - Assert.Equal(ValueGenerated.OnAdd, entityType.FindProperty(Customer.IdProperty.Name).ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); - Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); - Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm").ValueGenerated); - Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange").ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top").ValueGenerated); - Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom").ValueGenerated); + var entityType = model.FindEntityType(typeof(Quarks))!; + Assert.Equal(ValueGenerated.OnAdd, entityType.FindProperty(Customer.IdProperty.Name)!.ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up")!.ValueGenerated); + Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm")!.ValueGenerated); + Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom")!.ValueGenerated); } [ConditionalFact] @@ -1346,15 +1349,15 @@ public virtual void Properties_can_set_row_version() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); - Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); - Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Charm").ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up")!.ValueGenerated); + Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Charm")!.ValueGenerated); - Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); - Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); - Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); + Assert.True(entityType.FindProperty("Up")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Down")!.IsConcurrencyToken); + Assert.True(entityType.FindProperty("Charm")!.IsConcurrencyToken); } [ConditionalFact] @@ -1374,15 +1377,15 @@ public virtual void Can_set_max_length_for_properties() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); + Assert.Null(entityType.FindProperty(Customer.IdProperty.Name)!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Down")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty("Strange")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Bottom")!.GetMaxLength()); } [ConditionalFact] @@ -1405,15 +1408,15 @@ public virtual void Can_set_max_length_for_property_type() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - Assert.Equal(100, entityType.FindProperty("Strange").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); + Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Down")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Strange")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Bottom")!.GetMaxLength()); } [ConditionalFact] @@ -1495,15 +1498,15 @@ public virtual void Can_set_unbounded_max_length_for_property_type() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - Assert.Equal(-1, entityType.FindProperty("Down").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); - Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - Assert.Equal(-1, entityType.FindProperty("Bottom").GetMaxLength()); + Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty("Down")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty("Strange")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty("Bottom")!.GetMaxLength()); } [ConditionalFact] @@ -1523,22 +1526,22 @@ public virtual void Can_set_precision_and_scale_for_properties() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); - Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetScale()); - Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Up").GetScale()); - Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Down").GetScale()); - Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); - Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); - Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Top").GetScale()); - Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); + Assert.Null(entityType.FindProperty(Customer.IdProperty.Name)!.GetPrecision()); + Assert.Null(entityType.FindProperty(Customer.IdProperty.Name)!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Up")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Down")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Down")!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Charm")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Strange")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Strange")!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Top")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Bottom")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Bottom")!.GetScale()); } [ConditionalFact] @@ -1561,22 +1564,22 @@ public virtual void Can_set_precision_and_scale_for_property_type() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Equal(1, entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); - Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetScale()); - Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Up").GetScale()); - Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Down").GetScale()); - Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); - Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); - Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); - Assert.Equal(0, entityType.FindProperty("Top").GetScale()); - Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); - Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); + Assert.Equal(1, entityType.FindProperty(Customer.IdProperty.Name)!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Up")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Down")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Down")!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Charm")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Strange")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Strange")!.GetScale()); + Assert.Equal(1, entityType.FindProperty("Top")!.GetPrecision()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetScale()); + Assert.Equal(100, entityType.FindProperty("Bottom")!.GetPrecision()); + Assert.Equal(10, entityType.FindProperty("Bottom")!.GetScale()); } [ConditionalFact] @@ -1597,15 +1600,15 @@ public virtual void Can_set_custom_value_generator_for_properties() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); - Assert.IsType(entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); - Assert.IsType(entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); - Assert.IsType(entityType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); - Assert.IsType(entityType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); - Assert.IsType(entityType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); - Assert.IsType(entityType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); + Assert.Null(entityType.FindProperty(Customer.IdProperty.Name)!.GetValueGeneratorFactory()); + Assert.IsType(entityType.FindProperty("Up")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Down")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Charm")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Strange")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Top")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Bottom")!.GetValueGeneratorFactory()!(null!, null!)); } private class CustomValueGenerator : ValueGenerator @@ -1650,17 +1653,17 @@ public virtual void Throws_for_value_generator_that_cannot_be_constructed() b.Property(e => e.Down).HasValueGenerator(); }); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; Assert.Equal( CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), Assert.Throws( - () => entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)).Message); + () => entityType.FindProperty("Up")!.GetValueGeneratorFactory()!(null!, null!)).Message); Assert.Equal( CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), Assert.Throws( - () => entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)).Message); + () => entityType.FindProperty("Down")!.GetValueGeneratorFactory()!(null!, null!)).Message); } private class BadCustomValueGenerator1 : CustomValueGenerator @@ -1676,7 +1679,7 @@ private abstract class BadCustomValueGenerator2 : CustomValueGenerator protected class StringCollectionEntity { - public ICollection Property { get; set; } + public ICollection? Property { get; set; } } [ConditionalFact] @@ -1710,7 +1713,7 @@ protected virtual void Mapping_ignores_ignored_array() var model = modelBuilder.FinalizeModel(); - Assert.Null(model.FindEntityType(typeof(OneDee)).FindProperty("One")); + Assert.Null(model.FindEntityType(typeof(OneDee))!.FindProperty("One")); } [ConditionalFact] @@ -1722,7 +1725,7 @@ protected virtual void Mapping_ignores_ignored_two_dimensional_array() var model = modelBuilder.FinalizeModel(); - Assert.Null(model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); + Assert.Null(model.FindEntityType(typeof(TwoDee))!.FindProperty("Two")); } [ConditionalFact] @@ -1747,28 +1750,7 @@ protected virtual void Mapping_ignores_ignored_three_dimensional_array() var model = modelBuilder.FinalizeModel(); - Assert.Null(model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); - } - - protected class OneDee - { - public int Id { get; set; } - - public int[] One { get; set; } - } - - protected class TwoDee - { - public int Id { get; set; } - - public int[,] Two { get; set; } - } - - protected class ThreeDee - { - public int Id { get; set; } - - public int[,,] Three { get; set; } + Assert.Null(model.FindEntityType(typeof(ThreeDee))!.FindProperty("Three")); } [ConditionalFact] @@ -1782,7 +1764,7 @@ public virtual void Private_property_is_not_discovered_by_convention() var model = modelBuilder.FinalizeModel(); Assert.Empty( - model.FindEntityType(typeof(Gamma)).GetProperties() + model.FindEntityType(typeof(Gamma))!.GetProperties() .Where(p => p.Name == "PrivateProperty")); } @@ -1802,7 +1784,7 @@ protected virtual void Throws_for_int_keyed_dictionary() protected class IntDict { public int Id { get; set; } - public Dictionary Notes { get; set; } + public Dictionary? Notes { get; set; } } [ConditionalFact] @@ -1822,15 +1804,15 @@ public virtual void Can_set_unicode_for_properties() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); - Assert.True(entityType.FindProperty("Up").IsUnicode()); - Assert.False(entityType.FindProperty("Down").IsUnicode()); - Assert.True(entityType.FindProperty("Charm").IsUnicode()); - Assert.False(entityType.FindProperty("Strange").IsUnicode()); - Assert.True(entityType.FindProperty("Top").IsUnicode()); - Assert.False(entityType.FindProperty("Bottom").IsUnicode()); + Assert.Null(entityType.FindProperty(Customer.IdProperty.Name)!.IsUnicode()); + Assert.True(entityType.FindProperty("Up")!.IsUnicode()); + Assert.False(entityType.FindProperty("Down")!.IsUnicode()); + Assert.True(entityType.FindProperty("Charm")!.IsUnicode()); + Assert.False(entityType.FindProperty("Strange")!.IsUnicode()); + Assert.True(entityType.FindProperty("Top")!.IsUnicode()); + Assert.False(entityType.FindProperty("Bottom")!.IsUnicode()); } [ConditionalFact] @@ -1853,15 +1835,15 @@ public virtual void Can_set_unicode_for_property_type() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); + var entityType = model.FindEntityType(typeof(Quarks))!; - Assert.True(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); - Assert.True(entityType.FindProperty("Up").IsUnicode()); - Assert.False(entityType.FindProperty("Down").IsUnicode()); - Assert.True(entityType.FindProperty("Charm").IsUnicode()); - Assert.False(entityType.FindProperty("Strange").IsUnicode()); - Assert.True(entityType.FindProperty("Top").IsUnicode()); - Assert.False(entityType.FindProperty("Bottom").IsUnicode()); + Assert.True(entityType.FindProperty(Customer.IdProperty.Name)!.IsUnicode()); + Assert.True(entityType.FindProperty("Up")!.IsUnicode()); + Assert.False(entityType.FindProperty("Down")!.IsUnicode()); + Assert.True(entityType.FindProperty("Charm")!.IsUnicode()); + Assert.False(entityType.FindProperty("Strange")!.IsUnicode()); + Assert.True(entityType.FindProperty("Top")!.IsUnicode()); + Assert.False(entityType.FindProperty("Bottom")!.IsUnicode()); } [ConditionalFact] @@ -1884,7 +1866,7 @@ public virtual void PropertyBuilder_methods_can_be_chained() .HasValueGenerator(typeof(CustomValueGenerator)) .HasValueGeneratorFactory() .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) - .HasValueGenerator((_, __) => null) + .HasValueGenerator((_, __) => null!) .IsRequired(); [ConditionalFact] @@ -1898,7 +1880,7 @@ public virtual void Can_add_index() .HasIndex(ix => ix.Name); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Customer)); + var entityType = model.FindEntityType(typeof(Customer))!; var index = entityType.GetIndexes().Single(); Assert.Equal(Customer.NameProperty.Name, index.Properties.Single().Name); @@ -1919,7 +1901,7 @@ public virtual void Can_add_index_when_no_clr_property() }); var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Customer)); + var entityType = model.FindEntityType(typeof(Customer))!; var index = entityType.GetIndexes().Single(); Assert.Equal("Index", index.Properties.Single().Name); @@ -1939,19 +1921,19 @@ public virtual void Can_add_multiple_indexes() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Customer)); - var idProperty = entityType.FindProperty(nameof(Customer.Id)); - var nameProperty = entityType.FindProperty(nameof(Customer.Name)); + var entityType = model.FindEntityType(typeof(Customer))!; + var idProperty = entityType.FindProperty(nameof(Customer.Id))!; + var nameProperty = entityType.FindProperty(nameof(Customer.Name))!; Assert.Equal(4, entityType.GetIndexes().Count()); - var firstIndex = entityType.FindIndex(idProperty); + var firstIndex = entityType.FindIndex(idProperty)!; Assert.True(firstIndex.IsUnique); - var secondIndex = entityType.FindIndex(nameProperty); + var secondIndex = entityType.FindIndex(nameProperty)!; Assert.False(secondIndex.IsUnique); Assert.Equal("V1", secondIndex["A1"]); - var namedIndex = entityType.FindIndex("Named"); + var namedIndex = entityType.FindIndex("Named")!; Assert.False(namedIndex.IsUnique); - var descendingIndex = entityType.FindIndex("Descending"); + var descendingIndex = entityType.FindIndex("Descending")!; Assert.Equal(Array.Empty(), descendingIndex.IsDescending); } @@ -1968,7 +1950,7 @@ public virtual void Can_add_contained_indexes() ix => new { ix.Id }); var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Customer)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Customer))!; Assert.Equal(2, entityType.GetIndexes().Count()); Assert.True(firstIndexBuilder.Metadata.IsUnique); @@ -1983,14 +1965,14 @@ public virtual void Can_set_primary_key_by_convention_for_user_specified_shadow_ var entityBuilder = modelBuilder.Entity(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(EntityWithoutId)); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(EntityWithoutId))!; Assert.Null(entityType.FindPrimaryKey()); entityBuilder.Property("Id"); Assert.NotNull(entityType.FindPrimaryKey()); - AssertEqual(new[] { "Id" }, entityType.FindPrimaryKey().Properties.Select(p => p.Name)); + AssertEqual(new[] { "Id" }, entityType.FindPrimaryKey()!.Properties.Select(p => p.Name)); } [ConditionalFact] @@ -2001,13 +1983,13 @@ public virtual void Can_ignore_explicit_interface_implementation_property() Assert.DoesNotContain( nameof(IEntityBase.Target), - modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); + modelBuilder.Model.FindEntityType(typeof(EntityBase))!.GetProperties().Select(p => p.Name)); modelBuilder.Entity().Property(e => ((IEntityBase)e).Target); Assert.Contains( nameof(IEntityBase.Target), - modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); + modelBuilder.Model.FindEntityType(typeof(EntityBase))!.GetProperties().Select(p => p.Name)); } [ConditionalFact] @@ -2018,7 +2000,7 @@ public virtual void Can_set_key_on_an_entity_with_fields() modelBuilder.Entity().HasKey(e => e.Id); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(EntityWithFields)); + var entity = model.FindEntityType(typeof(EntityWithFields))!; var primaryKey = entity.FindPrimaryKey(); Assert.NotNull(primaryKey); var property = Assert.Single(primaryKey.Properties); @@ -2035,8 +2017,8 @@ public virtual void Can_set_composite_key_on_an_entity_with_fields() modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(EntityWithFields)); - var primaryKeyProperties = entity.FindPrimaryKey().Properties; + var entity = model.FindEntityType(typeof(EntityWithFields))!; + var primaryKeyProperties = entity.FindPrimaryKey()!.Properties; Assert.Equal(2, primaryKeyProperties.Count); var first = primaryKeyProperties[0]; var second = primaryKeyProperties[1]; @@ -2055,7 +2037,7 @@ public virtual void Can_set_alternate_key_on_an_entity_with_fields() modelBuilder.Entity().HasAlternateKey(e => e.CompanyId); - var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields))!; var properties = entity.GetProperties(); Assert.Single(properties); var property = properties.Single(); @@ -2074,7 +2056,7 @@ public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() modelBuilder.Entity().HasAlternateKey(e => new { e.TenantId, e.CompanyId }); - var keys = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetKeys(); + var keys = modelBuilder.Model.FindEntityType(typeof(EntityWithFields))!.GetKeys(); Assert.Single(keys); var properties = keys.Single().Properties; Assert.Equal(2, properties.Count); @@ -2096,7 +2078,7 @@ public virtual void Can_call_Property_on_an_entity_with_fields() modelBuilder.Entity().Property(e => e.Id); var model = modelBuilder.FinalizeModel(); - var properties = model.FindEntityType(typeof(EntityWithFields)).GetProperties(); + var properties = model.FindEntityType(typeof(EntityWithFields))!.GetProperties(); var property = Assert.Single(properties); Assert.Equal(nameof(EntityWithFields.Id), property.Name); Assert.Null(property.PropertyInfo); @@ -2111,7 +2093,7 @@ public virtual void Can_set_index_on_an_entity_with_fields() modelBuilder.Entity().HasNoKey().HasIndex(e => e.CompanyId); var model = modelBuilder.FinalizeModel(); - var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + var indexes = model.FindEntityType(typeof(EntityWithFields))!.GetIndexes(); var index = Assert.Single(indexes); var property = Assert.Single(index.Properties); Assert.Null(property.PropertyInfo); @@ -2126,7 +2108,7 @@ public virtual void Can_set_composite_index_on_an_entity_with_fields() modelBuilder.Entity().HasNoKey().HasIndex(e => new { e.TenantId, e.CompanyId }); var model = modelBuilder.FinalizeModel(); - var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + var indexes = model.FindEntityType(typeof(EntityWithFields))!.GetIndexes(); var index = Assert.Single(indexes); Assert.Equal(2, index.Properties.Count); var properties = index.Properties; @@ -2150,7 +2132,7 @@ public virtual void Can_ignore_a_field_on_an_entity_with_fields() .HasKey(e => e.Id); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(EntityWithFields)); + var entity = model.FindEntityType(typeof(EntityWithFields))!; var property = Assert.Single(entity.GetProperties()); Assert.Equal(nameof(EntityWithFields.Id), property.Name); } @@ -2166,7 +2148,7 @@ public virtual void Can_ignore_a_field_on_a_keyless_entity_with_fields() .Property(e => e.LastName); var model = modelBuilder.FinalizeModel(); - var entity = model.FindEntityType(typeof(KeylessEntityWithFields)); + var entity = model.FindEntityType(typeof(KeylessEntityWithFields))!; var property = Assert.Single(entity.GetProperties()); Assert.Equal(nameof(KeylessEntityWithFields.LastName), property.Name); } @@ -2188,7 +2170,7 @@ public virtual void Can_add_seed_data_objects() var finalModel = modelBuilder.FinalizeModel(); - var customer = finalModel.FindEntityType(typeof(Beta)); + var customer = finalModel.FindEntityType(typeof(Beta))!; var data = customer.GetSeedData(); Assert.Equal(2, data.Count()); Assert.Equal(-1, data.First()[nameof(Beta.Id)]); @@ -2214,7 +2196,7 @@ public virtual void Can_add_seed_data_anonymous_objects() var model = modelBuilder.FinalizeModel(); - var customer = model.FindEntityType(typeof(Beta)); + var customer = model.FindEntityType(typeof(Beta))!; var data = customer.GetSeedData(); Assert.Equal(2, data.Count()); Assert.Equal(-1, data.First().Values.Single()); @@ -2238,7 +2220,7 @@ public virtual void Can_add_seed_data_objects_indexed_property() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(IndexedClass)); + var entityType = model.FindEntityType(typeof(IndexedClass))!; var data = Assert.Single(entityType.GetSeedData()); Assert.Equal(-1, data["Id"]); Assert.Equal(2, data["Required"]); @@ -2260,7 +2242,7 @@ public virtual void Can_add_seed_data_anonymous_objects_indexed_property() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(IndexedClass)); + var entityType = model.FindEntityType(typeof(IndexedClass))!; var data = Assert.Single(entityType.GetSeedData()); Assert.Equal(-1, data["Id"]); Assert.Equal(2, data["Required"]); @@ -2283,7 +2265,7 @@ public virtual void Can_add_seed_data_objects_indexed_property_dictionary() var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); + var entityType = model.FindEntityType(typeof(IndexedClassByDictionary))!; var data = Assert.Single(entityType.GetSeedData()); Assert.Equal(-1, data["Id"]); Assert.Equal(2, data["Required"]); @@ -2304,7 +2286,7 @@ public virtual void Can_add_seed_data_anonymous_objects_indexed_property_diction var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); + var entityType = model.FindEntityType(typeof(IndexedClassByDictionary))!; var data = Assert.Single(entityType.GetSeedData()); Assert.Equal(-1, data["Id"]); Assert.Equal(2, data["Required"]); @@ -2323,7 +2305,7 @@ public virtual void EntityType_name_is_stored_culture_invariantly() var model = modelBuilder.FinalizeModel(); Assert.Equal(2, model.GetEntityTypes().Count()); - Assert.Equal(2, model.FindEntityType(typeof(Entityss)).GetNavigations().Count()); + Assert.Equal(2, model.FindEntityType(typeof(Entityss))!.GetNavigations().Count()); } protected class Entityß @@ -2334,8 +2316,8 @@ protected class Entityß protected class Entityss { public int Id { get; set; } - public Entityß Navigationß { get; set; } - public Entityß Navigationss { get; set; } + public Entityß? Navigationß { get; set; } + public Entityß? Navigationss { get; set; } } [ConditionalFact] @@ -2365,18 +2347,18 @@ public virtual void Can_add_shared_type_entity_type() Assert.NotNull(shared1); Assert.True(shared1.HasSharedClrType); Assert.Null(shared1.FindProperty("Id")); - Assert.Equal(typeof(int), shared1.FindProperty("Keys").ClrType); - Assert.Equal(typeof(byte[]), shared1.FindProperty("Values").ClrType); - Assert.Equal(typeof(string), shared1.FindProperty("Count").ClrType); + Assert.Equal(typeof(int), shared1.FindProperty("Keys")!.ClrType); + Assert.Equal(typeof(byte[]), shared1.FindProperty("Values")!.ClrType); + Assert.Equal(typeof(string), shared1.FindProperty("Count")!.ClrType); var shared2 = model.FindEntityType("Shared2"); Assert.NotNull(shared2); Assert.True(shared2.HasSharedClrType); Assert.NotNull(shared2.FindProperty("Id")); - var indexer = shared1.FindIndexerPropertyInfo(); - Assert.True(model.IsIndexerMethod(indexer.GetMethod)); - Assert.True(model.IsIndexerMethod(indexer.SetMethod)); + var indexer = shared1.FindIndexerPropertyInfo()!; + Assert.True(model.IsIndexerMethod(indexer.GetMethod!)); + Assert.True(model.IsIndexerMethod(indexer.SetMethod!)); Assert.Same(indexer, shared2.FindIndexerPropertyInfo()); } @@ -2391,5 +2373,1051 @@ public virtual void Cannot_add_shared_type_when_non_shared_exists() CoreStrings.ClashingNonSharedType("Shared1", nameof(Customer)), Assert.Throws(() => modelBuilder.SharedTypeEntity("Shared1")).Message); } + + [ConditionalFact] + public virtual void Can_set_primitive_collection_annotation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .PrimitiveCollection(c => c.Notes).HasAnnotation("foo", "bar"); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_primitive_collection_annotation_when_no_clr_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .PrimitiveCollection>(nameof(Customer.Notes)).HasAnnotation("foo", "bar"); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_primitive_collection_annotation_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); + + modelBuilder.Ignore(); + var propertyBuilder = modelBuilder + .Entity() + .PrimitiveCollection(c => c.Notes); + + var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection?>("Strange"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.True(entityType.FindProperty("Charm")!.IsNullable); // Because we can't detect the non-nullable reference type + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.Ignore(e => e.Up); + b.Ignore(e => e.Down); + b.Ignore("Charm"); + b.Ignore("Strange"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + Assert.Contains(nameof(CollectionQuarks.Id), entityType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(CollectionQuarks.Up), entityType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(CollectionQuarks.Down), entityType.GetProperties().Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void Can_override_navigations_as_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + modelBuilder.Entity(); + + var customer = model.FindEntityType(typeof(Customer))!; + Assert.NotNull(customer.FindNavigation(nameof(Customer.Orders))); + + modelBuilder.Entity().PrimitiveCollection(c => c.Orders); + + Assert.Null(customer.FindNavigation(nameof(Customer.Orders))); + var property = customer.FindProperty(nameof(Customer.Orders)); + Assert.NotNull(property); + Assert.NotNull(property.GetElementType()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).IsRequired(); + b.PrimitiveCollection(e => e.Down).IsRequired(); + b.PrimitiveCollection>("Charm").IsRequired(); + b.PrimitiveCollection?>("Strange").IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsNullable); + Assert.False(entityType.FindProperty("Down")!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.IsNullable); + Assert.False(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).IsRequired(false); + b.PrimitiveCollection(e => e.Down).IsRequired(false); + b.PrimitiveCollection>("Charm").IsRequired(false); + b.PrimitiveCollection?>("Strange").IsRequired(false); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.True(entityType.FindProperty("Up")!.IsNullable); + Assert.True(entityType.FindProperty("Down")!.IsNullable); + Assert.True(entityType.FindProperty("Charm")!.IsNullable); + Assert.True(entityType.FindProperty("Strange")!.IsNullable); + } + + [ConditionalFact] + public virtual void PrimitiveCollection_Key_properties_cannot_be_made_optional() + => Assert.Equal( + CoreStrings.KeyPropertyCannotBeNullable(nameof(CollectionQuarks.Down), nameof(CollectionQuarks), "{'" + nameof(CollectionQuarks.Down) + "'}"), + Assert.Throws( + () => + CreateModelBuilder().Entity( + b => + { + b.HasAlternateKey( + e => new { e.Down }); + b.PrimitiveCollection(e => e.Down).IsRequired(false); + })).Message); + + [ConditionalFact] + public virtual void Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Up"); + b.PrimitiveCollection?>("Down"); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection?>("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsShadowProperty()); + Assert.False(entityType.FindProperty("Down")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Charm")!.IsShadowProperty()); + Assert.True(entityType.FindProperty("Strange")!.IsShadowProperty()); + + Assert.Equal(-1, entityType.FindProperty("Up")!.GetShadowIndex()); + Assert.Equal(-1, entityType.FindProperty("Down")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Charm")!.GetShadowIndex()); + Assert.NotEqual(-1, entityType.FindProperty("Strange")!.GetShadowIndex()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_made_concurrency_tokens() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).IsConcurrencyToken(); + b.PrimitiveCollection(e => e.Down).IsConcurrencyToken(false); + b.PrimitiveCollection>("Charm").IsConcurrencyToken(); + b.PrimitiveCollection?>("Strange").IsConcurrencyToken(false); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.True(entityType.FindProperty("Up")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Down")!.IsConcurrencyToken); + Assert.True(entityType.FindProperty("Charm")!.IsConcurrencyToken); + Assert.False(entityType.FindProperty("Strange")!.IsConcurrencyToken); + + Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, entityType.GetChangeTrackingStrategy()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_have_access_mode_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); + b.PrimitiveCollection>("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); + b.PrimitiveCollection?>("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal(PropertyAccessMode.PreferField, entityType.FindProperty("Up")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, entityType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, entityType.FindProperty("Charm")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, entityType.FindProperty("Strange")!.GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Access_mode_can_be_overridden_at_entity_and_primitive_collection_levels() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field); + + modelBuilder.Entity( + b => + { + b.UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + b.PrimitiveCollection(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + b.PrimitiveCollection(e => e.Down).HasField("_forDown"); + }); + + var model = modelBuilder.FinalizeModel(); + Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); + + var collectionQuarksType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, collectionQuarksType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, collectionQuarksType.FindProperty("Down")!.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, collectionQuarksType.FindProperty("Up")!.GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_have_field_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Up").HasField("_forUp"); + b.PrimitiveCollection(e => e.Down).HasField("_forDown"); + b.PrimitiveCollection?>("_forWierd").HasField("_forWierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal("_forUp", entityType.FindProperty("Up")!.GetFieldName()); + Assert.Equal("_forDown", entityType.FindProperty("Down")!.GetFieldName()); + Assert.Equal("_forWierd", entityType.FindProperty("_forWierd")!.GetFieldName()); + } + + [ConditionalFact] + public virtual void HasField_for_primitive_collection_throws_if_field_is_not_found() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + Assert.Equal( + CoreStrings.MissingBackingField("_notFound", nameof(CollectionQuarks.Down), nameof(CollectionQuarks)), + Assert.Throws(() => b.PrimitiveCollection(e => e.Down).HasField("_notFound")).Message); + }); + } + + [ConditionalFact] + public virtual void HasField_for_primitive_collection_throws_if_field_is_wrong_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + Assert.Equal( + CoreStrings.BadBackingFieldType( + "_forUp", "ObservableCollection", nameof(CollectionQuarks), nameof(CollectionQuarks.Down), + "ObservableCollection"), + Assert.Throws(() => b.PrimitiveCollection(e => e.Down).HasField("_forUp")).Message); + }); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_be_set_to_generate_values_on_Add() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.HasKey(e => e.Id); + b.PrimitiveCollection(e => e.Up).ValueGeneratedOnAddOrUpdate(); + b.PrimitiveCollection(e => e.Down).ValueGeneratedNever(); + b.PrimitiveCollection>("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; + b.PrimitiveCollection>("Strange").ValueGeneratedNever(); + b.PrimitiveCollection>("Top").ValueGeneratedOnAddOrUpdate(); + b.PrimitiveCollection>("Bottom").ValueGeneratedOnUpdate(); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up")!.ValueGenerated); + Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm")!.ValueGenerated); + Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top")!.ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom")!.ValueGenerated); + } + + [ConditionalFact] + public virtual void Can_set_max_length_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).HasMaxLength(0); + b.PrimitiveCollection(e => e.Down).HasMaxLength(100); + b.PrimitiveCollection>("Charm").HasMaxLength(0); + b.PrimitiveCollection>("Strange").HasMaxLength(-1); + b.PrimitiveCollection("Top").HasMaxLength(0); + b.PrimitiveCollection("Bottom").HasMaxLength(100); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Id))!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Up")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Down")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Charm")!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty("Strange")!.GetMaxLength()); + Assert.Equal(0, entityType.FindProperty("Top")!.GetMaxLength()); + Assert.Equal(100, entityType.FindProperty("Bottom")!.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_sentinel_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).HasSentinel(1); + b.PrimitiveCollection(e => e.Down).HasSentinel("100"); + b.PrimitiveCollection("Charm").HasSentinel(-1); + b.PrimitiveCollection>("Strange").HasSentinel("-1"); + b.PrimitiveCollection("Top").HasSentinel(77); + b.PrimitiveCollection>("Bottom").HasSentinel("100"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal(0, entityType.FindProperty(nameof(CollectionQuarks.Id))!.Sentinel); + Assert.Equal(1, entityType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", entityType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, entityType.FindProperty("Charm")!.Sentinel); + Assert.Equal("-1", entityType.FindProperty("Strange")!.Sentinel); + Assert.Equal(77, entityType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", entityType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_custom_value_generator_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).HasValueGenerator(); + b.PrimitiveCollection(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); + b.PrimitiveCollection>("Strange").HasValueGenerator(); + b.PrimitiveCollection("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); + b.PrimitiveCollection>("Bottom").HasValueGeneratorFactory(); + }); + + var model = modelBuilder.FinalizeModel(); + + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Id))!.GetValueGeneratorFactory()); + Assert.IsType(entityType.FindProperty("Up")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Down")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Strange")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Top")!.GetValueGeneratorFactory()!(null!, null!)); + Assert.IsType(entityType.FindProperty("Bottom")!.GetValueGeneratorFactory()!(null!, null!)); + } + + [ConditionalFact] + public virtual void Throws_for_bad_value_generator_type_for_primitive_collection() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + Assert.Equal( + CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), + Assert.Throws(() => b.PrimitiveCollection(e => e.Down).HasValueGenerator(typeof(Random))).Message); + }); + } + + [ConditionalFact] + public virtual void Throws_for_primitive_collection_for_value_generator_that_cannot_be_constructed() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).HasValueGenerator(); + b.PrimitiveCollection(e => e.Down).HasValueGenerator(); + }); + + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), + Assert.Throws( + () => entityType.FindProperty("Up")!.GetValueGeneratorFactory()!(null!, null!)).Message); + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), + Assert.Throws( + () => entityType.FindProperty("Down")!.GetValueGeneratorFactory()!(null!, null!)).Message); + } + + [ConditionalFact] + protected virtual void Mapping_for_primitive_collection_ignores_ignored_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().Ignore(e => e.One); + + var model = modelBuilder.FinalizeModel(); + + Assert.Null(model.FindEntityType(typeof(OneDee))!.FindProperty("One")); + } + + [ConditionalFact] + public virtual void Private_primitive_collection_is_not_discovered_by_convention() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Empty( + model.FindEntityType(typeof(Gamma))!.GetProperties() + .Where(p => p.Name == "PrivateCollection")); + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_primitive_collections() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).IsUnicode(); + b.PrimitiveCollection(e => e.Down).IsUnicode(false); + b.PrimitiveCollection("Charm").IsUnicode(); + b.PrimitiveCollection>("Strange").IsUnicode(false); + b.PrimitiveCollection("Top").IsUnicode(); + b.PrimitiveCollection?>("Bottom").IsUnicode(false); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Id))!.IsUnicode()); + Assert.True(entityType.FindProperty("Up")!.IsUnicode()); + Assert.False(entityType.FindProperty("Down")!.IsUnicode()); + Assert.True(entityType.FindProperty("Charm")!.IsUnicode()); + Assert.False(entityType.FindProperty("Strange")!.IsUnicode()); + Assert.True(entityType.FindProperty("Top")!.IsUnicode()); + Assert.False(entityType.FindProperty("Bottom")!.IsUnicode()); + } + + [ConditionalFact] + public virtual void PrimitiveCollectionBuilder_methods_can_be_chained() + => CreateModelBuilder() + .Entity() + .PrimitiveCollection(e => e.Up) + .IsRequired() + .HasAnnotation("A", "V") + .IsConcurrencyToken() + .ValueGeneratedNever() + .ValueGeneratedOnAdd() + .ValueGeneratedOnAddOrUpdate() + .ValueGeneratedOnUpdate() + .IsUnicode() + .HasMaxLength(100) + .HasSentinel(null) + .HasValueGenerator() + .HasValueGenerator(typeof(CustomValueGenerator)) + .HasValueGeneratorFactory() + .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) + .IsRequired(); + + [ConditionalFact] + public virtual void Can_set_primary_key_by_convention_for_user_specified_shadow_primitive_collection() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + var entityBuilder = modelBuilder.Entity(); + + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(EntityWithoutId))!; + + Assert.Null(entityType.FindPrimaryKey()); + + entityBuilder.PrimitiveCollection>("Id"); + + Assert.NotNull(entityType.FindPrimaryKey()); + AssertEqual(new[] { "Id" }, entityType.FindPrimaryKey()!.Properties.Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void Can_set_key_for_primitive_collection_on_an_entity_with_fields() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(e => e.Id); + + var model = modelBuilder.FinalizeModel(); + var entity = model.FindEntityType(typeof(EntityWithFields))!; + var primaryKey = entity.FindPrimaryKey(); + Assert.NotNull(primaryKey); + var property = Assert.Single(primaryKey.Properties); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_set_composite_key_for_primitive_collection_on_an_entity_with_fields() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.CollectionCompanyId); // Issue #31417 + b.PrimitiveCollection(e => e.CollectionTenantId); + b.HasKey(e => new { e.CollectionCompanyId, e.CollectionTenantId }); + }); + + var model = modelBuilder.FinalizeModel(); + var entity = model.FindEntityType(typeof(EntityWithFields))!; + var primaryKeyProperties = entity.FindPrimaryKey()!.Properties; + Assert.Equal(2, primaryKeyProperties.Count); + var first = primaryKeyProperties[0]; + var second = primaryKeyProperties[1]; + Assert.Equal(nameof(EntityWithFields.CollectionCompanyId), first.Name); + Assert.Null(first.PropertyInfo); + Assert.NotNull(first.FieldInfo); + Assert.NotNull(first.GetElementType()); + Assert.Equal(nameof(EntityWithFields.CollectionTenantId), second.Name); + Assert.Null(second.PropertyInfo); + Assert.NotNull(second.FieldInfo); + Assert.NotNull(second.GetElementType()); + } + + [ConditionalFact] + public virtual void Can_set_alternate_key_for_primitive_collection_on_an_entity_with_fields() + { + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.CollectionCompanyId); + b.HasAlternateKey(e => e.CollectionCompanyId); + }); + + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields))!; + var properties = entity.GetProperties(); + Assert.Single(properties); + var property = properties.Single(); + Assert.Equal(nameof(EntityWithFields.CollectionCompanyId), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + Assert.NotNull(property.GetElementType()); + var keys = entity.GetKeys(); + var key = Assert.Single(keys); + Assert.Equal(properties, key.Properties); + } + + [ConditionalFact] + public virtual void Can_call_PrimitiveCollection_on_an_entity_with_fields() + { + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); + + modelBuilder.Entity().PrimitiveCollection(e => e.CollectionId); + modelBuilder.Entity().HasKey(e => e.CollectionId); + + var model = modelBuilder.FinalizeModel(); + var properties = model.FindEntityType(typeof(EntityWithFields))!.GetProperties(); + var property = Assert.Single(properties); + Assert.Equal(nameof(EntityWithFields.CollectionId), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + Assert.NotNull(property.GetElementType()); + } + + [ConditionalFact] + public virtual void Can_set_element_type_annotation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .PrimitiveCollection(c => c.Notes) + .ElementType() + .HasAnnotation("foo", "bar"); + + var elementType = modelBuilder.FinalizeModel() + .FindEntityType(typeof(Customer))! + .FindProperty(nameof(Customer.Notes))! + .GetElementType()!; + + Assert.Equal("bar", elementType["foo"]); + } + + [ConditionalFact] + public virtual void Element_types_are_nullable_by_default_if_the_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + b.PrimitiveCollection>("Stranger"); // Still optional since no NRT metadata available + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.IsNullable); + Assert.True(entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.IsNullable); // Issue #31416 + Assert.True(entityType.FindProperty("Charm")!.GetElementType()!.IsNullable); + Assert.True(entityType.FindProperty("Strange")!.GetElementType()!.IsNullable); + Assert.True(entityType.FindProperty("Stranger")!.GetElementType()!.IsNullable); + } + + [ConditionalFact] + public virtual void Element_types_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).ElementType().IsRequired(); + b.PrimitiveCollection(e => e.Down).ElementType().IsRequired(false); + b.PrimitiveCollection>("Charm").ElementType().IsRequired();; + b.PrimitiveCollection>("Strange").ElementType().IsRequired();; + b.PrimitiveCollection>("Stranger").ElementType().IsRequired();; // Still optional since no NRT metadata available + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.IsNullable); + Assert.True(entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.IsNullable); + Assert.False(entityType.FindProperty("Charm")!.GetElementType()!.IsNullable); + Assert.False(entityType.FindProperty("Strange")!.GetElementType()!.IsNullable); + Assert.False(entityType.FindProperty("Stranger")!.GetElementType()!.IsNullable); + } + + [ConditionalFact] + public virtual void Element_types_have_no_max_length_by_default() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + b.PrimitiveCollection>("Stranger"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.GetMaxLength()); + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.GetMaxLength()); + Assert.Null(entityType.FindProperty("Charm")!.GetElementType()!.GetMaxLength()); + Assert.Null(entityType.FindProperty("Strange")!.GetElementType()!.GetMaxLength()); + Assert.Null(entityType.FindProperty("Stranger")!.GetElementType()!.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_max_length() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Down).ElementType().HasMaxLength(-1); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange").ElementType().HasMaxLength(512); + b.PrimitiveCollection>("Stranger").ElementType().HasMaxLength(int.MaxValue); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.GetMaxLength()); + Assert.Equal(-1, entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.GetMaxLength()); + Assert.Null(entityType.FindProperty("Charm")!.GetElementType()!.GetMaxLength()); + Assert.Equal(512, entityType.FindProperty("Strange")!.GetElementType()!.GetMaxLength()); + Assert.Equal(int.MaxValue, entityType.FindProperty("Stranger")!.GetElementType()!.GetMaxLength()); + } + + [ConditionalFact] + public virtual void Element_types_have_default_precision_and_scale() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + b.PrimitiveCollection>("Stranger"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + var property = entityType.FindProperty(nameof(CollectionQuarks.Up))!; + Assert.Null(property.GetElementType()!.GetPrecision()); + Assert.Null(property.GetElementType()!.GetScale()); + property = entityType.FindProperty(nameof(CollectionQuarks.Down))!; + Assert.Null(property.GetElementType()!.GetPrecision()); + Assert.Null(property.GetElementType()!.GetScale()); + property = entityType.FindProperty("Charm")!; + Assert.Null(property.GetElementType()!.GetPrecision()); + Assert.Null(property.GetElementType()!.GetScale()); + property = entityType.FindProperty("Strange")!; + Assert.Null(property.GetElementType()!.GetPrecision()); + Assert.Null(property.GetElementType()!.GetScale()); + property = entityType.FindProperty("Stranger")!; + Assert.Null(property.GetElementType()!.GetPrecision()); + Assert.Null(property.GetElementType()!.GetScale()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_precision_and_scale() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Charm").ElementType(b => b.HasPrecision(5,6)); + b.PrimitiveCollection>("Strange").ElementType(b => b.HasPrecision(12)); + b.PrimitiveCollection>("Stranger"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + var elementType = entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!; + Assert.Null(elementType.GetPrecision()); + Assert.Null(elementType.GetScale()); + + elementType = entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!; + Assert.Null(elementType.GetPrecision()); + Assert.Null(elementType.GetScale()); + + elementType = entityType.FindProperty("Charm")!.GetElementType()!; + Assert.Equal(5, elementType.GetPrecision()); + Assert.Equal(6, elementType.GetScale()); + + elementType = entityType.FindProperty("Strange")!.GetElementType()!; + Assert.Equal(12, elementType.GetPrecision()); + Assert.Null(elementType.GetScale()); + + elementType = entityType.FindProperty("Stranger")!.GetElementType()!; + Assert.Null(elementType.GetPrecision()); + Assert.Null(elementType.GetScale()); + } + + [ConditionalFact] + public virtual void Element_types_have_default_unicode() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange"); + b.PrimitiveCollection>("Stranger"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.IsUnicode()); + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.IsUnicode()); + Assert.Null(entityType.FindProperty("Charm")!.GetElementType()!.IsUnicode()); + Assert.Null(entityType.FindProperty("Strange")!.GetElementType()!.IsUnicode()); + Assert.Null(entityType.FindProperty("Stranger")!.GetElementType()!.IsUnicode()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_unicode_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Down).ElementType().IsUnicode(false); + b.PrimitiveCollection>("Charm"); + b.PrimitiveCollection>("Strange").ElementType().IsUnicode(); + b.PrimitiveCollection>("Stranger").ElementType().IsUnicode(false); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty(nameof(CollectionQuarks.Up))!.GetElementType()!.IsUnicode()); + Assert.False(entityType.FindProperty(nameof(CollectionQuarks.Down))!.GetElementType()!.IsUnicode()); + Assert.Null(entityType.FindProperty("Charm")!.GetElementType()!.IsUnicode()); + Assert.True(entityType.FindProperty("Strange")!.GetElementType()!.IsUnicode()); + Assert.False(entityType.FindProperty("Stranger")!.GetElementType()!.IsUnicode()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_provider_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down).ElementType().HasConversion(); + b.PrimitiveCollection>("Charm").ElementType().HasConversion>(); + b.PrimitiveCollection("Strange").ElementType().HasConversion((ValueConverter?)null); + b.PrimitiveCollection>("Top").ElementType().HasConversion(new CustomValueComparer()); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; + + var up = entityType.FindProperty("Up")!.GetElementType()!; + Assert.Null(up.GetProviderClrType()); + Assert.IsType>(up.GetValueComparer()); + + var down = entityType.FindProperty("Down")!.GetElementType()!; + Assert.Same(typeof(byte[]), down.GetProviderClrType()); + Assert.IsType>(down.GetValueComparer()); + + var charm = entityType.FindProperty("Charm")!.GetElementType()!; + Assert.Same(typeof(long), charm.GetProviderClrType()); + Assert.IsType>(charm.GetValueComparer()); + + var strange = entityType.FindProperty("Strange")!.GetElementType()!; + Assert.Null(strange.GetProviderClrType()); + Assert.IsType>(strange.GetValueComparer()); + + var top = entityType.FindProperty("Top")!.GetElementType()!; + Assert.Same(typeof(string), top.GetProviderClrType()); + Assert.IsType>(top.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_non_generic_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + ValueConverter stringConverter = new StringToBytesConverter(Encoding.UTF8); + ValueConverter intConverter = new CastingConverter(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down).ElementType().HasConversion(stringConverter); + b.PrimitiveCollection("Charm").ElementType().HasConversion(intConverter, null); + b.PrimitiveCollection>("Strange").ElementType().HasConversion(stringConverter); + b.PrimitiveCollection>("Strange").ElementType().HasConversion((ValueConverter?)null); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Null(entityType.FindProperty("Up")!.GetElementType()!.GetValueConverter()); + + var down = entityType.FindProperty("Down")!.GetElementType()!; + Assert.Same(stringConverter, down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + + var charm = entityType.FindProperty("Charm")!.GetElementType()!; + Assert.Same(intConverter, charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + + Assert.Null(entityType.FindProperty("Strange")!.GetElementType()!.GetValueConverter()); + } + + [ConditionalFact] + public virtual void Element_types_can_have_custom_type_value_converter_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).ElementType().HasConversion>(); + b.PrimitiveCollection(e => e.Down).ElementType().HasConversion>(); + b.PrimitiveCollection("Charm").ElementType().HasConversion, CustomValueComparer>(); + b.PrimitiveCollection>("Strange").ElementType().HasConversion>(); + b.PrimitiveCollection>("Strange").ElementType().HasConversion((ValueConverter?)null); + b.PrimitiveCollection("Top").ElementType().HasConversion(new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; + + var up = entityType.FindProperty("Up")!.GetElementType()!; + Assert.Equal(typeof(int), up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + + var down = entityType.FindProperty("Down")!.GetElementType()!; + Assert.IsType(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + + var charm = entityType.FindProperty("Charm")!.GetElementType()!; + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + + var strange = entityType.FindProperty("Strange")!.GetElementType()!; + Assert.Null(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + + var top = entityType.FindProperty("Top")!.GetElementType()!; + Assert.Null(top.GetValueConverter()); + Assert.IsType>(top.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Primitive_collections_can_have_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up); + b.PrimitiveCollection(e => e.Down).ElementType().HasConversion( + new ValueConverter(v => int.Parse(v), v => v.ToString())!); + b.PrimitiveCollection>("Charm").ElementType().HasConversion( + new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); + b.PrimitiveCollection("Strange").ElementType().HasConversion( + new ValueConverter(v => v, v => (float)v), new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + var up = entityType.FindProperty("Up")!.GetElementType()!; + Assert.Null(up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + + var down = entityType.FindProperty("Down")!.GetElementType()!; + Assert.IsType>(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + + var charm = entityType.FindProperty("Charm")!.GetElementType()!; + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + + var strange = entityType.FindProperty("Strange")!.GetElementType()!; + Assert.IsType>(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Value_converter_type_on_primitive_collection_is_checked() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + Assert.Equal( + CoreStrings.ConverterPropertyMismatchElement("string", "CollectionQuarks", "Up", "int"), + Assert.Throws( + () => b.PrimitiveCollection(e => e.Up).ElementType().HasConversion( + new StringToBytesConverter(Encoding.UTF8))).Message); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + Assert.Null(entityType.FindProperty("Up")!.GetElementType()!.GetValueConverter()); + } } } diff --git a/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs b/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs index e03b48b9cc2..98a9e75b245 100644 --- a/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/PrimitiveCollectionsTestBase.cs @@ -1,1884 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable enable - -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Dynamic; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.TestModels; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; - -namespace Microsoft.EntityFrameworkCore.ModelBuilding; - -public abstract partial class ModelBuilderTest -{ - public abstract class PrimitiveCollectionsTestBase : ModelBuilderTestBase - { - [ConditionalFact] - public virtual void Can_set_property_annotation() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder - .Entity() - .PrimitiveCollection(c => c.Notes).HasAnnotation("foo", "bar"); - - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; - - Assert.Equal("bar", property["foo"]); - } - - [ConditionalFact] - public virtual void Can_set_property_annotation_when_no_clr_property() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder - .Entity() - .PrimitiveCollection>(nameof(Customer.Notes)).HasAnnotation("foo", "bar"); - - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Notes))!; - - Assert.Equal("bar", property["foo"]); - } - - [ConditionalFact] - public virtual void Can_set_property_annotation_by_type() - { - var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); - - modelBuilder.Ignore(); - var propertyBuilder = modelBuilder - .Entity() - .PrimitiveCollection(c => c.Notes); - - var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!; - - Assert.Equal("bar", property["foo"]); - } - - [ConditionalFact] - public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.Property(e => e.Up); - b.Property(e => e.Down); - b.Property>("Charm"); - b.Property?>("Strange"); - }); - - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; - - Assert.False(entityType.FindProperty("Up")!.IsNullable); - Assert.True(entityType.FindProperty("Down")!.IsNullable); - Assert.True(entityType.FindProperty("Charm")!.IsNullable); // Because we can't detect the non-nullable reference type - Assert.True(entityType.FindProperty("Strange")!.IsNullable); - } - - [ConditionalFact] - public virtual void Properties_can_be_ignored() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.Ignore(e => e.Up); - b.Ignore(e => e.Down); - b.Ignore("Charm"); - b.Ignore("Strange"); - }); - - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; - Assert.Contains(nameof(CollectionQuarks.Id), entityType.GetProperties().Select(p => p.Name)); - Assert.DoesNotContain(nameof(CollectionQuarks.Up), entityType.GetProperties().Select(p => p.Name)); - Assert.DoesNotContain(nameof(CollectionQuarks.Down), entityType.GetProperties().Select(p => p.Name)); - } - - [ConditionalFact] - public virtual void Can_override_navigations_as_properties() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - modelBuilder.Entity(); - - var customer = model.FindEntityType(typeof(Customer))!; - Assert.NotNull(customer.FindNavigation(nameof(Customer.Orders))); - - modelBuilder.Entity().PrimitiveCollection(c => c.Orders); - - Assert.Null(customer.FindNavigation(nameof(Customer.Orders))); - Assert.NotNull(customer.FindProperty(nameof(Customer.Orders))); - } - - [ConditionalFact] - public virtual void Properties_can_be_made_required() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.Property(e => e.Up).IsRequired(); - b.Property(e => e.Down).IsRequired(); - b.Property>("Charm").IsRequired(); - b.Property?>("Strange").IsRequired(); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; - - Assert.False(entityType.FindProperty("Up")!.IsNullable); - Assert.False(entityType.FindProperty("Down")!.IsNullable); - Assert.False(entityType.FindProperty("Charm")!.IsNullable); - Assert.False(entityType.FindProperty("Strange")!.IsNullable); - } - - [ConditionalFact] - public virtual void Properties_can_be_made_optional() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.Property(e => e.Up).IsRequired(false); - b.Property(e => e.Down).IsRequired(false); - b.Property>("Charm").IsRequired(false); - b.Property?>("Strange").IsRequired(false); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(CollectionQuarks))!; - - Assert.True(entityType.FindProperty("Up")!.IsNullable); - Assert.True(entityType.FindProperty("Down")!.IsNullable); - Assert.True(entityType.FindProperty("Charm")!.IsNullable); - Assert.True(entityType.FindProperty("Strange")!.IsNullable); - } - - [ConditionalFact] - public virtual void Key_properties_cannot_be_made_optional() - => Assert.Equal( - CoreStrings.KeyPropertyCannotBeNullable(nameof(CollectionQuarks.Down), nameof(CollectionQuarks), "{'" + nameof(CollectionQuarks.Down) + "'}"), - Assert.Throws( - () => - CreateModelBuilder().Entity( - b => - { - b.HasAlternateKey( - e => new { e.Down }); - b.Property(e => e.Down).IsRequired(false); - })).Message); - - [ConditionalFact] - public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.Property>("Up"); - b.Property?>("Down"); - b.Property>("Charm"); - b.Property?>("Strange"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(CollectionQuarks))!; - - Assert.False(entityType.FindProperty("Up")!.IsShadowProperty()); - Assert.False(entityType.FindProperty("Down")!.IsShadowProperty()); - Assert.True(entityType.FindProperty("Charm")!.IsShadowProperty()); - Assert.True(entityType.FindProperty("Strange")!.IsShadowProperty()); - - Assert.Equal(-1, entityType.FindProperty("Up")!.GetShadowIndex()); - Assert.Equal(-1, entityType.FindProperty("Down")!.GetShadowIndex()); - Assert.NotEqual(-1, entityType.FindProperty("Charm")!.GetShadowIndex()); - Assert.NotEqual(-1, entityType.FindProperty("Strange")!.GetShadowIndex()); - } - - // [ConditionalFact] - // public virtual void Properties_can_be_made_concurrency_tokens() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).IsConcurrencyToken(); - // b.Property(e => e.Down).IsConcurrencyToken(false); - // b.Property("Charm").IsConcurrencyToken(); - // b.Property("Strange").IsConcurrencyToken(false); - // b.Property("Top").IsConcurrencyToken(); - // b.Property("Bottom").IsConcurrencyToken(false); - // b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks)); - // - // Assert.False(entityType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); - // Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); - // Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); - // Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); - // Assert.False(entityType.FindProperty("Strange").IsConcurrencyToken); - // Assert.True(entityType.FindProperty("Top").IsConcurrencyToken); - // Assert.False(entityType.FindProperty("Bottom").IsConcurrencyToken); - // - // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); - // Assert.Equal(3, entityType.FindProperty("Up").GetOriginalValueIndex()); - // Assert.Equal(-1, entityType.FindProperty("Down").GetOriginalValueIndex()); - // Assert.Equal(1, entityType.FindProperty("Charm").GetOriginalValueIndex()); - // Assert.Equal(-1, entityType.FindProperty("Strange").GetOriginalValueIndex()); - // Assert.Equal(2, entityType.FindProperty("Top").GetOriginalValueIndex()); - // Assert.Equal(-1, entityType.FindProperty("Bottom").GetOriginalValueIndex()); - // - // Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, entityType.GetChangeTrackingStrategy()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_access_mode_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); - // b.Property("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); - // b.Property("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal(PropertyAccessMode.PreferField, entityType.FindProperty("Up").GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.Field, entityType.FindProperty("Down").GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.Property, entityType.FindProperty("Charm").GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, entityType.FindProperty("Strange").GetPropertyAccessMode()); - // } - // - // [ConditionalFact] - // public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field); - // - // modelBuilder.Entity( - // b => - // { - // b.HasKey(e => e.Id1); - // }); - // modelBuilder.Ignore(); - // - // modelBuilder.Entity( - // b => - // { - // b.UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); - // b.Property(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); - // b.Property(e => e.Down).HasField("_forDown"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); - // - // var hobsType = (IReadOnlyEntityType)model.FindEntityType(typeof(Hob)); - // Assert.Equal(PropertyAccessMode.Field, hobsType.GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.Field, hobsType.FindProperty("Id1").GetPropertyAccessMode()); - // - // var quarksType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); - // Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_provider_type_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down).HasConversion(); - // b.Property("Charm").HasConversion>(); - // b.Property("Strange").HasConversion( - // new CustomValueComparer(), new CustomValueComparer()); - // b.Property("Strange").HasConversion(null); - // b.Property("Top").HasConversion(new CustomValueComparer()); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // var up = entityType.FindProperty("Up"); - // Assert.Null(up.GetProviderClrType()); - // Assert.IsType>(up.GetValueComparer()); - // - // var down = entityType.FindProperty("Down"); - // Assert.Same(typeof(byte[]), down.GetProviderClrType()); - // Assert.IsType>(down.GetValueComparer()); - // Assert.IsType>(down.GetProviderValueComparer()); - // - // var charm = entityType.FindProperty("Charm"); - // Assert.Same(typeof(long), charm.GetProviderClrType()); - // Assert.IsType>(charm.GetValueComparer()); - // Assert.IsType>(charm.GetProviderValueComparer()); - // - // var strange = entityType.FindProperty("Strange"); - // Assert.Null(strange.GetProviderClrType()); - // Assert.IsType>(strange.GetValueComparer()); - // Assert.IsType>(strange.GetProviderValueComparer()); - // - // var top = entityType.FindProperty("Top"); - // Assert.Same(typeof(string), top.GetProviderClrType()); - // Assert.IsType>(top.GetValueComparer()); - // Assert.IsType>(top.GetProviderValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_provider_type_set_for_type() - // { - // var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down); - // b.Property("Charm"); - // b.Property("Strange"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty("Up").GetProviderClrType()); - // Assert.Same(typeof(byte[]), entityType.FindProperty("Down").GetProviderClrType()); - // Assert.Null(entityType.FindProperty("Charm").GetProviderClrType()); - // Assert.Same(typeof(byte[]), entityType.FindProperty("Strange").GetProviderClrType()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_non_generic_value_converter_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // ValueConverter stringConverter = new StringToBytesConverter(Encoding.UTF8); - // ValueConverter intConverter = new CastingConverter(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down).HasConversion(stringConverter); - // b.Property("Charm").HasConversion(intConverter, null, new CustomValueComparer()); - // b.Property("Strange").HasConversion(stringConverter); - // b.Property("Strange").HasConversion(null); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty("Up").GetValueConverter()); - // - // var down = entityType.FindProperty("Down"); - // Assert.Same(stringConverter, down.GetValueConverter()); - // Assert.IsType>(down.GetValueComparer()); - // Assert.IsType>(down.GetProviderValueComparer()); - // - // var charm = entityType.FindProperty("Charm"); - // Assert.Same(intConverter, charm.GetValueConverter()); - // Assert.IsType>(charm.GetValueComparer()); - // Assert.IsType>(charm.GetProviderValueComparer()); - // - // Assert.Null(entityType.FindProperty("Strange").GetValueConverter()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_custom_type_value_converter_type_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasConversion>(); - // b.Property(e => e.Down) - // .HasConversion, CustomValueComparer>(); - // b.Property("Charm").HasConversion, CustomValueComparer>(); - // b.Property("Strange").HasConversion>(); - // b.Property("Strange").HasConversion(null, null); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks)); - // - // var up = entityType.FindProperty("Up"); - // Assert.Equal(typeof(int), up.GetProviderClrType()); - // Assert.Null(up.GetValueConverter()); - // Assert.IsType>(up.GetValueComparer()); - // Assert.IsType>(up.GetProviderValueComparer()); - // - // var down = entityType.FindProperty("Down"); - // Assert.IsType(down.GetValueConverter()); - // Assert.IsType>(down.GetValueComparer()); - // Assert.IsType>(down.GetProviderValueComparer()); - // - // var charm = entityType.FindProperty("Charm"); - // Assert.IsType>(charm.GetValueConverter()); - // Assert.IsType>(charm.GetValueComparer()); - // Assert.IsType>(charm.GetProviderValueComparer()); - // - // var strange = entityType.FindProperty("Strange"); - // Assert.Null(strange.GetValueConverter()); - // Assert.IsType>(strange.GetValueComparer()); - // Assert.IsType>(strange.GetProviderValueComparer()); - // } - // - // private class UTF8StringToBytesConverter : StringToBytesConverter - // { - // public UTF8StringToBytesConverter() - // : base(Encoding.UTF8) - // { - // } - // } - // - // private class CustomValueComparer : ValueComparer - // { - // public CustomValueComparer() - // : base(false) - // { - // } - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_value_converter_set_inline() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down).HasConversion(v => int.Parse(v), v => v.ToString()); - // b.Property("Charm").HasConversion(v => (long)v, v => (int)v, new CustomValueComparer()); - // b.Property("Strange").HasConversion( - // v => (double)v, v => (float)v, new CustomValueComparer(), new CustomValueComparer()); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // var up = entityType.FindProperty("Up"); - // Assert.Null(up.GetProviderClrType()); - // Assert.Null(up.GetValueConverter()); - // Assert.IsType>(up.GetValueComparer()); - // Assert.IsType>(up.GetProviderValueComparer()); - // - // var down = entityType.FindProperty("Down"); - // Assert.IsType>(down.GetValueConverter()); - // Assert.IsType>(down.GetValueComparer()); - // Assert.IsType>(down.GetProviderValueComparer()); - // - // var charm = entityType.FindProperty("Charm"); - // Assert.IsType>(charm.GetValueConverter()); - // Assert.IsType>(charm.GetValueComparer()); - // Assert.IsType>(charm.GetProviderValueComparer()); - // - // var strange = entityType.FindProperty("Strange"); - // Assert.IsType>(strange.GetValueConverter()); - // Assert.IsType>(strange.GetValueComparer()); - // Assert.IsType>(strange.GetProviderValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_value_converter_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up); - // b.Property(e => e.Down).HasConversion( - // new ValueConverter(v => int.Parse(v), v => v.ToString())); - // b.Property("Charm").HasConversion( - // new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); - // b.Property("Strange").HasConversion( - // new ValueConverter(v => v, v => (float)v), new CustomValueComparer(), - // new CustomValueComparer()); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // var up = entityType.FindProperty("Up"); - // Assert.Null(up.GetProviderClrType()); - // Assert.Null(up.GetValueConverter()); - // Assert.IsType>(up.GetValueComparer()); - // Assert.IsType>(up.GetProviderValueComparer()); - // - // var down = entityType.FindProperty("Down"); - // Assert.IsType>(down.GetValueConverter()); - // Assert.IsType>(down.GetValueComparer()); - // Assert.IsType>(down.GetProviderValueComparer()); - // - // var charm = entityType.FindProperty("Charm"); - // Assert.IsType>(charm.GetValueConverter()); - // Assert.IsType>(charm.GetValueComparer()); - // Assert.IsType>(charm.GetProviderValueComparer()); - // - // var strange = entityType.FindProperty("Strange"); - // Assert.IsType>(strange.GetValueConverter()); - // Assert.IsType>(strange.GetValueComparer()); - // Assert.IsType>(strange.GetProviderValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void IEnumerable_properties_with_value_converter_set_are_not_discovered_as_navigations() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.ExpandoObject).HasConversion( - // v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)); - // - // var comparer = new ValueComparer( - // (v1, v2) => v1.SequenceEqual(v2), - // v => v.GetHashCode()); - // - // b.Property(e => e.ExpandoObject).Metadata.SetValueComparer(comparer); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); - // Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueConverter()); - // Assert.NotNull(entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueComparer()); - // } - // - // private static ExpandoObject DeserializeExpandoObject(string value) - // { - // dynamic obj = new ExpandoObject(); - // obj.Value = value; - // - // return obj; - // } - // - // private class ExpandoObjectConverter : ValueConverter - // { - // public ExpandoObjectConverter() - // : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) - // { - // } - // } - // - // private class ExpandoObjectComparer : ValueComparer - // { - // public ExpandoObjectComparer() - // : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) - // { - // } - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_value_converter_configured_by_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties(typeof(IWrapped<>)).AreUnicode(false); - // c.Properties().HaveMaxLength(20); - // c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); - // }); - // - // modelBuilder.Entity(); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); - // var wrappedProperty = entityType.FindProperty(nameof(WrappedStringEntity.WrappedString)); - // Assert.False(wrappedProperty.IsUnicode()); - // Assert.Equal(20, wrappedProperty.GetMaxLength()); - // Assert.IsType(wrappedProperty.GetValueConverter()); - // Assert.IsType>(wrappedProperty.GetValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void Value_converter_configured_on_non_nullable_type_is_applied() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveConversion, CustomValueComparer>(); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Wierd"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // var id = entityType.FindProperty("Id"); - // Assert.IsType>(id.GetValueConverter()); - // Assert.IsType>(id.GetValueComparer()); - // - // var wierd = entityType.FindProperty("Wierd"); - // Assert.IsType>(wierd.GetValueConverter()); - // Assert.IsType>(wierd.GetValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveConversion, CustomValueComparer>(); - // c.Properties() - // .HaveConversion, CustomValueComparer, CustomValueComparer>(); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Wierd"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // var id = entityType.FindProperty("Id"); - // Assert.IsType>(id.GetValueConverter()); - // Assert.IsType>(id.GetValueComparer()); - // Assert.IsType>(id.GetProviderValueComparer()); - // - // var wierd = entityType.FindProperty("Wierd"); - // Assert.IsType>(wierd.GetValueConverter()); - // Assert.IsType>(wierd.GetValueComparer()); - // Assert.IsType>(wierd.GetProviderValueComparer()); - // } - // - // [ConditionalFact] - // public virtual void Value_converter_configured_on_base_type_is_not_applied() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); - // }); - // - // modelBuilder.Entity(); - // - // Assert.Equal( - // CoreStrings.PropertyNotMapped( - // nameof(WrappedString), - // nameof(WrappedStringEntity), - // nameof(WrappedStringEntity.WrappedString)), - // Assert.Throws(() => modelBuilder.FinalizeModel()).Message); - // } - // - // private class WrappedStringToStringConverter : ValueConverter - // { - // public WrappedStringToStringConverter() - // : base(v => v.Value, v => new WrappedString { Value = v }) - // { - // } - // } - // - // [ConditionalFact] - // public virtual void Throws_for_conflicting_base_configurations_by_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties(); - // c.IgnoreAny>(); - // }); - // - // Assert.Equal( - // CoreStrings.TypeConfigurationConflict( - // nameof(WrappedString), "Property", - // "IWrapped", "Ignored"), - // Assert.Throws(() => modelBuilder.Entity()).Message); - // } - // - // [ConditionalFact] - // public virtual void Value_converter_type_is_checked() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // Assert.Equal( - // CoreStrings.ConverterPropertyMismatch("string", "Quarks", "Up", "int"), - // Assert.Throws( - // () => b.Property(e => e.Up).HasConversion( - // new StringToBytesConverter(Encoding.UTF8))).Message); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // Assert.Null(entityType.FindProperty("Up").GetValueConverter()); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_have_field_set() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Up").HasField("_forUp"); - // b.Property(e => e.Down).HasField("_forDown"); - // b.Property("_forWierd").HasField("_forWierd"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal("_forUp", entityType.FindProperty("Up").GetFieldName()); - // Assert.Equal("_forDown", entityType.FindProperty("Down").GetFieldName()); - // Assert.Equal("_forWierd", entityType.FindProperty("_forWierd").GetFieldName()); - // } - // - // [ConditionalFact] - // public virtual void HasField_throws_if_field_is_not_found() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // Assert.Equal( - // CoreStrings.MissingBackingField("_notFound", nameof(Quarks.Down), nameof(Quarks)), - // Assert.Throws(() => b.Property(e => e.Down).HasField("_notFound")).Message); - // }); - // } - // - // [ConditionalFact] - // public virtual void HasField_throws_if_field_is_wrong_type() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // Assert.Equal( - // CoreStrings.BadBackingFieldType("_forUp", "int", nameof(Quarks), nameof(Quarks.Down), "string"), - // Assert.Throws(() => b.Property(e => e.Down).HasField("_forUp")).Message); - // }); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_be_set_to_generate_values_on_Add() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.HasKey(e => e.Id); - // b.Property(e => e.Up).ValueGeneratedOnAddOrUpdate(); - // b.Property(e => e.Down).ValueGeneratedNever(); - // b.Property("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; - // b.Property("Strange").ValueGeneratedNever(); - // b.Property("Top").ValueGeneratedOnAddOrUpdate(); - // b.Property("Bottom").ValueGeneratedOnUpdate(); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // Assert.Equal(ValueGenerated.OnAdd, entityType.FindProperty(Customer.IdProperty.Name).ValueGenerated); - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); - // Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("Charm").ValueGenerated); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Strange").ValueGenerated); - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Top").ValueGenerated); - // Assert.Equal(ValueGenerated.OnUpdate, entityType.FindProperty("Bottom").ValueGenerated); - // } - // - // [ConditionalFact] - // public virtual void Properties_can_set_row_version() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.HasKey(e => e.Id); - // b.Property(e => e.Up).IsRowVersion(); - // b.Property(e => e.Down).ValueGeneratedNever(); - // b.Property("Charm").IsRowVersion(); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Up").ValueGenerated); - // Assert.Equal(ValueGenerated.Never, entityType.FindProperty("Down").ValueGenerated); - // Assert.Equal(ValueGenerated.OnAddOrUpdate, entityType.FindProperty("Charm").ValueGenerated); - // - // Assert.True(entityType.FindProperty("Up").IsConcurrencyToken); - // Assert.False(entityType.FindProperty("Down").IsConcurrencyToken); - // Assert.True(entityType.FindProperty("Charm").IsConcurrencyToken); - // } - // - // [ConditionalFact] - // public virtual void Can_set_max_length_for_properties() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasMaxLength(0); - // b.Property(e => e.Down).HasMaxLength(100); - // b.Property("Charm").HasMaxLength(0); - // b.Property("Strange").HasMaxLength(-1); - // b.Property("Top").HasMaxLength(0); - // b.Property("Bottom").HasMaxLength(100); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - // Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - // Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - // Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_max_length_for_property_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveMaxLength(0); - // c.Properties().HaveMaxLength(100); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("Top"); - // b.Property("Bottom"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - // Assert.Equal(100, entityType.FindProperty("Down").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - // Assert.Equal(100, entityType.FindProperty("Strange").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - // Assert.Equal(100, entityType.FindProperty("Bottom").GetMaxLength()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_sentinel_for_properties() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasSentinel(1); - // b.Property(e => e.Down).HasSentinel("100"); - // b.Property("Charm").HasSentinel(-1); - // b.Property("Strange").HasSentinel("-1"); - // b.Property("Top").HasSentinel(77); - // b.Property("Bottom").HasSentinel("100"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks))!; - // - // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name)!.Sentinel); - // Assert.Equal(1, entityType.FindProperty("Up")!.Sentinel); - // Assert.Equal("100", entityType.FindProperty("Down")!.Sentinel); - // Assert.Equal(-1, entityType.FindProperty("Charm")!.Sentinel); - // Assert.Equal("-1", entityType.FindProperty("Strange")!.Sentinel); - // Assert.Equal(77, entityType.FindProperty("Top")!.Sentinel); - // Assert.Equal("100", entityType.FindProperty("Bottom")!.Sentinel); - // } - // - // [ConditionalFact] - // public virtual void Can_set_sentinel_for_property_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveSentinel(-1); - // c.Properties().HaveSentinel("100"); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("Top"); - // b.Property("Bottom"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks))!; - // - // Assert.Equal(-1, entityType.FindProperty(Customer.IdProperty.Name)!.Sentinel); - // Assert.Equal(-1, entityType.FindProperty("Up")!.Sentinel); - // Assert.Equal("100", entityType.FindProperty("Down")!.Sentinel); - // Assert.Equal(-1, entityType.FindProperty("Charm")!.Sentinel); - // Assert.Equal("100", entityType.FindProperty("Strange")!.Sentinel); - // Assert.Equal(-1, entityType.FindProperty("Top")!.Sentinel); - // Assert.Equal("100", entityType.FindProperty("Bottom")!.Sentinel); - // } - // - // [ConditionalFact] - // public virtual void Can_set_unbounded_max_length_for_property_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HaveMaxLength(0); - // c.Properties().HaveMaxLength(-1); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("Top"); - // b.Property("Bottom"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Up").GetMaxLength()); - // Assert.Equal(-1, entityType.FindProperty("Down").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Charm").GetMaxLength()); - // Assert.Equal(-1, entityType.FindProperty("Strange").GetMaxLength()); - // Assert.Equal(0, entityType.FindProperty("Top").GetMaxLength()); - // Assert.Equal(-1, entityType.FindProperty("Bottom").GetMaxLength()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_precision_and_scale_for_properties() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasPrecision(1, 0); - // b.Property(e => e.Down).HasPrecision(100, 10); - // b.Property("Charm").HasPrecision(1, 0); - // b.Property("Strange").HasPrecision(100, 10); - // b.Property("Top").HasPrecision(1, 0); - // b.Property("Bottom").HasPrecision(100, 10); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); - // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetScale()); - // Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Up").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Down").GetScale()); - // Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); - // Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Top").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_precision_and_scale_for_property_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().HavePrecision(1, 0); - // c.Properties().HavePrecision(100, 10); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("Top"); - // b.Property("Bottom"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal(1, entityType.FindProperty(Customer.IdProperty.Name).GetPrecision()); - // Assert.Equal(0, entityType.FindProperty(Customer.IdProperty.Name).GetScale()); - // Assert.Equal(1, entityType.FindProperty("Up").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Up").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Down").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Down").GetScale()); - // Assert.Equal(1, entityType.FindProperty("Charm").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Charm").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Strange").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Strange").GetScale()); - // Assert.Equal(1, entityType.FindProperty("Top").GetPrecision()); - // Assert.Equal(0, entityType.FindProperty("Top").GetScale()); - // Assert.Equal(100, entityType.FindProperty("Bottom").GetPrecision()); - // Assert.Equal(10, entityType.FindProperty("Bottom").GetScale()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_custom_value_generator_for_properties() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasValueGenerator(); - // b.Property(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); - // b.Property("Charm").HasValueGenerator((_, __) => new CustomValueGenerator()); - // b.Property("Strange").HasValueGenerator(); - // b.Property("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); - // b.Property("Bottom").HasValueGeneratorFactory(); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); - // Assert.IsType(entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); - // Assert.IsType(entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); - // Assert.IsType(entityType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); - // Assert.IsType(entityType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); - // Assert.IsType(entityType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); - // Assert.IsType(entityType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); - // } - // - // private class CustomValueGenerator : ValueGenerator - // { - // public override int Next(EntityEntry entry) - // => throw new NotImplementedException(); - // - // public override bool GeneratesTemporaryValues - // => false; - // } - // - // private class CustomValueGeneratorFactory : ValueGeneratorFactory - // { - // public override ValueGenerator Create(IProperty property, ITypeBase entityType) - // => new CustomValueGenerator(); - // } - // - // [ConditionalFact] - // public virtual void Throws_for_bad_value_generator_type() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // Assert.Equal( - // CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), - // Assert.Throws(() => b.Property(e => e.Down).HasValueGenerator(typeof(Random))).Message); - // }); - // } - // - // [ConditionalFact] - // public virtual void Throws_for_value_generator_that_cannot_be_constructed() - // { - // var modelBuilder = CreateModelBuilder(); - // var model = modelBuilder.Model; - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).HasValueGenerator(); - // b.Property(e => e.Down).HasValueGenerator(); - // }); - // - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Equal( - // CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), - // Assert.Throws( - // () => entityType.FindProperty("Up").GetValueGeneratorFactory()(null, null)).Message); - // - // Assert.Equal( - // CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), - // Assert.Throws( - // () => entityType.FindProperty("Down").GetValueGeneratorFactory()(null, null)).Message); - // } - // - // private class BadCustomValueGenerator1 : CustomValueGenerator - // { - // public BadCustomValueGenerator1(string foo) - // { - // } - // } - // - // private abstract class BadCustomValueGenerator2 : CustomValueGenerator - // { - // } - // - // protected class StringCollectionEntity - // { - // public ICollection Property { get; set; } - // } - // - // [ConditionalFact] - // public virtual void Object_cannot_be_configured_as_property() - // => Assert.Equal( - // CoreStrings.UnconfigurableType("Dictionary", "Property", "SharedTypeEntityType", "object"), - // Assert.Throws(() => CreateModelBuilder(c => c.Properties())).Message); - // - // [ConditionalFact] - // public virtual void Property_bag_cannot_be_configured_as_property() - // { - // Assert.Equal( - // CoreStrings.UnconfigurableType( - // "Dictionary", "Property", "SharedTypeEntityType", "Dictionary"), - // Assert.Throws(() => CreateModelBuilder(c => c.Properties>())) - // .Message); - // - // Assert.Equal( - // CoreStrings.UnconfigurableType( - // "Dictionary", "Property", "SharedTypeEntityType", "IDictionary"), - // Assert.Throws(() => CreateModelBuilder(c => c.Properties>())) - // .Message); - // } - // - // [ConditionalFact] - // protected virtual void Mapping_ignores_ignored_array() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().Ignore(e => e.One); - // - // var model = modelBuilder.FinalizeModel(); - // - // Assert.Null(model.FindEntityType(typeof(OneDee)).FindProperty("One")); - // } - // - // [ConditionalFact] - // protected virtual void Mapping_ignores_ignored_two_dimensional_array() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().Ignore(e => e.Two); - // - // var model = modelBuilder.FinalizeModel(); - // - // Assert.Null(model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); - // } - // - // [ConditionalFact] - // protected virtual void Mapping_throws_for_non_ignored_three_dimensional_array() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity(); - // - // Assert.Equal( - // CoreStrings.PropertyNotAdded( - // typeof(ThreeDee).ShortDisplayName(), "Three", typeof(int[,,]).ShortDisplayName()), - // Assert.Throws(modelBuilder.FinalizeModel).Message); - // } - // - // [ConditionalFact] - // protected virtual void Mapping_ignores_ignored_three_dimensional_array() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().Ignore(e => e.Three); - // - // var model = modelBuilder.FinalizeModel(); - // - // Assert.Null(model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); - // } - // - // protected class OneDee - // { - // public int Id { get; set; } - // - // public int[] One { get; set; } - // } - // - // protected class TwoDee - // { - // public int Id { get; set; } - // - // public int[,] Two { get; set; } - // } - // - // protected class ThreeDee - // { - // public int Id { get; set; } - // - // public int[,,] Three { get; set; } - // } - // - // [ConditionalFact] - // public virtual void Private_property_is_not_discovered_by_convention() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Ignore(); - // modelBuilder.Entity(); - // - // var model = modelBuilder.FinalizeModel(); - // - // Assert.Empty( - // model.FindEntityType(typeof(Gamma)).GetProperties() - // .Where(p => p.Name == "PrivateProperty")); - // } - // - // [ConditionalFact] - // protected virtual void Throws_for_int_keyed_dictionary() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity(); - // - // Assert.Equal( - // CoreStrings.NavigationNotAdded( - // nameof(IntDict), nameof(IntDict.Notes), typeof(Dictionary).ShortDisplayName()), - // Assert.Throws(() => modelBuilder.FinalizeModel()).Message); - // } - // - // protected class IntDict - // { - // public int Id { get; set; } - // public Dictionary Notes { get; set; } - // } - // - // [ConditionalFact] - // public virtual void Can_set_unicode_for_properties() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.Property(e => e.Up).IsUnicode(); - // b.Property(e => e.Down).IsUnicode(false); - // b.Property("Charm").IsUnicode(); - // b.Property("Strange").IsUnicode(false); - // b.Property("Top").IsUnicode(); - // b.Property("Bottom").IsUnicode(false); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.Null(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); - // Assert.True(entityType.FindProperty("Up").IsUnicode()); - // Assert.False(entityType.FindProperty("Down").IsUnicode()); - // Assert.True(entityType.FindProperty("Charm").IsUnicode()); - // Assert.False(entityType.FindProperty("Strange").IsUnicode()); - // Assert.True(entityType.FindProperty("Top").IsUnicode()); - // Assert.False(entityType.FindProperty("Bottom").IsUnicode()); - // } - // - // [ConditionalFact] - // public virtual void Can_set_unicode_for_property_type() - // { - // var modelBuilder = CreateModelBuilder( - // c => - // { - // c.Properties().AreUnicode(); - // c.Properties().AreUnicode(false); - // }); - // - // modelBuilder.Entity( - // b => - // { - // b.Property("Charm"); - // b.Property("Strange"); - // b.Property("Top"); - // b.Property("Bottom"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Quarks)); - // - // Assert.True(entityType.FindProperty(Customer.IdProperty.Name).IsUnicode()); - // Assert.True(entityType.FindProperty("Up").IsUnicode()); - // Assert.False(entityType.FindProperty("Down").IsUnicode()); - // Assert.True(entityType.FindProperty("Charm").IsUnicode()); - // Assert.False(entityType.FindProperty("Strange").IsUnicode()); - // Assert.True(entityType.FindProperty("Top").IsUnicode()); - // Assert.False(entityType.FindProperty("Bottom").IsUnicode()); - // } - // - // [ConditionalFact] - // public virtual void PropertyBuilder_methods_can_be_chained() - // => CreateModelBuilder() - // .Entity() - // .Property(e => e.Up) - // .IsRequired() - // .HasAnnotation("A", "V") - // .IsConcurrencyToken() - // .ValueGeneratedNever() - // .ValueGeneratedOnAdd() - // .ValueGeneratedOnAddOrUpdate() - // .ValueGeneratedOnUpdate() - // .IsUnicode() - // .HasMaxLength(100) - // .HasSentinel(null) - // .HasPrecision(10, 1) - // .HasValueGenerator() - // .HasValueGenerator(typeof(CustomValueGenerator)) - // .HasValueGeneratorFactory() - // .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) - // .HasValueGenerator((_, __) => null) - // .IsRequired(); - // - // [ConditionalFact] - // public virtual void Can_add_index() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Ignore(); - // modelBuilder - // .Entity() - // .HasIndex(ix => ix.Name); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Customer)); - // - // var index = entityType.GetIndexes().Single(); - // Assert.Equal(Customer.NameProperty.Name, index.Properties.Single().Name); - // } - // - // [ConditionalFact] - // public virtual void Can_add_index_when_no_clr_property() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Ignore(); - // modelBuilder - // .Entity( - // b => - // { - // b.Property("Index"); - // b.HasIndex("Index"); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = model.FindEntityType(typeof(Customer)); - // - // var index = entityType.GetIndexes().Single(); - // Assert.Equal("Index", index.Properties.Single().Name); - // } - // - // [ConditionalFact] - // public virtual void Can_add_multiple_indexes() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Ignore(); - // var entityBuilder = modelBuilder.Entity(); - // entityBuilder.HasIndex(ix => ix.Id).IsUnique(); - // entityBuilder.HasIndex(ix => ix.Name).HasAnnotation("A1", "V1"); - // entityBuilder.HasIndex(ix => ix.Id, "Named"); - // entityBuilder.HasIndex(ix => ix.Id, "Descending").IsDescending(); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(Customer)); - // var idProperty = entityType.FindProperty(nameof(Customer.Id)); - // var nameProperty = entityType.FindProperty(nameof(Customer.Name)); - // - // Assert.Equal(4, entityType.GetIndexes().Count()); - // var firstIndex = entityType.FindIndex(idProperty); - // Assert.True(firstIndex.IsUnique); - // var secondIndex = entityType.FindIndex(nameProperty); - // Assert.False(secondIndex.IsUnique); - // Assert.Equal("V1", secondIndex["A1"]); - // var namedIndex = entityType.FindIndex("Named"); - // Assert.False(namedIndex.IsUnique); - // var descendingIndex = entityType.FindIndex("Descending"); - // Assert.Equal(Array.Empty(), descendingIndex.IsDescending); - // } - // - // [ConditionalFact] - // public virtual void Can_add_contained_indexes() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Ignore(); - // var entityBuilder = modelBuilder.Entity(); - // var firstIndexBuilder = entityBuilder.HasIndex( - // ix => new { ix.Id, ix.AlternateKey }).IsUnique(); - // var secondIndexBuilder = entityBuilder.HasIndex( - // ix => new { ix.Id }); - // - // var model = modelBuilder.FinalizeModel(); - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Customer)); - // - // Assert.Equal(2, entityType.GetIndexes().Count()); - // Assert.True(firstIndexBuilder.Metadata.IsUnique); - // Assert.False(secondIndexBuilder.Metadata.IsUnique); - // } - // - // [ConditionalFact] - // public virtual void Can_set_primary_key_by_convention_for_user_specified_shadow_property() - // { - // var modelBuilder = CreateModelBuilder(); - // var model = modelBuilder.Model; - // - // var entityBuilder = modelBuilder.Entity(); - // - // var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(EntityWithoutId)); - // - // Assert.Null(entityType.FindPrimaryKey()); - // - // entityBuilder.Property("Id"); - // - // Assert.NotNull(entityType.FindPrimaryKey()); - // AssertEqual(new[] { "Id" }, entityType.FindPrimaryKey().Properties.Select(p => p.Name)); - // } - // - // [ConditionalFact] - // public virtual void Can_ignore_explicit_interface_implementation_property() - // { - // var modelBuilder = CreateModelBuilder(); - // modelBuilder.Entity().HasNoKey().Ignore(e => ((IEntityBase)e).Target); - // - // Assert.DoesNotContain( - // nameof(IEntityBase.Target), - // modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); - // - // modelBuilder.Entity().Property(e => ((IEntityBase)e).Target); - // - // Assert.Contains( - // nameof(IEntityBase.Target), - // modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties().Select(p => p.Name)); - // } - // - // [ConditionalFact] - // public virtual void Can_set_key_on_an_entity_with_fields() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().HasKey(e => e.Id); - // - // var model = modelBuilder.FinalizeModel(); - // var entity = model.FindEntityType(typeof(EntityWithFields)); - // var primaryKey = entity.FindPrimaryKey(); - // Assert.NotNull(primaryKey); - // var property = Assert.Single(primaryKey.Properties); - // Assert.Equal(nameof(EntityWithFields.Id), property.Name); - // Assert.Null(property.PropertyInfo); - // Assert.NotNull(property.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_set_composite_key_on_an_entity_with_fields() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); - // - // var model = modelBuilder.FinalizeModel(); - // var entity = model.FindEntityType(typeof(EntityWithFields)); - // var primaryKeyProperties = entity.FindPrimaryKey().Properties; - // Assert.Equal(2, primaryKeyProperties.Count); - // var first = primaryKeyProperties[0]; - // var second = primaryKeyProperties[1]; - // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); - // Assert.Null(first.PropertyInfo); - // Assert.NotNull(first.FieldInfo); - // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); - // Assert.Null(second.PropertyInfo); - // Assert.NotNull(second.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_set_alternate_key_on_an_entity_with_fields() - // { - // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); - // - // modelBuilder.Entity().HasAlternateKey(e => e.CompanyId); - // - // var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); - // var properties = entity.GetProperties(); - // Assert.Single(properties); - // var property = properties.Single(); - // Assert.Equal(nameof(EntityWithFields.CompanyId), property.Name); - // Assert.Null(property.PropertyInfo); - // Assert.NotNull(property.FieldInfo); - // var keys = entity.GetKeys(); - // var key = Assert.Single(keys); - // Assert.Equal(properties, key.Properties); - // } - // - // [ConditionalFact] - // public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() - // { - // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); - // - // modelBuilder.Entity().HasAlternateKey(e => new { e.TenantId, e.CompanyId }); - // - // var keys = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetKeys(); - // Assert.Single(keys); - // var properties = keys.Single().Properties; - // Assert.Equal(2, properties.Count); - // var first = properties[0]; - // var second = properties[1]; - // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); - // Assert.Null(first.PropertyInfo); - // Assert.NotNull(first.FieldInfo); - // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); - // Assert.Null(second.PropertyInfo); - // Assert.NotNull(second.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_call_Property_on_an_entity_with_fields() - // { - // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); - // - // modelBuilder.Entity().Property(e => e.Id); - // - // var model = modelBuilder.FinalizeModel(); - // var properties = model.FindEntityType(typeof(EntityWithFields)).GetProperties(); - // var property = Assert.Single(properties); - // Assert.Equal(nameof(EntityWithFields.Id), property.Name); - // Assert.Null(property.PropertyInfo); - // Assert.NotNull(property.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_set_index_on_an_entity_with_fields() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().HasNoKey().HasIndex(e => e.CompanyId); - // - // var model = modelBuilder.FinalizeModel(); - // var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); - // var index = Assert.Single(indexes); - // var property = Assert.Single(index.Properties); - // Assert.Null(property.PropertyInfo); - // Assert.NotNull(property.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_set_composite_index_on_an_entity_with_fields() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity().HasNoKey().HasIndex(e => new { e.TenantId, e.CompanyId }); - // - // var model = modelBuilder.FinalizeModel(); - // var indexes = model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); - // var index = Assert.Single(indexes); - // Assert.Equal(2, index.Properties.Count); - // var properties = index.Properties; - // var first = properties[0]; - // var second = properties[1]; - // Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); - // Assert.Null(first.PropertyInfo); - // Assert.NotNull(first.FieldInfo); - // Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); - // Assert.Null(second.PropertyInfo); - // Assert.NotNull(second.FieldInfo); - // } - // - // [ConditionalFact] - // public virtual void Can_ignore_a_field_on_an_entity_with_fields() - // { - // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); - // - // modelBuilder.Entity() - // .Ignore(e => e.CompanyId) - // .HasKey(e => e.Id); - // - // var model = modelBuilder.FinalizeModel(); - // var entity = model.FindEntityType(typeof(EntityWithFields)); - // var property = Assert.Single(entity.GetProperties()); - // Assert.Equal(nameof(EntityWithFields.Id), property.Name); - // } - // - // [ConditionalFact] - // public virtual void Can_ignore_a_field_on_a_keyless_entity_with_fields() - // { - // var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); - // - // modelBuilder.Entity() - // .HasNoKey() - // .Ignore(e => e.FirstName) - // .Property(e => e.LastName); - // - // var model = modelBuilder.FinalizeModel(); - // var entity = model.FindEntityType(typeof(KeylessEntityWithFields)); - // var property = Assert.Single(entity.GetProperties()); - // Assert.Equal(nameof(KeylessEntityWithFields.LastName), property.Name); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_objects() - // { - // var modelBuilder = CreateModelBuilder(); - // var model = modelBuilder.Model; - // modelBuilder.Ignore(); - // modelBuilder.Entity( - // c => - // { - // c.HasData( - // new Beta { Id = -1, Name = " -1" }); - // var customers = new List { new() { Id = -2 } }; - // c.HasData(customers); - // }); - // - // var finalModel = modelBuilder.FinalizeModel(); - // - // var customer = finalModel.FindEntityType(typeof(Beta)); - // var data = customer.GetSeedData(); - // Assert.Equal(2, data.Count()); - // Assert.Equal(-1, data.First()[nameof(Beta.Id)]); - // Assert.Equal(" -1", data.First()[nameof(Beta.Name)]); - // Assert.Equal(-2, data.Last()[nameof(Beta.Id)]); - // - // var _ = finalModel.ToDebugString(); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_anonymous_objects() - // { - // var modelBuilder = CreateModelBuilder(); - // modelBuilder.Ignore(); - // modelBuilder.Entity( - // c => - // { - // c.HasData( - // new { Id = -1 }); - // var customers = new List { new { Id = -2 } }; - // c.HasData(customers); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var customer = model.FindEntityType(typeof(Beta)); - // var data = customer.GetSeedData(); - // Assert.Equal(2, data.Count()); - // Assert.Equal(-1, data.First().Values.Single()); - // Assert.Equal(-2, data.Last().Values.Single()); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_objects_indexed_property() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.IndexerProperty("Required"); - // b.IndexerProperty("Optional"); - // var d = new IndexedClass { Id = -1 }; - // d["Required"] = 2; - // b.HasData(d); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(IndexedClass)); - // var data = Assert.Single(entityType.GetSeedData()); - // Assert.Equal(-1, data["Id"]); - // Assert.Equal(2, data["Required"]); - // Assert.Null(data["Optional"]); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_anonymous_objects_indexed_property() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity( - // b => - // { - // b.IndexerProperty("Required"); - // b.IndexerProperty("Optional"); - // b.HasData(new { Id = -1, Required = 2 }); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(IndexedClass)); - // var data = Assert.Single(entityType.GetSeedData()); - // Assert.Equal(-1, data["Id"]); - // Assert.Equal(2, data["Required"]); - // Assert.False(data.ContainsKey("Optional")); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_objects_indexed_property_dictionary() - // { - // var modelBuilder = CreateModelBuilder(); - // modelBuilder.Entity( - // b => - // { - // b.IndexerProperty("Required"); - // b.IndexerProperty("Optional"); - // var d = new IndexedClassByDictionary { Id = -1 }; - // d["Required"] = 2; - // b.HasData(d); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); - // var data = Assert.Single(entityType.GetSeedData()); - // Assert.Equal(-1, data["Id"]); - // Assert.Equal(2, data["Required"]); - // Assert.Null(data["Optional"]); - // } - // - // [ConditionalFact] - // public virtual void Can_add_seed_data_anonymous_objects_indexed_property_dictionary() - // { - // var modelBuilder = CreateModelBuilder(); - // modelBuilder.Entity( - // b => - // { - // b.IndexerProperty("Required"); - // b.IndexerProperty("Optional"); - // b.HasData(new { Id = -1, Required = 2 }); - // }); - // - // var model = modelBuilder.FinalizeModel(); - // - // var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); - // var data = Assert.Single(entityType.GetSeedData()); - // Assert.Equal(-1, data["Id"]); - // Assert.Equal(2, data["Required"]); - // Assert.False(data.ContainsKey("Optional")); - // } - // - // [ConditionalFact] //Issue#12617 - // [UseCulture("de-DE")] - // public virtual void EntityType_name_is_stored_culture_invariantly() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity(); - // modelBuilder.Entity(); - // - // var model = modelBuilder.FinalizeModel(); - // - // Assert.Equal(2, model.GetEntityTypes().Count()); - // Assert.Equal(2, model.FindEntityType(typeof(Entityss)).GetNavigations().Count()); - // } - // - // protected class Entityß - // { - // public int Id { get; set; } - // } - // - // protected class Entityss - // { - // public int Id { get; set; } - // public Entityß Navigationß { get; set; } - // public Entityß Navigationss { get; set; } - // } - // - // [ConditionalFact] - // public virtual void Can_add_shared_type_entity_type() - // { - // var modelBuilder = CreateModelBuilder(); - // modelBuilder.SharedTypeEntity>( - // "Shared1", b => - // { - // b.IndexerProperty("Key"); - // b.Property("Keys"); - // b.Property("Values"); - // b.Property("Count"); - // b.HasKey("Key"); - // }); - // - // modelBuilder.SharedTypeEntity>("Shared2", b => b.IndexerProperty("Id")); - // - // Assert.Equal( - // CoreStrings.ClashingSharedType(typeof(Dictionary).ShortDisplayName()), - // Assert.Throws(() => modelBuilder.Entity>()).Message); - // - // var model = modelBuilder.FinalizeModel(); - // Assert.Equal(2, model.GetEntityTypes().Count()); - // - // var shared1 = model.FindEntityType("Shared1"); - // Assert.NotNull(shared1); - // Assert.True(shared1.HasSharedClrType); - // Assert.Null(shared1.FindProperty("Id")); - // Assert.Equal(typeof(int), shared1.FindProperty("Keys").ClrType); - // Assert.Equal(typeof(byte[]), shared1.FindProperty("Values").ClrType); - // Assert.Equal(typeof(string), shared1.FindProperty("Count").ClrType); - // - // var shared2 = model.FindEntityType("Shared2"); - // Assert.NotNull(shared2); - // Assert.True(shared2.HasSharedClrType); - // Assert.NotNull(shared2.FindProperty("Id")); - // - // var indexer = shared1.FindIndexerPropertyInfo(); - // Assert.True(model.IsIndexerMethod(indexer.GetMethod)); - // Assert.True(model.IsIndexerMethod(indexer.SetMethod)); - // Assert.Same(indexer, shared2.FindIndexerPropertyInfo()); - // } - // - // [ConditionalFact] - // public virtual void Cannot_add_shared_type_when_non_shared_exists() - // { - // var modelBuilder = CreateModelBuilder(); - // - // modelBuilder.Entity(); - // - // Assert.Equal( - // CoreStrings.ClashingNonSharedType("Shared1", nameof(Customer)), - // Assert.Throws(() => modelBuilder.SharedTypeEntity("Shared1")).Message); - // } - } -} +// #nullable enable +// +// using System.Collections.ObjectModel; +// using Microsoft.EntityFrameworkCore.Metadata.Internal; +// +// namespace Microsoft.EntityFrameworkCore.ModelBuilding; +// +// public abstract partial class ModelBuilderTest +// { +// public abstract class PrimitiveCollectionsTestBase : ModelBuilderTestBase +// { +// } +// } diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index af6cbca250d..273d572b963 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -308,13 +308,20 @@ public string? Down // INotify interfaces not really implemented; just marking the classes to test metadata construction protected class CollectionQuarks : INotifyPropertyChanging, INotifyPropertyChanged { - private ObservableCollection _forUp = null!; + private ObservableCollection _forUp = null!; private ObservableCollection? _forDown; +#pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable IDE0051 // Remove unused private members +#pragma warning disable CS0169 // Remove unused private fields + private ObservableCollection? _forWierd; +#pragma warning restore CS0169 // Remove unused private fields +#pragma warning restore IDE0051 // Remove unused private members +#pragma warning restore IDE0044 // Add readonly modifier public int Id { get; set; } // ReSharper disable once ConvertToAutoProperty - public ObservableCollection Up + public ObservableCollection Up { get => _forUp; set => _forUp = value; @@ -517,6 +524,7 @@ protected class Gamma { public int Id { get; set; } private int PrivateProperty { get; set; } + private List PrivateCollection { get; set; } = null!; public List? Alphas { get; set; } } @@ -770,6 +778,9 @@ public class EntityWithFields public long Id; public int CompanyId; public int TenantId; + public long[] CollectionId = null!; + public int[] CollectionCompanyId = null!; + public int[] CollectionTenantId = null!; public KeylessEntityWithFields? KeylessEntity; } @@ -890,6 +901,7 @@ protected class ComplexProperties public DoubleProperty? DoubleProperty { get; set; } public IndexedClass? IndexedClass { get; set; } public Quarks? Quarks { get; set; } + public CollectionQuarks? CollectionQuarks { get; set; } [NotMapped] public DynamicProperty? DynamicProperty { get; set; } @@ -1339,4 +1351,25 @@ protected class OwnedOtter { public decimal Number { get; set; } } + + protected class OneDee + { + public int Id { get; set; } + + public int[]? One { get; set; } + } + + protected class TwoDee + { + public int Id { get; set; } + + public int[,]? Two { get; set; } + } + + protected class ThreeDee + { + public int Id { get; set; } + + public int[,,]? Three { get; set; } + } }