From d4b0d9dcdab73a549ae80fb310be91d6448da240 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 11 May 2019 16:37:11 -0700 Subject: [PATCH] Add Cosmos metadata extensions for conventions Move Discriminator metadata to Core Part of #214 Part of #13603 --- .../CosmosEntityTypeBuilderExtensions.cs | 133 ++++++- .../Extensions/CosmosEntityTypeExtensions.cs | 108 ++++++ .../Extensions/CosmosMetadataExtensions.cs | 62 --- .../CosmosModelBuilderExtensions.cs | 51 ++- .../Extensions/CosmosModelExtensions.cs | 55 +++ .../CosmosOwnedNavigationBuilderExtensions.cs | 56 --- .../CosmosPropertyBuilderExtensions.cs | 68 +++- .../Extensions/CosmosPropertyExtensions.cs | 55 +++ .../Infrastructure/CosmosModelCustomizer.cs | 3 +- .../Internal/CosmosConventionSetBuilder.cs | 8 +- .../CosmosDiscriminatorConvention.cs} | 84 ++-- .../Internal/DiscriminatorConvention.cs | 71 ---- .../Internal/StoreKeyConvention.cs | 2 +- .../Metadata/CosmosEntityTypeAnnotations.cs | 150 -------- .../Metadata/CosmosModelAnnotations.cs | 36 -- .../Metadata/CosmosPropertyAnnotations.cs | 36 -- .../Metadata/ICosmosEntityTypeAnnotations.cs | 15 - .../Metadata/ICosmosModelAnnotations.cs | 10 - .../Metadata/ICosmosPropertyAnnotations.cs | 10 - .../Internal/CosmosAnnotationNames.cs | 8 +- .../Metadata/Internal/CosmosAnnotations.cs | 43 --- .../CosmosEntityTypeBuilderAnnotations.cs | 90 ----- .../Internal/CosmosEntityTypeExtensions.cs | 26 ++ ...CosmosInternalMetadataBuilderExtensions.cs | 51 --- .../Internal/CosmosModelBuilderAnnotations.cs | 55 --- .../CosmosPropertyBuilderAnnotations.cs | 47 --- .../Metadata/Internal/EntityTypeExtensions.cs | 16 - .../CosmosEntityQueryableExpressionVisitor.cs | 2 +- ...mosMemberAccessBindingExpressionVisitor.cs | 2 +- .../Expressions/Internal/SelectExpression.cs | 6 +- .../Query/Internal/EntityShaper.cs | 10 +- .../Internal/ValueBufferFactoryFactory.cs | 5 +- .../Storage/Internal/CosmosDatabaseCreator.cs | 4 +- .../Storage/Internal/CosmosDatabaseWrapper.cs | 8 +- .../Update/Internal/DocumentSource.cs | 13 +- .../Internal/DiscriminatorValueGenerator.cs | 22 -- .../Internal/IdValueGenerator.cs | 4 +- .../Design/CSharpSnapshotGenerator.cs | 8 +- .../RelationalEntityTypeBuilderExtensions.cs | 358 +----------------- .../RelationalEntityTypeExtensions.cs | 160 -------- .../RelationalModelValidator.cs | 54 +-- .../RelationalConventionSetBuilder.cs | 6 +- .../Metadata/RelationalAnnotationNames.cs | 10 - .../Properties/RelationalStrings.Designer.cs | 64 ---- .../Properties/RelationalStrings.resx | 24 -- .../ConventionEntityTypeExtensions.cs | 50 +++ src/EFCore/Extensions/EntityTypeExtensions.cs | 24 ++ .../Extensions/MutableEntityTypeExtensions.cs | 28 ++ src/EFCore/Infrastructure/ModelValidator.cs | 64 +++- .../Metadata/Builders/DiscriminatorBuilder.cs | 6 +- .../Builders/DiscriminatorBuilder`.cs | 0 .../Metadata/Builders/EntityTypeBuilder.cs | 48 ++- .../Metadata/Builders/EntityTypeBuilder`.cs | 21 +- .../IConventionDiscriminatorBuilder.cs | 0 .../Builders/IConventionEntityTypeBuilder.cs | 94 +++++ src/EFCore/Metadata/Builders/IndexBuilder.cs | 7 +- .../InvertibleRelationshipBuilderBase.cs | 7 +- src/EFCore/Metadata/Builders/KeyBuilder.cs | 7 +- .../Metadata/Builders/PropertyBuilder.cs | 7 +- .../Builders/RelationshipBuilderBase.cs | 15 +- .../ProviderConventionSetBuilder.cs | 3 + .../Internal/DiscriminatorConvention.cs | 23 +- src/EFCore/Metadata/CoreAnnotationNames.cs | 10 + src/EFCore/Metadata/IEntityType.cs | 4 +- src/EFCore/Metadata/Internal/EntityType.cs | 58 +++ .../Internal/InternalEntityTypeBuilder.cs | 154 ++++++++ src/EFCore/Properties/CoreStrings.Designer.cs | 64 ++++ src/EFCore/Properties/CoreStrings.resx | 24 ++ .../Internal/DiscriminatorValueGenerator.cs | 0 .../CosmosEndToEndTest.cs | 7 +- .../NestedDocumentsTest.cs | 2 +- .../Internal/DiscriminatorConventionTest.cs | 100 ----- .../Metadata/CosmosBuilderExtensionsTest.cs | 14 +- .../Metadata/CosmosMetadataExtensionsTest.cs | 14 +- .../Design/CSharpMigrationsGeneratorTest.cs | 12 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 8 +- ...elationalModelValidatorDependenciesTest.cs | 4 +- .../RelationalModelValidatorTest.cs | 80 +--- ...RelationalMetadataBuilderExtensionsTest.cs | 226 +---------- .../RelationalMetadataExtensionsTest.cs | 98 ----- .../SqlServerModelValidatorTest.cs | 2 +- .../SqliteModelValidatorTest.cs | 2 +- .../Infrastructure/ModelValidatorTest.cs | 103 ++++- .../Metadata/EntityTypeExtensionsTest.cs | 112 ++++++ .../Metadata/Internal/EntityTypeTest.cs | 50 +-- .../Internal/InternalEntityTypeBuilderTest.cs | 241 +++++++++++- .../Metadata/MetadataBuilderTest.cs | 36 -- 87 files changed, 1733 insertions(+), 2205 deletions(-) create mode 100644 src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs delete mode 100644 src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs create mode 100644 src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs delete mode 100644 src/EFCore.Cosmos/Extensions/CosmosOwnedNavigationBuilderExtensions.cs create mode 100644 src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs rename src/EFCore.Cosmos/Metadata/{Internal/CosmosAnnotationsBuilder.cs => Conventions/Internal/CosmosDiscriminatorConvention.cs} (52%) delete mode 100644 src/EFCore.Cosmos/Metadata/Conventions/Internal/DiscriminatorConvention.cs delete mode 100644 src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/CosmosPropertyAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/ICosmosEntityTypeAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/ICosmosPropertyAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs create mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyBuilderAnnotations.cs delete mode 100644 src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs delete mode 100644 src/EFCore.Cosmos/ValueGeneration/Internal/DiscriminatorValueGenerator.cs rename src/{EFCore.Relational => EFCore}/Metadata/Builders/DiscriminatorBuilder.cs (96%) rename src/{EFCore.Relational => EFCore}/Metadata/Builders/DiscriminatorBuilder`.cs (100%) rename src/{EFCore.Relational => EFCore}/Metadata/Builders/IConventionDiscriminatorBuilder.cs (100%) rename src/{EFCore.Relational => EFCore}/Metadata/Conventions/Internal/DiscriminatorConvention.cs (80%) rename src/{EFCore.Relational => EFCore}/ValueGeneration/Internal/DiscriminatorValueGenerator.cs (100%) delete mode 100644 test/EFCore.Cosmos.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs rename test/EFCore.Relational.Tests/{ => Infrastructure}/RelationalModelValidatorDependenciesTest.cs (84%) rename test/EFCore.Relational.Tests/{ => Infrastructure}/RelationalModelValidatorTest.cs (94%) rename test/EFCore.SqlServer.Tests/{ => Infrastructure}/SqlServerModelValidatorTest.cs (99%) rename test/EFCore.Sqlite.Tests/{ => Infrastructure}/SqliteModelValidatorTest.cs (98%) diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs index 30a21325e34..110ae9e1692 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs @@ -3,10 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace @@ -18,36 +15,150 @@ namespace Microsoft.EntityFrameworkCore public static class CosmosEntityTypeBuilderExtensions { /// - /// Configures the container that the entity maps to when targeting Azure Cosmos. + /// Configures the container that the entity type maps to when targeting Azure Cosmos. /// /// The builder for the entity type being configured. /// The name of the container. /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToContainer( + public static EntityTypeBuilder ForCosmosToContainer( [NotNull] this EntityTypeBuilder entityTypeBuilder, [CanBeNull] string name) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Check.NullButNotEmpty(name, nameof(name)); - entityTypeBuilder.GetInfrastructure() - .Cosmos(ConfigurationSource.Explicit) - .ToContainer(name); + entityTypeBuilder.Metadata.SetCosmosContainerName(name); return entityTypeBuilder; } /// - /// Configures the container that the entity maps to when targeting Azure Cosmos. + /// Configures the container that the entity type maps to when targeting Azure Cosmos. /// /// The entity type being configured. /// The builder for the entity type being configured. /// The name of the container. /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToContainer( + public static EntityTypeBuilder ForCosmosToContainer( [NotNull] this EntityTypeBuilder entityTypeBuilder, [CanBeNull] string name) where TEntity : class - => (EntityTypeBuilder)ToContainer((EntityTypeBuilder)entityTypeBuilder, name); + => (EntityTypeBuilder)ForCosmosToContainer((EntityTypeBuilder)entityTypeBuilder, name); + + /// + /// Configures the container that the entity type maps to when targeting Azure Cosmos. + /// + /// The builder for the entity type being configured. + /// The name of the container. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// null otherwise. + /// + public static IConventionEntityTypeBuilder ForCosmosToContainer( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.ForCosmosCanSetContainer(name, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetCosmosContainerName(name, fromDataAnnotation); + + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the container that the entity type maps to can be set + /// from the current configuration source + /// + /// The builder for the entity type being configured. + /// The name of the container. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the configuration can be applied. + public static bool ForCosmosCanSetContainer( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.ContainerName, name, fromDataAnnotation); + } + + /// + /// Configures the property name that the entity is mapped to when stored as an embedded document. + /// + /// The builder for the entity type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ForCosmosToProperty( + [NotNull] this OwnedNavigationBuilder entityTypeBuilder, + [CanBeNull] string name) + { + entityTypeBuilder.OwnedEntityType.SetCosmosContainingPropertyName(name); + + return entityTypeBuilder; + } + + /// + /// Configures the property name that the entity is mapped to when stored as an embedded document. + /// + /// The builder for the entity type being configured. + /// The name of the parent property. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ForCosmosToProperty( + [NotNull] this OwnedNavigationBuilder entityTypeBuilder, + [CanBeNull] string name) + where TEntity : class + where TDependentEntity : class + { + entityTypeBuilder.OwnedEntityType.SetCosmosContainingPropertyName(name); + + return entityTypeBuilder; + } + + /// + /// Configures the property name that the entity is mapped to when stored as an embedded document. + /// + /// The builder for the entity type being configured. + /// The name of the parent property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// null otherwise. + /// + public static IConventionEntityTypeBuilder ForCosmosToProperty( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.ForCosmosCanSetProperty(name, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetCosmosContainingPropertyName(name, fromDataAnnotation); + + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the parent property name to which the entity type is mapped to can be set + /// from the current configuration source + /// + /// The builder for the entity type being configured. + /// The name of the parent property. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the configuration can be applied. + public static bool ForCosmosCanSetProperty( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.PropertyName, name, fromDataAnnotation); + } } } diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs new file mode 100644 index 00000000000..0d4aeadf2f6 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Extension methods for for Cosmos metadata. + /// + public static class CosmosEntityTypeExtensions + { + /// + /// Returns the name of the container to which the entity type is mapped. + /// + /// The entity type to get the container name for. + /// The name of the container to which the entity type is mapped. + public static string GetCosmosContainerName([NotNull] this IEntityType entityType) => + entityType.BaseType != null + ? entityType.RootType().GetCosmosContainerName() + : (string)entityType[CosmosAnnotationNames.ContainerName] + ?? GetCosmosDefaultContainerName(entityType); + + private static string GetCosmosDefaultContainerName(IEntityType entityType) + => entityType.Model.GetCosmosDefaultContainerName() + ?? entityType.ShortName(); + + /// + /// Sets the name of the container to which the entity type is mapped. + /// + /// The entity type to set the container name for. + /// The name to set. + public static void SetCosmosContainerName([NotNull] this IMutableEntityType entityType, [CanBeNull] string name) + => entityType.SetOrRemoveAnnotation( + CosmosAnnotationNames.ContainerName, + Check.NullButNotEmpty(name, nameof(name))); + + /// + /// Sets the name of the container to which the entity type is mapped. + /// + /// The entity type to set the container name for. + /// The name to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetCosmosContainerName( + [NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false) + => entityType.SetOrRemoveAnnotation( + CosmosAnnotationNames.ContainerName, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation); + + /// + /// Gets the for the container to which the entity type is mapped. + /// + /// The entity type to find configuration source for. + /// The for the container to which the entity type is mapped. + public static ConfigurationSource? GetCosmosContainerNameConfigurationSource([NotNull] this IConventionEntityType entityType) + => entityType.FindAnnotation(CosmosAnnotationNames.ContainerName) + ?.GetConfigurationSource(); + + /// + /// Returns the name of the parent property to which the entity type is mapped. + /// + /// The entity type to get the containing property name for. + /// The name of the parent property to which the entity type is mapped. + public static string GetCosmosContainingPropertyName([NotNull] this IEntityType entityType) => + entityType[CosmosAnnotationNames.PropertyName] as string + ?? GetDefaultContainingPropertyName(entityType); + + private static string GetDefaultContainingPropertyName(IEntityType entityType) + => entityType.FindOwnership()?.PrincipalToDependent.Name; + + /// + /// Sets the name of the parent property to which the entity type is mapped. + /// + /// The entity type to set the containing property name for. + /// The name to set. + public static void SetCosmosContainingPropertyName([NotNull] this IMutableEntityType entityType, [CanBeNull] string name) + => entityType.SetOrRemoveAnnotation( + CosmosAnnotationNames.PropertyName, + Check.NullButNotEmpty(name, nameof(name))); + + /// + /// Sets the name of the parent property to which the entity type is mapped. + /// + /// The entity type to set the containing property name for. + /// The name to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetCosmosContainingPropertyName( + [NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false) + => entityType.SetOrRemoveAnnotation( + CosmosAnnotationNames.PropertyName, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation); + + /// + /// Gets the for the parent property to which the entity type is mapped. + /// + /// The entity type to find configuration source for. + /// The for the parent property to which the entity type is mapped. + public static ConfigurationSource? GetCosmosContainingPropertyNameConfigurationSource([NotNull] this IConventionEntityType entityType) + => entityType.FindAnnotation(CosmosAnnotationNames.PropertyName) + ?.GetConfigurationSource(); + } +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs deleted file mode 100644 index 2c738df771c..00000000000 --- a/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Cosmos.Metadata; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Microsoft.EntityFrameworkCore -{ - /// - /// Cosmos-specific extension methods for metadata. - /// - public static class CosmosMetadataExtensions - { - /// - /// Gets the Cosmos-specific metadata for a model. - /// - /// The model to get metadata for. - /// The Cosmos-specific metadata for the model. - public static ICosmosModelAnnotations Cosmos(this IModel model) - => new CosmosModelAnnotations(model); - - /// - /// Gets the Cosmos-specific metadata for a model. - /// - /// The model to get metadata for. - /// The Cosmos-specific metadata for the model. - public static CosmosModelAnnotations Cosmos(this IMutableModel model) - => (CosmosModelAnnotations)Cosmos((IModel)model); - - /// - /// Gets the Cosmos-specific metadata for an entity type. - /// - /// The entity type to get metadata for. - /// The Cosmos-specific metadata for the entity type. - public static ICosmosEntityTypeAnnotations Cosmos(this IEntityType entityType) - => new CosmosEntityTypeAnnotations(entityType); - - /// - /// Gets the Cosmos-specific metadata for an entity type. - /// - /// The entity type to get metadata for. - /// The Cosmos-specific metadata for the entity type. - public static CosmosEntityTypeAnnotations Cosmos(this IMutableEntityType entityType) - => (CosmosEntityTypeAnnotations)Cosmos((IEntityType)entityType); - - /// - /// Gets the Cosmos-specific metadata for a property. - /// - /// The property to get metadata for. - /// The Cosmos-specific metadata for the property. - public static ICosmosPropertyAnnotations Cosmos(this IProperty property) - => new CosmosPropertyAnnotations(property); - - /// - /// Gets the Cosmos-specific metadata for a property. - /// - /// The property to get metadata for. - /// The Cosmos-specific metadata for the property. - public static CosmosPropertyAnnotations Cosmos(this IMutableProperty property) - => (CosmosPropertyAnnotations)Cosmos((IProperty)property); - } -} diff --git a/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs index fad46c19a50..5b3789e487a 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosModelBuilderExtensions.cs @@ -3,8 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace @@ -22,16 +21,60 @@ public static class CosmosModelBuilderExtensions /// The model builder. /// The default container name. /// The same builder instance so that multiple calls can be chained. - public static ModelBuilder HasDefaultContainerName( + public static ModelBuilder ForCosmosHasDefaultContainerName( [NotNull] this ModelBuilder modelBuilder, [CanBeNull] string name) { Check.NotNull(modelBuilder, nameof(modelBuilder)); Check.NullButNotEmpty(name, nameof(name)); - modelBuilder.GetInfrastructure().Cosmos(ConfigurationSource.Explicit).HasDefaultContainerName(name); + modelBuilder.Model.SetCosmosDefaultContainerName(name); return modelBuilder; } + + /// + /// Configures the default container name that will be used if no name + /// is explicitly configured for an entity type. + /// + /// The model builder. + /// The default container name. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// null otherwise. + /// + public static IConventionModelBuilder ForCosmosHasDefaultContainerName( + [NotNull] this IConventionModelBuilder modelBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + { + if (modelBuilder.ForCosmosCanSetDefaultContainerName(name, fromDataAnnotation)) + { + return null; + } + + modelBuilder.Metadata.SetCosmosDefaultContainerName(name, fromDataAnnotation); + + return modelBuilder; + } + + /// + /// Returns a value indicating whether the given schema can be set as default. + /// + /// The model builder. + /// The default container name. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the given container name can be set as default. + public static bool ForCosmosCanSetDefaultContainerName( + [NotNull] this IConventionModelBuilder modelBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + { + Check.NotNull(modelBuilder, nameof(modelBuilder)); + Check.NullButNotEmpty(name, nameof(name)); + + return modelBuilder.CanSetAnnotation(CosmosAnnotationNames.ContainerName, name, fromDataAnnotation); + } } } diff --git a/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs new file mode 100644 index 00000000000..2c6f0588082 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Extension methods for for Cosmos metadata. + /// + public static class CosmosModelExtensions + { + /// + /// Returns the default container name. + /// + /// The model. + /// The default container name. + public static string GetCosmosDefaultContainerName([NotNull] this IModel model) + => (string)model[CosmosAnnotationNames.ContainerName]; + + /// + /// Sets the default container name. + /// + /// The model. + /// The name to set. + public static void SetCosmosDefaultContainerName([NotNull] this IMutableModel model, [CanBeNull] string name) + => model.SetOrRemoveAnnotation( + CosmosAnnotationNames.ContainerName, + Check.NullButNotEmpty(name, nameof(name))); + + /// + /// Sets the default container name. + /// + /// The model. + /// The name to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetCosmosDefaultContainerName([NotNull] this IConventionModel model, [CanBeNull] string name, bool fromDataAnnotation = false) + => model.SetOrRemoveAnnotation( + CosmosAnnotationNames.ContainerName, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation); + + /// + /// Returns the configuration source for the default container name. + /// + /// The model. + /// The configuration source for the default container name. + public static ConfigurationSource? GetCosmosDefaultContainerNameConfigurationSource([NotNull] this IConventionModel model) + => model.FindAnnotation(CosmosAnnotationNames.ContainerName)?.GetConfigurationSource(); + } +} diff --git a/src/EFCore.Cosmos/Extensions/CosmosOwnedNavigationBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosOwnedNavigationBuilderExtensions.cs deleted file mode 100644 index 25f2a4da665..00000000000 --- a/src/EFCore.Cosmos/Extensions/CosmosOwnedNavigationBuilderExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore -{ - /// - /// Cosmos-specific extension methods for . - /// - public static class CosmosOwnedNavigationBuilderExtensions - { - /// - /// Configures the property name that the entity is mapped to when stored as an embedded document. - /// - /// If an empty string is supplied then the property will not be persisted. - /// The builder for the entity type being configured. - /// The name of the container. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToProperty( - [NotNull] this OwnedNavigationBuilder entityTypeBuilder, - [CanBeNull] string name) - { - entityTypeBuilder.GetInfrastructure() - .Cosmos(ConfigurationSource.Explicit) - .ToProperty(name); - - return entityTypeBuilder; - } - - /// - /// Configures the property name that the entity is mapped to when stored as an embedded document. - /// - /// If an empty string is supplied then the property will not be persisted. - /// The builder for the entity type being configured. - /// The name of the container. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToProperty( - [NotNull] this OwnedNavigationBuilder entityTypeBuilder, - [CanBeNull] string name) - where TEntity : class - where TDependentEntity : class - { - entityTypeBuilder.GetInfrastructure() - .Cosmos(ConfigurationSource.Explicit) - .ToProperty(name); - - return entityTypeBuilder; - } - } -} diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs index 7e5e2a08a17..488914db29a 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs @@ -3,12 +3,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Utilities; -namespace Microsoft.EntityFrameworkCore.Cosmos +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore { /// /// Cosmos-specific extension methods for . @@ -22,14 +21,16 @@ public static class CosmosPropertyBuilderExtensions /// The builder for the property being configured. /// The name of the container. /// The same builder instance so that multiple calls can be chained. - public static PropertyBuilder ToProperty( + public static PropertyBuilder ForCosmosToProperty( [NotNull] this PropertyBuilder propertyBuilder, [CanBeNull] string name) - => propertyBuilder.GetInfrastructure() - .Cosmos(ConfigurationSource.Explicit) - .ToProperty(name) - ? propertyBuilder - : propertyBuilder; + { + Check.NotNull(propertyBuilder, nameof(propertyBuilder)); + + propertyBuilder.Metadata.SetCosmosPropertyName(name); + + return propertyBuilder; + } /// /// Configures the property name that the property is mapped to when targeting Azure Cosmos. @@ -39,9 +40,52 @@ public static PropertyBuilder ToProperty( /// The builder for the property being configured. /// The name of the container. /// The same builder instance so that multiple calls can be chained. - public static PropertyBuilder ToProperty( + public static PropertyBuilder ForCosmosToProperty( [NotNull] this PropertyBuilder propertyBuilder, [CanBeNull] string name) - => (PropertyBuilder)ToProperty((PropertyBuilder)propertyBuilder, name); + => (PropertyBuilder)ForCosmosToProperty((PropertyBuilder)propertyBuilder, name); + + /// + /// + /// Configures the property name that the property is mapped to when targeting Azure Cosmos. + /// + /// + /// If an empty string is supplied then the property will not be persisted. + /// + /// + /// The builder for the property being configured. + /// The name of the container. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// null otherwise. + /// + public static IConventionPropertyBuilder ForCosmosToProperty( + [NotNull] this IConventionPropertyBuilder propertyBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.ForCosmosCanSetProperty(name, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetCosmosPropertyName(name, fromDataAnnotation); + + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given property name can be set. + /// + /// The builder for the property being configured. + /// The name of the container. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the property name can be set. + public static bool ForCosmosCanSetProperty( + [NotNull] this IConventionPropertyBuilder propertyBuilder, + [CanBeNull] string name, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(CosmosAnnotationNames.PropertyName, name, fromDataAnnotation); } } diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs new file mode 100644 index 00000000000..39e156405a3 --- /dev/null +++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Extension methods for for Cosmos metadata. + /// + public static class CosmosPropertyExtensions + { + /// + /// Returns the property name used when targeting Cosmos. + /// + /// The property. + /// The property name used when targeting Cosmos. + public static string GetCosmosPropertyName([NotNull] this IProperty property) => + (string)property[CosmosAnnotationNames.PropertyName] + ?? property.Name; + + /// + /// Sets the property name used when targeting Cosmos. + /// + /// The property. + /// The name to set. + public static void SetCosmosPropertyName([NotNull] this IMutableProperty property, [CanBeNull] string name) + => property.SetOrRemoveAnnotation( + CosmosAnnotationNames.PropertyName, + name); + + /// + /// Sets the property name used when targeting Cosmos. + /// + /// The property. + /// The name to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetCosmosPropertyName([NotNull] this IConventionProperty property, [CanBeNull] string name, bool fromDataAnnotation = false) + => property.SetOrRemoveAnnotation( + CosmosAnnotationNames.PropertyName, + name, + fromDataAnnotation); + + /// + /// Gets the for the property name used when targeting Cosmos. + /// + /// The property. + /// The for the property name used when targeting Cosmos. + public static ConfigurationSource? GetCosmosPropertyNameConfigurationSource([NotNull] this IConventionProperty property) + => property.FindAnnotation(CosmosAnnotationNames.PropertyName)?.GetConfigurationSource(); + } +} diff --git a/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs b/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs index a8b96594461..bb519e5fab4 100644 --- a/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs +++ b/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -39,7 +38,7 @@ public CosmosModelCustomizer(ModelCustomizerDependencies dependencies) /// public override void Customize(ModelBuilder modelBuilder, DbContext context) { - modelBuilder.GetInfrastructure().Cosmos(ConfigurationSource.Convention).HasDefaultContainerName(context.GetType().Name); + ((IConventionModel)modelBuilder.Model).Builder.ForCosmosHasDefaultContainerName(context.GetType().Name); base.Customize(modelBuilder, context); } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs index d6741c7c019..47eae27b83d 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; @@ -20,13 +20,15 @@ public override ConventionSet CreateConventionSet() { var conventionSet = base.CreateConventionSet(); - var discriminatorConvention = new DiscriminatorConvention(); + var discriminatorConvention = new CosmosDiscriminatorConvention(Dependencies.Logger); var storeKeyConvention = new StoreKeyConvention(); conventionSet.EntityTypeAddedConventions.Add(storeKeyConvention); conventionSet.EntityTypeAddedConventions.Add(discriminatorConvention); + ReplaceConvention(conventionSet.EntityTypeRemovedConventions, (DiscriminatorConvention)discriminatorConvention); + conventionSet.BaseEntityTypeChangedConventions.Add(storeKeyConvention); - conventionSet.BaseEntityTypeChangedConventions.Add(discriminatorConvention); + ReplaceConvention(conventionSet.BaseEntityTypeChangedConventions, (DiscriminatorConvention)discriminatorConvention); conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention); diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosDiscriminatorConvention.cs similarity index 52% rename from src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs rename to src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosDiscriminatorConvention.cs index 00a7c2f1eec..779570cc2af 100644 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosDiscriminatorConvention.cs @@ -1,11 +1,14 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal +namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -13,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class CosmosAnnotationsBuilder : CosmosAnnotations + public class CosmosDiscriminatorConvention : DiscriminatorConvention, IEntityTypeAddedConvention { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -21,13 +24,9 @@ public class CosmosAnnotationsBuilder : CosmosAnnotations /// 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 CosmosAnnotationsBuilder( - [NotNull] InternalAnnotatableBuilder internalBuilder, - ConfigurationSource configurationSource) - : base(internalBuilder.Metadata) + public CosmosDiscriminatorConvention([NotNull] IDiagnosticsLogger logger) + : base(logger) { - MetadataBuilder = internalBuilder; - ConfigurationSource = configurationSource; } /// @@ -36,15 +35,18 @@ public CosmosAnnotationsBuilder( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ConfigurationSource ConfigurationSource { get; } + public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder) + { + var entityType = entityTypeBuilder.Metadata; + if (entityTypeBuilder.Metadata.BaseType == null + && !entityTypeBuilder.Metadata.GetDerivedTypes().Any()) + { + ((IConventionEntityTypeBuilder)entityTypeBuilder).HasDiscriminator(typeof(string)) + .HasValue(entityType, entityType.ShortName()); + } - /// - /// 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 InternalAnnotatableBuilder MetadataBuilder { get; } + return entityTypeBuilder; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -52,21 +54,33 @@ public CosmosAnnotationsBuilder( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override bool SetAnnotation( - string relationalAnnotationName, - object value) - => MetadataBuilder.HasAnnotation(relationalAnnotationName, value, ConfigurationSource) != null; + public override bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityType oldBaseType) + { + IConventionEntityTypeBuilder conventionEntityTypeBuilder = entityTypeBuilder; + IConventionDiscriminatorBuilder discriminator; + var entityType = entityTypeBuilder.Metadata; + if (entityType.BaseType == null) + { + discriminator = conventionEntityTypeBuilder.HasDiscriminator(typeof(string)); + } + else + { + discriminator = ((IConventionEntityTypeBuilder)entityType.BaseType.Builder)?.HasDiscriminator(typeof(string)); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public override bool CanSetAnnotation( - string relationalAnnotationName, - object value) - => MetadataBuilder.CanSetAnnotation(relationalAnnotationName, value, ConfigurationSource); + if (entityType.BaseType.BaseType == null) + { + discriminator?.HasValue(entityType.BaseType, entityType.BaseType.ShortName()); + } + } + + if (discriminator != null) + { + discriminator.HasValue(entityTypeBuilder.Metadata, entityTypeBuilder.Metadata.ShortName()); + SetDefaultDiscriminatorValues(entityType.GetDerivedTypes(), discriminator); + } + + return true; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -74,7 +88,9 @@ public override bool CanSetAnnotation( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override bool RemoveAnnotation(string annotationName) - => MetadataBuilder.RemoveAnnotation(annotationName, ConfigurationSource) != null; + public override bool Apply(InternalModelBuilder modelBuilder, EntityType type) + { + return true; + } } } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/DiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/DiscriminatorConvention.cs deleted file mode 100644 index 43c2b3d536e..00000000000 --- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/DiscriminatorConvention.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal -{ - public class DiscriminatorConvention : IEntityTypeAddedConvention, IBaseTypeChangedConvention - { - public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder) - { - if (entityTypeBuilder.Metadata.BaseType == null - && !entityTypeBuilder.Metadata.GetDerivedTypes().Any()) - { - ConfigureDiscriminator(entityTypeBuilder); - } - - return entityTypeBuilder; - } - - public bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityType oldBaseType) - { - var entityType = entityTypeBuilder.Metadata; - if (entityType.BaseType == null) - { - ConfigureDiscriminator(entityTypeBuilder); - SetDefaultDiscriminatorValues(entityType.GetDerivedTypes()); - } - else - { - if (entityType.BaseType.Cosmos().DiscriminatorProperty == null) - { - ConfigureDiscriminator(entityType.BaseType.Builder); - } - - entityType.Cosmos().DiscriminatorProperty = null; - SetDefaultDiscriminatorValues(entityType.GetDerivedTypesInclusive()); - } - - return true; - } - - private static void ConfigureDiscriminator(InternalEntityTypeBuilder entityTypeBuilder) - { - var propertyBuilder = entityTypeBuilder.Property(typeof(string), "Discriminator", ConfigurationSource.Convention); - propertyBuilder.IsRequired(true, ConfigurationSource.Convention); - propertyBuilder.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention); - propertyBuilder.HasValueGenerator( - (_, et) => new DiscriminatorValueGenerator(et.Cosmos().DiscriminatorValue), - ConfigurationSource.Convention); - - var entityType = entityTypeBuilder.Metadata; - - entityType.Cosmos().DiscriminatorProperty = propertyBuilder.Metadata; - entityType.Cosmos().DiscriminatorValue = entityType.ShortName(); - } - - private static void SetDefaultDiscriminatorValues(IEnumerable entityTypes) - { - foreach (var entityType in entityTypes) - { - entityType.Cosmos().DiscriminatorValue = entityType.ShortName(); - } - } - } -} diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs index 66e279d39dc..4b8a1ce3826 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs @@ -32,7 +32,7 @@ public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuild entityTypeBuilder.HasKey(new[] { idProperty.Metadata }, ConfigurationSource.Convention); var jObjectProperty = entityTypeBuilder.Property(typeof(JObject), JObjectPropertyName, ConfigurationSource.Convention); - jObjectProperty.Cosmos(ConfigurationSource.Convention).ToProperty(""); + jObjectProperty.ForCosmosToProperty(""); jObjectProperty.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Convention); } else diff --git a/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs b/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs deleted file mode 100644 index 94dc2f4ffda..00000000000 --- a/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public class CosmosEntityTypeAnnotations : ICosmosEntityTypeAnnotations - { - public CosmosEntityTypeAnnotations(IEntityType entityType) - : this(new CosmosAnnotations(entityType)) - { - } - - protected CosmosEntityTypeAnnotations(CosmosAnnotations annotations) => Annotations = annotations; - - protected virtual CosmosAnnotations Annotations { get; } - - protected virtual IEntityType EntityType => (IEntityType)Annotations.Metadata; - - protected virtual CosmosModelAnnotations GetAnnotations(IModel model) - => new CosmosModelAnnotations(model); - - protected virtual CosmosEntityTypeAnnotations GetAnnotations([NotNull] IEntityType entityType) - => new CosmosEntityTypeAnnotations(entityType); - - public virtual string ContainerName - { - get => EntityType.BaseType != null - ? GetAnnotations(EntityType.RootType()).ContainerName - : ((string)Annotations.Metadata[CosmosAnnotationNames.ContainerName]) - ?? GetDefaultContainerName(); - - [param: CanBeNull] set => SetContainerName(value); - } - - private string GetDefaultContainerName() => GetAnnotations(EntityType.Model).DefaultContainerName - ?? EntityType.ShortName(); - - protected virtual bool SetContainerName([CanBeNull] string value) - => Annotations.SetAnnotation( - CosmosAnnotationNames.ContainerName, - Check.NullButNotEmpty(value, nameof(value))); - - public virtual IProperty DiscriminatorProperty - { - get - { - if (EntityType.BaseType != null) - { - return GetAnnotations(EntityType.RootType()).DiscriminatorProperty; - } - - var propertyName = (string)Annotations.Metadata[CosmosAnnotationNames.DiscriminatorProperty]; - - return propertyName == null ? null : EntityType.FindProperty(propertyName); - } - [param: CanBeNull] set => SetDiscriminatorProperty(value); - } - - protected virtual bool SetDiscriminatorProperty([CanBeNull] IProperty value) - => SetDiscriminatorProperty(value, DiscriminatorProperty?.ClrType); - - protected virtual bool SetDiscriminatorProperty([CanBeNull] IProperty value, [CanBeNull] Type oldDiscriminatorType) - { - if (value != null) - { - if (EntityType != EntityType.RootType()) - { - // TODO: Throw an exception - //throw new InvalidOperationException( - // RelationalStrings.DiscriminatorPropertyMustBeOnRoot(EntityType.DisplayName())); - } - - if (value.DeclaringEntityType != EntityType) - { - // TODO: Throw an exception - //throw new InvalidOperationException( - // RelationalStrings.DiscriminatorPropertyNotFound(value.Name, EntityType.DisplayName())); - } - } - - if (value == null - || value.ClrType != oldDiscriminatorType) - { - foreach (var derivedType in EntityType.GetDerivedTypesInclusive()) - { - GetAnnotations(derivedType).RemoveDiscriminatorValue(); - } - } - - return Annotations.SetAnnotation( - CosmosAnnotationNames.DiscriminatorProperty, - value?.Name); - } - - protected virtual bool RemoveDiscriminatorValue() - => Annotations.RemoveAnnotation(CosmosAnnotationNames.DiscriminatorValue); - - public virtual object DiscriminatorValue - { - get => Annotations.Metadata[CosmosAnnotationNames.DiscriminatorValue]; - [param: CanBeNull] set => SetDiscriminatorValue(value); - } - - protected virtual bool SetDiscriminatorValue([CanBeNull] object value) - { - if (value != null - && DiscriminatorProperty == null) - { - // TODO: Throw an exception - //throw new InvalidOperationException( - // RelationalStrings.NoDiscriminatorForValue(EntityType.DisplayName(), EntityType.RootType().DisplayName())); - } - - if (value != null - && !DiscriminatorProperty.ClrType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo())) - { - // TODO: Throw an exception - //throw new InvalidOperationException( - // RelationalStrings.DiscriminatorValueIncompatible( - // value, DiscriminatorProperty.Name, DiscriminatorProperty.ClrType)); - } - - return Annotations.SetAnnotation(CosmosAnnotationNames.DiscriminatorValue, value); - } - - public string ContainingPropertyName - { - get => Annotations.Metadata[CosmosAnnotationNames.PropertyName] as string - ?? GetDefaultContainingPropertyName(); - - [param: CanBeNull] set => SetPropertyName(value); - } - - private string GetDefaultContainingPropertyName() - => EntityType.FindOwnership()?.PrincipalToDependent.Name; - - protected virtual bool SetPropertyName([CanBeNull] string value) - => Annotations.SetAnnotation( - CosmosAnnotationNames.PropertyName, - Check.NullButNotEmpty(value, nameof(value))); - } -} diff --git a/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs b/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs deleted file mode 100644 index f3e8f98e5d3..00000000000 --- a/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public class CosmosModelAnnotations : ICosmosModelAnnotations - { - public CosmosModelAnnotations(IModel model) - : this(new CosmosAnnotations(model)) - { - } - - protected CosmosModelAnnotations(CosmosAnnotations annotations) => Annotations = annotations; - - protected virtual CosmosAnnotations Annotations { get; } - - protected virtual IModel Model => (IModel)Annotations.Metadata; - - public virtual string DefaultContainerName - { - get => (string)Annotations.Metadata[CosmosAnnotationNames.ContainerName]; - - [param: CanBeNull] set => SetDefaultContainerName(value); - } - - protected virtual bool SetDefaultContainerName([CanBeNull] string value) - => Annotations.SetAnnotation( - CosmosAnnotationNames.ContainerName, - Check.NullButNotEmpty(value, nameof(value))); - } -} diff --git a/src/EFCore.Cosmos/Metadata/CosmosPropertyAnnotations.cs b/src/EFCore.Cosmos/Metadata/CosmosPropertyAnnotations.cs deleted file mode 100644 index 84354595e15..00000000000 --- a/src/EFCore.Cosmos/Metadata/CosmosPropertyAnnotations.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public class CosmosPropertyAnnotations : ICosmosPropertyAnnotations - { - public CosmosPropertyAnnotations(IProperty property) - : this(new CosmosAnnotations(property)) - { - } - - protected CosmosPropertyAnnotations(CosmosAnnotations annotations) => Annotations = annotations; - - protected virtual CosmosAnnotations Annotations { get; } - - protected virtual IProperty Property => (IProperty)Annotations.Metadata; - - public virtual string PropertyName - { - get => ((string)Annotations.Metadata[CosmosAnnotationNames.PropertyName]) - ?? Property.Name; - - [param: CanBeNull] set => SetPropertyName(value); - } - - protected virtual bool SetPropertyName([CanBeNull] string value) - => Annotations.SetAnnotation( - CosmosAnnotationNames.PropertyName, - value); - } -} diff --git a/src/EFCore.Cosmos/Metadata/ICosmosEntityTypeAnnotations.cs b/src/EFCore.Cosmos/Metadata/ICosmosEntityTypeAnnotations.cs deleted file mode 100644 index 17cdc6ec48d..00000000000 --- a/src/EFCore.Cosmos/Metadata/ICosmosEntityTypeAnnotations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public interface ICosmosEntityTypeAnnotations - { - string ContainerName { get; } - IProperty DiscriminatorProperty { get; } - object DiscriminatorValue { get; } - string ContainingPropertyName { get; } - } -} diff --git a/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs b/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs deleted file mode 100644 index 47b79283024..00000000000 --- a/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public interface ICosmosModelAnnotations - { - string DefaultContainerName { get; } - } -} diff --git a/src/EFCore.Cosmos/Metadata/ICosmosPropertyAnnotations.cs b/src/EFCore.Cosmos/Metadata/ICosmosPropertyAnnotations.cs deleted file mode 100644 index fe0dea5748f..00000000000 --- a/src/EFCore.Cosmos/Metadata/ICosmosPropertyAnnotations.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata -{ - public interface ICosmosPropertyAnnotations - { - string PropertyName { get; } - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs index 789dfaa2f93..b4bc86ffd9f 100644 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs +++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs @@ -3,12 +3,16 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static class CosmosAnnotationNames { public const string Prefix = "Cosmos:"; public const string ContainerName = Prefix + "ContainerName"; public const string PropertyName = Prefix + "PropertyName"; - public const string DiscriminatorProperty = Prefix + "DiscriminatorProperty"; - public const string DiscriminatorValue = Prefix + "DiscriminatorValue"; } } diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotations.cs deleted file mode 100644 index 34ba1717997..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotations.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - public class CosmosAnnotations - { - public CosmosAnnotations([NotNull] IAnnotatable metadata) - { - Check.NotNull(metadata, nameof(metadata)); - - Metadata = metadata; - } - - public virtual IAnnotatable Metadata { get; } - - public virtual bool SetAnnotation( - [NotNull] string annotationName, - [CanBeNull] object value) - { - ((IMutableAnnotatable)Metadata)[annotationName] = value; - - return true; - } - - public virtual bool CanSetAnnotation( - [NotNull] string relationalAnnotationName, - [CanBeNull] object value) - => true; - - public virtual bool RemoveAnnotation([NotNull] string annotationName) - { - ((IMutableAnnotatable)Metadata).RemoveAnnotation(annotationName); - - return true; - } - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs deleted file mode 100644 index 4bb5c2cad1b..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - public class CosmosEntityTypeBuilderAnnotations : CosmosEntityTypeAnnotations - { - /// - /// 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 CosmosEntityTypeBuilderAnnotations( - [NotNull] InternalEntityTypeBuilder internalBuilder, - ConfigurationSource configurationSource) - : base(new CosmosAnnotationsBuilder(internalBuilder, 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. - /// - protected new virtual CosmosAnnotationsBuilder Annotations => (CosmosAnnotationsBuilder)base.Annotations; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected virtual InternalEntityTypeBuilder EntityTypeBuilder => (InternalEntityTypeBuilder)Annotations.MetadataBuilder; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected override CosmosModelAnnotations GetAnnotations(IModel model) - => new CosmosModelBuilderAnnotations( - ((Model)model).Builder, - Annotations.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. - /// - protected override CosmosEntityTypeAnnotations GetAnnotations(IEntityType entityType) - => new CosmosEntityTypeBuilderAnnotations( - ((EntityType)entityType).Builder, - Annotations.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 bool ToContainer([CanBeNull] string name) - { - Check.NullButNotEmpty(name, nameof(name)); - - return SetContainerName(name); - } - - /// - /// 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 ToProperty([CanBeNull] string name) - { - Check.NullButNotEmpty(name, nameof(name)); - - return SetPropertyName(name); - } - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs new file mode 100644 index 00000000000..17e2d61ec40 --- /dev/null +++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static class CosmosEntityTypeExtensions + { + /// + /// 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 static bool IsDocumentRoot(this IEntityType entityType) + => entityType.BaseType?.IsDocumentRoot() ?? !entityType.IsOwned() + || entityType[CosmosAnnotationNames.ContainerName] != null; + } +} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs deleted file mode 100644 index a99d73e45cd..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static class CosmosInternalMetadataBuilderExtensions - { - /// - /// 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 static CosmosModelBuilderAnnotations Cosmos( - [NotNull] this InternalModelBuilder builder, - ConfigurationSource configurationSource) - => new CosmosModelBuilderAnnotations(builder, 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 static CosmosEntityTypeBuilderAnnotations Cosmos( - [NotNull] this InternalEntityTypeBuilder builder, - ConfigurationSource configurationSource) - => new CosmosEntityTypeBuilderAnnotations(builder, 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 static CosmosPropertyBuilderAnnotations Cosmos( - [NotNull] this InternalPropertyBuilder builder, - ConfigurationSource configurationSource) - => new CosmosPropertyBuilderAnnotations(builder, configurationSource); - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs deleted file mode 100644 index 7417911433c..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public class CosmosModelBuilderAnnotations : CosmosModelAnnotations - { - /// - /// 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 CosmosModelBuilderAnnotations( - [NotNull] InternalModelBuilder internalBuilder, - ConfigurationSource configurationSource) - : base(new CosmosAnnotationsBuilder(internalBuilder, 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. - /// - protected new virtual CosmosAnnotationsBuilder Annotations => (CosmosAnnotationsBuilder)base.Annotations; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected virtual InternalModelBuilder ModelBuilder => (InternalModelBuilder)Annotations.MetadataBuilder; - - /// - /// 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 HasDefaultContainerName([CanBeNull] string name) => SetDefaultContainerName(name); - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyBuilderAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyBuilderAnnotations.cs deleted file mode 100644 index da1f6f6e32d..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyBuilderAnnotations.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public class CosmosPropertyBuilderAnnotations : CosmosPropertyAnnotations - { - /// - /// 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 CosmosPropertyBuilderAnnotations( - [NotNull] InternalPropertyBuilder internalBuilder, - ConfigurationSource configurationSource) - : base(new CosmosAnnotationsBuilder(internalBuilder, 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. - /// - protected new virtual CosmosAnnotationsBuilder Annotations => (CosmosAnnotationsBuilder)base.Annotations; - - /// - /// 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 ToProperty([CanBeNull] string name) => SetPropertyName(name); - } -} diff --git a/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs deleted file mode 100644 index 1bbaeefb750..00000000000 --- a/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal -{ - public static class EntityTypeExtensions - { - public static bool IsDocumentRoot(this IEntityType entityType) - => entityType.BaseType == null - ? !entityType.IsOwned() - || entityType[CosmosAnnotationNames.ContainerName] != null - : entityType.BaseType.IsDocumentRoot(); - } -} diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs index 774acea0db1..1d0481d9242 100644 --- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs @@ -49,7 +49,7 @@ protected override Expression VisitEntityQueryable([NotNull] Type elementType) QueryModelVisitor.QueryCompilationContext.IsAsyncQuery, new DocumentQueryExpression( QueryModelVisitor.QueryCompilationContext.IsAsyncQuery, - entityType.Cosmos().ContainerName, + entityType.GetCosmosContainerName(), new SelectExpression(entityType, _querySource, _sqlGeneratorFactory)), new EntityShaper( entityType, diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosMemberAccessBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosMemberAccessBindingExpressionVisitor.cs index 143542eba38..d50acc88385 100644 --- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosMemberAccessBindingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosMemberAccessBindingExpressionVisitor.cs @@ -163,7 +163,7 @@ private static Expression CreateGetValueExpression( Expression jObjectExpression, IProperty property) { - var storeName = property.Cosmos().PropertyName; + var storeName = property.GetCosmosPropertyName(); if (storeName.Length == 0) { return null; diff --git a/src/EFCore.Cosmos/Query/Expressions/Internal/SelectExpression.cs b/src/EFCore.Cosmos/Query/Expressions/Internal/SelectExpression.cs index 6af351e2168..f0f6e7f88f2 100644 --- a/src/EFCore.Cosmos/Query/Expressions/Internal/SelectExpression.cs +++ b/src/EFCore.Cosmos/Query/Expressions/Internal/SelectExpression.cs @@ -47,11 +47,11 @@ public BinaryExpression GetDiscriminatorPredicate(IEntityType entityType) var concreteEntityTypes = entityType.GetConcreteTypesInHierarchy().ToList(); - var discriminatorProperty = entityType.Cosmos().DiscriminatorProperty; + var discriminatorProperty = entityType.GetDiscriminatorProperty(); var discriminatorPredicate = Equal( new KeyAccessExpression(discriminatorProperty, FromExpression), - Constant(concreteEntityTypes[0].Cosmos().DiscriminatorValue, discriminatorProperty.ClrType)); + Constant(concreteEntityTypes[0].GetDiscriminatorValue(), discriminatorProperty.ClrType)); if (concreteEntityTypes.Count > 1) { @@ -60,7 +60,7 @@ var concreteEntityTypes .Skip(1) .Select( concreteEntityType - => Constant(concreteEntityType.Cosmos().DiscriminatorValue, discriminatorProperty.ClrType)) + => Constant(concreteEntityType.GetDiscriminatorValue(), discriminatorProperty.ClrType)) .Aggregate( discriminatorPredicate, (current, discriminatorValue) => OrElse( diff --git a/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs b/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs index 97204f75724..aee65878169 100644 --- a/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs +++ b/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs @@ -124,11 +124,11 @@ var materializer return Expression.Lambda(materializer, materializationContextParameter); } - var discriminatorProperty = firstEntityType.Cosmos().DiscriminatorProperty; + var discriminatorProperty = firstEntityType.GetDiscriminatorProperty(); var firstDiscriminatorValue = Expression.Constant( - firstEntityType.Cosmos().DiscriminatorValue, + firstEntityType.GetDiscriminatorValue(), discriminatorProperty.ClrType); var discriminatorValueVariable @@ -191,7 +191,7 @@ var blockExpressions var discriminatorValue = Expression.Constant( - concreteEntityType.Cosmos().DiscriminatorValue, + concreteEntityType.GetDiscriminatorValue(), discriminatorProperty.ClrType); materializer @@ -294,7 +294,7 @@ private static object ShapeNestedEntities( var nestedFk = nestedNavigation.ForeignKey; if (nestedFk.IsUnique) { - if (!(jObject[nestedFk.DeclaringEntityType.Cosmos().ContainingPropertyName] is JObject nestedJObject)) + if (!(jObject[nestedFk.DeclaringEntityType.GetCosmosContainingPropertyName()] is JObject nestedJObject)) { continue; } @@ -310,7 +310,7 @@ private static object ShapeNestedEntities( else { var nestedEntities = new List(); - if (jObject[nestedFk.DeclaringEntityType.Cosmos().ContainingPropertyName] is JArray jArray + if (jObject[nestedFk.DeclaringEntityType.GetCosmosContainingPropertyName()] is JArray jArray && jArray.Count != 0) { foreach (JObject nestedJObject in jArray) diff --git a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs index 939fa8311ef..78ede33623a 100644 --- a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs @@ -3,14 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Newtonsoft.Json.Linq; using Remotion.Linq.Parsing.ExpressionVisitors; @@ -54,7 +51,7 @@ private static Expression CreateGetValueExpression( return jObjectExpression; } - var storeName = property.Cosmos().PropertyName; + var storeName = property.GetCosmosPropertyName(); if (storeName.Length == 0) { var type = property.FindMapping()?.Converter?.ProviderClrType diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs index a0bad312969..fdc654aadef 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs @@ -34,7 +34,7 @@ public bool EnsureCreated() var created = _cosmosClient.CreateDatabaseIfNotExists(); foreach (var entityType in _model.GetEntityTypes()) { - created |= _cosmosClient.CreateContainerIfNotExists(entityType.Cosmos().ContainerName, "__partitionKey"); + created |= _cosmosClient.CreateContainerIfNotExists(entityType.GetCosmosContainerName(), "__partitionKey"); } if (created) @@ -60,7 +60,7 @@ public async Task EnsureCreatedAsync(CancellationToken cancellationToken = var created = await _cosmosClient.CreateDatabaseIfNotExistsAsync(cancellationToken); foreach (var entityType in _model.GetEntityTypes()) { - created |= await _cosmosClient.CreateContainerIfNotExistsAsync(entityType.Cosmos().ContainerName, "__partitionKey", cancellationToken); + created |= await _cosmosClient.CreateContainerIfNotExistsAsync(entityType.GetCosmosContainerName(), "__partitionKey", cancellationToken); } if (created) diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs index 24266994adf..766877ca6a2 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs @@ -177,8 +177,8 @@ private bool Save(IUpdateEntry entry) document = documentSource.CreateDocument(entry); document["__partitionKey"] = "0"; - document[entityType.Cosmos().DiscriminatorProperty.Cosmos().PropertyName] = - JToken.FromObject(entityType.Cosmos().DiscriminatorValue, CosmosClientWrapper.Serializer); + document[entityType.GetDiscriminatorProperty().GetCosmosPropertyName()] = + JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer); } return _cosmosClient.ReplaceItem( @@ -233,8 +233,8 @@ private Task SaveAsync(IUpdateEntry entry, CancellationToken cancellationT document = documentSource.CreateDocument(entry); document["__partitionKey"] = "0"; - document[entityType.Cosmos().DiscriminatorProperty.Cosmos().PropertyName] = - JToken.FromObject(entityType.Cosmos().DiscriminatorValue, CosmosClientWrapper.Serializer); + document[entityType.GetDiscriminatorProperty().GetCosmosPropertyName()] = + JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer); } return _cosmosClient.ReplaceItemAsync( diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs index 818145f8923..a7e54611856 100644 --- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs +++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs @@ -1,16 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.EntityFrameworkCore.Cosmos.Update.Internal @@ -23,7 +20,7 @@ public class DocumentSource public DocumentSource(IEntityType entityType, CosmosDatabaseWrapper database) { - _collectionId = entityType.Cosmos().ContainerName; + _collectionId = entityType.GetCosmosContainerName(); _database = database; _idProperty = entityType.FindProperty(StoreKeyConvention.IdPropertyName); } @@ -39,7 +36,7 @@ public JObject CreateDocument(IUpdateEntry entry) var document = new JObject(); foreach (var property in entry.EntityType.GetProperties()) { - var storeName = property.Cosmos().PropertyName; + var storeName = property.GetCosmosPropertyName(); if (storeName.Length != 0) { document[storeName] = ConvertPropertyValue(property, entry.GetCurrentValue(property)); @@ -57,7 +54,7 @@ public JObject CreateDocument(IUpdateEntry entry) } var nestedValue = entry.GetCurrentValue(ownedNavigation); - var nestedPropertyName = fk.DeclaringEntityType.Cosmos().ContainingPropertyName; + var nestedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName(); if (nestedValue == null) { document[nestedPropertyName] = null; @@ -91,7 +88,7 @@ public JObject UpdateDocument(JObject document, IUpdateEntry entry) if (entry.EntityState == EntityState.Added || entry.IsModified(property)) { - var storeName = property.Cosmos().PropertyName; + var storeName = property.GetCosmosPropertyName(); if (storeName.Length != 0) { document[storeName] = ConvertPropertyValue(property, entry.GetCurrentValue(property)); @@ -112,7 +109,7 @@ public JObject UpdateDocument(JObject document, IUpdateEntry entry) var nestedDocumentSource = _database.GetDocumentSource(fk.DeclaringEntityType); var nestedValue = entry.GetCurrentValue(ownedNavigation); - var nestedPropertyName = fk.DeclaringEntityType.Cosmos().ContainingPropertyName; + var nestedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName(); if (nestedValue == null) { if (document[nestedPropertyName] != null) diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/DiscriminatorValueGenerator.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/DiscriminatorValueGenerator.cs deleted file mode 100644 index 51723f482df..00000000000 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/DiscriminatorValueGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ValueGeneration; - -namespace Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal -{ - public class DiscriminatorValueGenerator : ValueGenerator - { - private readonly object _discriminator; - - public DiscriminatorValueGenerator(object discriminator) - { - _discriminator = discriminator; - } - - protected override object NextValue(EntityEntry entry) => _discriminator; - - public override bool GeneratesTemporaryValues => false; - } -} diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs index 2bdf67cc3bf..c8e2ea98067 100644 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs +++ b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs @@ -19,9 +19,9 @@ protected override object NextValue([NotNull] EntityEntry entry) var builder = new StringBuilder(); var pk = entry.Metadata.FindPrimaryKey(); - var discriminator = entry.Metadata.Cosmos().DiscriminatorValue; + var discriminator = entry.Metadata.GetDiscriminatorValue(); if (discriminator != null - && !pk.Properties.Contains(entry.Metadata.Cosmos().DiscriminatorProperty)) + && !pk.Properties.Contains(entry.Metadata.GetDiscriminatorProperty())) { AppendString(builder,discriminator); builder.Append("|"); diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 9af38d3f0af..c96febf3492 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -700,8 +700,8 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(");"); } - var discriminatorPropertyAnnotation = annotations.FirstOrDefault(a => a.Name == RelationalAnnotationNames.DiscriminatorProperty); - var discriminatorValueAnnotation = annotations.FirstOrDefault(a => a.Name == RelationalAnnotationNames.DiscriminatorValue); + var discriminatorPropertyAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorProperty); + var discriminatorValueAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); if ((discriminatorPropertyAnnotation ?? discriminatorValueAnnotation) != null) { @@ -709,7 +709,7 @@ protected virtual void GenerateEntityTypeAnnotations( .AppendLine() .Append(builderName) .Append(".") - .Append(nameof(RelationalEntityTypeBuilderExtensions.HasDiscriminator)); + .Append("HasDiscriminator"); if (discriminatorPropertyAnnotation?.Value != null) { @@ -745,7 +745,7 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder .Append(".") - .Append(nameof(DiscriminatorBuilder.HasValue)) + .Append("HasValue") .Append("(") .Append(Code.UnknownLiteral(value)) .Append(")"); diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index 56839c6b361..1f91412dcea 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -2,14 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq.Expressions; -using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore @@ -19,11 +16,6 @@ namespace Microsoft.EntityFrameworkCore /// public static class RelationalEntityTypeBuilderExtensions { - private static readonly string DefaultDiscriminatorName = "Discriminator"; - - // ReSharper disable once InconsistentNaming - private static readonly Type DefaultDiscriminatorType = typeof(string); - /// /// Configures the view or table that the entity type maps to when targeting a relational database. /// @@ -343,354 +335,6 @@ public static EntityTypeBuilder ToView( where TEntity : class => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema); - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// A builder that allows the discriminator column to be configured. - public static DiscriminatorBuilder HasDiscriminator([NotNull] this EntityTypeBuilder entityTypeBuilder) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - - var propertyBuilder = new PropertyBuilder( - (IMutableProperty)GetOrCreateDiscriminatorProperty( - ((IConventionEntityType)entityTypeBuilder.Metadata).Builder, null, null, true).Metadata); - return DiscriminatorBuilder(entityTypeBuilder.Metadata, propertyBuilder); - } - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// The name of the discriminator column. - /// The type of values stored in the discriminator column. - /// A builder that allows the discriminator column to be configured. - public static DiscriminatorBuilder HasDiscriminator( - [NotNull] this EntityTypeBuilder entityTypeBuilder, - [NotNull] string name, - [NotNull] Type type) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - Check.NotEmpty(name, nameof(name)); - Check.NotNull(type, nameof(type)); - - return DiscriminatorBuilder( - entityTypeBuilder.Metadata, entityTypeBuilder.Property(type, name)); - } - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The type of values stored in the discriminator column. - /// The builder for the entity type being configured. - /// The name of the discriminator column. - /// A builder that allows the discriminator column to be configured. - public static DiscriminatorBuilder HasDiscriminator( - [NotNull] this EntityTypeBuilder entityTypeBuilder, - [NotNull] string name) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - Check.NotEmpty(name, nameof(name)); - - return new DiscriminatorBuilder( - DiscriminatorBuilder( - entityTypeBuilder.Metadata, entityTypeBuilder.Property(typeof(TDiscriminator), name))); - } - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The entity type being configured. - /// The type of values stored in the discriminator column. - /// The builder for the entity type being configured. - /// - /// A lambda expression representing the property to be used as the discriminator ( - /// blog => blog.Discriminator). - /// - /// A builder that allows the discriminator column to be configured. - public static DiscriminatorBuilder HasDiscriminator( - [NotNull] this EntityTypeBuilder entityTypeBuilder, - [NotNull] Expression> propertyExpression) - where TEntity : class - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - Check.NotNull(propertyExpression, nameof(propertyExpression)); - - return new DiscriminatorBuilder( - DiscriminatorBuilder(entityTypeBuilder.Metadata, entityTypeBuilder.Property(propertyExpression))); - } - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder that allows the discriminator column to be configured. - public static IConventionDiscriminatorBuilder HasDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, bool fromDataAnnotation = false) - => DiscriminatorBuilder( - entityTypeBuilder, GetOrCreateDiscriminatorProperty(entityTypeBuilder, type: null, name: null, fromDataAnnotation: false), - fromDataAnnotation); - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// The type of values stored in the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the discriminator was configured, - /// null otherwise. - /// - public static IConventionDiscriminatorBuilder HasDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [NotNull] Type type, bool fromDataAnnotation = false) - => CanSetDiscriminator(entityTypeBuilder, type, fromDataAnnotation) - ? DiscriminatorBuilder( - entityTypeBuilder, GetOrCreateDiscriminatorProperty(entityTypeBuilder, type, name: null, fromDataAnnotation), - fromDataAnnotation) - : null; - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// The name of the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the discriminator was configured, - /// null otherwise. - /// - public static IConventionDiscriminatorBuilder HasDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [NotNull] string name, bool fromDataAnnotation = false) - => CanSetDiscriminator(entityTypeBuilder, name, fromDataAnnotation) - ? DiscriminatorBuilder( - entityTypeBuilder, GetOrCreateDiscriminatorProperty(entityTypeBuilder, type: null, name, fromDataAnnotation), - fromDataAnnotation) - : null; - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// The name of the discriminator column. - /// The type of values stored in the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the discriminator was configured, - /// null otherwise. - /// - public static IConventionDiscriminatorBuilder HasDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [NotNull] string name, [NotNull] Type type, - bool fromDataAnnotation = false) - => CanSetDiscriminator(entityTypeBuilder, type, name, fromDataAnnotation) - ? DiscriminatorBuilder( - entityTypeBuilder, entityTypeBuilder.Metadata.RootType().Builder.Property(type, name, fromDataAnnotation), - fromDataAnnotation) - : null; - - /// - /// Configures the discriminator column used to identify which entity type each row in a table represents - /// when an inheritance hierarchy is mapped to a single table in a relational database. - /// - /// The builder for the entity type being configured. - /// The property mapped to the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the discriminator was configured, - /// null otherwise. - /// - public static IConventionDiscriminatorBuilder HasDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [NotNull] MemberInfo memberInfo, bool fromDataAnnotation = false) - => CanSetDiscriminator(entityTypeBuilder, memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), fromDataAnnotation) - ? DiscriminatorBuilder( - entityTypeBuilder, entityTypeBuilder.Metadata.RootType().Builder.Property(memberInfo, fromDataAnnotation), - fromDataAnnotation) - : null; - - /// - /// Removes the discriminator property from this entity type. - /// This method is usually called when the entity type is no longer mapped to the same table as any other type in - /// the hierarchy or when this entity type is no longer the root type. - /// - /// The builder for the entity type being configured. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the discriminator was configured, - /// null otherwise. - /// - public static IConventionEntityTypeBuilder HasNoDeclaredDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, bool fromDataAnnotation = false) - { - var discriminatorName = (string)entityTypeBuilder.Metadata[RelationalAnnotationNames.DiscriminatorProperty]; - if (discriminatorName == null) - { - return entityTypeBuilder; - } - - var discriminatorProperty = entityTypeBuilder.Metadata.FindProperty(discriminatorName); - if (discriminatorProperty != null) - { - if (!CanSetDiscriminator(entityTypeBuilder, discriminatorProperty, null, null, fromDataAnnotation)) - { - return null; - } - - discriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedShadowProperties( - new[] - { - discriminatorProperty - }); - } - - entityTypeBuilder.Metadata.SetDiscriminatorProperty(null, fromDataAnnotation); - return entityTypeBuilder; - } - - private static IConventionPropertyBuilder GetOrCreateDiscriminatorProperty( - IConventionEntityTypeBuilder entityTypeBuilder, Type type, string name, bool fromDataAnnotation) - { - var discriminatorProperty = entityTypeBuilder.Metadata.GetDiscriminatorProperty(); - if ((name != null && discriminatorProperty?.Name != name) - || (type != null && discriminatorProperty?.ClrType != type)) - { - discriminatorProperty = null; - } - - return entityTypeBuilder.Metadata.RootType().Builder.Property( - type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, - name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, - setTypeConfigurationSource: type != null, - fromDataAnnotation); - } - - private static DiscriminatorBuilder DiscriminatorBuilder( - IMutableEntityType entityType, - [NotNull] PropertyBuilder discriminatorPropertyBuilder) - { - var rootTypeBuilder = new EntityTypeBuilder(entityType.RootType()); - - var discriminatorProperty = (IConventionProperty)discriminatorPropertyBuilder.Metadata; - // Make sure the property is on the root type - discriminatorPropertyBuilder = discriminatorProperty.GetTypeConfigurationSource() != null - ? rootTypeBuilder.Property(discriminatorProperty.ClrType, discriminatorProperty.Name) - : rootTypeBuilder.Property(discriminatorProperty.Name); - - var oldDiscriminatorProperty = entityType.GetDiscriminatorProperty() as IConventionProperty; - if (oldDiscriminatorProperty?.Builder != null - && oldDiscriminatorProperty != discriminatorProperty) - { - oldDiscriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedShadowProperties( - new[] - { - oldDiscriminatorProperty - }); - } - - rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty); - discriminatorPropertyBuilder.IsRequired(); - discriminatorPropertyBuilder.HasValueGenerator(DiscriminatorValueGenerator.Factory); - - return new DiscriminatorBuilder(entityType); - } - - private static IConventionDiscriminatorBuilder DiscriminatorBuilder( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionPropertyBuilder discriminatorPropertyBuilder, - bool fromDataAnnotation) - { - if (discriminatorPropertyBuilder == null) - { - return null; - } - - var rootTypeBuilder = entityTypeBuilder.Metadata.RootType().Builder; - var discriminatorProperty = discriminatorPropertyBuilder.Metadata; - // Make sure the property is on the root type - discriminatorPropertyBuilder = rootTypeBuilder.Property( - discriminatorProperty.ClrType, discriminatorProperty.Name, setTypeConfigurationSource: false); - - var oldDiscriminatorProperty = entityTypeBuilder.Metadata.GetDiscriminatorProperty() as IConventionProperty; - if (oldDiscriminatorProperty?.Builder != null - && oldDiscriminatorProperty != discriminatorProperty) - { - oldDiscriminatorProperty.Builder.IsRequired(null, fromDataAnnotation); - oldDiscriminatorProperty.Builder.HasValueGenerator((Type)null, fromDataAnnotation); - oldDiscriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedShadowProperties( - new[] - { - oldDiscriminatorProperty - }); - } - - rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, fromDataAnnotation); - discriminatorPropertyBuilder.IsRequired(true, fromDataAnnotation); - discriminatorPropertyBuilder.HasValueGenerator(DiscriminatorValueGenerator.Factory, fromDataAnnotation); - - return new DiscriminatorBuilder((IMutableEntityType)entityTypeBuilder.Metadata); - } - - /// - /// Returns a value indicating whether the discriminator column can be configured. - /// - /// The builder for the entity type being configured. - /// The name of the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the configuration can be applied. - public static bool CanSetDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) - => CanSetDiscriminator( - entityTypeBuilder, entityTypeBuilder.Metadata.GetDiscriminatorProperty(), name, discriminatorType: null, - fromDataAnnotation); - - /// - /// Returns a value indicating whether the discriminator column can be configured. - /// - /// The builder for the entity type being configured. - /// The type of values stored in the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the configuration can be applied. - public static bool CanSetDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] Type type, bool fromDataAnnotation = false) - => CanSetDiscriminator( - entityTypeBuilder, entityTypeBuilder.Metadata.GetDiscriminatorProperty(), name: null, type, fromDataAnnotation); - - /// - /// Returns a value indicating whether the discriminator column can be configured. - /// - /// The builder for the entity type being configured. - /// The type of values stored in the discriminator column. - /// The name of the discriminator column. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the configuration can be applied. - public static bool CanSetDiscriminator( - [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, - [NotNull] Type type, - [NotNull] string name, - bool fromDataAnnotation = false) - => CanSetDiscriminator( - entityTypeBuilder, entityTypeBuilder.Metadata.GetDiscriminatorProperty(), name, type, fromDataAnnotation); - - private static bool CanSetDiscriminator( - IConventionEntityTypeBuilder entityTypeBuilder, - IProperty discriminatorProperty, - string name, - Type discriminatorType, - bool fromDataAnnotation) - => discriminatorProperty == null - || ((name != null || discriminatorType != null) - && (name == null || discriminatorProperty.Name == name) - && (discriminatorType == null || discriminatorProperty.ClrType == discriminatorType)) - || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - .Overrides(entityTypeBuilder.Metadata.GetDiscriminatorPropertyConfigurationSource()); - /// /// Configures a database check constraint when targeting a relational database. /// @@ -784,7 +428,7 @@ public static IConventionEntityTypeBuilder HasCheckConstraint( } if (!(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - .Overrides(entityTypeBuilder.Metadata.GetDiscriminatorPropertyConfigurationSource())) + .Overrides(constraint.GetConfigurationSource())) { return null; } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 09b3bbd0022..53880f6d37c 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -142,165 +142,5 @@ public static void SetSchema( public static ConfigurationSource? GetSchemaConfigurationSource([NotNull] this IConventionEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.Schema) ?.GetConfigurationSource(); - - /// - /// Returns the that will be used for storing a discriminator value. - /// - /// The entity type to get the discriminator property for. - public static IProperty GetDiscriminatorProperty([NotNull] this IEntityType entityType) - { - if (entityType.BaseType != null) - { - return entityType.RootType().GetDiscriminatorProperty(); - } - - var propertyName = (string)entityType[RelationalAnnotationNames.DiscriminatorProperty]; - - return propertyName == null ? null : entityType.FindProperty(propertyName); - } - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The entity type to set the discriminator property for. - /// The property to set. - public static void SetDiscriminatorProperty([NotNull] this IMutableEntityType entityType, [CanBeNull] IProperty property) - { - CheckDiscriminatorProperty(entityType, property); - - var oldDiscriminatorType = entityType.GetDiscriminatorProperty()?.ClrType; - if (property == null - || property.ClrType != oldDiscriminatorType) - { - foreach (var derivedType in entityType.GetDerivedTypesInclusive()) - { - derivedType.RemoveDiscriminatorValue(); - } - } - - entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.DiscriminatorProperty, property?.Name); - } - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The entity type to set the discriminator property for. - /// The property to set. - /// Indicates whether the configuration was specified using a data annotation. - public static void SetDiscriminatorProperty( - [NotNull] this IConventionEntityType entityType, [CanBeNull] IProperty property, bool fromDataAnnotation = false) - { - CheckDiscriminatorProperty(entityType, property); - - if (property != null - && !property.ClrType.IsInstanceOfType(entityType.GetDiscriminatorValue())) - { - foreach (var derivedType in entityType.GetDerivedTypesInclusive()) - { - derivedType.RemoveDiscriminatorValue(); - } - } - - entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.DiscriminatorProperty, property?.Name, fromDataAnnotation); - } - - private static void CheckDiscriminatorProperty(IEntityType entityType, IProperty property) - { - if (property != null) - { - if (entityType != entityType.RootType()) - { - throw new InvalidOperationException( - RelationalStrings.DiscriminatorPropertyMustBeOnRoot(entityType.DisplayName())); - } - - if (property.DeclaringEntityType != entityType) - { - throw new InvalidOperationException( - RelationalStrings.DiscriminatorPropertyNotFound(property.Name, entityType.DisplayName())); - } - } - } - - /// - /// Gets the for the discriminator property. - /// - /// The entity type to find configuration source for. - /// The or null if no discriminator property has been set. - public static ConfigurationSource? GetDiscriminatorPropertyConfigurationSource([NotNull] this IConventionEntityType entityType) - => entityType.FindAnnotation(RelationalAnnotationNames.DiscriminatorProperty) - ?.GetConfigurationSource(); - - /// - /// Returns the discriminator value for this entity type. - /// - /// The entity type to find the discriminator value for. - /// The discriminator value for this entity type. - public static object GetDiscriminatorValue([NotNull] this IEntityType entityType) - => entityType[RelationalAnnotationNames.DiscriminatorValue]; - - /// - /// Sets the discriminator value for this entity type. - /// - /// The entity type to set the discriminator value for. - /// The value to set. - public static void SetDiscriminatorValue([NotNull] this IMutableEntityType entityType, [CanBeNull] object value) - { - CheckDiscriminatorValue(entityType, value); - - entityType.SetAnnotation(RelationalAnnotationNames.DiscriminatorValue, value); - } - - /// - /// Sets the discriminator value for this entity type. - /// - /// The entity type to set the discriminator value for. - /// The value to set. - /// Indicates whether the configuration was specified using a data annotation. - public static void SetDiscriminatorValue( - [NotNull] this IConventionEntityType entityType, [CanBeNull] object value, bool fromDataAnnotation = false) - { - CheckDiscriminatorValue(entityType, value); - - entityType.SetAnnotation(RelationalAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); - } - - private static void CheckDiscriminatorValue(IEntityType entityType, object value) - { - if (value != null - && entityType.GetDiscriminatorProperty() == null) - { - throw new InvalidOperationException( - RelationalStrings.NoDiscriminatorForValue(entityType.DisplayName(), entityType.RootType().DisplayName())); - } - - if (value != null - && !entityType.GetDiscriminatorProperty().ClrType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo())) - { - throw new InvalidOperationException( - RelationalStrings.DiscriminatorValueIncompatible( - value, entityType.GetDiscriminatorProperty().Name, entityType.GetDiscriminatorProperty().ClrType)); - } - } - - /// - /// Removes the discriminator value for this entity type. - /// - public static void RemoveDiscriminatorValue([NotNull] this IMutableEntityType entityType) - => entityType.RemoveAnnotation(RelationalAnnotationNames.DiscriminatorValue); - - /// - /// Removes the discriminator value for this entity type. - /// - public static void RemoveDiscriminatorValue([NotNull] this IConventionEntityType entityType) - => entityType.RemoveAnnotation(RelationalAnnotationNames.DiscriminatorValue); - - /// - /// Gets the for the discriminator value. - /// - /// The or null if no discriminator value has been set. - public static ConfigurationSource? GetDiscriminatorValueConfigurationSource([NotNull] this IConventionEntityType entityType) - => entityType.FindAnnotation(RelationalAnnotationNames.DiscriminatorValue) - ?.GetConfigurationSource(); } } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 82944434748..9503be768dd 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -483,7 +483,9 @@ protected virtual void ValidateSharedIndexesCompatibility( /// The table name. /// The logger to use. protected virtual void ValidateSharedKeysCompatibility( - [NotNull] IReadOnlyList mappedTypes, [NotNull] string tableName, [NotNull] IDiagnosticsLogger logger) + [NotNull] IReadOnlyList mappedTypes, + [NotNull] string tableName, + [NotNull] IDiagnosticsLogger logger) { var keyMappings = new Dictionary(); @@ -521,11 +523,6 @@ protected virtual void ValidateSharedKeysCompatibility( /// The logger to use. protected virtual void ValidateInheritanceMapping([NotNull] IModel model, [NotNull] IDiagnosticsLogger logger) { - foreach (var rootEntityType in model.GetRootEntityTypes()) - { - ValidateDiscriminatorValues(rootEntityType); - } - foreach (var entityType in model.GetEntityTypes()) { if (entityType.BaseType != null @@ -539,51 +536,6 @@ protected virtual void ValidateInheritanceMapping([NotNull] IModel model, [NotNu } } - private static void ValidateDiscriminator(IEntityType entityType) - { - if (entityType.GetDiscriminatorProperty() == null) - { - throw new InvalidOperationException( - RelationalStrings.NoDiscriminatorProperty(entityType.DisplayName())); - } - - if (entityType.GetDiscriminatorValue() == null) - { - throw new InvalidOperationException( - RelationalStrings.NoDiscriminatorValue(entityType.DisplayName())); - } - } - - private static void ValidateDiscriminatorValues(IEntityType rootEntityType) - { - var discriminatorValues = new Dictionary(); - var derivedTypes = rootEntityType.GetDerivedTypesInclusive().ToList(); - if (derivedTypes.Count == 1) - { - return; - } - - foreach (var derivedType in derivedTypes) - { - if (derivedType.ClrType?.IsInstantiable() != true) - { - continue; - } - - ValidateDiscriminator(derivedType); - - var discriminatorValue = derivedType.GetDiscriminatorValue(); - if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType)) - { - throw new InvalidOperationException( - RelationalStrings.DuplicateDiscriminatorValue( - derivedType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName())); - } - - discriminatorValues[discriminatorValue] = derivedType; - } - } - private static string Format(string schema, string name) => (string.IsNullOrEmpty(schema) ? "" : schema + ".") + name; } diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index 4ed6fec8446..c4430323e92 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -73,19 +73,15 @@ public override ConventionSet CreateConventionSet() conventionSet.PropertyAddedConventions.Add(relationalColumnAttributeConvention); - var sharedTableConvention = new SharedTableConvention(logger); - - var discriminatorConvention = new DiscriminatorConvention(logger); var storeGenerationConvention = new StoreGenerationConvention(); conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention(logger)); - conventionSet.EntityTypeRemovedConventions.Add(discriminatorConvention); - conventionSet.BaseEntityTypeChangedConventions.Add(discriminatorConvention); conventionSet.BaseEntityTypeChangedConventions.Add( new TableNameFromDbSetConvention(Dependencies.Context?.Context, Dependencies.SetFinder, logger)); conventionSet.PropertyFieldChangedConventions.Add(relationalColumnAttributeConvention); conventionSet.PropertyAnnotationChangedConventions.Add(storeGenerationConvention); conventionSet.PropertyAnnotationChangedConventions.Add((RelationalValueGeneratorConvention)valueGeneratorConvention); + var sharedTableConvention = new SharedTableConvention(logger); conventionSet.ModelBuiltConventions.Add(storeGenerationConvention); conventionSet.ModelBuiltConventions.Add(sharedTableConvention); diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index fddcf10c854..8e8f4a17e8a 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -69,16 +69,6 @@ public static class RelationalAnnotationNames /// public const string CheckConstraints = Prefix + "CheckConstraints"; - /// - /// The name for discriminator property annotations. - /// - public const string DiscriminatorProperty = Prefix + "DiscriminatorProperty"; - - /// - /// The name for discriminator value annotations. - /// - public const string DiscriminatorValue = Prefix + "DiscriminatorValue"; - /// /// The name for filter annotations. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 7feaf497fff..c9740772b0d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -157,22 +157,6 @@ public static string UnableToDiscriminate([CanBeNull] object entityType) GetString("UnableToDiscriminate", nameof(entityType)), entityType); - /// - /// A discriminator property cannot be set for the entity type '{entityType}' because it is not the root of an inheritance hierarchy. - /// - public static string DiscriminatorPropertyMustBeOnRoot([CanBeNull] object entityType) - => string.Format( - GetString("DiscriminatorPropertyMustBeOnRoot", nameof(entityType)), - entityType); - - /// - /// Unable to set property '{property}' as a discriminator for entity type '{entityType}' because it is not a property of '{entityType}'. - /// - public static string DiscriminatorPropertyNotFound([CanBeNull] object property, [CanBeNull] object entityType) - => string.Format( - GetString("DiscriminatorPropertyNotFound", nameof(property), nameof(entityType)), - property, entityType); - /// /// The Include operation is not supported when calling a stored procedure. /// @@ -203,46 +187,6 @@ public static string IncompatibleTableNoRelationship([CanBeNull] object table, [ GetString("IncompatibleTableNoRelationship", nameof(table), nameof(entityType), nameof(otherEntityType)), table, entityType, otherEntityType); - /// - /// Cannot configure the discriminator value for entity type '{entityType}' because it doesn't derive from '{rootEntityType}'. - /// - public static string DiscriminatorEntityTypeNotDerived([CanBeNull] object entityType, [CanBeNull] object rootEntityType) - => string.Format( - GetString("DiscriminatorEntityTypeNotDerived", nameof(entityType), nameof(rootEntityType)), - entityType, rootEntityType); - - /// - /// Cannot set discriminator value '{value}' for discriminator property '{discriminator}' because it is not assignable to property of type '{discriminatorType}'. - /// - public static string DiscriminatorValueIncompatible([CanBeNull] object value, [CanBeNull] object discriminator, [CanBeNull] object discriminatorType) - => string.Format( - GetString("DiscriminatorValueIncompatible", nameof(value), nameof(discriminator), nameof(discriminatorType)), - value, discriminator, discriminatorType); - - /// - /// Cannot set discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property set. - /// - public static string NoDiscriminatorForValue([CanBeNull] object entityType, [CanBeNull] object rootEntityType) - => string.Format( - GetString("NoDiscriminatorForValue", nameof(entityType), nameof(rootEntityType)), - entityType, rootEntityType); - - /// - /// The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator property configured. - /// - public static string NoDiscriminatorProperty([CanBeNull] object entityType) - => string.Format( - GetString("NoDiscriminatorProperty", nameof(entityType)), - entityType); - - /// - /// The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator value configured. - /// - public static string NoDiscriminatorValue([CanBeNull] object entityType) - => string.Format( - GetString("NoDiscriminatorValue", nameof(entityType)), - entityType); - /// /// No value provided for required parameter '{parameter}'. /// @@ -345,14 +289,6 @@ public static string IncorrectDefaultValueType([CanBeNull] object value, [CanBeN GetString("IncorrectDefaultValueType", nameof(value), nameof(valueType), nameof(property), nameof(propertyType), nameof(entityType)), value, valueType, property, propertyType, entityType); - /// - /// The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy needs to have a unique discriminator value. - /// - public static string DuplicateDiscriminatorValue([CanBeNull] object entityType1, [CanBeNull] object discriminatorValue, [CanBeNull] object entityType2) - => string.Format( - GetString("DuplicateDiscriminatorValue", nameof(entityType1), nameof(discriminatorValue), nameof(entityType2)), - entityType1, discriminatorValue, entityType2); - /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different nullability. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 162bc02e51a..cc6e575c8b2 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -229,12 +229,6 @@ Unable to materialize entity of type '{entityType}'. No discriminators were matched. - - A discriminator property cannot be set for the entity type '{entityType}' because it is not the root of an inheritance hierarchy. - - - Unable to set property '{property}' as a discriminator for entity type '{entityType}' because it is not a property of '{entityType}'. - An ambient transaction has been detected. The current provider does not support ambient transactions. See http://go.microsoft.com/fwlink/?LinkId=800142 Warning RelationalEventId.AmbientTransactionWarning @@ -291,21 +285,6 @@ Property '{property}' on entity type '{entityType}' is part of a primary or alternate key but has a constant default value set. Constant default values are not useful for primary or alternate keys since these properties must always have non-null unqiue values. Warning RelationalEventId.ModelValidationKeyDefaultValueWarning string string - - Cannot configure the discriminator value for entity type '{entityType}' because it doesn't derive from '{rootEntityType}'. - - - Cannot set discriminator value '{value}' for discriminator property '{discriminator}' because it is not assignable to property of type '{discriminatorType}'. - - - Cannot set discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property set. - - - The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator property configured. - - - The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator value configured. - The LINQ expression '{expression}' could not be translated and will be evaluated locally. Warning RelationalEventId.QueryClientEvaluationWarning object @@ -361,9 +340,6 @@ Cannot set default value '{value}' of type '{valueType}' on property '{property}' of type '{propertyType}' in entity type '{entityType}'. - - The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy needs to have a unique discriminator value. - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different nullability. diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index 563c85b5b6e..1b479615a17 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -425,5 +425,55 @@ public static void SetDefiningQuery( /// The configuration source for . public static ConfigurationSource? GetDefiningQueryConfigurationSource([NotNull] this IConventionEntityType entityType) => entityType.FindAnnotation(CoreAnnotationNames.DefiningQuery)?.GetConfigurationSource(); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The entity type to set the discriminator property for. + /// The property to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetDiscriminatorProperty( + [NotNull] this IConventionEntityType entityType, [CanBeNull] IProperty property, bool fromDataAnnotation = false) + => Check.NotNull(entityType, nameof(entityType)).AsEntityType() + .SetDiscriminatorProperty( + property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// Gets the for the discriminator property. + /// + /// The entity type to find configuration source for. + /// The or null if no discriminator property has been set. + public static ConfigurationSource? GetDiscriminatorPropertyConfigurationSource([NotNull] this IConventionEntityType entityType) + => entityType.FindAnnotation(CoreAnnotationNames.DiscriminatorProperty) + ?.GetConfigurationSource(); + + /// + /// Sets the discriminator value for this entity type. + /// + /// The entity type to set the discriminator value for. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetDiscriminatorValue( + [NotNull] this IConventionEntityType entityType, [CanBeNull] object value, bool fromDataAnnotation = false) + { + entityType.AsEntityType().CheckDiscriminatorValue(entityType, value); + + entityType.SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); + } + + /// + /// Removes the discriminator value for this entity type. + /// + public static void RemoveDiscriminatorValue([NotNull] this IConventionEntityType entityType) + => entityType.RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); + + /// + /// Gets the for the discriminator value. + /// + /// The or null if no discriminator value has been set. + public static ConfigurationSource? GetDiscriminatorValueConfigurationSource([NotNull] this IConventionEntityType entityType) + => entityType.FindAnnotation(CoreAnnotationNames.DiscriminatorValue) + ?.GetConfigurationSource(); } } diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index 532c61f63f7..c0e351e4d16 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Text; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -523,5 +524,28 @@ public static LambdaExpression GetDefiningQuery([NotNull] this IEntityType entit return (LambdaExpression)entityType[CoreAnnotationNames.DefiningQuery]; } + /// + /// Returns the that will be used for storing a discriminator value. + /// + /// The entity type to get the discriminator property for. + public static IProperty GetDiscriminatorProperty([NotNull] this IEntityType entityType) + { + if (entityType.BaseType != null) + { + return entityType.RootType().GetDiscriminatorProperty(); + } + + var propertyName = (string)entityType[CoreAnnotationNames.DiscriminatorProperty]; + + return propertyName == null ? null : entityType.FindProperty(propertyName); + } + + /// + /// Returns the discriminator value for this entity type. + /// + /// The entity type to find the discriminator value for. + /// The discriminator value for this entity type. + public static object GetDiscriminatorValue([NotNull] this IEntityType entityType) + => entityType[CoreAnnotationNames.DiscriminatorValue]; } } diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index 5a172caf073..0db15c2a085 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -393,5 +393,33 @@ public static void SetDefiningQuery( [CanBeNull] LambdaExpression definingQuery) => Check.NotNull(entityType, nameof(entityType)).AsEntityType() .SetDefiningQuery(definingQuery, ConfigurationSource.Explicit); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The entity type to set the discriminator property for. + /// The property to set. + public static void SetDiscriminatorProperty([NotNull] this IMutableEntityType entityType, [CanBeNull] IProperty property) + => Check.NotNull(entityType, nameof(entityType)).AsEntityType() + .SetDiscriminatorProperty(property, ConfigurationSource.Explicit); + + /// + /// Sets the discriminator value for this entity type. + /// + /// The entity type to set the discriminator value for. + /// The value to set. + public static void SetDiscriminatorValue([NotNull] this IMutableEntityType entityType, [CanBeNull] object value) + { + entityType.AsEntityType().CheckDiscriminatorValue(entityType, value); + + entityType.SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); + } + + /// + /// Removes the discriminator value for this entity type. + /// + /// The entity type to remove the discriminator value for. + public static void RemoveDiscriminatorValue([NotNull] this IMutableEntityType entityType) + => entityType.RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); } } diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 1a03b9380a6..1afd3ac674f 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -64,6 +64,7 @@ public virtual void Validate(IModel model, IDiagnosticsLogger(); foreach (var entityType in model.GetEntityTypes()) { - ValidateClrInheritance(model, entityType, validEntityTypes, logger); + ValidateClrInheritance(model, entityType, validEntityTypes); } } private void ValidateClrInheritance( [NotNull] IModel model, [NotNull] IEntityType entityType, - [NotNull] HashSet validEntityTypes, - [NotNull] IDiagnosticsLogger logger) + [NotNull] HashSet validEntityTypes) { Check.NotNull(model, nameof(model)); Check.NotNull(entityType, nameof(entityType)); @@ -487,6 +487,64 @@ private void ValidateClrInheritance( validEntityTypes.Add(entityType); } + /// + /// Validates the mapping/configuration of inheritance in the model. + /// + /// The model to validate. + /// The logger to use. + protected virtual void ValidateDiscriminatorValues([NotNull] IModel model, [NotNull] IDiagnosticsLogger logger) + { + foreach (var rootEntityType in model.GetRootEntityTypes()) + { + ValidateDiscriminatorValues(rootEntityType); + } + } + + private static void ValidateDiscriminator(IEntityType entityType) + { + if (entityType.GetDiscriminatorProperty() == null) + { + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorProperty(entityType.DisplayName())); + } + + if (entityType.GetDiscriminatorValue() == null) + { + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorValue(entityType.DisplayName())); + } + } + + private static void ValidateDiscriminatorValues(IEntityType rootEntityType) + { + var discriminatorValues = new Dictionary(); + var derivedTypes = rootEntityType.GetDerivedTypesInclusive().ToList(); + if (derivedTypes.Count == 1) + { + return; + } + + foreach (var derivedType in derivedTypes) + { + if (derivedType.ClrType?.IsInstantiable() != true) + { + continue; + } + + ValidateDiscriminator(derivedType); + + var discriminatorValue = derivedType.GetDiscriminatorValue(); + if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType)) + { + throw new InvalidOperationException( + CoreStrings.DuplicateDiscriminatorValue( + derivedType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName())); + } + + discriminatorValues[discriminatorValue] = derivedType; + } + } + /// /// Validates the mapping/configuration of change tracking in the model. /// diff --git a/src/EFCore.Relational/Metadata/Builders/DiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs similarity index 96% rename from src/EFCore.Relational/Metadata/Builders/DiscriminatorBuilder.cs rename to src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs index fe119a1c983..058c2a4525c 100644 --- a/src/EFCore.Relational/Metadata/Builders/DiscriminatorBuilder.cs +++ b/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs @@ -94,7 +94,7 @@ private DiscriminatorBuilder HasValue( && entityTypeBuilder.HasBaseType(baseEntityTypeBuilder.Metadata, configurationSource) == null) { throw new InvalidOperationException( - RelationalStrings.DiscriminatorEntityTypeNotDerived( + CoreStrings.DiscriminatorEntityTypeNotDerived( entityTypeBuilder.Metadata.DisplayName(), baseEntityTypeBuilder.Metadata.DisplayName())); } @@ -105,7 +105,7 @@ private DiscriminatorBuilder HasValue( } else { - if (!entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.DiscriminatorValue, value, configurationSource)) + if (!entityTypeBuilder.CanSetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, configurationSource)) { return null; } @@ -139,7 +139,7 @@ bool IConventionDiscriminatorBuilder.CanSetValue(IConventionEntityType entityTyp return false; } - return entityType.Builder.CanSetAnnotation(RelationalAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); + return entityType.Builder.CanSetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); } #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Builders/DiscriminatorBuilder`.cs b/src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs similarity index 100% rename from src/EFCore.Relational/Metadata/Builders/DiscriminatorBuilder`.cs rename to src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 03dc71229f2..dcd3adf42b4 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -26,7 +26,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// and it is not designed to be directly constructed in your application code. /// /// - public class EntityTypeBuilder : IInfrastructure, IInfrastructure + public class EntityTypeBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -54,11 +54,6 @@ public EntityTypeBuilder([NotNull] IMutableEntityType entityType) /// public virtual IMutableEntityType Metadata => Builder.Metadata; - /// - /// The model that the entity type belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - /// /// Adds or updates an annotation on the entity type. If an annotation with the key specified in /// already exists its value will be updated. @@ -863,6 +858,47 @@ public virtual DataBuilder HasData([NotNull] IEnumerable data) return new DataBuilder(); } + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// A builder that allows the discriminator column to be configured. + public virtual DiscriminatorBuilder HasDiscriminator() + => Builder.DiscriminatorBuilder( + Builder.GetOrCreateDiscriminatorProperty(null, null, true), ConfigurationSource.Explicit); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The name of the discriminator column. + /// The type of values stored in the discriminator column. + /// A builder that allows the discriminator column to be configured. + public virtual DiscriminatorBuilder HasDiscriminator( + [NotNull] string name, + [NotNull] Type type) + { + Check.NotEmpty(name, nameof(name)); + Check.NotNull(type, nameof(type)); + + return Builder.DiscriminatorBuilder(Property(type, name).GetInfrastructure(), ConfigurationSource.Explicit); + } + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The type of values stored in the discriminator column. + /// The name of the discriminator column. + /// A builder that allows the discriminator column to be configured. + public virtual DiscriminatorBuilder HasDiscriminator([NotNull] string name) + { + Check.NotEmpty(name, nameof(name)); + + return new DiscriminatorBuilder( + Builder.DiscriminatorBuilder(Property(typeof(TDiscriminator), name).GetInfrastructure(), ConfigurationSource.Explicit)); + } + #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 2c3c40c90de..959f2ed7be3 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -755,6 +755,25 @@ public virtual DataBuilder HasData([NotNull] IEnumerable data) return new DataBuilder(); } - private InternalEntityTypeBuilder Builder => this.GetInfrastructure(); + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The type of values stored in the discriminator column. + /// + /// A lambda expression representing the property to be used as the discriminator ( + /// blog => blog.Discriminator). + /// + /// A builder that allows the discriminator column to be configured. + public virtual DiscriminatorBuilder HasDiscriminator( + [NotNull] Expression> propertyExpression) + { + Check.NotNull(propertyExpression, nameof(propertyExpression)); + + return new DiscriminatorBuilder( + Builder.DiscriminatorBuilder(Property(propertyExpression).GetInfrastructure(), ConfigurationSource.Explicit)); + } + + private InternalEntityTypeBuilder Builder => this.GetInfrastructure(); } } diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs similarity index 100% rename from src/EFCore.Relational/Metadata/Builders/IConventionDiscriminatorBuilder.cs rename to src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index 6f18b1b003b..77c9afb471e 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -493,5 +493,99 @@ IConventionEntityTypeBuilder UsePropertyAccessMode( /// Indicates whether the configuration was specified using a data annotation. /// true if the given can be set. bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator column to be configured. + IConventionDiscriminatorBuilder HasDiscriminator(bool fromDataAnnotation = false); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The type of values stored in the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// null otherwise. + /// + IConventionDiscriminatorBuilder HasDiscriminator([NotNull] Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The name of the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// null otherwise. + /// + IConventionDiscriminatorBuilder HasDiscriminator([NotNull] string name, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The name of the discriminator column. + /// The type of values stored in the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// null otherwise. + /// + IConventionDiscriminatorBuilder HasDiscriminator([NotNull] string name, [NotNull] Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator column used to identify which entity type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The property mapped to the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// null otherwise. + /// + IConventionDiscriminatorBuilder HasDiscriminator([NotNull] MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Removes the discriminator property from this entity type. + /// This method is usually called when the entity type is no longer mapped to the same table as any other type in + /// the hierarchy or when this entity type is no longer the root type. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// null otherwise. + /// + IConventionEntityTypeBuilder HasNoDeclaredDiscriminator(bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator column can be configured. + /// + /// The name of the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the configuration can be applied. + bool CanSetDiscriminator([CanBeNull] string name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator column can be configured. + /// + /// The type of values stored in the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the configuration can be applied. + bool CanSetDiscriminator([CanBeNull] Type type, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator column can be configured. + /// + /// The type of values stored in the discriminator column. + /// The name of the discriminator column. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the configuration can be applied. + bool CanSetDiscriminator([NotNull] Type type, [NotNull] string name, bool fromDataAnnotation = false); } } diff --git a/src/EFCore/Metadata/Builders/IndexBuilder.cs b/src/EFCore/Metadata/Builders/IndexBuilder.cs index 61cb4049329..e8468f8e083 100644 --- a/src/EFCore/Metadata/Builders/IndexBuilder.cs +++ b/src/EFCore/Metadata/Builders/IndexBuilder.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// and it is not designed to be directly constructed in your application code. /// /// - public class IndexBuilder : IInfrastructure, IInfrastructure + public class IndexBuilder : IInfrastructure { private readonly InternalIndexBuilder _builder; @@ -46,11 +46,6 @@ public IndexBuilder([NotNull] IMutableIndex index) /// public virtual IMutableIndex Metadata => Builder.Metadata; - /// - /// The model that the index belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - /// /// Adds or updates an annotation on the index. If an annotation with the key specified in /// diff --git a/src/EFCore/Metadata/Builders/InvertibleRelationshipBuilderBase.cs b/src/EFCore/Metadata/Builders/InvertibleRelationshipBuilderBase.cs index 7a1f69165f2..98f95f9f292 100644 --- a/src/EFCore/Metadata/Builders/InvertibleRelationshipBuilderBase.cs +++ b/src/EFCore/Metadata/Builders/InvertibleRelationshipBuilderBase.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// /// Base class used for configuring an invertible relationship. /// - public abstract class InvertibleRelationshipBuilderBase : IInfrastructure, IInfrastructure + public abstract class InvertibleRelationshipBuilderBase : IInfrastructure { private readonly IReadOnlyList _foreignKeyProperties; private readonly IReadOnlyList _principalKeyProperties; @@ -125,11 +125,6 @@ protected InvertibleRelationshipBuilderBase( /// public virtual IMutableForeignKey Metadata => Builder.Metadata; - /// - /// The model that this relationship belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/KeyBuilder.cs b/src/EFCore/Metadata/Builders/KeyBuilder.cs index e80df1ef13c..39fac40ef91 100644 --- a/src/EFCore/Metadata/Builders/KeyBuilder.cs +++ b/src/EFCore/Metadata/Builders/KeyBuilder.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// and it is not designed to be directly constructed in your application code. /// /// - public class KeyBuilder : IInfrastructure, IInfrastructure + public class KeyBuilder : IInfrastructure { private readonly InternalKeyBuilder _builder; @@ -46,11 +46,6 @@ public KeyBuilder([NotNull] IMutableKey key) /// public virtual IMutableKey Metadata => Builder.Metadata; - /// - /// The model that the key belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - /// /// Adds or updates an annotation on the key. If an annotation with the key specified in /// diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 5d700f87484..c6f1353c3c8 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -21,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// and it is not designed to be directly constructed in your application code. /// /// - public class PropertyBuilder : IInfrastructure, IInfrastructure + public class PropertyBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -47,11 +47,6 @@ public PropertyBuilder([NotNull] IMutableProperty property) /// public virtual IMutableProperty Metadata => Builder.Metadata; - /// - /// The model that the property belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - /// /// Adds or updates an annotation on the property. If an annotation with the key specified in /// already exists its value will be updated. diff --git a/src/EFCore/Metadata/Builders/RelationshipBuilderBase.cs b/src/EFCore/Metadata/Builders/RelationshipBuilderBase.cs index 439a020b74a..492f5bc3ef3 100644 --- a/src/EFCore/Metadata/Builders/RelationshipBuilderBase.cs +++ b/src/EFCore/Metadata/Builders/RelationshipBuilderBase.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// /// Base class used for configuring a relationship. /// - public abstract class RelationshipBuilderBase : IInfrastructure, IInfrastructure + public abstract class RelationshipBuilderBase : IInfrastructure { private readonly IReadOnlyList _foreignKeyProperties; private readonly IReadOnlyList _principalKeyProperties; @@ -59,18 +59,18 @@ protected RelationshipBuilderBase( DependentEntityType = oldBuilder.DependentEntityType; _foreignKeyProperties = foreignKeySet ? builder.Metadata.Properties - : ((EntityType)DependentEntityType).Builder.GetActualProperties(oldBuilder._foreignKeyProperties, null); + : ((EntityType)oldBuilder.DependentEntityType).Builder.GetActualProperties(oldBuilder._foreignKeyProperties, null); _principalKeyProperties = principalKeySet ? builder.Metadata.PrincipalKey.Properties - : ((EntityType)PrincipalEntityType).Builder.GetActualProperties(oldBuilder._principalKeyProperties, null); + : ((EntityType)oldBuilder.PrincipalEntityType).Builder.GetActualProperties(oldBuilder._principalKeyProperties, null); _required = requiredSet ? builder.Metadata.IsRequired : oldBuilder._required; var foreignKey = builder.Metadata; ForeignKey.AreCompatible( - (EntityType)PrincipalEntityType, - (EntityType)DependentEntityType, + (EntityType)oldBuilder.PrincipalEntityType, + (EntityType)oldBuilder.DependentEntityType, foreignKey.DependentToPrincipal?.GetIdentifyingMemberInfo(), foreignKey.PrincipalToDependent?.GetIdentifyingMemberInfo(), _foreignKeyProperties, @@ -104,11 +104,6 @@ protected RelationshipBuilderBase( /// public virtual IMutableForeignKey Metadata => Builder.Metadata; - /// - /// The model that this relationship belongs to. - /// - IMutableModel IInfrastructure.Instance => Builder.ModelBuilder.Metadata; - /// /// Gets the internal builder being used to configure this relationship. /// diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index f8af6e8b155..e450a46bf84 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -88,7 +88,9 @@ var servicePropertyDiscoveryConvention conventionSet.EntityTypeIgnoredConventions.Add(inversePropertyAttributeConvention); + var discriminatorConvention = new DiscriminatorConvention(logger); conventionSet.EntityTypeRemovedConventions.Add(new OwnedTypesConvention(logger)); + conventionSet.EntityTypeRemovedConventions.Add(discriminatorConvention); var foreignKeyIndexConvention = new ForeignKeyIndexConvention(logger); var valueGeneratorConvention = new ValueGeneratorConvention(logger); @@ -100,6 +102,7 @@ var servicePropertyDiscoveryConvention conventionSet.BaseEntityTypeChangedConventions.Add(relationshipDiscoveryConvention); conventionSet.BaseEntityTypeChangedConventions.Add(foreignKeyIndexConvention); conventionSet.BaseEntityTypeChangedConventions.Add(valueGeneratorConvention); + conventionSet.BaseEntityTypeChangedConventions.Add(discriminatorConvention); var foreignKeyPropertyDiscoveryConvention = new ForeignKeyPropertyDiscoveryConvention(logger); diff --git a/src/EFCore.Relational/Metadata/Conventions/Internal/DiscriminatorConvention.cs b/src/EFCore/Metadata/Conventions/Internal/DiscriminatorConvention.cs similarity index 80% rename from src/EFCore.Relational/Metadata/Conventions/Internal/DiscriminatorConvention.cs rename to src/EFCore/Metadata/Conventions/Internal/DiscriminatorConvention.cs index 427b85d1b26..426212d1cc8 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Internal/DiscriminatorConvention.cs +++ b/src/EFCore/Metadata/Conventions/Internal/DiscriminatorConvention.cs @@ -49,9 +49,10 @@ public virtual bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityTyp && oldBaseType.BaseType == null && oldBaseType.GetDirectlyDerivedTypes().Count == 0) { - oldBaseType.Builder?.HasNoDeclaredDiscriminator(); + ((IConventionEntityTypeBuilder)oldBaseType.Builder)?.HasNoDeclaredDiscriminator(); } + IConventionEntityTypeBuilder conventionEntityTypeBuilder = entityTypeBuilder; var entityType = entityTypeBuilder.Metadata; var derivedEntityTypes = entityType.GetDerivedTypes().ToList(); @@ -60,20 +61,20 @@ public virtual bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityTyp { if (derivedEntityTypes.Count == 0) { - entityTypeBuilder.HasNoDeclaredDiscriminator(); + conventionEntityTypeBuilder.HasNoDeclaredDiscriminator(); return true; } - discriminator = entityTypeBuilder.HasDiscriminator(typeof(string)); + discriminator = conventionEntityTypeBuilder.HasDiscriminator(typeof(string)); } else { - if (entityTypeBuilder.HasNoDeclaredDiscriminator() == null) + if (conventionEntityTypeBuilder.HasNoDeclaredDiscriminator() == null) { return true; } - var rootTypeBuilder = entityType.RootType().Builder; + IConventionEntityTypeBuilder rootTypeBuilder = entityType.RootType().Builder; discriminator = rootTypeBuilder?.HasDiscriminator(typeof(string)); if (entityType.BaseType.BaseType == null) @@ -104,14 +105,20 @@ public virtual bool Apply(InternalModelBuilder modelBuilder, EntityType type) && oldBaseType.BaseType == null && oldBaseType.GetDirectlyDerivedTypes().Count == 0) { - oldBaseType.Builder?.HasNoDeclaredDiscriminator(); + ((IConventionEntityTypeBuilder)oldBaseType.Builder)?.HasNoDeclaredDiscriminator(); } return true; } - private static void SetDefaultDiscriminatorValues( - IReadOnlyList entityTypes, IConventionDiscriminatorBuilder discriminator) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected void SetDefaultDiscriminatorValues( + IEnumerable entityTypes, IConventionDiscriminatorBuilder discriminator) { foreach (var entityType in entityTypes) { diff --git a/src/EFCore/Metadata/CoreAnnotationNames.cs b/src/EFCore/Metadata/CoreAnnotationNames.cs index 933013cb7b1..4926444bb30 100644 --- a/src/EFCore/Metadata/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/CoreAnnotationNames.cs @@ -50,6 +50,16 @@ public static class CoreAnnotationNames /// public const string OwnedTypes = "OwnedTypes"; + /// + /// The name for discriminator property annotations. + /// + public const string DiscriminatorProperty = "DiscriminatorProperty"; + + /// + /// The name for discriminator value annotations. + /// + public const string DiscriminatorValue = "DiscriminatorValue"; + /// /// Indicates the to use for the annotated item. /// diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 13adb481864..eaf787c1aca 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -37,7 +37,7 @@ public interface IEntityType : ITypeBase /// Returns null if no key is defined for the given properties. /// /// The properties that make up the key. - /// The key, or null if none is defined. + /// The key, or null if none is defined. IKey FindKey([NotNull] IReadOnlyList properties); /// @@ -57,7 +57,7 @@ public interface IEntityType : ITypeBase /// is defined on when the relationship targets a derived type in an inheritance hierarchy (since the key is defined on the /// base type of the hierarchy). /// - /// The foreign key, or null if none is defined. + /// The foreign key, or null if none is defined. IForeignKey FindForeignKey( [NotNull] IReadOnlyList properties, [NotNull] IKey principalKey, diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 5eea1c264e4..c9b889602bc 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2486,6 +2486,64 @@ public virtual void SetDefiningQuery([CanBeNull] LambdaExpression definingQuery, this.SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, definingQuery, configurationSource); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetDiscriminatorProperty([CanBeNull] IProperty property, ConfigurationSource configurationSource) + { + CheckDiscriminatorProperty(property); + + if (property != null + && !property.ClrType.IsInstanceOfType(this.GetDiscriminatorValue())) + { + foreach (var derivedType in GetDerivedTypesInclusive()) + { + ((IMutableEntityType)derivedType).RemoveDiscriminatorValue(); + } + } + + this.SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource); + } + + private void CheckDiscriminatorProperty(IProperty property) + { + if (property != null) + { + if (this != RootType()) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyMustBeOnRoot(this.DisplayName())); + } + + if (property.DeclaringEntityType != this) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyNotFound(property.Name, this.DisplayName())); + } + } + } + + public virtual void CheckDiscriminatorValue(IEntityType entityType, object value) + { + if (value != null + && entityType.GetDiscriminatorProperty() == null) + { + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorForValue(entityType.DisplayName(), entityType.RootType().DisplayName())); + } + + if (value != null + && !entityType.GetDiscriminatorProperty().ClrType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo())) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorValueIncompatible( + value, entityType.GetDiscriminatorProperty().Name, entityType.GetDiscriminatorProperty().ClrType)); + } + } + #endregion #region Explicit interface implementations diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index ea2fe733f9a..dc65371623b 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Internal { @@ -3223,6 +3224,85 @@ public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessM => configurationSource.Overrides(Metadata.GetPropertyAccessModeConfigurationSource()) || Metadata.GetPropertyAccessMode() == propertyAccessMode; + private static readonly string DefaultDiscriminatorName = "Discriminator"; + + // ReSharper disable once InconsistentNaming + private static readonly Type DefaultDiscriminatorType = typeof(string); + + /// + /// 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 GetOrCreateDiscriminatorProperty(Type type, string name, bool fromDataAnnotation) + { + var discriminatorProperty = Metadata.GetDiscriminatorProperty(); + if ((name != null && discriminatorProperty?.Name != name) + || (type != null && discriminatorProperty?.ClrType != type)) + { + discriminatorProperty = null; + } + + var configurationSource = fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention; + return Metadata.RootType().Builder.Property( + type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, + name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, + configurationSource, + typeConfigurationSource: type != null ? configurationSource : (ConfigurationSource?)null); + } + + public virtual DiscriminatorBuilder DiscriminatorBuilder( + InternalPropertyBuilder discriminatorPropertyBuilder, + ConfigurationSource configurationSource) + { + if (discriminatorPropertyBuilder == null) + { + return null; + } + + var rootTypeBuilder = Metadata.RootType().Builder; + var discriminatorProperty = discriminatorPropertyBuilder.Metadata; + // Make sure the property is on the root type + discriminatorPropertyBuilder = rootTypeBuilder.Property( + discriminatorProperty.ClrType, discriminatorProperty.Name, configurationSource, null); + + var oldDiscriminatorProperty = Metadata.GetDiscriminatorProperty() as Property; + if (oldDiscriminatorProperty?.Builder != null + && oldDiscriminatorProperty != discriminatorProperty) + { + oldDiscriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedShadowProperties( + new[] + { + oldDiscriminatorProperty + }); + + if (oldDiscriminatorProperty.Builder != null) + { + oldDiscriminatorProperty.Builder.IsRequired(null, configurationSource); + oldDiscriminatorProperty.Builder.HasValueGenerator((Type)null, configurationSource); + } + } + + rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, configurationSource); + discriminatorPropertyBuilder.IsRequired(true, configurationSource); + discriminatorPropertyBuilder.HasValueGenerator(DiscriminatorValueGenerator.Factory, configurationSource); + + return new DiscriminatorBuilder(Metadata); + } + + private bool CanSetDiscriminator( + IProperty discriminatorProperty, + string name, + Type discriminatorType, + bool fromDataAnnotation) + => discriminatorProperty == null + || ((name != null || discriminatorType != null) + && (name == null || discriminatorProperty.Name == name) + && (discriminatorType == null || discriminatorProperty.ClrType == discriminatorType)) + || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + .Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource()); + IConventionEntityType IConventionEntityTypeBuilder.Metadata => Metadata; IConventionEntityTypeBuilder IConventionEntityTypeBuilder.HasBaseType(IConventionEntityType baseEntityType, bool fromDataAnnotation) @@ -3418,5 +3498,79 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.UsePropertyAccessMode( bool IConventionEntityTypeBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + IConventionDiscriminatorBuilder IConventionEntityTypeBuilder.HasDiscriminator(bool fromDataAnnotation) + => DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name: null, fromDataAnnotation: false), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + IConventionDiscriminatorBuilder IConventionEntityTypeBuilder.HasDiscriminator(Type type, bool fromDataAnnotation) + => ((IConventionEntityTypeBuilder)this).CanSetDiscriminator(type, fromDataAnnotation) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type, name: null, fromDataAnnotation), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + : null; + + IConventionDiscriminatorBuilder IConventionEntityTypeBuilder.HasDiscriminator(string name, bool fromDataAnnotation) + => ((IConventionEntityTypeBuilder)this).CanSetDiscriminator(name, fromDataAnnotation) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name, fromDataAnnotation), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + : null; + + IConventionDiscriminatorBuilder IConventionEntityTypeBuilder.HasDiscriminator(string name, Type type, bool fromDataAnnotation) + => ((IConventionEntityTypeBuilder)this).CanSetDiscriminator(type, name, fromDataAnnotation) + ? DiscriminatorBuilder( + Metadata.RootType().Builder.Property( + type, name, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + : null; + + IConventionDiscriminatorBuilder IConventionEntityTypeBuilder.HasDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) + => ((IConventionEntityTypeBuilder)this).CanSetDiscriminator(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), fromDataAnnotation) + ? DiscriminatorBuilder( + Metadata.RootType().Builder.Property( + memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + : null; + + IConventionEntityTypeBuilder IConventionEntityTypeBuilder.HasNoDeclaredDiscriminator(bool fromDataAnnotation) + { + var discriminatorName = (string)Metadata[CoreAnnotationNames.DiscriminatorProperty]; + if (discriminatorName == null) + { + return this; + } + + var discriminatorProperty = Metadata.FindProperty(discriminatorName); + if (discriminatorProperty != null) + { + if (!CanSetDiscriminator(discriminatorProperty, null, null, fromDataAnnotation)) + { + return null; + } + + discriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedShadowProperties( + new[] + { + discriminatorProperty + }); + } + + Metadata.SetDiscriminatorProperty(null, fromDataAnnotation); + return this; + } + + bool IConventionEntityTypeBuilder.CanSetDiscriminator(string name, bool fromDataAnnotation) + => CanSetDiscriminator( + Metadata.GetDiscriminatorProperty(), name, discriminatorType: null, + fromDataAnnotation); + + bool IConventionEntityTypeBuilder.CanSetDiscriminator(Type type, bool fromDataAnnotation) + => CanSetDiscriminator(Metadata.GetDiscriminatorProperty(), name: null, type, fromDataAnnotation); + + bool IConventionEntityTypeBuilder.CanSetDiscriminator(Type type, string name, bool fromDataAnnotation) + => CanSetDiscriminator(Metadata.GetDiscriminatorProperty(), name, type, fromDataAnnotation); } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 15af48bc1fe..0853dc53198 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1996,6 +1996,70 @@ public static string FieldNameMismatch([CanBeNull] object field, [CanBeNull] obj GetString("FieldNameMismatch", nameof(field), nameof(entityType), nameof(property)), field, entityType, property); + /// + /// Cannot configure the discriminator value for entity type '{entityType}' because it doesn't derive from '{rootEntityType}'. + /// + public static string DiscriminatorEntityTypeNotDerived([CanBeNull] object entityType, [CanBeNull] object rootEntityType) + => string.Format( + GetString("DiscriminatorEntityTypeNotDerived", nameof(entityType), nameof(rootEntityType)), + entityType, rootEntityType); + + /// + /// A discriminator property cannot be set for the entity type '{entityType}' because it is not the root of an inheritance hierarchy. + /// + public static string DiscriminatorPropertyMustBeOnRoot([CanBeNull] object entityType) + => string.Format( + GetString("DiscriminatorPropertyMustBeOnRoot", nameof(entityType)), + entityType); + + /// + /// Unable to set property '{property}' as a discriminator for entity type '{entityType}' because it is not a property of '{entityType}'. + /// + public static string DiscriminatorPropertyNotFound([CanBeNull] object property, [CanBeNull] object entityType) + => string.Format( + GetString("DiscriminatorPropertyNotFound", nameof(property), nameof(entityType)), + property, entityType); + + /// + /// Cannot set discriminator value '{value}' for discriminator property '{discriminator}' because it is not assignable to property of type '{discriminatorType}'. + /// + public static string DiscriminatorValueIncompatible([CanBeNull] object value, [CanBeNull] object discriminator, [CanBeNull] object discriminatorType) + => string.Format( + GetString("DiscriminatorValueIncompatible", nameof(value), nameof(discriminator), nameof(discriminatorType)), + value, discriminator, discriminatorType); + + /// + /// The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy needs to have a unique discriminator value. + /// + public static string DuplicateDiscriminatorValue([CanBeNull] object entityType1, [CanBeNull] object discriminatorValue, [CanBeNull] object entityType2) + => string.Format( + GetString("DuplicateDiscriminatorValue", nameof(entityType1), nameof(discriminatorValue), nameof(entityType2)), + entityType1, discriminatorValue, entityType2); + + /// + /// Cannot set discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property set. + /// + public static string NoDiscriminatorForValue([CanBeNull] object entityType, [CanBeNull] object rootEntityType) + => string.Format( + GetString("NoDiscriminatorForValue", nameof(entityType), nameof(rootEntityType)), + entityType, rootEntityType); + + /// + /// The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator property configured. + /// + public static string NoDiscriminatorProperty([CanBeNull] object entityType) + => string.Format( + GetString("NoDiscriminatorProperty", nameof(entityType)), + entityType); + + /// + /// The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator value configured. + /// + public static string NoDiscriminatorValue([CanBeNull] object entityType) + => string.Format( + GetString("NoDiscriminatorValue", nameof(entityType)), + entityType); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index eddbc163fe8..216a9eaca90 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1129,4 +1129,28 @@ The specified field '{field}' cannot be used for the property '{entityType}.{property}' because it does not match the property name. + + Cannot configure the discriminator value for entity type '{entityType}' because it doesn't derive from '{rootEntityType}'. + + + A discriminator property cannot be set for the entity type '{entityType}' because it is not the root of an inheritance hierarchy. + + + Unable to set property '{property}' as a discriminator for entity type '{entityType}' because it is not a property of '{entityType}'. + + + Cannot set discriminator value '{value}' for discriminator property '{discriminator}' because it is not assignable to property of type '{discriminatorType}'. + + + The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy needs to have a unique discriminator value. + + + Cannot set discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property set. + + + The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator property configured. + + + The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator value configured. + \ No newline at end of file diff --git a/src/EFCore.Relational/ValueGeneration/Internal/DiscriminatorValueGenerator.cs b/src/EFCore/ValueGeneration/Internal/DiscriminatorValueGenerator.cs similarity index 100% rename from src/EFCore.Relational/ValueGeneration/Internal/DiscriminatorValueGenerator.cs rename to src/EFCore/ValueGeneration/Internal/DiscriminatorValueGenerator.cs diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosEndToEndTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosEndToEndTest.cs index dab95f13e43..81fe4bc114a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CosmosEndToEndTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CosmosEndToEndTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Cosmos.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; @@ -271,8 +270,8 @@ public ExtraCustomerContext(DbContextOptions dbContextOptions) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.HasDefaultContainerName(nameof(CustomerContext)); - modelBuilder.Entity().Property("EMail").ToProperty("e-mail"); + modelBuilder.ForCosmosHasDefaultContainerName(nameof(CustomerContext)); + modelBuilder.Entity().Property("EMail").ForCosmosToProperty("e-mail"); } } @@ -318,7 +317,7 @@ public UnmpappedCustomerContext(DbContextOptions dbContextOptions) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(c => c.Name).ToProperty(""); + modelBuilder.Entity().Property(c => c.Name).ForCosmosToProperty(""); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs b/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs index 8dc16b72abf..fbc15e1d07b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs @@ -374,7 +374,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( eb => eb.OwnsMany(v => v.Addresses, b => { - b.ToProperty("Stored Addresses"); + b.ForCosmosToProperty("Stored Addresses"); b.HasKey(v => new { v.Street, v.City }); })); } diff --git a/test/EFCore.Cosmos.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs b/test/EFCore.Cosmos.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs deleted file mode 100644 index b488a95168e..00000000000 --- a/test/EFCore.Cosmos.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.EntityFrameworkCore.Cosmos.TestUtilities; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Xunit; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal -{ - public class DiscriminatorConventionTest - { - [Fact] - public void Creates_discriminator_property_and_sets_discriminator_value_for_entityType() - { - var entityTypeBuilder = CreateInternalEntityTypeBuilder(); - var discriminatorProperty = entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty; - - Assert.NotNull(discriminatorProperty); - Assert.Equal("Discriminator", discriminatorProperty.Name); - Assert.Equal(typeof(string), discriminatorProperty.ClrType); - Assert.Equal(typeof(Entity).Name, entityTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - } - - [Fact] - public void Sets_discriminator_for_two_level_hierarchy() - { - var entityTypeBuilder = CreateInternalEntityTypeBuilder(); - - var baseTypeBuilder = entityTypeBuilder.ModelBuilder.Entity(typeof(EntityBase), ConfigurationSource.Explicit); - var discriminator = entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty; - - Assert.NotNull(discriminator); - Assert.Same(discriminator, baseTypeBuilder.Metadata.Cosmos().DiscriminatorProperty); - Assert.Equal(typeof(EntityBase).Name, baseTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(Entity).Name, entityTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - - Assert.NotNull(entityTypeBuilder.HasBaseType((Type)null, ConfigurationSource.DataAnnotation)); - Assert.NotSame( - baseTypeBuilder.Metadata.Cosmos().DiscriminatorProperty, - entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty); - Assert.Equal(typeof(EntityBase).Name, baseTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(Entity).Name, entityTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - } - - [Fact] - public void Sets_discriminator_for_three_level_hierarchy() - { - var entityTypeBuilder = CreateInternalEntityTypeBuilder(); - var baseTypeBuilder = entityTypeBuilder.ModelBuilder.Entity(typeof(EntityBase), ConfigurationSource.Explicit); - var derivedTypeBuilder = entityTypeBuilder.ModelBuilder.Entity(typeof(DerivedEntity), ConfigurationSource.Explicit); - - var baseDiscriminator = baseTypeBuilder.Metadata.Cosmos().DiscriminatorProperty; - var discriminator = entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty; - var derivedDiscriminator = derivedTypeBuilder.Metadata.Cosmos().DiscriminatorProperty; - - Assert.NotNull(discriminator); - Assert.Same(discriminator, baseDiscriminator); - Assert.Same(discriminator, derivedDiscriminator); - Assert.Equal(typeof(EntityBase).Name, baseTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(Entity).Name, entityTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(DerivedEntity).Name, derivedTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - - Assert.NotNull(entityTypeBuilder.HasBaseType((Type)null, ConfigurationSource.DataAnnotation)); - Assert.NotSame( - baseTypeBuilder.Metadata.Cosmos().DiscriminatorProperty, - entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty); - Assert.Same( - entityTypeBuilder.Metadata.Cosmos().DiscriminatorProperty, - derivedTypeBuilder.Metadata.Cosmos().DiscriminatorProperty); - Assert.Equal(typeof(EntityBase).Name, baseTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(Entity).Name, entityTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - Assert.Equal(typeof(DerivedEntity).Name, derivedTypeBuilder.Metadata.Cosmos().DiscriminatorValue); - } - - private class EntityBase - { - } - - private class Entity : EntityBase - { - public int Id { get; set; } - - public string Name { get; set; } - } - - private class DerivedEntity : Entity - { - } - - private static InternalEntityTypeBuilder CreateInternalEntityTypeBuilder() - { - var modelBuilder = CosmosTestHelpers.Instance.CreateConventionBuilder().GetInfrastructure(); - - return modelBuilder.Entity(typeof(T), ConfigurationSource.Explicit); - } - } -} diff --git a/test/EFCore.Cosmos.Tests/Metadata/CosmosBuilderExtensionsTest.cs b/test/EFCore.Cosmos.Tests/Metadata/CosmosBuilderExtensionsTest.cs index 0cda5fd8287..6cc9d4f6bc5 100644 --- a/test/EFCore.Cosmos.Tests/Metadata/CosmosBuilderExtensionsTest.cs +++ b/test/EFCore.Cosmos.Tests/Metadata/CosmosBuilderExtensionsTest.cs @@ -18,19 +18,19 @@ public void Default_container_name_is_used_if_not_set() var entityType = modelBuilder.Model.FindEntityType(typeof(Customer)); - Assert.Equal("Customer", entityType.Cosmos().ContainerName); - Assert.Null(modelBuilder.Model.Cosmos().DefaultContainerName); + Assert.Equal("Customer", entityType.GetCosmosContainerName()); + Assert.Null(modelBuilder.Model.GetCosmosDefaultContainerName()); - modelBuilder.HasDefaultContainerName("db0"); + modelBuilder.ForCosmosHasDefaultContainerName("db0"); - Assert.Equal("db0", entityType.Cosmos().ContainerName); - Assert.Equal("db0", modelBuilder.Model.Cosmos().DefaultContainerName); + Assert.Equal("db0", entityType.GetCosmosContainerName()); + Assert.Equal("db0", modelBuilder.Model.GetCosmosDefaultContainerName()); modelBuilder .Entity() - .ToContainer("db1"); + .ForCosmosToContainer("db1"); - Assert.Equal("db1", entityType.Cosmos().ContainerName); + Assert.Equal("db1", entityType.GetCosmosContainerName()); } protected virtual ModelBuilder CreateConventionModelBuilder() => CosmosTestHelpers.Instance.CreateConventionBuilder(); diff --git a/test/EFCore.Cosmos.Tests/Metadata/CosmosMetadataExtensionsTest.cs b/test/EFCore.Cosmos.Tests/Metadata/CosmosMetadataExtensionsTest.cs index b08f8ba418b..4a0e17c21da 100644 --- a/test/EFCore.Cosmos.Tests/Metadata/CosmosMetadataExtensionsTest.cs +++ b/test/EFCore.Cosmos.Tests/Metadata/CosmosMetadataExtensionsTest.cs @@ -17,16 +17,16 @@ public void Can_get_and_set_collection_name() var entityType = modelBuilder .Entity(); - Assert.Equal(nameof(Customer), entityType.Metadata.Cosmos().ContainerName); + Assert.Equal(nameof(Customer), entityType.Metadata.GetCosmosContainerName()); - entityType.ToContainer("Customizer"); - Assert.Equal("Customizer", entityType.Metadata.Cosmos().ContainerName); + entityType.ForCosmosToContainer("Customizer"); + Assert.Equal("Customizer", entityType.Metadata.GetCosmosContainerName()); - entityType.ToContainer(null); - Assert.Equal(nameof(Customer), entityType.Metadata.Cosmos().ContainerName); + entityType.ForCosmosToContainer(null); + Assert.Equal(nameof(Customer), entityType.Metadata.GetCosmosContainerName()); - modelBuilder.HasDefaultContainerName("Unicorn"); - Assert.Equal("Unicorn", entityType.Metadata.Cosmos().ContainerName); + modelBuilder.ForCosmosHasDefaultContainerName("Unicorn"); + Assert.Equal("Unicorn", entityType.Metadata.GetCosmosContainerName()); } private class Customer diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index c2ee3d824bd..554e74b4990 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -89,15 +89,15 @@ public void Test_new_annotations_handled_for_entity_types() + @"(""WithAnnotations"",""MySchema"");" + _nl) }, { - RelationalAnnotationNames.DiscriminatorProperty, + CoreAnnotationNames.DiscriminatorProperty, ("Id", - _toTable + _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.HasDiscriminator) + _toTable + _nl + "modelBuilder.HasDiscriminator" + @"(""Id"");" + _nl) }, { - RelationalAnnotationNames.DiscriminatorValue, + CoreAnnotationNames.DiscriminatorValue, ("MyDiscriminatorValue", - _toTable + _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.HasDiscriminator) + _toTable + _nl + "modelBuilder.HasDiscriminator" + "()." + nameof(DiscriminatorBuilder.HasValue) + @"(""MyDiscriminatorValue"");" + _nl) } }; @@ -130,8 +130,8 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.Name, RelationalAnnotationNames.SequencePrefix, RelationalAnnotationNames.CheckConstraints, - RelationalAnnotationNames.DiscriminatorProperty, - RelationalAnnotationNames.DiscriminatorValue, + CoreAnnotationNames.DiscriminatorProperty, + CoreAnnotationNames.DiscriminatorValue, RelationalAnnotationNames.Filter, RelationalAnnotationNames.DbFunction, RelationalAnnotationNames.MaxIdentifierLength diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 1c47740a85e..3af8ed1b3b0 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -519,12 +519,12 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() });"), o => { - Assert.Equal("Discriminator", o.FindEntityType(typeof(BaseEntity))[RelationalAnnotationNames.DiscriminatorProperty]); - Assert.Equal("BaseEntity", o.FindEntityType(typeof(BaseEntity))[RelationalAnnotationNames.DiscriminatorValue]); + Assert.Equal("Discriminator", o.FindEntityType(typeof(BaseEntity))[CoreAnnotationNames.DiscriminatorProperty]); + Assert.Equal("BaseEntity", o.FindEntityType(typeof(BaseEntity))[CoreAnnotationNames.DiscriminatorValue]); Assert.Equal( "AnotherDerivedEntity", - o.FindEntityType(typeof(AnotherDerivedEntity))[RelationalAnnotationNames.DiscriminatorValue]); - Assert.Equal("DerivedEntity", o.FindEntityType(typeof(DerivedEntity))[RelationalAnnotationNames.DiscriminatorValue]); + o.FindEntityType(typeof(AnotherDerivedEntity))[CoreAnnotationNames.DiscriminatorValue]); + Assert.Equal("DerivedEntity", o.FindEntityType(typeof(DerivedEntity))[CoreAnnotationNames.DiscriminatorValue]); }); } diff --git a/test/EFCore.Relational.Tests/RelationalModelValidatorDependenciesTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorDependenciesTest.cs similarity index 84% rename from test/EFCore.Relational.Tests/RelationalModelValidatorDependenciesTest.cs rename to test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorDependenciesTest.cs index 4303adbc26f..821d0f1e3b5 100644 --- a/test/EFCore.Relational.Tests/RelationalModelValidatorDependenciesTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorDependenciesTest.cs @@ -1,11 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; -namespace Microsoft.EntityFrameworkCore +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.Infrastructure { public class RelationalModelValidatorDependenciesTest { diff --git a/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs similarity index 94% rename from test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs rename to test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index c26424f3d90..0d2d8518641 100644 --- a/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -7,7 +7,6 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; @@ -16,7 +15,7 @@ using Xunit; // ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore.Infrastructure { public class RelationalModelValidatorTest : ModelValidatorTestBase { @@ -994,83 +993,6 @@ public virtual void Passes_for_non_hierarchical_model() Validate(model); } - [Fact] - public virtual void Detects_missing_discriminator_property() - { - var model = CreateConventionlessModelBuilder().Model; - - var entityA = model.AddEntityType(typeof(A)); - SetPrimaryKey(entityA); - AddProperties(entityA); - - var entityC = model.AddEntityType(typeof(C)); - entityC.BaseType = entityA; - - VerifyError(RelationalStrings.NoDiscriminatorProperty(entityA.DisplayName()), model); - } - - [Fact] - public virtual void Detects_missing_discriminator_value_on_base() - { - var model = CreateConventionlessModelBuilder().Model; - - var entityA = model.AddEntityType(typeof(A)); - SetPrimaryKey(entityA); - AddProperties(entityA); - - var entityC = model.AddEntityType(typeof(C)); - SetBaseType(entityC, entityA); - - entityA.SetDiscriminatorProperty(entityA.AddProperty("D", typeof(int))); - entityC.SetDiscriminatorValue(1); - - VerifyError(RelationalStrings.NoDiscriminatorValue(entityA.DisplayName()), model); - } - - [Fact] - public virtual void Detects_missing_discriminator_value_on_leaf() - { - var model = CreateConventionlessModelBuilder().Model; - - var entityAbstract = model.AddEntityType(typeof(Abstract)); - SetPrimaryKey(entityAbstract); - AddProperties(entityAbstract); - - var entityGeneric = model.AddEntityType(typeof(Generic)); - SetBaseType(entityGeneric, entityAbstract); - - entityAbstract.SetDiscriminatorProperty(entityAbstract.AddProperty("D", typeof(int))); - entityAbstract.SetDiscriminatorValue(0); - - VerifyError(RelationalStrings.NoDiscriminatorValue(entityGeneric.DisplayName()), model); - } - - [Fact] - public virtual void Detects_missing_non_string_discriminator_values() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity(); - modelBuilder.Entity().HasDiscriminator("ClassType") - .HasValue(0) - .HasValue(1); - - var model = modelBuilder.Model; - VerifyError(RelationalStrings.NoDiscriminatorValue(typeof(C).Name), model); - } - - [Fact] - public virtual void Detects_duplicate_discriminator_values() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity().HasDiscriminator("ClassType") - .HasValue(1) - .HasValue(1) - .HasValue(2); - - var model = modelBuilder.Model; - VerifyError(RelationalStrings.DuplicateDiscriminatorValue(typeof(C).Name, 1, typeof(A).Name), model); - } - [Fact] public virtual void Does_not_detect_missing_discriminator_value_for_abstract_class() { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs index 42cae102626..f9a92a46e33 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs @@ -178,226 +178,22 @@ public void Can_access_relationship() } [Fact] - public void Can_access_discriminator() + public void Can_access_check_constraint() { var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); + var model = typeBuilder.Metadata.Model; - Assert.NotNull(typeBuilder.HasDiscriminator()); - Assert.Equal("Discriminator", typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s > p")); + Assert.Equal("Splew", model.GetCheckConstraints().Single().Name); + Assert.Equal("s > p", model.GetCheckConstraints().Single().Sql); - Assert.NotNull(typeBuilder.HasNoDeclaredDiscriminator()); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorProperty()); - Assert.Equal(0, typeBuilder.Metadata.GetProperties().Count()); + Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)); + Assert.Equal("Splew", model.GetCheckConstraints().Single().Name); + Assert.Equal("s < p", model.GetCheckConstraints().Single().Sql); - Assert.NotNull(typeBuilder.HasDiscriminator("Splod", typeof(int?))); - Assert.Equal("Splod", typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(int?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - Assert.Equal("Splod", typeBuilder.Metadata.GetProperties().Single().Name); - - Assert.NotNull(typeBuilder.HasDiscriminator(Splot.SplowedProperty, fromDataAnnotation: true)); - Assert.Equal(Splot.SplowedProperty.Name, typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(int?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - Assert.Equal(Splot.SplowedProperty.Name, typeBuilder.Metadata.GetProperties().Single().Name); - - Assert.Null(typeBuilder.HasDiscriminator("Splew", typeof(int?))); - Assert.Equal(Splot.SplowedProperty.Name, typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(int?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - - Assert.NotNull(typeBuilder.HasDiscriminator(typeof(int), fromDataAnnotation: true)); - Assert.Null(typeBuilder.HasDiscriminator(typeof(long))); - Assert.Equal("Discriminator", typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(int), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - - Assert.Null(typeBuilder.HasNoDeclaredDiscriminator()); - } - - [Fact] - public void Discriminator_is_not_set_if_ignored() - { - var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); - typeBuilder.Ignore("Splod", ConfigurationSource.Explicit); - - Assert.NotNull(typeBuilder.HasDiscriminator("Splew", typeof(string))); - Assert.Equal("Splew", typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - - Assert.Null(typeBuilder.HasDiscriminator("Splod", typeof(int?))); - Assert.Equal("Splew", typeBuilder.Metadata.GetDiscriminatorProperty().Name); - Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); - } - - [Fact] - public void Discriminator_is_not_set_if_default_ignored() - { - var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); - typeBuilder.Ignore("Discriminator", ConfigurationSource.Explicit); - - Assert.Null(typeBuilder.HasDiscriminator()); - Assert.Equal(0, typeBuilder.Metadata.GetProperties().Count()); - } - - [Fact] - public void Can_access_discriminator_value() - { - var typeBuilder = CreateBuilder().Entity("Splot", ConfigurationSource.Convention); - var derivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention); - derivedTypeBuilder.HasBaseType(typeBuilder.Metadata, ConfigurationSource.DataAnnotation); - var otherDerivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention); - - Assert.NotNull(typeBuilder.HasDiscriminator()); - Assert.Equal(1, typeBuilder.Metadata.GetDeclaredProperties().Count()); - Assert.Equal(0, derivedTypeBuilder.Metadata.GetDeclaredProperties().Count()); - - var discriminatorBuilder = typeBuilder - .HasDiscriminator(Splot.SplowedProperty.Name, Splot.SplowedProperty.PropertyType); - Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); - Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); - Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); - - Assert.Same(typeBuilder.Metadata, otherDerivedTypeBuilder.Metadata.BaseType); - Assert.Equal(1, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal( - 2, typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Equal( - 3, typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Same(typeBuilder.Metadata, typeBuilder.ModelBuilder.Metadata.FindEntityType("Splow").BaseType); - - discriminatorBuilder = typeBuilder.HasDiscriminator(fromDataAnnotation: true); - Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 4, fromDataAnnotation: true)); - Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 5, fromDataAnnotation: true)); - Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 6, fromDataAnnotation: true)); - Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal( - 5, typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Equal( - 6, typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - - discriminatorBuilder = typeBuilder.HasDiscriminator(); - Assert.Null(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); - Assert.Null(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); - Assert.Null(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); - Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal( - 5, typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Equal( - 6, typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - - Assert.NotNull(typeBuilder.HasNoDeclaredDiscriminator(fromDataAnnotation: true)); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorProperty()); - Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Empty(typeBuilder.Metadata.GetProperties()); - } - - [Fact] - public void Changing_discriminator_type_removes_values() - { - var typeBuilder = CreateBuilder().Entity("Splot", ConfigurationSource.Convention); - var derivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention); - derivedTypeBuilder.HasBaseType(typeBuilder.Metadata, ConfigurationSource.DataAnnotation); - var otherDerivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention); - - Assert.NotNull(typeBuilder.HasDiscriminator()); - Assert.Equal(1, typeBuilder.Metadata.GetDeclaredProperties().Count()); - Assert.Equal(0, derivedTypeBuilder.Metadata.GetDeclaredProperties().Count()); - - var discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(int)); - Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); - Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); - Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); - - discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(string)); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Null( - typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Null( - typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, "4")); - Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, "5")); - Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, "6")); - - discriminatorBuilder = typeBuilder.HasDiscriminator("Splotted", typeof(string)); - - Assert.NotNull(discriminatorBuilder); - Assert.Equal("4", typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal( - "5", typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Equal( - "6", typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - - discriminatorBuilder = typeBuilder.HasDiscriminator(typeof(int)); - - Assert.NotNull(discriminatorBuilder); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Null( - typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - Assert.Null( - typeBuilder.ModelBuilder.Entity("Splod", ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - } - - [Fact] - public void Can_access_discriminator_value_generic() - { - var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); - - var discriminatorBuilder = new DiscriminatorBuilder( - (DiscriminatorBuilder)typeBuilder.HasDiscriminator(Splot.SplowedProperty)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splot), 1)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splow), 2)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splod), 3)); - - var splow = typeBuilder.ModelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention).Metadata; - var splod = typeBuilder.ModelBuilder.Entity(typeof(Splod), ConfigurationSource.Convention).Metadata; - Assert.Equal(1, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal(2, splow.GetDiscriminatorValue()); - Assert.Equal( - 3, typeBuilder.ModelBuilder.Entity(typeof(Splod), ConfigurationSource.Convention) - .Metadata.GetDiscriminatorValue()); - - discriminatorBuilder = new DiscriminatorBuilder( - (DiscriminatorBuilder)typeBuilder.HasDiscriminator(fromDataAnnotation: true)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splot), 4)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splow), 5)); - Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splod), 6)); - Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal(5, splow.GetDiscriminatorValue()); - Assert.Equal(6, splod.GetDiscriminatorValue()); - - var conventionDiscriminatorBuilder = typeBuilder.HasDiscriminator(); - Assert.Null(conventionDiscriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); - Assert.Null(conventionDiscriminatorBuilder.HasValue(splow, 2)); - Assert.Null(conventionDiscriminatorBuilder.HasValue(splod, 3)); - Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Equal(5, splow.GetDiscriminatorValue()); - Assert.Equal(6, splod.GetDiscriminatorValue()); - } - - [Fact] - public void DiscriminatorValue_throws_if_base_cannot_be_set() - { - var modelBuilder = CreateBuilder(); - var typeBuilder = modelBuilder.Entity("Splot", ConfigurationSource.Convention); - var nonDerivedTypeBuilder = modelBuilder.Entity("Splow", ConfigurationSource.Convention); - nonDerivedTypeBuilder.HasBaseType( - modelBuilder.Entity("Splod", ConfigurationSource.Convention).Metadata, ConfigurationSource.Explicit); - - var discriminatorBuilder = typeBuilder.HasDiscriminator(); - Assert.Equal( - RelationalStrings.DiscriminatorEntityTypeNotDerived("Splow", "Splot"), - Assert.Throws(() - => discriminatorBuilder.HasValue(nonDerivedTypeBuilder.Metadata, "1")).Message); + Assert.Null(typeBuilder.HasCheckConstraint("Splew", "s > p")); + Assert.Equal("Splew", model.GetCheckConstraints().Single().Name); + Assert.Equal("s < p", model.GetCheckConstraints().Single().Sql); } private class Splot diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 4548b5421e5..66ea962c088 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -352,104 +352,6 @@ public void Can_get_and_set_discriminator() Assert.Null(entityType.GetDiscriminatorProperty()); } - [Fact] - public void Setting_discriminator_on_non_root_type_throws() - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - - var entityType = modelBuilder - .Entity() - .Metadata; - var property = entityType.AddProperty("D", typeof(string)); - - var derivedType = modelBuilder - .Entity() - .Metadata; - derivedType.BaseType = entityType; - - Assert.Equal( - RelationalStrings.DiscriminatorPropertyMustBeOnRoot(nameof(SpecialCustomer)), - Assert.Throws(() => derivedType.SetDiscriminatorProperty(property)).Message); - } - - [Fact] - public void Setting_discriminator_from_different_entity_type_throws() - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - - var entityType = modelBuilder - .Entity() - .Metadata; - - var otherType = modelBuilder - .Entity() - .Metadata; - - var property = entityType.AddProperty("D", typeof(string)); - - Assert.Equal( - RelationalStrings.DiscriminatorPropertyNotFound("D", nameof(SpecialCustomer)), - Assert.Throws(() => otherType.SetDiscriminatorProperty(property)).Message); - } - - [Fact] - public void Can_get_and_set_discriminator_value() - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - - var entityType = modelBuilder - .Entity() - .Metadata; - - var property = entityType.AddProperty("D", typeof(string)); - entityType.SetDiscriminatorProperty(property); - - Assert.Null(entityType.GetDiscriminatorValue()); - - entityType.SetDiscriminatorValue("V"); - - Assert.Equal("V", entityType.GetDiscriminatorValue()); - - entityType.SetDiscriminatorValue(null); - - Assert.Null(entityType.GetDiscriminatorValue()); - } - - [Fact] - public void Setting_discriminator_value_when_discriminator_not_set_throws() - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - - var entityType = modelBuilder - .Entity() - .Metadata; - - Assert.Equal( - RelationalStrings.NoDiscriminatorForValue("Customer", "Customer"), - Assert.Throws( - () => entityType.SetDiscriminatorValue("V")).Message); - } - - [Fact] - public void Setting_incompatible_discriminator_value_throws() - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - - var entityType = modelBuilder - .Entity() - .Metadata; - - var property = entityType.AddProperty("D", typeof(int)); - entityType.SetDiscriminatorProperty(property); - - Assert.Equal( - RelationalStrings.DiscriminatorValueIncompatible("V", "D", typeof(int)), - Assert.Throws( - () => entityType.SetDiscriminatorValue("V")).Message); - - entityType.SetDiscriminatorValue(null); - } - [Fact] public void Can_get_and_set_schema_name_on_model() { diff --git a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs similarity index 99% rename from test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs rename to test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 90cf5c21d67..7b95c2e37aa 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -12,7 +12,7 @@ using Xunit; // ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore.Infrastructure { public class SqlServerModelValidatorTest : RelationalModelValidatorTest { diff --git a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs similarity index 98% rename from test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs rename to test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs index a5730bdc983..949c4b34fd7 100644 --- a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs @@ -10,7 +10,7 @@ using Xunit; // ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore.Infrastructure { public class SqliteModelValidatorTest : RelationalModelValidatorTest { diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 6022fd4baf6..fd525f56e45 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -22,17 +22,13 @@ public class ModelValidatorTest : ModelValidatorTestBase [Fact] public virtual void Detects_filter_on_derived_type() { - var model = CreateConventionlessModelBuilder().Model; - var entityTypeA = model.AddEntityType(typeof(A)); - SetPrimaryKey(entityTypeA); - AddProperties(entityTypeA); - - var entityTypeD = model.AddEntityType(typeof(D)); - SetBaseType(entityTypeD, entityTypeA); + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + var entityTypeD = modelBuilder.Entity().Metadata; entityTypeD.SetQueryFilter((Expression>)(_ => true)); - VerifyError(CoreStrings.BadFilterDerivedType(entityTypeD.GetQueryFilter(), entityTypeD.DisplayName()), model); + VerifyError(CoreStrings.BadFilterDerivedType(entityTypeD.GetQueryFilter(), entityTypeD.DisplayName()), modelBuilder.Model); } [Fact] @@ -341,16 +337,11 @@ public virtual void Passes_on_diamond_path_to_root_principal_property() [Fact] public virtual void Passes_on_correct_inheritance() { - var model = CreateConventionlessModelBuilder().Model; - - var entityA = model.AddEntityType(typeof(A)); - SetPrimaryKey(entityA); - AddProperties(entityA); - - var entityD = model.AddEntityType(typeof(D)); - SetBaseType(entityD, entityA); + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity(); - Validate(model); + Validate(modelBuilder.Model); } [Fact] @@ -1051,6 +1042,84 @@ public virtual void Detects_collection_navigations_in_seeds(bool sensitiveDataLo modelBuilder.Model); } + + [Fact] + public virtual void Detects_missing_discriminator_property() + { + var model = CreateConventionlessModelBuilder().Model; + + var entityA = model.AddEntityType(typeof(A)); + SetPrimaryKey(entityA); + AddProperties(entityA); + + var entityC = model.AddEntityType(typeof(C)); + entityC.BaseType = entityA; + + VerifyError(CoreStrings.NoDiscriminatorProperty(entityA.DisplayName()), model); + } + + [Fact] + public virtual void Detects_missing_discriminator_value_on_base() + { + var model = CreateConventionlessModelBuilder().Model; + + var entityA = model.AddEntityType(typeof(A)); + SetPrimaryKey(entityA); + AddProperties(entityA); + + var entityC = model.AddEntityType(typeof(C)); + SetBaseType(entityC, entityA); + + entityA.SetDiscriminatorProperty(entityA.AddProperty("D", typeof(int))); + entityC.SetDiscriminatorValue(1); + + VerifyError(CoreStrings.NoDiscriminatorValue(entityA.DisplayName()), model); + } + + [Fact] + public virtual void Detects_missing_discriminator_value_on_leaf() + { + var model = CreateConventionlessModelBuilder().Model; + + var entityAbstract = model.AddEntityType(typeof(Abstract)); + SetPrimaryKey(entityAbstract); + AddProperties(entityAbstract); + + var entityGeneric = model.AddEntityType(typeof(Generic)); + SetBaseType(entityGeneric, entityAbstract); + + entityAbstract.SetDiscriminatorProperty(entityAbstract.AddProperty("D", typeof(int))); + entityAbstract.SetDiscriminatorValue(0); + + VerifyError(CoreStrings.NoDiscriminatorValue(entityGeneric.DisplayName()), model); + } + + [Fact] + public virtual void Detects_missing_non_string_discriminator_values() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity().HasDiscriminator("ClassType") + .HasValue(0) + .HasValue(1); + + var model = modelBuilder.Model; + VerifyError(CoreStrings.NoDiscriminatorValue(typeof(C).Name), model); + } + + [Fact] + public virtual void Detects_duplicate_discriminator_values() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().HasDiscriminator("ClassType") + .HasValue(1) + .HasValue(1) + .HasValue(2); + + var model = modelBuilder.Model; + VerifyError(CoreStrings.DuplicateDiscriminatorValue(typeof(C).Name, 1, typeof(A).Name), model); + } + // INotify interfaces not really implemented; just marking the classes to test metadata construction private class FullNotificationEntity : INotifyPropertyChanging, INotifyPropertyChanged { diff --git a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs index fd27447f367..ad078dfd2f3 100644 --- a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs +++ b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs @@ -1,8 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using System.Reflection; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; @@ -100,6 +103,104 @@ public void Can_get_proper_table_name_for_generic_entityType() entityType.DisplayName()); } + [Fact] + public void Setting_discriminator_on_non_root_type_throws() + { + var modelBuilder = new ModelBuilder(new ConventionSet()); + + var entityType = modelBuilder + .Entity() + .Metadata; + var property = entityType.AddProperty("D", typeof(string)); + + var derivedType = modelBuilder + .Entity() + .Metadata; + derivedType.BaseType = entityType; + + Assert.Equal( + CoreStrings.DiscriminatorPropertyMustBeOnRoot(nameof(SpecialCustomer)), + Assert.Throws(() => derivedType.SetDiscriminatorProperty(property)).Message); + } + + [Fact] + public void Setting_discriminator_from_different_entity_type_throws() + { + var modelBuilder = new ModelBuilder(new ConventionSet()); + + var entityType = modelBuilder + .Entity() + .Metadata; + + var otherType = modelBuilder + .Entity() + .Metadata; + + var property = entityType.AddProperty("D", typeof(string)); + + Assert.Equal( + CoreStrings.DiscriminatorPropertyNotFound("D", nameof(SpecialCustomer)), + Assert.Throws(() => otherType.SetDiscriminatorProperty(property)).Message); + } + + [Fact] + public void Can_get_and_set_discriminator_value() + { + var modelBuilder = new ModelBuilder(new ConventionSet()); + + var entityType = modelBuilder + .Entity() + .Metadata; + + var property = entityType.AddProperty("D", typeof(string)); + entityType.SetDiscriminatorProperty(property); + + Assert.Null(entityType.GetDiscriminatorValue()); + + entityType.SetDiscriminatorValue("V"); + + Assert.Equal("V", entityType.GetDiscriminatorValue()); + + entityType.SetDiscriminatorValue(null); + + Assert.Null(entityType.GetDiscriminatorValue()); + } + + [Fact] + public void Setting_discriminator_value_when_discriminator_not_set_throws() + { + var modelBuilder = new ModelBuilder(new ConventionSet()); + + var entityType = modelBuilder + .Entity() + .Metadata; + + Assert.Equal( + CoreStrings.NoDiscriminatorForValue("Customer", "Customer"), + Assert.Throws( + () => entityType.SetDiscriminatorValue("V")).Message); + } + + [Fact] + public void Setting_incompatible_discriminator_value_throws() + { + var modelBuilder = new ModelBuilder(new ConventionSet()); + + var entityType = modelBuilder + .Entity() + .Metadata; + + var property = entityType.AddProperty("D", typeof(int)); + entityType.SetDiscriminatorProperty(property); + + Assert.Equal( + CoreStrings.DiscriminatorValueIncompatible("V", "D", typeof(int)), + Assert.Throws( + () => entityType.SetDiscriminatorValue("V")).Message); + + entityType.SetDiscriminatorValue(null); + } + private static IMutableModel CreateModel() => new Model(); private class A @@ -116,5 +217,16 @@ private class SelfRef public SelfRef SelfRefDependent { get; set; } public int? SelfRefId { get; set; } } + + private class Customer + { + public int Id { get; set; } + public string Name { get; set; } + public Guid AlternateId { get; set; } + } + + private class SpecialCustomer : Customer + { + } } } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index f16406c2907..32deac62b73 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -1941,10 +1941,10 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level1Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level1Reference").GetStoreGeneratedIndex()); - Assert.Equal(3, type.PropertyCount()); + Assert.Equal(4, type.PropertyCount()); Assert.Equal(2, type.NavigationCount()); - Assert.Equal(1, type.ShadowPropertyCount()); - Assert.Equal(3, type.OriginalValueCount()); + Assert.Equal(2, type.ShadowPropertyCount()); + Assert.Equal(4, type.OriginalValueCount()); Assert.Equal(4, type.RelationshipPropertyCount()); Assert.Equal(2, type.StoreGeneratedCount()); @@ -1953,8 +1953,8 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); Assert.Equal(2, type.FindProperty("Prop1").GetIndex()); - Assert.Equal(3, type.FindProperty("Level2ReferenceId").GetIndex()); - Assert.Equal(4, type.FindProperty("Prop2").GetIndex()); + Assert.Equal(4, type.FindProperty("Level2ReferenceId").GetIndex()); + Assert.Equal(5, type.FindProperty("Prop2").GetIndex()); Assert.Equal(0, type.FindNavigation("Level1Collection").GetIndex()); Assert.Equal(1, type.FindNavigation("Level1Reference").GetIndex()); Assert.Equal(2, type.FindNavigation("Level2Collection").GetIndex()); @@ -1963,14 +1963,14 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindProperty("Id").GetShadowIndex()); Assert.Equal(0, type.FindProperty("Level1ReferenceId").GetShadowIndex()); Assert.Equal(-1, type.FindProperty("Prop1").GetShadowIndex()); - Assert.Equal(1, type.FindProperty("Level2ReferenceId").GetShadowIndex()); + Assert.Equal(2, type.FindProperty("Level2ReferenceId").GetShadowIndex()); Assert.Equal(-1, type.FindProperty("Prop2").GetShadowIndex()); Assert.Equal(0, type.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetOriginalValueIndex()); Assert.Equal(2, type.FindProperty("Prop1").GetOriginalValueIndex()); - Assert.Equal(3, type.FindProperty("Level2ReferenceId").GetOriginalValueIndex()); - Assert.Equal(4, type.FindProperty("Prop2").GetOriginalValueIndex()); + Assert.Equal(4, type.FindProperty("Level2ReferenceId").GetOriginalValueIndex()); + Assert.Equal(5, type.FindProperty("Prop2").GetOriginalValueIndex()); Assert.Equal(0, type.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetRelationshipIndex()); @@ -1992,10 +1992,10 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level2Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level2Reference").GetStoreGeneratedIndex()); - Assert.Equal(5, type.PropertyCount()); + Assert.Equal(6, type.PropertyCount()); Assert.Equal(4, type.NavigationCount()); - Assert.Equal(2, type.ShadowPropertyCount()); - Assert.Equal(5, type.OriginalValueCount()); + Assert.Equal(3, type.ShadowPropertyCount()); + Assert.Equal(6, type.OriginalValueCount()); Assert.Equal(7, type.RelationshipPropertyCount()); Assert.Equal(3, type.StoreGeneratedCount()); @@ -2004,10 +2004,10 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); Assert.Equal(2, type.FindProperty("Prop1").GetIndex()); - Assert.Equal(3, type.FindProperty("Level2ReferenceId").GetIndex()); - Assert.Equal(4, type.FindProperty("Prop2").GetIndex()); - Assert.Equal(5, type.FindProperty("Level3ReferenceId").GetIndex()); - Assert.Equal(6, type.FindProperty("Prop3").GetIndex()); + Assert.Equal(4, type.FindProperty("Level2ReferenceId").GetIndex()); + Assert.Equal(5, type.FindProperty("Prop2").GetIndex()); + Assert.Equal(6, type.FindProperty("Level3ReferenceId").GetIndex()); + Assert.Equal(7, type.FindProperty("Prop3").GetIndex()); Assert.Equal(0, type.FindNavigation("Level1Collection").GetIndex()); Assert.Equal(1, type.FindNavigation("Level1Reference").GetIndex()); Assert.Equal(2, type.FindNavigation("Level2Collection").GetIndex()); @@ -2018,18 +2018,18 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindProperty("Id").GetShadowIndex()); Assert.Equal(0, type.FindProperty("Level1ReferenceId").GetShadowIndex()); Assert.Equal(-1, type.FindProperty("Prop1").GetShadowIndex()); - Assert.Equal(1, type.FindProperty("Level2ReferenceId").GetShadowIndex()); + Assert.Equal(2, type.FindProperty("Level2ReferenceId").GetShadowIndex()); Assert.Equal(-1, type.FindProperty("Prop2").GetShadowIndex()); - Assert.Equal(2, type.FindProperty("Level3ReferenceId").GetShadowIndex()); + Assert.Equal(3, type.FindProperty("Level3ReferenceId").GetShadowIndex()); Assert.Equal(-1, type.FindProperty("Prop3").GetShadowIndex()); Assert.Equal(0, type.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetOriginalValueIndex()); Assert.Equal(2, type.FindProperty("Prop1").GetOriginalValueIndex()); - Assert.Equal(3, type.FindProperty("Level2ReferenceId").GetOriginalValueIndex()); - Assert.Equal(4, type.FindProperty("Prop2").GetOriginalValueIndex()); - Assert.Equal(5, type.FindProperty("Level3ReferenceId").GetOriginalValueIndex()); - Assert.Equal(6, type.FindProperty("Prop3").GetOriginalValueIndex()); + Assert.Equal(4, type.FindProperty("Level2ReferenceId").GetOriginalValueIndex()); + Assert.Equal(5, type.FindProperty("Prop2").GetOriginalValueIndex()); + Assert.Equal(6, type.FindProperty("Level3ReferenceId").GetOriginalValueIndex()); + Assert.Equal(7, type.FindProperty("Prop3").GetOriginalValueIndex()); Assert.Equal(0, type.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetRelationshipIndex()); @@ -2059,10 +2059,10 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level3Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level3Reference").GetStoreGeneratedIndex()); - Assert.Equal(7, type.PropertyCount()); + Assert.Equal(8, type.PropertyCount()); Assert.Equal(6, type.NavigationCount()); - Assert.Equal(3, type.ShadowPropertyCount()); - Assert.Equal(7, type.OriginalValueCount()); + Assert.Equal(4, type.ShadowPropertyCount()); + Assert.Equal(8, type.OriginalValueCount()); Assert.Equal(10, type.RelationshipPropertyCount()); Assert.Equal(4, type.StoreGeneratedCount()); } @@ -2080,6 +2080,8 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasOne(e => e.Level1Reference).WithMany(e => e.Level1Collection); modelBuilder.Entity().HasOne(e => e.Level2Reference).WithMany(e => e.Level2Collection); modelBuilder.Entity().HasOne(e => e.Level3Reference).WithMany(e => e.Level3Collection); + + modelBuilder.Entity().HasDiscriminator("Z"); } } diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index b2e6e0e914d..d54649b259f 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -8,12 +8,12 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; -// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedMember.Global // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Local // ReSharper disable MemberCanBePrivate.Local @@ -2732,6 +2732,228 @@ public void Setting_base_type_removes_duplicate_service_properties() Assert.Equal(1, derivedEntityBuilder.Metadata.GetServiceProperties().Count()); } + [Fact] + public void Can_access_discriminator() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity(typeof(Order), ConfigurationSource.Convention); + + Assert.NotNull(typeBuilder.HasDiscriminator()); + Assert.Equal("Discriminator", typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + + Assert.NotNull(typeBuilder.HasNoDeclaredDiscriminator()); + Assert.Null(typeBuilder.Metadata.GetDiscriminatorProperty()); + Assert.Equal(0, typeBuilder.Metadata.GetProperties().Count()); + + Assert.NotNull(typeBuilder.HasDiscriminator("Splod", typeof(int?))); + Assert.Equal("Splod", typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(int?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + Assert.Equal("Splod", typeBuilder.Metadata.GetProperties().Single().Name); + + Assert.NotNull(typeBuilder.HasDiscriminator(Order.CustomerUniqueProperty, fromDataAnnotation: true)); + Assert.Equal(Order.CustomerUniqueProperty.Name, typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(Guid?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + Assert.Equal(Order.CustomerUniqueProperty.Name, typeBuilder.Metadata.GetProperties().Single().Name); + + Assert.Null(typeBuilder.HasDiscriminator("Splew", typeof(int?))); + Assert.Equal(Order.CustomerUniqueProperty.Name, typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(Guid?), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + Assert.Equal(Order.CustomerUniqueProperty.Name, typeBuilder.Metadata.GetProperties().Single().Name); + + Assert.NotNull(typeBuilder.HasDiscriminator(typeof(int), fromDataAnnotation: true)); + Assert.Null(typeBuilder.HasDiscriminator(typeof(long))); + Assert.Equal("Discriminator", typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(int), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + + Assert.Null(typeBuilder.HasNoDeclaredDiscriminator()); + } + + [Fact] + public void Discriminator_is_not_set_if_ignored() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity(typeof(Order), ConfigurationSource.Convention); + typeBuilder.Ignore("Splod", true); + + Assert.NotNull(typeBuilder.HasDiscriminator("Splew", typeof(string))); + Assert.Equal("Splew", typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + + Assert.Null(typeBuilder.HasDiscriminator("Splod", typeof(int?))); + Assert.Equal("Splew", typeBuilder.Metadata.GetDiscriminatorProperty().Name); + Assert.Equal(typeof(string), typeBuilder.Metadata.GetDiscriminatorProperty().ClrType); + } + + [Fact] + public void Discriminator_is_not_set_if_default_ignored() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity(typeof(Order), ConfigurationSource.Convention); + typeBuilder.Ignore("Discriminator", true); + + Assert.Null(typeBuilder.HasDiscriminator()); + Assert.Equal(0, typeBuilder.Metadata.GetProperties().Count()); + } + + [Fact] + public void Can_access_discriminator_value() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity("Splot", ConfigurationSource.Convention); + var derivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splod"); + derivedTypeBuilder.HasBaseType(typeBuilder.Metadata, fromDataAnnotation: true); + var otherDerivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splow"); + + Assert.NotNull(typeBuilder.HasDiscriminator()); + Assert.Equal(1, typeBuilder.Metadata.GetDeclaredProperties().Count()); + Assert.Equal(0, derivedTypeBuilder.Metadata.GetDeclaredProperties().Count()); + + var discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(int?)); + Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); + Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); + Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); + + Assert.Same(typeBuilder.Metadata, otherDerivedTypeBuilder.Metadata.BaseType); + Assert.Equal(1, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal( + 2, typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Equal( + 3, typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + Assert.Same(typeBuilder.Metadata, typeBuilder.ModelBuilder.Metadata.FindEntityType("Splow").BaseType); + + discriminatorBuilder = typeBuilder.HasDiscriminator(fromDataAnnotation: true); + Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 4, fromDataAnnotation: true)); + Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 5, fromDataAnnotation: true)); + Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 6, fromDataAnnotation: true)); + Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal( + 5, typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Equal( + 6, typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + + discriminatorBuilder = typeBuilder.HasDiscriminator(); + Assert.Null(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); + Assert.Null(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); + Assert.Null(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); + Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal( + 5, typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Equal( + 6, typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + + Assert.NotNull(typeBuilder.HasNoDeclaredDiscriminator(fromDataAnnotation: true)); + Assert.Null(typeBuilder.Metadata.GetDiscriminatorProperty()); + Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Empty(typeBuilder.Metadata.GetProperties()); + } + + [Fact] + public void Changing_discriminator_type_removes_values() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity("Splot", ConfigurationSource.Convention); + var derivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splod"); + derivedTypeBuilder.HasBaseType(typeBuilder.Metadata, fromDataAnnotation: true); + var otherDerivedTypeBuilder = typeBuilder.ModelBuilder.Entity("Splow"); + + Assert.NotNull(typeBuilder.HasDiscriminator()); + Assert.Equal(1, typeBuilder.Metadata.GetDeclaredProperties().Count()); + Assert.Equal(0, derivedTypeBuilder.Metadata.GetDeclaredProperties().Count()); + + var discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(int)); + Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); + Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, 2)); + Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); + + discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(string)); + Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null( + typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Null( + typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, "4")); + Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, "5")); + Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, "6")); + + discriminatorBuilder = typeBuilder.HasDiscriminator("Splotted", typeof(string)); + + Assert.NotNull(discriminatorBuilder); + Assert.Equal("4", typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal( + "5", typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Equal( + "6", typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + + discriminatorBuilder = typeBuilder.HasDiscriminator(typeof(int)); + + Assert.NotNull(discriminatorBuilder); + Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null( + typeBuilder.ModelBuilder.Entity("Splow") + .Metadata.GetDiscriminatorValue()); + Assert.Null( + typeBuilder.ModelBuilder.Entity("Splod") + .Metadata.GetDiscriminatorValue()); + } + + [Fact] + public void Can_access_discriminator_value_generic() + { + IConventionEntityTypeBuilder typeBuilder = CreateModelBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); + + var discriminatorBuilder = new DiscriminatorBuilder( + (DiscriminatorBuilder)typeBuilder.HasDiscriminator(Splot.SplowedProperty)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splot), 1)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splow), 2)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splod), 3)); + + var splow = typeBuilder.ModelBuilder.Entity(typeof(Splow)).Metadata; + var splod = typeBuilder.ModelBuilder.Entity(typeof(Splod)).Metadata; + Assert.Equal(1, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal(2, splow.GetDiscriminatorValue()); + Assert.Equal( + 3, typeBuilder.ModelBuilder.Entity(typeof(Splod)) + .Metadata.GetDiscriminatorValue()); + + discriminatorBuilder = new DiscriminatorBuilder( + (DiscriminatorBuilder)typeBuilder.HasDiscriminator(fromDataAnnotation: true)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splot), 4)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splow), 5)); + Assert.NotNull(discriminatorBuilder.HasValue(typeof(Splod), 6)); + Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal(5, splow.GetDiscriminatorValue()); + Assert.Equal(6, splod.GetDiscriminatorValue()); + + var conventionDiscriminatorBuilder = typeBuilder.HasDiscriminator(); + Assert.Null(conventionDiscriminatorBuilder.HasValue(typeBuilder.Metadata, 1)); + Assert.Null(conventionDiscriminatorBuilder.HasValue(splow, 2)); + Assert.Null(conventionDiscriminatorBuilder.HasValue(splod, 3)); + Assert.Equal(4, typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Equal(5, splow.GetDiscriminatorValue()); + Assert.Equal(6, splod.GetDiscriminatorValue()); + } + + [Fact] + public void DiscriminatorValue_throws_if_base_cannot_be_set() + { + IConventionModelBuilder modelBuilder = CreateModelBuilder(); + var typeBuilder = modelBuilder.Entity("Splot"); + var nonDerivedTypeBuilder = modelBuilder.Entity("Splow"); + nonDerivedTypeBuilder.HasBaseType(modelBuilder.Entity("Splod").Metadata, true); + + var discriminatorBuilder = typeBuilder.HasDiscriminator(); + Assert.Equal( + CoreStrings.DiscriminatorEntityTypeNotDerived("Splow", "Splot"), + Assert.Throws(() + => discriminatorBuilder.HasValue(nonDerivedTypeBuilder.Metadata, "1")).Message); + } + private InternalModelBuilder CreateModelBuilder() => new InternalModelBuilder(new Model()); private class Order @@ -2831,5 +3053,20 @@ private class CustomerMinimal public int Id { get; set; } public ICollection Orders { get; set; } } + + private class Splot + { + public static readonly PropertyInfo SplowedProperty = typeof(Splot).GetProperty("Splowed"); + + public int? Splowed { get; set; } + } + + private class Splow : Splot + { + } + + private class Splod : Splow + { + } } } diff --git a/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs b/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs index 9befbc60a26..f26f8a62056 100644 --- a/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs @@ -31,7 +31,6 @@ public void Can_write_convention_model_builder_extension() Assert.Equal("V2.Annotation", model["Annotation"]); Assert.Equal("V2.Metadata", model["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -51,7 +50,6 @@ public void Can_write_convention_entity_builder_extension() Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -71,7 +69,6 @@ public void Can_write_convention_entity_builder_extension_and_use_with_generic_b Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -91,7 +88,6 @@ public void Can_write_generic_convention_entity_builder_extension() Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -112,7 +108,6 @@ public void Can_write_convention_key_builder_extension() Assert.Equal("V2.Annotation", key["Annotation"]); Assert.Equal("V2.Metadata", key["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -133,7 +128,6 @@ public void Can_write_convention_property_builder_extension() Assert.Equal("V2.Annotation", property["Annotation"]); Assert.Equal("V2.Metadata", property["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -154,7 +148,6 @@ public void Can_write_convention_index_builder_extension() Assert.Equal("V2.Annotation", index["Annotation"]); Assert.Equal("V2.Metadata", index["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -176,7 +169,6 @@ public void Can_write_convention_one_to_many_builder_extension() Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -198,7 +190,6 @@ public void Can_write_convention_many_to_one_builder_extension() Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -221,7 +212,6 @@ public void Can_write_convention_one_to_one_builder_extension() Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -239,7 +229,6 @@ public void Can_write_convention_model_builder_extension_with_common_name() Assert.Equal("V2.Annotation", model["Annotation"]); Assert.Equal("V2.Metadata", model["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -259,7 +248,6 @@ public void Can_write_convention_entity_builder_extension_with_common_name() Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -279,7 +267,6 @@ public void Can_write_convention_entity_builder_extension_and_use_with_generic_b Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -299,7 +286,6 @@ public void Can_write_generic_convention_entity_builder_extension_with_common_na Assert.Equal("V2.Annotation", entityType["Annotation"]); Assert.Equal("V2.Metadata", entityType["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -320,7 +306,6 @@ public void Can_write_convention_key_builder_extension_with_common_name() Assert.Equal("V2.Annotation", key["Annotation"]); Assert.Equal("V2.Metadata", key["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -341,7 +326,6 @@ public void Can_write_convention_property_builder_extension_with_common_name() Assert.Equal("V2.Annotation", property["Annotation"]); Assert.Equal("V2.Metadata", property["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -362,7 +346,6 @@ public void Can_write_convention_index_builder_extension_with_common_name() Assert.Equal("V2.Annotation", index["Annotation"]); Assert.Equal("V2.Metadata", index["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -384,7 +367,6 @@ public void Can_write_convention_one_to_many_builder_extension_with_common_name( Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -406,7 +388,6 @@ public void Can_write_convention_many_to_one_builder_extension_with_common_name( Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } [Fact] @@ -429,7 +410,6 @@ public void Can_write_convention_one_to_one_builder_extension_with_common_name() Assert.Equal("V2.Annotation", foreignKey["Annotation"]); Assert.Equal("V2.Metadata", foreignKey["Metadata"]); - Assert.Equal("V2.Model", model["Model"]); } protected virtual ModelBuilder CreateModelBuilder() @@ -468,7 +448,6 @@ public static ModelBuilder ModelBuilderExtension(this ModelBuilder builder, stri { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Model["Metadata"] = value + ".Metadata"; - builder.Model["Model"] = value + ".Model"; return builder; } @@ -477,7 +456,6 @@ public static EntityTypeBuilder EntityBuilderExtension(this EntityTypeBuilder bu { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -488,7 +466,6 @@ public static EntityTypeBuilder GenericEntityBuilderExtension( { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -497,7 +474,6 @@ public static KeyBuilder KeyBuilderExtension(this KeyBuilder builder, string val { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -506,7 +482,6 @@ public static PropertyBuilder PropertyBuilderExtension(this PropertyBuilder buil { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -515,7 +490,6 @@ public static IndexBuilder IndexBuilderExtension(this IndexBuilder builder, stri { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -524,7 +498,6 @@ public static ReferenceCollectionBuilder OneToManyBuilderExtension(this Referenc { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -533,7 +506,6 @@ public static ReferenceCollectionBuilder ManyToOneBuilderExtension(this Referenc { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -542,7 +514,6 @@ public static ReferenceReferenceBuilder OneToOneBuilderExtension(this ReferenceR { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -560,7 +531,6 @@ public static EntityTypeBuilder SharedNameExtension(this EntityTypeBuilder build { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -571,7 +541,6 @@ public static EntityTypeBuilder SharedNameExtension( { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -580,7 +549,6 @@ public static KeyBuilder SharedNameExtension(this KeyBuilder builder, string val { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -589,7 +557,6 @@ public static PropertyBuilder SharedNameExtension(this PropertyBuilder builder, { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -598,7 +565,6 @@ public static IndexBuilder SharedNameExtension(this IndexBuilder builder, string { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -607,7 +573,6 @@ public static ReferenceCollectionBuilder SharedNameExtension(this ReferenceColle { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; } @@ -616,7 +581,6 @@ public static ReferenceReferenceBuilder SharedNameExtension(this ReferenceRefere { builder.HasAnnotation("Annotation", value + ".Annotation"); builder.Metadata["Metadata"] = value + ".Metadata"; - builder.GetInfrastructure()["Model"] = value + ".Model"; return builder; }