diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 53c17d48f14..7a77dfccea6 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1391,6 +1391,30 @@ protected override void ValidateInheritanceMapping( ValidateNonTphMapping(entityType, forTables: false); ValidateNonTphMapping(entityType, forTables: true); + + var derivedTypes = entityType.GetDerivedTypesInclusive().ToList(); + var discriminatorValues = new Dictionary(); + foreach (var derivedType in derivedTypes) + { + if (!derivedType.ClrType.IsInstantiable()) + { + continue; + } + var discriminatorValue = derivedType.GetDiscriminatorValue(); + if (discriminatorValue is not string valueString) + { + throw new InvalidOperationException( + RelationalStrings.NonTphDiscriminatorValueNotString(discriminatorValue, derivedType.DisplayName())); + } + + if (discriminatorValues.TryGetValue(valueString, out var duplicateEntityType)) + { + throw new InvalidOperationException(RelationalStrings.EntityShortNameNotUnique( + derivedType.Name, discriminatorValue, duplicateEntityType.Name)); + } + + discriminatorValues[valueString] = derivedType; + } } } @@ -1469,7 +1493,7 @@ private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTa { return; } - + var internalForeignKey = rootEntityType.FindRowInternalForeignKeys(storeObject.Value).FirstOrDefault(); if (internalForeignKey != null && derivedTypes.Count > 1 diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs index a47423326c0..8bab3bf5e09 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs @@ -31,15 +31,6 @@ public static IEnumerable GetViewOrTableMappings(this IEntity ?? entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings)) ?? Enumerable.Empty(); - /// - /// 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 IReadOnlyList GetTptDiscriminatorValues(this IReadOnlyEntityType entityType) - => entityType.GetConcreteDerivedTypesInclusive().Select(et => et.ShortName()).ToList(); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index fa0837fdcd5..d140c6c8953 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1405,6 +1405,22 @@ public static string ViewOverrideMismatch(object? propertySpecification, object? public static string VisitChildrenMustBeOverridden => GetString("VisitChildrenMustBeOverridden"); + /// + /// The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call entityTypeBuilder.Metadata.SetDiscriminatorValue("NewShortName"). + /// + public static string EntityShortNameNotUnique(object? entityType1, object? discriminatorValue, object? entityType2) + => string.Format( + GetString("EntityShortNameNotUnique", nameof(entityType1), nameof(discriminatorValue), nameof(entityType2)), + entityType1, discriminatorValue, entityType2); + + /// + /// The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead. + /// + public static string NonTphDiscriminatorValueNotString(object? value, object? entityType) + => string.Format( + GetString("NonTphDiscriminatorValueNotString", nameof(value), nameof(entityType)), + value, entityType); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name)!; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 6ec2bde2fff..40b82f8b9cf 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -340,6 +340,9 @@ Either {param1} or {param2} must be null. + + The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call modelBuilder.Entity<TEntity>().Metadata.SetDiscriminatorValue("NewShortName"). + An error occurred while reading a database value for property '{entityType}.{property}'. See the inner exception for more information. @@ -763,6 +766,9 @@ Cannot set 'PropagatesNullability' on parameter '{parameterName}' of DbFunction '{functionName}' since function does not represent a scalar function. + + The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead. + The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. diff --git a/src/EFCore.Relational/Query/EntityProjectionExpression.cs b/src/EFCore.Relational/Query/EntityProjectionExpression.cs index 15e8efdd055..bab17066aae 100644 --- a/src/EFCore.Relational/Query/EntityProjectionExpression.cs +++ b/src/EFCore.Relational/Query/EntityProjectionExpression.cs @@ -123,7 +123,7 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy var discriminatorExpression = DiscriminatorExpression; if (DiscriminatorExpression is CaseExpression caseExpression) { - var entityTypesToSelect = derivedType.GetTptDiscriminatorValues(); + var entityTypesToSelect = derivedType.GetConcreteDerivedTypesInclusive().Select(e => e.GetDiscriminatorValue()).ToList(); var whenClauses = caseExpression.WhenClauses .Where(wc => entityTypesToSelect.Contains((string)((SqlConstantExpression)wc.Result).Value!)) .ToList(); diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 4bf41afde23..4db87cba93a 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1128,7 +1128,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp } // TPT or TPC - var discriminatorValues = derivedType.GetTptDiscriminatorValues(); + var discriminatorValues = derivedType.GetConcreteDerivedTypesInclusive().Select(e => e.GetDiscriminatorValue()).ToList(); if (entityReferenceExpression.SubqueryEntity != null) { var entityShaper = (EntityShaperExpression)entityReferenceExpression.SubqueryEntity.ShaperExpression; diff --git a/src/EFCore/Infrastructure/ConventionAnnotatable.cs b/src/EFCore/Infrastructure/ConventionAnnotatable.cs index 3554e0a72f4..f4fd65eeeb6 100644 --- a/src/EFCore/Infrastructure/ConventionAnnotatable.cs +++ b/src/EFCore/Infrastructure/ConventionAnnotatable.cs @@ -133,9 +133,16 @@ public override void SetAnnotation(string name, object? value) /// Removes the given annotation from this object. /// /// The annotation to remove. + /// The configuration source of the annotation to be removed. /// The annotation that was removed. - public new virtual ConventionAnnotation? RemoveAnnotation(string name) - => (ConventionAnnotation?)base.RemoveAnnotation(name); + public virtual ConventionAnnotation? RemoveAnnotation(string name, ConfigurationSource configurationSource) + { + var annotation = FindAnnotation(name); + return annotation == null + || !configurationSource.Overrides(annotation.GetConfigurationSource()) + ? null + : (ConventionAnnotation?)base.RemoveAnnotation(name); + } /// protected override Annotation CreateAnnotation(string name, object? value) @@ -201,8 +208,8 @@ IConventionAnnotation IConventionAnnotatable.AddAnnotation(string name, object? /// [DebuggerStepThrough] - IConventionAnnotation? IConventionAnnotatable.RemoveAnnotation(string name) - => RemoveAnnotation(name); + IConventionAnnotation? IConventionAnnotatable.RemoveAnnotation(string name, bool fromDataAnnotation) + => RemoveAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index d53e42b96dc..30f82089f18 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -604,13 +604,19 @@ protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType) continue; } - var discriminatorValue = derivedType.GetDiscriminatorValue(); + var discriminatorValue = derivedType[CoreAnnotationNames.DiscriminatorValue]; if (discriminatorValue == null) { throw new InvalidOperationException( CoreStrings.NoDiscriminatorValue(derivedType.DisplayName())); } + if (!discriminatorProperty.ClrType.IsInstanceOfType(discriminatorValue)) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorValueIncompatible(discriminatorValue, discriminatorProperty.Name, discriminatorProperty.ClrType)); + } + if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType)) { throw new InvalidOperationException( diff --git a/src/EFCore/Metadata/IConventionAnnotatable.cs b/src/EFCore/Metadata/IConventionAnnotatable.cs index 71c8243bda6..52c89ffd1b5 100644 --- a/src/EFCore/Metadata/IConventionAnnotatable.cs +++ b/src/EFCore/Metadata/IConventionAnnotatable.cs @@ -66,8 +66,9 @@ public interface IConventionAnnotatable : IReadOnlyAnnotatable /// Removes the annotation with the given name from this object. /// /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. /// The annotation that was removed. - IConventionAnnotation? RemoveAnnotation(string name); + IConventionAnnotation? RemoveAnnotation(string name, bool fromDataAnnotation = false); /// /// Gets the annotation with the given name, throwing if it does not exist. diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index 60258e72781..396cf991faa 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -120,15 +120,16 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// Indicates whether the configuration was specified using a data annotation. /// The configured value. object? SetDiscriminatorValue(object? value, bool fromDataAnnotation = false) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, EntityType.CheckDiscriminatorValue(this, value), fromDataAnnotation) + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation) ?.Value; /// /// Removes the discriminator value for this entity type. /// + /// Indicates whether the configuration was specified using a data annotation. /// The removed discriminator value. - object? RemoveDiscriminatorValue() - => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value; + object? RemoveDiscriminatorValue(bool fromDataAnnotation = false) + => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue, fromDataAnnotation)?.Value; /// /// Gets the for the discriminator value. diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index 3b1b160c5f0..35090e0b6de 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -80,7 +80,7 @@ void SetDiscriminatorMappingComplete(bool? complete) /// /// The value to set. void SetDiscriminatorValue(object? value) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, EntityType.CheckDiscriminatorValue(this, value)); + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); /// /// Removes the discriminator value for this entity type. diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index f3e53bfee95..54983abbe34 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -70,7 +70,15 @@ bool GetIsDiscriminatorMappingComplete() /// /// The discriminator value for this entity type. object? GetDiscriminatorValue() - => this[CoreAnnotationNames.DiscriminatorValue]; + { + var annotation = FindAnnotation(CoreAnnotationNames.DiscriminatorValue); + return annotation != null + ? annotation.Value + : !ClrType.IsInstantiable() + || (BaseType == null && GetDirectlyDerivedTypes().Count() == 0) + ? null + : (object)ShortName(); + } /// /// Gets all types in the model from which a given entity type derives, starting with the root. diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 8079da830b2..d3687903017 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -3032,7 +3033,7 @@ public virtual IEnumerable GetDerivedServiceProperties() { return Enumerable.Empty>(); } - + List? propertiesList = null; Dictionary? propertiesMap = null; var data = new List>(); @@ -3056,7 +3057,7 @@ public virtual IEnumerable GetDerivedServiceProperties() { continue; } - + ValueConverter? valueConverter = null; if (providerValues && propertyBase is IProperty property @@ -3347,12 +3348,12 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() if (((property == null && BaseType == null) || (property != null && !property.ClrType.IsInstanceOfType(((IReadOnlyEntityType)this).GetDiscriminatorValue())))) { - ((IMutableEntityType)this).RemoveDiscriminatorValue(); + RemoveDiscriminatorValue(this, configurationSource); if (BaseType == null) { foreach (var derivedType in GetDerivedTypes()) { - ((IMutableEntityType)derivedType).RemoveDiscriminatorValue(); + RemoveDiscriminatorValue(derivedType, configurationSource); } } } @@ -3361,6 +3362,18 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() == property?.Name ? property : (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); + + static void RemoveDiscriminatorValue(IReadOnlyEntityType entityType, ConfigurationSource configurationSource) + { + if (configurationSource is ConfigurationSource.Convention or ConfigurationSource.DataAnnotation) + { + ((IConventionEntityType)entityType).RemoveDiscriminatorValue(configurationSource == ConfigurationSource.DataAnnotation); + } + else + { + ((IMutableEntityType)entityType).RemoveDiscriminatorValue(); + } + } } private void CheckDiscriminatorProperty(Property? property) @@ -3395,35 +3408,6 @@ private void CheckDiscriminatorProperty(Property? property) return (string?)this[CoreAnnotationNames.DiscriminatorProperty]; } - /// - /// 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 object? CheckDiscriminatorValue(IReadOnlyEntityType entityType, object? value) - { - if (value is null) - { - return value; - } - - var discriminatorProperty = entityType.FindDiscriminatorProperty(); - if (discriminatorProperty is null) - { - throw new InvalidOperationException( - CoreStrings.NoDiscriminatorForValue(entityType.DisplayName(), entityType.GetRootType().DisplayName())); - } - - if (!discriminatorProperty.ClrType.IsInstanceOfType(value)) - { - throw new InvalidOperationException( - CoreStrings.DiscriminatorValueIncompatible(value, discriminatorProperty.Name, discriminatorProperty.ClrType)); - } - - return value; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 6cf71024d68..06014a71ef9 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -667,12 +667,12 @@ public static string DiscriminatorPropertyNotFound(object? property, object? ent property, entityType); /// - /// Cannot set discriminator value '{value}' for discriminator property '{discriminator}' because it is not assignable to type '{discriminatorType}'. + /// The discriminator value '{value}' for the entity type '{entityType}' because it is not assignable to type '{discriminatorType}'. /// - public static string DiscriminatorValueIncompatible(object? value, object? discriminator, object? discriminatorType) + public static string DiscriminatorValueIncompatible(object? value, object? entityType, object? discriminatorType) => string.Format( - GetString("DiscriminatorValueIncompatible", nameof(value), nameof(discriminator), nameof(discriminatorType)), - value, discriminator, discriminatorType); + GetString("DiscriminatorValueIncompatible", nameof(value), nameof(entityType), nameof(discriminatorType)), + value, entityType, discriminatorType); /// /// The annotation '{annotation}' cannot be added because an annotation with the same name already exists on the object {annotatable} @@ -1695,14 +1695,6 @@ public static string NoClrNavigation(object? navigation, object? entityType) GetString("NoClrNavigation", nameof(navigation), nameof(entityType)), navigation, entityType); - /// - /// Cannot set the discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property configured. - /// - public static string NoDiscriminatorForValue(object? entityType, 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. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index e6debfc4779..f5985d0d46b 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -364,7 +364,7 @@ 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 type '{discriminatorType}'. + The discriminator value '{value}' for the entity type '{entityType}' because it is not assignable to type '{discriminatorType}'. The annotation '{annotation}' cannot be added because an annotation with the same name already exists on the object {annotatable} @@ -1049,9 +1049,6 @@ The navigation '{navigation}' cannot be added to the entity type '{entityType}' because there is no corresponding CLR property on the underlying type and navigations properties cannot be added in shadow state. - - Cannot set the discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property configured. - The entity type '{entityType}' is part of a hierarchy, but does not have a discriminator property configured. diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 89b102108f1..f8d6bec37a2 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1080,7 +1080,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var runtimeEntityType = model.AddEntityType( ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase"", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), - baseEntityType); + baseEntityType, + discriminatorValue: ""PrincipalBase""); var id = runtimeEntityType.AddProperty( ""Id"", @@ -1549,7 +1550,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var runtimeEntityType = model.AddEntityType( ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>"", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), - baseEntityType); + baseEntityType, + discriminatorValue: ""PrincipalDerived>""); return runtimeEntityType; } @@ -1828,7 +1830,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.False(principalDerived.IsOwned()); Assert.IsType(principalDerived.ConstructorBinding); Assert.Equal(ChangeTrackingStrategy.Snapshot, principalDerived.GetChangeTrackingStrategy()); - Assert.Null(principalDerived.GetDiscriminatorValue()); + Assert.Equal("PrincipalDerived>", principalDerived.GetDiscriminatorValue()); var tptForeignKey = principalDerived.GetForeignKeys().Single(); Assert.False(tptForeignKey.IsOwnership); @@ -2358,7 +2360,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var runtimeEntityType = model.AddEntityType( ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase"", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), - baseEntityType); + baseEntityType, + discriminatorValue: ""PrincipalBase""); var id = runtimeEntityType.AddProperty( ""Id"", @@ -2463,7 +2466,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var runtimeEntityType = model.AddEntityType( ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>"", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), - baseEntityType); + baseEntityType, + discriminatorValue: ""PrincipalDerived>""); return runtimeEntityType; } @@ -2495,7 +2499,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal("PrincipalBaseView", principalBase.GetViewName()); Assert.Equal("TPC", principalBase.GetViewSchema()); - Assert.Null(principalBase.GetDiscriminatorValue()); + Assert.Equal("PrincipalBase", principalBase.GetDiscriminatorValue()); Assert.Null(principalBase.FindDiscriminatorProperty()); Assert.Equal("TPC", principalBase.GetMappingStrategy()); @@ -2514,7 +2518,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal("PrincipalDerivedView", principalDerived.GetViewName()); Assert.Equal("TPC", principalBase.GetViewSchema()); - Assert.Null(principalDerived.GetDiscriminatorValue()); + Assert.Equal("PrincipalDerived>", principalDerived.GetDiscriminatorValue()); Assert.Null(principalDerived.FindDiscriminatorProperty()); Assert.Equal("TPC", principalDerived.GetMappingStrategy()); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 5d619bab8b6..b430e23a33e 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -485,9 +485,9 @@ public virtual void Passes_for_incompatible_uniquified_check_constraints_with_sh var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); + modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 10"); + modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 10"); modelBuilder.Entity().ToTable("Table"); var model = Validate(modelBuilder); @@ -502,9 +502,9 @@ public virtual void Passes_for_compatible_shared_check_constraints_with_shared_t var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); + modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); + modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); modelBuilder.Entity().ToTable("Table"); var model = Validate(modelBuilder); @@ -1853,7 +1853,7 @@ public virtual void Detects_clashing_entity_types_in_views_TPC() modelBuilder.Entity().UseTpcMappingStrategy(); modelBuilder.Entity().ToTable("Cat").ToView("Cat"); modelBuilder.Entity().ToTable("Dog").ToView("Cat"); - + VerifyError( RelationalStrings.NonTphViewClash(nameof(Dog), nameof(Cat), "Cat"), modelBuilder); @@ -1945,7 +1945,7 @@ public virtual void Passes_on_valid_view_sharing_with_TPC() Validate(modelBuilder); } - + [ConditionalFact] public virtual void Detects_view_sharing_on_base_with_TPC() { @@ -2511,6 +2511,56 @@ public virtual void Detects_triggers_on_unmapped_entity_types() VerifyError(RelationalStrings.TriggerOnUnmappedEntityType("Animal_Trigger", "Animal"), modelBuilder); } + [ConditionalFact] + public virtual void Throws_when_non_tph_entity_type_short_names_are_not_unique() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity().ToTable("TpcDerived1"); + modelBuilder.Entity().ToTable("TpcDerived2"); + + VerifyError( + RelationalStrings.EntityShortNameNotUnique( + "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+Outer2+TpcDerived", + "TpcDerived", + "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+Outer+TpcDerived"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Throws_when_non_tph_entity_type_discriminator_set_to_non_string() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity().ToTable("TpcDerived1"); + modelBuilder.Entity().ToTable("TpcDerived2"); + modelBuilder.Entity().Metadata.SetDiscriminatorProperty(null); + modelBuilder.Entity().Metadata.SetDiscriminatorValue(1); + + VerifyError(RelationalStrings.NonTphDiscriminatorValueNotString(1, "TpcDerived"), modelBuilder); + } + + private class TpcBase + { + public int Id { get; set; } + } + + private class Outer + { + public class TpcDerived : TpcBase + { + public string Value { get; set; } + } + } + + private class Outer2 + { + public class TpcDerived : TpcBase + { + public string Value { get; set; } + } + } + protected override void SetBaseType(IMutableEntityType entityType, IMutableEntityType baseEntityType) { base.SetBaseType(entityType, baseEntityType); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 81374ce8d1b..e03720f7505 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -180,9 +180,9 @@ private static void AssertDefaultMappings(IRelationalModel model, Mapping mappin Assert.False(specialityColumn.IsNullable); Assert.Null(customerType.FindDiscriminatorProperty()); - Assert.Null(customerType.GetDiscriminatorValue()); + Assert.Equal("Customer", customerType.GetDiscriminatorValue()); Assert.Null(specialCustomerType.FindDiscriminatorProperty()); - Assert.Null(specialCustomerType.GetDiscriminatorValue()); + Assert.Equal("SpecialCustomer", specialCustomerType.GetDiscriminatorValue()); } else { @@ -393,9 +393,9 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) Assert.False(specialityColumn.IsNullable); Assert.Null(customerType.FindDiscriminatorProperty()); - Assert.Null(customerType.GetDiscriminatorValue()); + Assert.Equal("Customer", customerType.GetDiscriminatorValue()); Assert.Null(specialCustomerType.FindDiscriminatorProperty()); - Assert.Null(specialCustomerType.GetDiscriminatorValue()); + Assert.Equal("SpecialCustomer", specialCustomerType.GetDiscriminatorValue()); } else { @@ -765,9 +765,9 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.NotNull(anotherSpecialCustomerDbIndex.MappedIndexes.Single()); Assert.Null(customerType.FindDiscriminatorProperty()); - Assert.Null(customerType.GetDiscriminatorValue()); + Assert.Equal("Customer", customerType.GetDiscriminatorValue()); Assert.Null(specialCustomerType.FindDiscriminatorProperty()); - Assert.Null(specialCustomerType.GetDiscriminatorValue()); + Assert.Equal("SpecialCustomer", specialCustomerType.GetDiscriminatorValue()); } else { diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index 41fc5d719c1..dc852cb12b5 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -162,7 +162,8 @@ protected override void Initialize() typeof(IMutableModel).GetMethod(nameof(IMutableModel.AddOwned)), typeof(IMutableModel).GetMethod(nameof(IMutableModel.AddShared)), typeof(IMutableEntityType).GetMethod(nameof(IMutableEntityType.AddData)), - typeof(IConventionEntityType).GetMethod(nameof(IConventionEntityType.LeastDerivedType)) + typeof(IConventionEntityType).GetMethod(nameof(IConventionEntityType.LeastDerivedType)), + typeof(IConventionEntityType).GetMethod(nameof(IConventionEntityType.RemoveDiscriminatorValue)) }; } } diff --git a/test/EFCore.Tests/Metadata/Conventions/DiscriminatorConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/DiscriminatorConventionTest.cs index 16e9526ce93..f91dffa9265 100644 --- a/test/EFCore.Tests/Metadata/Conventions/DiscriminatorConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/DiscriminatorConventionTest.cs @@ -117,8 +117,8 @@ public void Does_nothing_if_explicit_discriminator_is_not_compatible() Assert.Same(discriminator, ((IReadOnlyEntityType)baseTypeBuilder.Metadata).FindDiscriminatorProperty()); Assert.Equal("T", discriminator.Name); Assert.Equal(typeof(int), discriminator.ClrType); - Assert.Null(baseTypeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Null(entityTypeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null(baseTypeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); + Assert.Null(entityTypeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); } [ConditionalFact] @@ -135,8 +135,8 @@ public void Does_nothing_if_explicit_discriminator_set_on_derived_type() Assert.Null(((IReadOnlyEntityType)entityTypeBuilder.Metadata).FindDiscriminatorProperty()); Assert.Null(((IReadOnlyEntityType)baseTypeBuilder.Metadata).FindDiscriminatorProperty()); - Assert.Null(baseTypeBuilder.Metadata.GetDiscriminatorValue()); - Assert.Null(entityTypeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null(baseTypeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); + Assert.Null(entityTypeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); entityTypeBuilder.HasBaseType((Type)null, ConfigurationSource.DataAnnotation); diff --git a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs index ae4e6ba6279..88baa1d27e3 100644 --- a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs +++ b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs @@ -160,41 +160,6 @@ public void Can_get_and_set_discriminator_value() Assert.Null(entityType.GetDiscriminatorValue()); } - [ConditionalFact] - public void Setting_discriminator_value_when_discriminator_not_set_throws() - { - var modelBuilder = new ModelBuilder(); - - var entityType = modelBuilder - .Entity() - .Metadata; - - Assert.Equal( - CoreStrings.NoDiscriminatorForValue("Customer", "Customer"), - Assert.Throws( - () => entityType.SetDiscriminatorValue("V")).Message); - } - - [ConditionalFact] - public void Setting_incompatible_discriminator_value_throws() - { - var modelBuilder = new ModelBuilder(); - - 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(); diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index f80624120cb..62e78665be9 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -3197,7 +3197,7 @@ public void Can_access_discriminator_value() Assert.NotNull(typeBuilder.HasNoDiscriminator(fromDataAnnotation: true)); Assert.Null(typeBuilder.Metadata.FindDiscriminatorProperty()); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null(typeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.Empty(typeBuilder.Metadata.GetProperties()); } @@ -3219,13 +3219,13 @@ public void Changing_discriminator_type_removes_values() Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, 3)); discriminatorBuilder = typeBuilder.HasDiscriminator("Splowed", typeof(string)); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null(typeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.Null( typeBuilder.ModelBuilder.Entity("Splow") - .Metadata.GetDiscriminatorValue()); + .Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.Null( typeBuilder.ModelBuilder.Entity("Splod") - .Metadata.GetDiscriminatorValue()); + .Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.NotNull(discriminatorBuilder.HasValue(typeBuilder.Metadata, "4")); Assert.NotNull(discriminatorBuilder.HasValue(otherDerivedTypeBuilder.Metadata, "5")); Assert.NotNull(discriminatorBuilder.HasValue(derivedTypeBuilder.Metadata, "6")); @@ -3244,13 +3244,13 @@ public void Changing_discriminator_type_removes_values() discriminatorBuilder = typeBuilder.HasDiscriminator(typeof(int)); Assert.NotNull(discriminatorBuilder); - Assert.Null(typeBuilder.Metadata.GetDiscriminatorValue()); + Assert.Null(typeBuilder.Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.Null( typeBuilder.ModelBuilder.Entity("Splow") - .Metadata.GetDiscriminatorValue()); + .Metadata[CoreAnnotationNames.DiscriminatorValue]); Assert.Null( typeBuilder.ModelBuilder.Entity("Splod") - .Metadata.GetDiscriminatorValue()); + .Metadata[CoreAnnotationNames.DiscriminatorValue]); } [ConditionalFact]