From 7d5813056e8c7c8ff1ee97a1fb0e9ecae8aea1c2 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 3 Mar 2025 10:37:09 -0800 Subject: [PATCH] Add support for complex type discriminator. Breaking change: IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet signature changed Part of #31376 --- .../CosmosDiscriminatorConvention.cs | 8 +- .../Conventions/CosmosJsonIdConvention.cs | 21 +- .../Design/CSharpSnapshotGenerator.cs | 55 +++- .../CSharpRuntimeModelCodeGenerator.cs | 34 ++- .../RelationalModelValidator.cs | 5 + .../DiscriminatorLengthConvention.cs | 2 +- src/EFCore/Infrastructure/ModelValidator.cs | 70 ++++- .../Builders/ComplexPropertyBuilder.cs | 87 ++++-- .../Builders/ComplexPropertyBuilder`.cs | 55 +++- .../ComplexTypeDiscriminatorBuilder.cs | 133 ++++++++ .../ComplexTypeDiscriminatorBuilder`.cs | 34 +++ .../Builders/IConventionComplexTypeBuilder.cs | 57 ++++ ...nventionComplexTypeDiscriminatorBuilder.cs | 34 +++ .../Builders/IConventionEntityTypeBuilder.cs | 40 --- .../Builders/IConventionTypeBaseBuilder.cs | 40 +++ .../Conventions/DiscriminatorConvention.cs | 32 +- .../IDiscriminatorPropertySetConvention.cs | 6 +- .../ConventionDispatcher.ConventionScope.cs | 2 +- ...entionDispatcher.DelayedConventionScope.cs | 10 +- ...tionDispatcher.ImmediateConventionScope.cs | 12 +- .../Internal/ConventionDispatcher.cs | 4 +- .../Conventions/RuntimeModelConvention.cs | 20 +- src/EFCore/Metadata/IComplexType.cs | 26 ++ src/EFCore/Metadata/IConventionComplexType.cs | 35 +++ src/EFCore/Metadata/IConventionEntityType.cs | 52 ---- src/EFCore/Metadata/IConventionTypeBase.cs | 95 ++++++ src/EFCore/Metadata/IEntityType.cs | 10 +- src/EFCore/Metadata/IMutableComplexType.cs | 36 +++ src/EFCore/Metadata/IMutableEntityType.cs | 32 +- src/EFCore/Metadata/IMutableTypeBase.cs | 62 ++++ src/EFCore/Metadata/IReadOnlyComplexType.cs | 34 +++ src/EFCore/Metadata/IReadOnlyEntityType.cs | 52 +--- src/EFCore/Metadata/IReadOnlyTypeBase.cs | 73 +++++ src/EFCore/Metadata/ITypeBase.cs | 32 ++ src/EFCore/Metadata/Internal/ComplexType.cs | 90 +++++- src/EFCore/Metadata/Internal/EntityType.cs | 104 ++----- .../Internal/InternalComplexTypeBuilder.cs | 122 ++++++++ .../Internal/InternalEntityTypeBuilder.cs | 237 +------------- .../Internal/InternalTypeBaseBuilder.cs | 289 +++++++++++++++++- src/EFCore/Metadata/Internal/TypeBase.cs | 206 ++++++++++++- src/EFCore/Metadata/RuntimeComplexProperty.cs | 3 + src/EFCore/Metadata/RuntimeComplexType.cs | 43 ++- src/EFCore/Metadata/RuntimeEntityType.cs | 28 +- src/EFCore/Metadata/RuntimeModel.cs | 6 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 77 ++++- .../Query/StructuralTypeShaperExpression.cs | 6 +- .../CosmosModelBuilderGenericTest.cs | 2 +- ...cipalDerivedDependentBasebyteEntityType.cs | 2 +- ...cipalDerivedDependentBasebyteEntityType.cs | 2 +- .../TestUtilities/CosmosTestStore.cs | 15 + .../DesignApiConsistencyTest.cs | 2 +- ...rpMigrationsGeneratorTest.ModelSnapshot.cs | 15 +- .../CompiledModelRelationalTestBase.cs | 1 + .../Internal/MigrationsModelDifferTest.cs | 3 +- .../RelationalApiConsistencyTest.cs | 39 +-- .../ApiConsistencyTestBase.cs | 65 ++-- .../ModelBuilderTest.ComplexType.cs | 50 ++- .../ModelBuilding/ModelBuilderTest.Generic.cs | 23 ++ .../ModelBuilderTest.NonGeneric.cs | 22 ++ .../ModelBuilderTest.TestModel.cs | 2 +- .../ModelBuilding/ModelBuilderTest.cs | 11 + .../TestUtilities/SqlServerTestStore.cs | 36 +-- .../TestUtilities/SqliteTestStore.cs | 10 +- test/EFCore.Tests/ApiConsistencyTest.cs | 30 +- .../Infrastructure/ModelValidatorTest.cs | 22 ++ .../Conventions/ConventionDispatcherTest.cs | 4 +- 66 files changed, 2106 insertions(+), 761 deletions(-) create mode 100644 src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs create mode 100644 src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index fd42271c82b..2990718e5c0 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -97,14 +97,14 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui /// public override void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) { - var entityType = entityTypeBuilder.Metadata; - if (entityType.IsDocumentRoot()) + if (structuralTypeBuilder.Metadata is not IConventionEntityType entityType + || entityType.IsDocumentRoot()) { - base.ProcessDiscriminatorPropertySet(entityTypeBuilder, name, context); + base.ProcessDiscriminatorPropertySet(structuralTypeBuilder, name, context); } } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs index 45d69cae227..c3748e1974c 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs @@ -174,16 +174,10 @@ private void ProcessEntityType(IConventionEntityType entityType, IConventionCont } // Don't chain, because each of these could return null if the property has been explicitly configured with some other value. - computedIdPropertyBuilder = computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName) - ?? computedIdPropertyBuilder; - - computedIdPropertyBuilder = computedIdPropertyBuilder.IsRequired(true) - ?? computedIdPropertyBuilder; - - computedIdPropertyBuilder = computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory)) - ?? computedIdPropertyBuilder; - + computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName); + computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory)); computedIdPropertyBuilder.AfterSave(PropertySaveBehavior.Throw); + computedIdPropertyBuilder.IsRequired(true); } /// @@ -327,8 +321,13 @@ public virtual void ProcessModelAnnotationChanged( /// public virtual void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) - => ProcessEntityType(entityTypeBuilder.Metadata, context); + { + if (structuralTypeBuilder is IConventionEntityTypeBuilder entityTypeBuilder) + { + ProcessEntityType(entityTypeBuilder.Metadata, context); + } + } } diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index d5efd7f6a81..6eaa03404eb 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -628,9 +628,60 @@ protected virtual void GenerateComplexPropertyAnnotations( IComplexProperty property, IndentedStringBuilder stringBuilder) { + var discriminatorProperty = property.ComplexType.FindDiscriminatorProperty(); + if (discriminatorProperty != null) + { + stringBuilder + .AppendLine() + .Append(propertyBuilderName) + .Append('.') + .Append("HasDiscriminator"); + + if (discriminatorProperty.DeclaringType == property.ComplexType + && discriminatorProperty.Name != "Discriminator") + { + var propertyClrType = FindValueConverter(discriminatorProperty)?.ProviderClrType + .MakeNullable(discriminatorProperty.IsNullable) + ?? discriminatorProperty.ClrType; + stringBuilder + .Append('<') + .Append(Code.Reference(propertyClrType)) + .Append(">(") + .Append(Code.Literal(discriminatorProperty.Name)) + .Append(')'); + } + else + { + stringBuilder + .Append("()"); + } + + var discriminatorValue = property.ComplexType.GetDiscriminatorValue(); + if (discriminatorValue != null) + { + if (discriminatorProperty != null) + { + var valueConverter = FindValueConverter(discriminatorProperty); + if (valueConverter != null) + { + discriminatorValue = valueConverter.ConvertToProvider(discriminatorValue); + } + } + + stringBuilder + .Append('.') + .Append("HasValue") + .Append('(') + .Append(Code.UnknownLiteral(discriminatorValue)) + .Append(')'); + } + + stringBuilder.AppendLine(";"); + } + var propertyAnnotations = Dependencies.AnnotationCodeGenerator - .FilterIgnoredAnnotations(property.GetAnnotations()) - .ToDictionary(a => a.Name, a => a); + .FilterIgnoredAnnotations(property.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); var typeAnnotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(property.ComplexType.GetAnnotations()) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 78b54d3c672..ad23c6aacac 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -925,14 +925,6 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator .Append(_code.Literal(entityType.HasSharedClrType)); } - var discriminatorProperty = entityType.GetDiscriminatorPropertyName(); - if (discriminatorProperty != null) - { - mainBuilder.AppendLine(",") - .Append("discriminatorProperty: ") - .Append(_code.Literal(discriminatorProperty)); - } - var changeTrackingStrategy = entityType.GetChangeTrackingStrategy(); if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) { @@ -959,6 +951,14 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator .Append(_code.Literal(true)); } + var discriminatorProperty = entityType.GetDiscriminatorPropertyName(); + if (discriminatorProperty != null) + { + mainBuilder.AppendLine(",") + .Append("discriminatorProperty: ") + .Append(_code.Literal(discriminatorProperty)); + } + var discriminatorValue = entityType.GetDiscriminatorValue(); if (discriminatorValue != null) { @@ -2182,6 +2182,24 @@ private void CreateComplexProperty( .Append(_code.Literal(true)); } + var discriminatorPropertyName = complexType.GetDiscriminatorPropertyName(); + if (discriminatorPropertyName != null) + { + mainBuilder.AppendLine(",") + .Append("discriminatorProperty: ") + .Append(_code.Literal(discriminatorPropertyName)); + } + + var discriminatorValue = complexType.GetDiscriminatorValue(); + if (discriminatorValue != null) + { + AddNamespace(discriminatorValue.GetType(), parameters.Namespaces); + + mainBuilder.AppendLine(",") + .Append("discriminatorValue: ") + .Append(_code.UnknownLiteral(discriminatorValue)); + } + mainBuilder.AppendLine(",") .Append("propertyCount: ") .Append(_code.Literal(complexType.GetDeclaredProperties().Count())); diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index a2bd60aaecc..84bf260d1ec 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1957,6 +1957,11 @@ protected override void ValidateInheritanceMapping( var discriminatorValues = new Dictionary(); foreach (var derivedType in derivedTypes) { + foreach (var complexProperty in derivedType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + var discriminatorValue = derivedType.GetDiscriminatorValue(); if (!derivedType.ClrType.IsInstantiable() || discriminatorValue is null) diff --git a/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs b/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs index cb149ff89f4..5c9fee094a2 100644 --- a/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs @@ -54,7 +54,7 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, && !discriminatorProperty.IsForeignKey()) { var maxDiscriminatorValueLength = - entityType.GetDerivedTypesInclusive().Select(e => ((string)e.GetDiscriminatorValue()!).Length).Max(); + entityType.GetDerivedTypesInclusive().Select(e => (e.GetDiscriminatorValue() as string)?.Length ?? 0).Max(); var previous = 1; var current = 1; diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 9012bc3a4bc..a6b08037638 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -640,12 +640,16 @@ protected virtual void ValidateInheritanceMapping( /// The entity type to validate. protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType) { - var derivedTypes = rootEntityType.GetDerivedTypesInclusive().ToList(); + var derivedTypes = rootEntityType.GetDerivedTypesInclusive(); var discriminatorProperty = rootEntityType.FindDiscriminatorProperty(); if (discriminatorProperty == null) { - if (derivedTypes.Count == 1) + if (!derivedTypes.Skip(1).Any()) { + foreach (var complexProperty in rootEntityType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } return; } @@ -654,6 +658,68 @@ protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType) } var discriminatorValues = new Dictionary(discriminatorProperty.GetKeyValueComparer()); + foreach (var derivedType in derivedTypes) + { + foreach (var complexProperty in derivedType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + + if (!derivedType.ClrType.IsInstantiable()) + { + continue; + } + + 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, derivedType.DisplayName(), discriminatorProperty.ClrType.DisplayName())); + } + + if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType)) + { + throw new InvalidOperationException( + CoreStrings.DuplicateDiscriminatorValue( + derivedType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName())); + } + + discriminatorValues[discriminatorValue] = derivedType; + } + } + + /// + /// Validates the discriminator and values for the given complex type and nested ones. + /// + /// The entity type to validate. + protected virtual void ValidateDiscriminatorValues(IComplexType complexType) + { + foreach (var complexProperty in complexType.GetComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + + var derivedTypes = complexType.GetDerivedTypesInclusive(); + var discriminatorProperty = complexType.FindDiscriminatorProperty(); + if (discriminatorProperty == null) + { + if (!derivedTypes.Skip(1).Any()) + { + return; + } + + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorProperty(complexType.DisplayName())); + } + + var discriminatorValues = new Dictionary(discriminatorProperty.GetKeyValueComparer()); foreach (var derivedType in derivedTypes) { diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index 5bc54a973d2..aa8be6da059 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -168,11 +168,11 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName) /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -188,11 +188,11 @@ public virtual ComplexTypePropertyBuilder Property(string /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -225,11 +225,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -245,11 +245,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -265,7 +265,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type pr /// If no property with the given name exists, then a new property will be added. /// /// - /// Indexer properties are stored in the entity using + /// Indexer properties are stored in the complex type using /// an indexer /// supplying the provided property name. /// @@ -284,7 +284,7 @@ public virtual ComplexTypePropertyBuilder IndexerProperty /// If no property with the given name exists, then a new property will be added. /// /// - /// Indexer properties are stored in the entity using + /// Indexer properties are stored in the complex type using /// an indexer /// supplying the provided property name. /// @@ -563,8 +563,8 @@ public virtual ComplexPropertyBuilder Ignore(string propertyName) } /// - /// Configures the to be used for this entity type. - /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. /// /// The change tracking strategy to be used. /// The same builder instance so that multiple configuration calls can be chained. @@ -623,6 +623,53 @@ public virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAcces return this; } + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator() + => TypeBuilder.HasDiscriminator(ConfigurationSource.Explicit)!; + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The name of the discriminator property. + /// The type of values stored in the discriminator property. + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator( + string name, + Type type) + { + Check.NotEmpty(name, nameof(name)); + Check.NotNull(type, nameof(type)); + + return TypeBuilder.HasDiscriminator(name, type, ConfigurationSource.Explicit)!; + } + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The type of values stored in the discriminator property. + /// The name of the discriminator property. + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator(string name) + { + Check.NotEmpty(name, nameof(name)); + + return new ComplexTypeDiscriminatorBuilder( + TypeBuilder.HasDiscriminator(name, typeof(TDiscriminator), ConfigurationSource.Explicit)!); + } + + /// + /// Configures the complex type as having no discriminator property. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasNoDiscriminator() + { + TypeBuilder.HasNoDiscriminator(ConfigurationSource.Explicit); + return this; + } + #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index 7997d990656..9a08411918b 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -32,7 +32,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) } /// - /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// Adds or updates an annotation on the complex property. If an annotation with the key specified in /// already exists its value will be updated. /// /// The key of the annotation to be added or updated. @@ -42,7 +42,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) => (ComplexPropertyBuilder)base.HasPropertyAnnotation(annotation, value); /// - /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// Adds or updates an annotation on the complex type. If an annotation with the key specified in /// already exists its value will be updated. /// /// The key of the annotation to be added or updated. @@ -86,7 +86,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) => (ComplexPropertyBuilder)base.HasField(fieldName); /// - /// Returns an object that can be used to configure a property of the entity type. + /// Returns an object that can be used to configure a property of the complex type. /// If the specified property is not already part of the model, it will be added. /// /// @@ -101,7 +101,7 @@ public virtual ComplexTypePropertyBuilder Property(Express .Metadata); /// - /// Returns an object that can be used to configure a primitive collection property of the entity type. + /// Returns an object that can be used to configure a primitive collection property of the complex type. /// If the specified property is not already part of the model, it will be added. /// /// @@ -332,8 +332,8 @@ public virtual ComplexPropertyBuilder ComplexProperty( } /// - /// Excludes the given property from the entity type. This method is typically used to remove properties - /// or navigations from the entity type that were added by convention. + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// or navigations from the complex type that were added by convention. /// /// /// A lambda expression representing the property to be ignored @@ -344,16 +344,16 @@ public virtual ComplexPropertyBuilder Ignore(Expression - /// Excludes the given property from the entity type. This method is typically used to remove properties - /// or navigations from the entity type that were added by convention. + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// or navigations from the complex type that were added by convention. /// - /// The name of the property to be removed from the entity type. + /// The name of the property to be removed from the complex type. public new virtual ComplexPropertyBuilder Ignore(string propertyName) => (ComplexPropertyBuilder)base.Ignore(propertyName); /// - /// Configures the to be used for this entity type. - /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. /// /// The change tracking strategy to be used. /// The same builder instance so that multiple configuration calls can be chained. @@ -381,22 +381,47 @@ public virtual ComplexPropertyBuilder Ignore(Expression (ComplexPropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); /// - /// Sets the to use for all properties of this entity type. + /// Sets the to use for all properties of this complex type. /// /// /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. /// Properties are used for all other accesses. Calling this method will change that behavior - /// for all properties of this entity type as described in the enum. + /// for all properties of this complex type as described in the enum. /// /// - /// Calling this method overrides for all properties of this entity type any access mode that was + /// Calling this method overrides for all properties of this complex type any access mode that was /// set on the model. /// /// - /// The to use for properties of this entity type. + /// The to use for properties of this complex type. /// The same builder instance so that multiple configuration calls can be chained. public new virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => (ComplexPropertyBuilder)base.UseDefaultPropertyAccessMode(propertyAccessMode); + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The type of values stored in the discriminator property. + /// + /// A lambda expression representing the property to be used as the discriminator ( + /// blog => blog.Discriminator). + /// + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator( + Expression> propertyExpression) + { + Check.NotNull(propertyExpression, nameof(propertyExpression)); + + return new ComplexTypeDiscriminatorBuilder( + TypeBuilder.HasDiscriminator(propertyExpression.GetMemberAccess(), ConfigurationSource.Explicit)!); + } + + /// + /// Configures the entity type as having no discriminator property. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasNoDiscriminator() + => (ComplexPropertyBuilder)base.HasNoDiscriminator(); } diff --git a/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs new file mode 100644 index 00000000000..3f352a33375 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexTypeDiscriminatorBuilder : IConventionComplexTypeDiscriminatorBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypeDiscriminatorBuilder(IMutableComplexType complexType) + => ComplexTypeBuilder = ((ComplexType)complexType).Builder; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexTypeBuilder ComplexTypeBuilder { get; } + + /// + /// Configures the default discriminator value to use. + /// + /// The discriminator value. + /// The same builder so that multiple calls can be chained. + public virtual ComplexTypeDiscriminatorBuilder HasValue(object? value) + => HasValue(ComplexTypeBuilder, value, ConfigurationSource.Explicit)!; + + private ComplexTypeDiscriminatorBuilder? HasValue( + InternalComplexTypeBuilder? complexTypeBuilder, + object? value, + ConfigurationSource configurationSource) + { + if (complexTypeBuilder == null) + { + return null; + } + + var baseComplexTypeBuilder = ComplexTypeBuilder; + if (!baseComplexTypeBuilder.Metadata.IsAssignableFrom(complexTypeBuilder.Metadata) + && (!baseComplexTypeBuilder.Metadata.ClrType.IsAssignableFrom(complexTypeBuilder.Metadata.ClrType) + || complexTypeBuilder.HasBaseType(baseComplexTypeBuilder.Metadata, configurationSource) == null)) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorEntityTypeNotDerived( + complexTypeBuilder.Metadata.DisplayName(), + baseComplexTypeBuilder.Metadata.DisplayName())); + } + + if (configurationSource == ConfigurationSource.Explicit) + { + ((IMutableComplexType)complexTypeBuilder.Metadata).SetDiscriminatorValue(value); + } + else + { + if (!((IConventionComplexTypeDiscriminatorBuilder)this).CanSetValue( + value, configurationSource == ConfigurationSource.DataAnnotation)) + { + return null; + } + + ((IConventionComplexType)complexTypeBuilder.Metadata) + .SetDiscriminatorValue(value, configurationSource == ConfigurationSource.DataAnnotation); + } + + return this; + } + + /// + IConventionComplexType IConventionComplexTypeDiscriminatorBuilder.ComplexType + { + [DebuggerStepThrough] + get => ComplexTypeBuilder.Metadata; + } + + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeDiscriminatorBuilder.HasValue(object? value, bool fromDataAnnotation) + => HasValue( + ComplexTypeBuilder, value, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + bool IConventionComplexTypeDiscriminatorBuilder.CanSetValue(object? value, bool fromDataAnnotation) + => ((IConventionComplexTypeBuilder)ComplexTypeBuilder).CanSetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs new file mode 100644 index 00000000000..f5d1fd95ac3 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +/// The type of the discriminator property. +public class ComplexTypeDiscriminatorBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder builder) + => Builder = builder; + + private ComplexTypeDiscriminatorBuilder Builder { get; } + + /// + /// Configures the default discriminator value to use. + /// + /// The discriminator value. + /// The same builder so that multiple calls can be chained. + public virtual ComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => new(Builder.HasValue(value)); +} diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs index f2558141ad4..eebef69dc3e 100644 --- a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs @@ -134,4 +134,61 @@ public interface IConventionComplexTypeBuilder : IConventionTypeBaseBuilder new IConventionComplexTypeBuilder? UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex 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 property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex 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 property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex 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 property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(string name, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex 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 property. + /// The type of values stored in the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(string name, Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex 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 property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Removes the discriminator property from this complex type. + /// This method is usually called when the complex type is no longer mapped to the same table as any other type in + /// the hierarchy or when this complex 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, + /// otherwise. + /// + IConventionComplexTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs new file mode 100644 index 00000000000..84f3309c4a5 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values from conventions. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexTypeDiscriminatorBuilder +{ + /// + /// Gets the complex type on which the discriminator is being configured. + /// + IConventionComplexType ComplexType { get; } + + /// + /// Configures the discriminator value to use. + /// + /// The discriminator value. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder so that multiple calls can be chained. + IConventionComplexTypeDiscriminatorBuilder? HasValue(object? value, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator value can be set from this configuration source. + /// + /// The discriminator value. + /// Indicates whether the configuration was specified using a data annotation. + /// if the discriminator value can be set from this configuration source. + bool CanSetValue(object? value, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index 97310be44a7..3ae88c32f91 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -989,46 +989,6 @@ bool CanHaveTrigger( /// IConventionEntityTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false); - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The name of the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(string name, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The type of values stored in the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(Type type, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The type of values stored in the discriminator property. - /// The name of the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(string name, Type type, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The property mapped to the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be removed. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the discriminator property can be removed. - bool CanRemoveDiscriminator(bool fromDataAnnotation = false); - /// /// Gets or creates a builder for the target of a potential navigation. /// diff --git a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs index 26ce5a70949..93975fbc427 100644 --- a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -399,4 +399,44 @@ bool CanHaveComplexIndexerProperty( /// Indicates whether the configuration was specified using a data annotation. /// if the given can be set. bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The name of the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(string name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The type of values stored in the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(Type type, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The type of values stored in the discriminator property. + /// The name of the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(string name, Type type, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The property mapped to the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be removed. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the discriminator property can be removed. + bool CanRemoveDiscriminator(bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs b/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs index 67b68bbfdb0..4d93d6cfe7f 100644 --- a/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs +++ b/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs @@ -75,7 +75,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( /// public virtual void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) { @@ -84,10 +84,22 @@ public virtual void ProcessDiscriminatorPropertySet( return; } - var discriminator = entityTypeBuilder.HasDiscriminator(name, typeof(string)); - if (discriminator != null) + if (structuralTypeBuilder is IConventionEntityTypeBuilder entityTypeBuilder) { - SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); + var discriminator = entityTypeBuilder.HasDiscriminator(name, typeof(string)); + if (discriminator != null) + { + SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); + } + } + else + { + var complexTypeBuilder = (IConventionComplexTypeBuilder)structuralTypeBuilder; + var discriminator = complexTypeBuilder.HasDiscriminator(name, typeof(string)); + if (discriminator != null) + { + SetDefaultDiscriminatorValue(complexTypeBuilder.Metadata, discriminator); + } } } @@ -121,4 +133,16 @@ protected virtual void SetDefaultDiscriminatorValues( discriminatorBuilder.HasValue(entityType, entityType.GetDefaultDiscriminatorValue()); } } + + /// + /// Configures the discriminator value for the given complex type. + /// + /// The complex type to configure. + /// The discriminator builder. + protected virtual void SetDefaultDiscriminatorValue( + IConventionComplexType complexType, + IConventionComplexTypeDiscriminatorBuilder discriminatorBuilder) + { + discriminatorBuilder.HasValue(complexType.GetDefaultDiscriminatorValue()); + } } diff --git a/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs b/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs index 85e3bf295d1..63ca3b4847b 100644 --- a/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs +++ b/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// -/// Represents an operation that should be performed when a discriminator property is set for an entity type. +/// Represents an operation that should be performed when a discriminator property is set for a type. /// /// /// See Model building conventions for more information and examples. @@ -14,11 +14,11 @@ public interface IDiscriminatorPropertySetConvention : IConvention /// /// Called after a discriminator property is set. /// - /// The builder for the entity type. + /// The builder for the type. /// The name of the discriminator property. /// Additional information associated with convention execution. void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context); } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index 6ccb4600a06..5a179129c29 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -70,7 +70,7 @@ public int GetLeafCount() string name); public abstract string? OnDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name); public abstract IConventionKey? OnEntityTypePrimaryKeyChanged( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index e5195facd77..1a91165384b 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -80,9 +80,9 @@ public override string OnEntityTypeMemberIgnored(IConventionEntityTypeBuilder en return name; } - public override string? OnDiscriminatorPropertySet(IConventionEntityTypeBuilder entityTypeBuilder, string? name) + public override string? OnDiscriminatorPropertySet(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) { - Add(new OnDiscriminatorPropertySetNode(entityTypeBuilder, name)); + Add(new OnDiscriminatorPropertySetNode(structuralTypeBuilder, name)); return name; } @@ -523,13 +523,13 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnEntityTypeMemberIgnored(EntityTypeBuilder, Name); } - private sealed class OnDiscriminatorPropertySetNode(IConventionEntityTypeBuilder entityTypeBuilder, string? name) : ConventionNode + private sealed class OnDiscriminatorPropertySetNode(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) : ConventionNode { - public IConventionEntityTypeBuilder EntityTypeBuilder { get; } = entityTypeBuilder; + public IConventionTypeBaseBuilder StructuralTypeBuilder { get; } = structuralTypeBuilder; public string? Name { get; } = name; public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnDiscriminatorPropertySet(EntityTypeBuilder, Name); + => dispatcher._immediateConventionScope.OnDiscriminatorPropertySet(StructuralTypeBuilder, Name); } private sealed class OnEntityTypeBaseTypeChangedNode( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index ca81bc4da76..aefed1db3d9 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -256,15 +256,15 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !entityTypeBuilder.Metadata.IsIgnored(name) ? null : name; } - public override string? OnDiscriminatorPropertySet(IConventionEntityTypeBuilder entityTypeBuilder, string? name) + public override string? OnDiscriminatorPropertySet(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) { - if (!entityTypeBuilder.Metadata.IsInModel) + if (!structuralTypeBuilder.Metadata.IsInModel) { return null; } #if DEBUG - var initialValue = entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(); + var initialValue = structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(); #endif using (dispatcher.DelayConventions()) { @@ -273,20 +273,20 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB foreach (var entityTypeConvention in conventionSet.DiscriminatorPropertySetConventions) { entityTypeConvention.ProcessDiscriminatorPropertySet( - entityTypeBuilder, name, _nullableStringConventionContext); + structuralTypeBuilder, name, _nullableStringConventionContext); if (_nullableStringConventionContext.ShouldStopProcessing()) { return _nullableStringConventionContext.Result; } #if DEBUG Check.DebugAssert( - initialValue == entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(), + initialValue == structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(); + return structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(); } public override IConventionEntityType? OnEntityTypeBaseTypeChanged( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index a223d479e2c..7fad1e7d49c 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -136,9 +136,9 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual string? OnDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name) - => _scope.OnDiscriminatorPropertySet(entityTypeBuilder, name); + => _scope.OnDiscriminatorPropertySet(structuralTypeBuilder, name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 3e4c032235b..ff39b266bf0 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -245,10 +245,10 @@ private static RuntimeEntityType Create(IEntityType entityType, RuntimeModel mod entityType.ClrType, entityType.BaseType == null ? null : model.FindEntityType(entityType.BaseType.Name)!, entityType.HasSharedClrType, - entityType.GetDiscriminatorPropertyName(), entityType.GetChangeTrackingStrategy(), entityType.FindIndexerPropertyInfo(), entityType.IsPropertyBag, + entityType.GetDiscriminatorPropertyName(), entityType.GetDiscriminatorValue(), derivedTypesCount: entityType.GetDirectlyDerivedTypes().Count(), propertyCount: entityType.GetDeclaredProperties().Count(), @@ -510,23 +510,25 @@ protected virtual void ProcessServicePropertyAnnotations( private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeTypeBase runtimeStructuralType) { + var complexType = complexProperty.ComplexType; var runtimeComplexProperty = runtimeStructuralType.AddComplexProperty( complexProperty.Name, complexProperty.ClrType, - complexProperty.ComplexType.Name, - complexProperty.ComplexType.ClrType, + complexType.Name, + complexType.ClrType, complexProperty.PropertyInfo, complexProperty.FieldInfo, complexProperty.GetPropertyAccessMode(), complexProperty.IsNullable, complexProperty.IsCollection, - complexProperty.ComplexType.GetChangeTrackingStrategy(), - complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag, - propertyCount: complexProperty.ComplexType.GetDeclaredProperties().Count(), - complexPropertyCount: complexProperty.ComplexType.GetDeclaredComplexProperties().Count()); + complexType.GetChangeTrackingStrategy(), + complexType.FindIndexerPropertyInfo(), + complexType.IsPropertyBag, + complexType.GetDiscriminatorPropertyName(), + complexType.GetDiscriminatorValue(), + propertyCount: complexType.GetDeclaredProperties().Count(), + complexPropertyCount: complexType.GetDeclaredComplexProperties().Count()); - var complexType = complexProperty.ComplexType; var runtimeComplexType = runtimeComplexProperty.ComplexType; foreach (var property in complexType.GetProperties()) diff --git a/src/EFCore/Metadata/IComplexType.cs b/src/EFCore/Metadata/IComplexType.cs index cc26dde9b0e..2afdce621e1 100644 --- a/src/EFCore/Metadata/IComplexType.cs +++ b/src/EFCore/Metadata/IComplexType.cs @@ -16,6 +16,32 @@ public interface IComplexType : IReadOnlyComplexType, ITypeBase /// new IComplexProperty ComplexProperty { get; } + /// + /// Gets the base type of this complex type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IComplexType? BaseType { get; } + + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + /// /// Gets the entity type on which the complex property chain is declared. /// diff --git a/src/EFCore/Metadata/IConventionComplexType.cs b/src/EFCore/Metadata/IConventionComplexType.cs index bd2dcb09a38..7745994f11a 100644 --- a/src/EFCore/Metadata/IConventionComplexType.cs +++ b/src/EFCore/Metadata/IConventionComplexType.cs @@ -27,4 +27,39 @@ public interface IConventionComplexType : IReadOnlyComplexType, IConventionTypeB /// Gets the associated property. /// new IConventionComplexProperty ComplexProperty { get; } + + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + new IConventionComplexType? BaseType { get; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IConventionComplexType GetRootType() + => (IConventionComplexType)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); } diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index 0e56d116b9a..254311839f9 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -56,27 +56,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The configuration source for . ConfigurationSource? GetQueryFilterConfigurationSource(); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - new IConventionProperty? FindDiscriminatorProperty() - => (IConventionProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The property to set. - /// Indicates whether the configuration was specified using a data annotation. - /// The discriminator property. - IConventionProperty? SetDiscriminatorProperty(IReadOnlyProperty? property, bool fromDataAnnotation = false); - - /// - /// Gets the for the discriminator property. - /// - /// The or if no discriminator property has been set. - ConfigurationSource? GetDiscriminatorPropertyConfigurationSource(); - /// /// Sets the value indicating whether the discriminator mapping is complete. /// @@ -93,31 +72,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas ConfigurationSource? GetDiscriminatorMappingCompleteConfigurationSource() => FindAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete)?.GetConfigurationSource(); - /// - /// Sets the discriminator value for this entity type. - /// - /// The value to set. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - object? SetDiscriminatorValue(object? value, bool fromDataAnnotation = false) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation) - ?.Value; - - /// - /// Removes the discriminator value for this entity type. - /// - /// The removed discriminator value. - object? RemoveDiscriminatorValue() - => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value; - - /// - /// Gets the for the discriminator value. - /// - /// The or if no discriminator value has been set. - ConfigurationSource? GetDiscriminatorValueConfigurationSource() - => FindAnnotation(CoreAnnotationNames.DiscriminatorValue) - ?.GetConfigurationSource(); - /// /// Sets the base type of this entity type. Returns if this is not a derived type in an inheritance hierarchy. /// @@ -126,12 +80,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The new base type. IConventionEntityType? SetBaseType(IConventionEntityType? entityType, bool fromDataAnnotation = false); - /// - /// Returns the configuration source for the property. - /// - /// The configuration source for the property. - ConfigurationSource? GetBaseTypeConfigurationSource(); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 01904e20875..2e20cb235ee 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -37,6 +37,101 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable new IConventionEntityType ContainingEntityType => (IConventionEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + new IConventionTypeBase? BaseType { get; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IConventionTypeBase GetRootType() + => (IConventionTypeBase)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); + + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + new IConventionProperty? FindDiscriminatorProperty() + => (IConventionProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The property to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The discriminator property. + IConventionProperty? SetDiscriminatorProperty(IReadOnlyProperty? property, bool fromDataAnnotation = false); + + /// + /// Gets the for the discriminator property. + /// + /// The or if no discriminator property has been set. + ConfigurationSource? GetDiscriminatorPropertyConfigurationSource(); + + /// + /// Sets the discriminator value for this type. + /// + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + object? SetDiscriminatorValue(object? value, bool fromDataAnnotation = false) + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation) + ?.Value; + + /// + /// Removes the discriminator value for this type. + /// + /// The removed discriminator value. + object? RemoveDiscriminatorValue() + => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value; + + /// + /// Gets the for the discriminator value. + /// + /// The or if no discriminator value has been set. + ConfigurationSource? GetDiscriminatorValueConfigurationSource() + => FindAnnotation(CoreAnnotationNames.DiscriminatorValue) + ?.GetConfigurationSource(); + + /// + /// Sets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + /// The base type. + /// Indicates whether the configuration was specified using a data annotation. + /// The new base type. + IConventionTypeBase? SetBaseType(IConventionTypeBase? structuralType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for the property. + /// + /// The configuration source for the property. + ConfigurationSource? GetBaseTypeConfigurationSource(); + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 9bd3499963f..53a3bb83c9c 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -25,12 +25,6 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// InstantiationBinding? ServiceOnlyConstructorBinding { get; } - /// - /// Returns the that will be used for storing a discriminator value. - /// - new IProperty? FindDiscriminatorProperty() - => (IProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - /// /// Gets the root base type for a given entity type. /// @@ -73,14 +67,14 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// The derived types. new IEnumerable GetDerivedTypes() - => ((IReadOnlyEntityType)this).GetDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. new IEnumerable GetDerivedTypesInclusive() - => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. diff --git a/src/EFCore/Metadata/IMutableComplexType.cs b/src/EFCore/Metadata/IMutableComplexType.cs index 4e9056ba685..0053729426b 100644 --- a/src/EFCore/Metadata/IMutableComplexType.cs +++ b/src/EFCore/Metadata/IMutableComplexType.cs @@ -22,4 +22,40 @@ public interface IMutableComplexType : IReadOnlyComplexType, IMutableTypeBase /// Gets the associated property. /// new IMutableComplexProperty ComplexProperty { get; } + + /// + /// Gets or sets the base type of this complex type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IMutableComplexType? BaseType { get; } + + /// + /// Gets the root base type for a given complex type. + /// + /// + /// The root base type. If the given complex type is not a derived type, then the same complex type is returned. + /// + new IMutableComplexType GetRootType() + => (IMutableComplexType)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); } diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index d2321aa2b9a..53d118fd707 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -43,19 +43,6 @@ public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase /// The LINQ expression filter. void SetQueryFilter(LambdaExpression? queryFilter); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - new IMutableProperty? FindDiscriminatorProperty() - => (IMutableProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The property to set. - void SetDiscriminatorProperty(IReadOnlyProperty? property); - /// /// Sets the value indicating whether the discriminator mapping is complete. /// @@ -63,19 +50,6 @@ public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase void SetDiscriminatorMappingComplete(bool? complete) => SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete); - /// - /// Sets the discriminator value for this entity type. - /// - /// The value to set. - void SetDiscriminatorValue(object? value) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); - - /// - /// Removes the discriminator value for this entity type. - /// - void RemoveDiscriminatorValue() - => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// @@ -113,21 +87,21 @@ void RemoveDiscriminatorValue() /// /// The derived types. new IEnumerable GetDerivedTypes() - => ((IReadOnlyEntityType)this).GetDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. new IEnumerable GetDerivedTypesInclusive() - => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. new IEnumerable GetDirectlyDerivedTypes() - => ((IReadOnlyEntityType)this).GetDirectlyDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); /// /// Gets the root base type for a given entity type. diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index 3c5080be740..69efecea0e5 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -32,6 +32,42 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable new IMutableEntityType ContainingEntityType => (IMutableEntityType)this; + /// + /// Gets or sets the base type of this type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IMutableTypeBase? BaseType { get; set; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IMutableTypeBase GetRootType() + => (IMutableTypeBase)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. @@ -60,6 +96,32 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable /// The list of ignored member names. IEnumerable GetIgnoredMembers(); + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + new IMutableProperty? FindDiscriminatorProperty() + => (IMutableProperty?)((IReadOnlyTypeBase)this).FindDiscriminatorProperty(); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The property to set. + void SetDiscriminatorProperty(IReadOnlyProperty? property); + + /// + /// Sets the discriminator value for this type. + /// + /// The value to set. + void SetDiscriminatorValue(object? value) + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); + + /// + /// Removes the discriminator value for this type. + /// + void RemoveDiscriminatorValue() + => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); + /// /// Adds a property to this type. /// diff --git a/src/EFCore/Metadata/IReadOnlyComplexType.cs b/src/EFCore/Metadata/IReadOnlyComplexType.cs index 6d6633a641c..e23f8c684fd 100644 --- a/src/EFCore/Metadata/IReadOnlyComplexType.cs +++ b/src/EFCore/Metadata/IReadOnlyComplexType.cs @@ -19,6 +19,12 @@ public interface IReadOnlyComplexType : IReadOnlyTypeBase /// IReadOnlyComplexProperty ComplexProperty { get; } + /// + /// Gets the base type of this complex type. Returns if this is not a + /// derived type in an inheritance hierarchy. + /// + new IReadOnlyComplexType? BaseType { get; } + /// /// Gets a value indicating whether given type is one of the containing types for this complex type. /// @@ -44,6 +50,34 @@ bool IsContainedBy(Type type) return false; } + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => new[] { this }.Concat(GetDerivedTypes()); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Gets the root base type for a given entity type. + /// + /// + /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. + /// + new IReadOnlyComplexType GetRootType() + => BaseType?.GetRootType() ?? this; + /// /// /// Creates a human-readable representation of the given metadata. diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index b5ee5ba7083..0b04f92494c 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -18,7 +18,7 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase /// Gets the base type of this entity type. Returns if this is not a /// derived type in an inheritance hierarchy. /// - IReadOnlyEntityType? BaseType { get; } + new IReadOnlyEntityType? BaseType { get; } /// /// Gets the data stored in the model for the given entity type. @@ -35,22 +35,6 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase /// The LINQ expression filter. LambdaExpression? GetQueryFilter(); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - IReadOnlyProperty? FindDiscriminatorProperty() - { - var propertyName = GetDiscriminatorPropertyName(); - return propertyName == null ? null : FindProperty(propertyName); - } - - /// - /// Returns the name of the property that will be used for storing a discriminator value. - /// - /// The name of the property that will be used for storing a discriminator value. - string? GetDiscriminatorPropertyName(); - /// /// Returns the value indicating whether the discriminator mapping is complete for this entity type. /// @@ -58,28 +42,6 @@ bool GetIsDiscriminatorMappingComplete() => (bool?)this[CoreAnnotationNames.DiscriminatorMappingComplete] ?? true; - /// - /// Returns the discriminator value for this entity type. - /// - /// The discriminator value for this entity type. - object? GetDiscriminatorValue() - { - var annotation = FindAnnotation(CoreAnnotationNames.DiscriminatorValue); - return annotation != null - ? annotation.Value - : !ClrType.IsInstantiable() - || (BaseType == null && GetDirectlyDerivedTypes().Count() == 0) - ? null - : (object?)GetDefaultDiscriminatorValue(); - } - - /// - /// Returns the default discriminator value that would be used for this entity type. - /// - /// The default discriminator value for this entity type. - string GetDefaultDiscriminatorValue() - => !HasSharedClrType ? ClrType.ShortDisplayName() : ShortName(); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// @@ -123,20 +85,22 @@ IEnumerable GetAllBaseTypesInclusiveAscending() /// Gets all types in the model that derive from this entity type. /// /// The derived types. - IEnumerable GetDerivedTypes(); + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. - IEnumerable GetDerivedTypesInclusive() - => new[] { this }.Concat(GetDerivedTypes()); + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. - IEnumerable GetDirectlyDerivedTypes(); + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); /// /// Returns all the derived types of this entity type, including the type itself, @@ -152,7 +116,7 @@ IEnumerable GetConcreteDerivedTypesInclusive() /// /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. /// - IReadOnlyEntityType GetRootType() + new IReadOnlyEntityType GetRootType() => BaseType?.GetRootType() ?? this; /// diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index 5da87dc25aa..b08a3e3eb71 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -24,6 +25,12 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable IReadOnlyEntityType ContainingEntityType => (IReadOnlyEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a + /// derived type in an inheritance hierarchy. + /// + IReadOnlyTypeBase? BaseType { get; } + /// /// Gets the name of this type. /// @@ -177,6 +184,72 @@ bool IsAssignableFrom(IReadOnlyTypeBase derivedType) bool IsStrictlyDerivedFrom(IReadOnlyTypeBase baseType) => this != Check.NotNull(baseType, nameof(baseType)) && baseType.IsAssignableFrom(this); + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + IEnumerable GetDerivedTypes(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + IEnumerable GetDerivedTypesInclusive() + => new[] { this }.Concat(GetDerivedTypes()); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Gets the root base type for a given entity type. + /// + /// + /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. + /// + IReadOnlyTypeBase GetRootType() + => BaseType?.GetRootType() ?? this; + + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + IReadOnlyProperty? FindDiscriminatorProperty() + { + var propertyName = GetDiscriminatorPropertyName(); + return propertyName == null ? null : FindProperty(propertyName); + } + + /// + /// Returns the name of the property that will be used for storing a discriminator value. + /// + /// The name of the property that will be used for storing a discriminator value. + string? GetDiscriminatorPropertyName(); + + /// + /// Returns the discriminator value for this type. + /// + /// The discriminator value for this type. + object? GetDiscriminatorValue() + { + var annotation = FindAnnotation(CoreAnnotationNames.DiscriminatorValue); + return annotation != null + ? annotation.Value + : !ClrType.IsInstantiable() + || (BaseType == null && GetDirectlyDerivedTypes().Count() == 0) + ? null + : (object?)GetDefaultDiscriminatorValue(); + } + + /// + /// Returns the default discriminator value that would be used for this type. + /// + /// The default discriminator value for this type. + string GetDefaultDiscriminatorValue() + => !HasSharedClrType ? ClrType.ShortDisplayName() : ShortName(); + /// /// Gets the property with the given name. Returns if no property with the given name is defined. /// diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index 45aa87e5bc3..e0d92258e51 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -22,11 +22,43 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable new IEntityType ContainingEntityType => (IEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new ITypeBase? BaseType { get; } + /// /// Gets the for the preferred constructor. /// InstantiationBinding? ConstructorBinding { get; } + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Returns the that will be used for storing a discriminator value. + /// + new IProperty? FindDiscriminatorProperty() + => (IProperty?)((IReadOnlyTypeBase)this).FindDiscriminatorProperty(); + /// /// Gets a property on the given type. Returns if no property is found. /// diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs index b906a8a1c5b..35e15e5726f 100644 --- a/src/EFCore/Metadata/Internal/ComplexType.cs +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -137,6 +137,15 @@ public virtual void SetRemovedFromModel() public new virtual ComplexType? BaseType => (ComplexType?)base.BaseType; + /// + /// 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 TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource configurationSource) + => SetBaseType((ComplexType?)newBaseType, 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 @@ -216,7 +225,7 @@ public virtual void SetRemovedFromModel() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + public override ConfigurationSource? GetBaseTypeConfigurationSource() => _baseTypeConfigurationSource; [DebuggerStepThrough] @@ -290,7 +299,7 @@ public virtual bool IsAssignableFrom(ComplexType derivedType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ComplexType GetRootType() + public new virtual ComplexType GetRootType() => BaseType?.GetRootType() ?? this; /// @@ -529,6 +538,53 @@ public override string ToString() #region Explicit interface implementations + /// + /// 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. + /// + IReadOnlyComplexType? IReadOnlyComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// 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. + /// + IMutableComplexType? IMutableComplexType.BaseType + { + get => BaseType; + } + + /// + /// 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. + /// + IConventionComplexType? IConventionComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// 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. + /// + IComplexType? IComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + /// /// 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 @@ -685,5 +741,35 @@ IEntityType ITypeBase.ContainingEntityType get => ContainingEntityType; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDerivedTypes() + => GetDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + #endregion } diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 7cadc45f845..a0133ed02b4 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -330,6 +330,15 @@ private string DisplayName() private void UpdateIsKeylessConfigurationSource(ConfigurationSource configurationSource) => _isKeylessConfigurationSource = configurationSource.Max(_isKeylessConfigurationSource); + /// + /// 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 TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource configurationSource) + => SetBaseType((EntityType?)newBaseType, 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 @@ -423,9 +432,15 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + public override ConfigurationSource? GetBaseTypeConfigurationSource() => _baseTypeConfigurationSource; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); @@ -475,8 +490,8 @@ private bool InheritsFrom(EntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual EntityType GetRootType() - => (EntityType)((IReadOnlyEntityType)this).GetRootType(); + new public virtual EntityType GetRootType() + => (EntityType)((IReadOnlyTypeBase)this).GetRootType(); /// /// Runs the conventions when an annotation was set or removed. @@ -2913,65 +2928,6 @@ public virtual PropertyAccessMode GetNavigationAccessMode() public virtual LambdaExpression? SetDefiningQuery(LambdaExpression? definingQuery, ConfigurationSource configurationSource) => (LambdaExpression?)SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, definingQuery, configurationSource)?.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 - /// 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 Property? SetDiscriminatorProperty(Property? property, ConfigurationSource configurationSource) - { - if ((string?)this[CoreAnnotationNames.DiscriminatorProperty] == property?.Name) - { - return property; - } - - CheckDiscriminatorProperty(property); - - SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource); - - return Model.ConventionDispatcher.OnDiscriminatorPropertySet(Builder, property?.Name) == property?.Name - ? property - : (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - } - - private void CheckDiscriminatorProperty(Property? property) - { - if (property != null) - { - if (BaseType != null) - { - throw new InvalidOperationException( - CoreStrings.DiscriminatorPropertyMustBeOnRoot(DisplayName())); - } - - if (property.DeclaringType != this) - { - throw new InvalidOperationException( - CoreStrings.DiscriminatorPropertyNotFound(property.Name, DisplayName())); - } - } - } - - /// - /// Returns the name of the property that will be used for storing a discriminator value. - /// - /// The name of the property that will be used for storing a discriminator value. - public virtual string? GetDiscriminatorPropertyName() - => BaseType is null - ? (string?)this[CoreAnnotationNames.DiscriminatorProperty] - : ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - public virtual ConfigurationSource? GetDiscriminatorPropertyConfigurationSource() - => FindAnnotation(CoreAnnotationNames.DiscriminatorProperty)?.GetConfigurationSource(); - /// /// 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 @@ -3225,30 +3181,6 @@ IModel ITypeBase.Model get => BaseType; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableEntityType.SetDiscriminatorProperty(IReadOnlyProperty? property) - => SetDiscriminatorProperty((Property?)property, ConfigurationSource.Explicit); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.SetDiscriminatorProperty( - IReadOnlyProperty? property, - bool fromDataAnnotation) - => SetDiscriminatorProperty( - (Property?)property, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs index c32a85e7df1..6c150269c17 100644 --- a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs @@ -503,6 +503,60 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => configurationSource.Overrides(Metadata.GetServiceOnlyConstructorBindingConfigurationSource()) || Metadata.ServiceOnlyConstructorBinding == constructorBinding; + /// + /// 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 ComplexTypeDiscriminatorBuilder? HasDiscriminator(ConfigurationSource configurationSource) + => DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo: null, 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 ComplexTypeDiscriminatorBuilder? HasDiscriminator( + string? name, + Type? type, + ConfigurationSource configurationSource) + { + Check.DebugAssert(name != null || type != null, $"Either {nameof(name)} or {nameof(type)} should be non-null"); + + return CanSetDiscriminator(name, type, configurationSource) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type, name, memberInfo: null, configurationSource)) + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeDiscriminatorBuilder? HasDiscriminator(MemberInfo memberInfo, ConfigurationSource configurationSource) + => CanSetDiscriminator( + Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo, configurationSource)) + : null; + + private ComplexTypeDiscriminatorBuilder? DiscriminatorBuilder(InternalPropertyBuilder? discriminatorPropertyBuilder) + => discriminatorPropertyBuilder == null ? null : new ComplexTypeDiscriminatorBuilder(Metadata); + + /// + /// 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 new virtual InternalComplexTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + => (InternalComplexTypeBuilder?)base.HasNoDiscriminator(configurationSource); + IConventionComplexType IConventionComplexTypeBuilder.Metadata { [DebuggerStepThrough] @@ -617,4 +671,72 @@ IConventionComplexTypeBuilder IConventionComplexTypeBuilder.RemoveUnusedImplicit bool fromDataAnnotation) => (IConventionComplexTypeBuilder?)UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(bool fromDataAnnotation) + => HasDiscriminator( + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(Type type, bool fromDataAnnotation) + => HasDiscriminator( + name: null, Check.NotNull(type, nameof(type)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(string name, bool fromDataAnnotation) + => HasDiscriminator( + Check.NotEmpty(name, nameof(name)), type: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(string name, Type type, bool fromDataAnnotation) + => HasDiscriminator( + Check.NotEmpty(name, nameof(name)), Check.NotNull(type, nameof(type)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) + => HasDiscriminator( + memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public IConventionComplexTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false) + => HasNoDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 58c607bc23e..7f04bcd9f9e 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -4352,8 +4352,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( /// public virtual DiscriminatorBuilder? HasDiscriminator(ConfigurationSource configurationSource) => DiscriminatorBuilder( - GetOrCreateDiscriminatorProperty(type: null, name: null, ConfigurationSource.Convention), - configurationSource); + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo: null, configurationSource)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4370,8 +4369,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( return CanSetDiscriminator(name, type, configurationSource) ? DiscriminatorBuilder( - GetOrCreateDiscriminatorProperty(type, name, configurationSource), - configurationSource) + GetOrCreateDiscriminatorProperty(type, name, memberInfo: null, configurationSource)) : null; } @@ -4385,87 +4383,11 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => CanSetDiscriminator( Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) ? DiscriminatorBuilder( - Metadata.GetRootType().Builder.Property( - memberInfo, configurationSource), - configurationSource) + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo, configurationSource)) : null; - private const string DefaultDiscriminatorName = "Discriminator"; - - private static readonly Type DefaultDiscriminatorType = typeof(string); - - private InternalPropertyBuilder? GetOrCreateDiscriminatorProperty(Type? type, string? name, ConfigurationSource configurationSource) - { - var discriminatorProperty = ((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty(); - if ((name != null && discriminatorProperty?.Name != name) - || (type != null && discriminatorProperty?.ClrType != type)) - { - discriminatorProperty = null; - } - - return Metadata.GetRootType().Builder.Property( - type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, - name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, - typeConfigurationSource: type != null ? configurationSource : null, - configurationSource)?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention); - } - - private DiscriminatorBuilder? DiscriminatorBuilder( - InternalPropertyBuilder? discriminatorPropertyBuilder, - ConfigurationSource configurationSource) - { - if (discriminatorPropertyBuilder == null) - { - return null; - } - - var rootTypeBuilder = Metadata.GetRootType().Builder; - var discriminatorProperty = discriminatorPropertyBuilder.Metadata; - // Make sure the property is on the root type - discriminatorPropertyBuilder = rootTypeBuilder.Property( - discriminatorProperty.ClrType, discriminatorProperty.Name, null, ConfigurationSource.Convention)!; - - RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource); - - rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, configurationSource); - - RemoveIncompatibleDiscriminatorValues(Metadata, discriminatorProperty, configurationSource); - - discriminatorPropertyBuilder.IsRequired(true, ConfigurationSource.Convention); - discriminatorPropertyBuilder.HasValueGeneratorFactory( - typeof(DiscriminatorValueGeneratorFactory), ConfigurationSource.Convention); - - return new DiscriminatorBuilder(Metadata); - } - - private void RemoveIncompatibleDiscriminatorValues( - EntityType entityType, - Property? newDiscriminatorProperty, - ConfigurationSource configurationSource) - { - if ((newDiscriminatorProperty != null || entityType.BaseType != null) - && (newDiscriminatorProperty == null - || newDiscriminatorProperty.ClrType.IsInstanceOfType(((IReadOnlyEntityType)entityType).GetDiscriminatorValue()))) - { - return; - } - - if (configurationSource.Overrides(((IConventionEntityType)entityType).GetDiscriminatorValueConfigurationSource())) - { - ((IMutableEntityType)entityType).RemoveDiscriminatorValue(); - } - - if (entityType.BaseType == null) - { - foreach (var derivedType in entityType.GetDerivedTypes()) - { - if (configurationSource.Overrides(((IConventionEntityType)derivedType).GetDiscriminatorValueConfigurationSource())) - { - ((IMutableEntityType)derivedType).RemoveDiscriminatorValue(); - } - } - } - } + private DiscriminatorBuilder? DiscriminatorBuilder(InternalPropertyBuilder? discriminatorPropertyBuilder) + => discriminatorPropertyBuilder == null ? null : new DiscriminatorBuilder(Metadata); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4473,27 +4395,14 @@ private void RemoveIncompatibleDiscriminatorValues( /// 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 InternalEntityTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + public override InternalEntityTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) { - if (Metadata[CoreAnnotationNames.DiscriminatorProperty] == null) - { - return this; - } - - if (!configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) + var builder = base.HasNoDiscriminator(configurationSource); + if (builder == null) { return null; } - if (((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty()?.DeclaringType == Metadata) - { - RemoveUnusedDiscriminatorProperty(null, configurationSource); - } - - Metadata.SetDiscriminatorProperty(null, configurationSource); - - RemoveIncompatibleDiscriminatorValues(Metadata, null, configurationSource); - if (configurationSource == ConfigurationSource.Explicit) { ((IMutableEntityType)Metadata).SetDiscriminatorMappingComplete(null); @@ -4508,78 +4417,6 @@ private void RemoveIncompatibleDiscriminatorValues( return this; } - private void RemoveUnusedDiscriminatorProperty(Property? newDiscriminatorProperty, ConfigurationSource configurationSource) - { - var oldDiscriminatorProperty = ((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty() as Property; - if (oldDiscriminatorProperty?.IsInModel == true - && oldDiscriminatorProperty != newDiscriminatorProperty) - { - oldDiscriminatorProperty.DeclaringType.Builder.RemoveUnusedImplicitProperties( - new[] { oldDiscriminatorProperty }); - - if (oldDiscriminatorProperty.IsInModel) - { - oldDiscriminatorProperty.Builder.IsRequired(null, configurationSource); - oldDiscriminatorProperty.Builder.HasValueGenerator((Type?)null, 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 CanSetDiscriminator(string? name, Type? type, ConfigurationSource configurationSource) - => name == null && type == null - ? CanRemoveDiscriminator(configurationSource) - : CanSetDiscriminator(((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty(), name, type, configurationSource); - - private bool CanSetDiscriminator( - IReadOnlyProperty? discriminatorProperty, - string? name, - Type? discriminatorType, - ConfigurationSource configurationSource) - => ((name == null && discriminatorType == null) - || ((name == null || discriminatorProperty?.Name == name) - && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) - || configurationSource.Overrides(Metadata.GetRootType().GetDiscriminatorPropertyConfigurationSource())) - && (discriminatorProperty != null - || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( - discriminatorType ?? DefaultDiscriminatorType, - name ?? DefaultDiscriminatorName, - typeConfigurationSource: discriminatorType != null - ? configurationSource - : null)); - - private bool CanRemoveDiscriminator(ConfigurationSource configurationSource) - => CanSetAnnotation(CoreAnnotationNames.DiscriminatorProperty, null, configurationSource); - - private bool CanAddDiscriminatorProperty( - Type propertyType, - string name, - ConfigurationSource? typeConfigurationSource) - { - var conflictingProperty = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault(); - if (conflictingProperty != null - && (conflictingProperty.IsShadowProperty() || conflictingProperty.IsIndexerProperty()) - && conflictingProperty.ClrType != propertyType - && typeConfigurationSource != null - && !typeConfigurationSource.Overrides(conflictingProperty.GetTypeConfigurationSource())) - { - return false; - } - - var memberInfo = Metadata.IsPropertyBag - ? null - : Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault(); - - return memberInfo == null - || propertyType == memberInfo.GetMemberType() - || typeConfigurationSource == null; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5597,64 +5434,6 @@ bool IConventionEntityTypeBuilder.CanSetDefiningQuery(LambdaExpression? query, b IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoDiscriminator(bool fromDataAnnotation) => HasNoDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(string name, bool fromDataAnnotation) - => CanSetDiscriminator( - name, type: null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(Type type, bool fromDataAnnotation) - => CanSetDiscriminator( - name: null, type, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(string name, Type type, bool fromDataAnnotation) - => CanSetDiscriminator( - name, type, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) - => CanSetDiscriminator( - Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanRemoveDiscriminator(bool fromDataAnnotation) - => CanRemoveDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index 0e889e243f4..213ba106bdb 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -13,7 +13,8 @@ namespace Microsoft.EntityFrameworkCore.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 abstract class InternalTypeBaseBuilder : AnnotatableBuilder, +public abstract class InternalTypeBaseBuilder : + AnnotatableBuilder, IConventionTypeBaseBuilder { /// @@ -169,9 +170,9 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing ConfigurationSource? configurationSource, bool skipTypeCheck = false) { - var entityType = Metadata; + var structuralType = Metadata; List? propertiesToDetach = null; - var existingProperty = entityType.FindProperty(propertyName); + var existingProperty = structuralType.FindProperty(propertyName); if (existingProperty != null) { if (existingProperty.DeclaringType != Metadata) @@ -181,7 +182,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing Metadata.RemoveIgnored(propertyName); } - entityType = (EntityType)existingProperty.DeclaringType; + structuralType = existingProperty.DeclaringType; } if (IsCompatible(memberInfo, existingProperty) @@ -274,7 +275,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing RemoveMembersInHierarchy(propertyName, configurationSource.Value); } - builder = entityType.AddProperty( + builder = structuralType.AddProperty( propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; detachedProperties?.Attach(this); @@ -1022,7 +1023,7 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo Metadata.RemoveIgnored(propertyName); } - typeBase = (EntityType)existingComplexProperty.DeclaringType; + typeBase = existingComplexProperty.DeclaringType; } var existingComplexType = existingComplexProperty.ComplexType; @@ -1336,6 +1337,224 @@ public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessM => configurationSource.Overrides(((IConventionTypeBase)Metadata).GetPropertyAccessModeConfigurationSource()) || ((IConventionTypeBase)Metadata).GetPropertyAccessMode() == propertyAccessMode; + private const string DefaultDiscriminatorName = "Discriminator"; + + 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 InternalTypeBaseBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + { + if (Metadata[CoreAnnotationNames.DiscriminatorProperty] == null) + { + return this; + } + + if (!configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) + { + return null; + } + + if (((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty()?.DeclaringType == Metadata) + { + RemoveUnusedDiscriminatorProperty(null, configurationSource); + } + + Metadata.SetDiscriminatorProperty(null, configurationSource); + + RemoveIncompatibleDiscriminatorValues(Metadata, null, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 InternalPropertyBuilder? GetOrCreateDiscriminatorProperty( + Type? type, string? name, MemberInfo? memberInfo, ConfigurationSource configurationSource) + { + if (memberInfo != null) + { + type ??= memberInfo.GetMemberType(); + name ??= memberInfo.Name; + } + else + { + var existingDiscriminatorProperty = ((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty(); + if ((name == null || (existingDiscriminatorProperty?.Name) == name) + && (type == null || (existingDiscriminatorProperty?.ClrType) == type)) + { + type ??= existingDiscriminatorProperty?.ClrType; + name ??= existingDiscriminatorProperty?.Name; + } + + type ??= DefaultDiscriminatorType; + name ??= DefaultDiscriminatorName; + } + + var rootType = Metadata.GetRootType(); + var discriminatorPropertyBuilder = rootType.Builder.Property( + type, + name, + memberInfo, + typeConfigurationSource: type != null ? ConfigurationSource.Convention : null, + ConfigurationSource.Convention); + + if (discriminatorPropertyBuilder == null) + { + if (configurationSource != ConfigurationSource.Convention) + { + rootType.Builder.RemoveProperty(rootType.FindProperty(name)!, configurationSource); + discriminatorPropertyBuilder = rootType.Builder.Property( + type, + name, + memberInfo, + typeConfigurationSource: type != null ? ConfigurationSource.Convention : null, + ConfigurationSource.Convention); + } + + if (discriminatorPropertyBuilder == null) + { + return null; + } + } + + var discriminatorProperty = discriminatorPropertyBuilder.Metadata; + RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource); + + rootType.SetDiscriminatorProperty(discriminatorProperty, configurationSource); + + RemoveIncompatibleDiscriminatorValues(Metadata, discriminatorProperty, configurationSource); + + discriminatorPropertyBuilder.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention); + discriminatorPropertyBuilder.IsRequired(true, ConfigurationSource.Convention); + discriminatorPropertyBuilder.HasValueGeneratorFactory( + typeof(DiscriminatorValueGeneratorFactory), ConfigurationSource.Convention); + + return discriminatorPropertyBuilder; + } + + private void RemoveUnusedDiscriminatorProperty(Property? newDiscriminatorProperty, ConfigurationSource configurationSource) + { + var oldDiscriminatorProperty = ((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty() as Property; + if (oldDiscriminatorProperty?.IsInModel == true + && oldDiscriminatorProperty != newDiscriminatorProperty) + { + oldDiscriminatorProperty.DeclaringType.Builder.RemoveUnusedImplicitProperties([oldDiscriminatorProperty]); + + if (oldDiscriminatorProperty.IsInModel) + { + // TODO: Remove this once layering is implemented, #15898 + oldDiscriminatorProperty.Builder.AfterSave(null, ConfigurationSource.Convention); + oldDiscriminatorProperty.Builder.IsRequired(null, ConfigurationSource.Convention); + oldDiscriminatorProperty.Builder.HasValueGenerator((Type?)null, ConfigurationSource.Convention); + } + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetDiscriminator(string? name, Type? type, ConfigurationSource configurationSource) + => name == null && type == null + ? CanRemoveDiscriminator(configurationSource) + : CanSetDiscriminator(((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty(), name, type, configurationSource); + + private bool CanSetDiscriminator( + IReadOnlyProperty? discriminatorProperty, + string? name, + Type? discriminatorType, + ConfigurationSource configurationSource) + => ((name == null && discriminatorType == null) + || ((name == null || discriminatorProperty?.Name == name) + && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) + || configurationSource.Overrides(Metadata.GetRootType().GetDiscriminatorPropertyConfigurationSource())) + && (discriminatorProperty != null + || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( + discriminatorType ?? DefaultDiscriminatorType, + name ?? DefaultDiscriminatorName, + typeConfigurationSource: discriminatorType != null + ? configurationSource + : null)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveDiscriminator(ConfigurationSource configurationSource) + => CanSetAnnotation(CoreAnnotationNames.DiscriminatorProperty, null, configurationSource); + + private bool CanAddDiscriminatorProperty( + Type propertyType, + string name, + ConfigurationSource? typeConfigurationSource) + { + var conflictingProperty = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault(); + if (conflictingProperty != null + && (conflictingProperty.IsShadowProperty() || conflictingProperty.IsIndexerProperty()) + && conflictingProperty.ClrType != propertyType + && typeConfigurationSource != null + && !typeConfigurationSource.Overrides(conflictingProperty.GetTypeConfigurationSource())) + { + return false; + } + + var memberInfo = Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + + return memberInfo == null + || propertyType == memberInfo.GetMemberType() + || typeConfigurationSource == null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual void RemoveIncompatibleDiscriminatorValues( + TypeBase structuralType, + Property? newDiscriminatorProperty, + ConfigurationSource configurationSource) + { + if ((newDiscriminatorProperty != null || structuralType.BaseType != null) + && (newDiscriminatorProperty == null + || newDiscriminatorProperty.ClrType.IsInstanceOfType(((IReadOnlyTypeBase)structuralType).GetDiscriminatorValue()))) + { + return; + } + + if (configurationSource.Overrides(((IConventionTypeBase)structuralType).GetDiscriminatorValueConfigurationSource())) + { + ((IMutableTypeBase)structuralType).RemoveDiscriminatorValue(); + } + + if (structuralType.BaseType == null) + { + foreach (var derivedType in structuralType.GetDerivedTypes()) + { + if (configurationSource.Overrides(((IConventionTypeBase)derivedType).GetDiscriminatorValueConfigurationSource())) + { + ((IMutableTypeBase)derivedType).RemoveDiscriminatorValue(); + } + } + } + } + IConventionTypeBase IConventionTypeBaseBuilder.Metadata { [DebuggerStepThrough] @@ -1375,6 +1594,16 @@ IConventionTypeBase IConventionTypeBaseBuilder.Metadata => (IConventionTypeBaseBuilder?)base.HasNoAnnotation( name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanRemoveDiscriminator(bool fromDataAnnotation) + => CanRemoveDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1768,4 +1997,52 @@ bool IConventionTypeBaseBuilder.CanSetChangeTrackingStrategy(ChangeTrackingStrat bool IConventionTypeBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(string name, bool fromDataAnnotation) + => CanSetDiscriminator( + name, type: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(Type type, bool fromDataAnnotation) + => CanSetDiscriminator( + name: null, type, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(string name, Type type, bool fromDataAnnotation) + => CanSetDiscriminator( + name, type, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) + => CanSetDiscriminator( + Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 720db7259b9..e654a0e2f7e 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -155,7 +155,7 @@ public virtual InternalTypeBaseBuilder Builder /// 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 TypeBase? BaseType + public virtual TypeBase? BaseType { get => _baseType; set => _baseType = value; @@ -167,9 +167,25 @@ protected virtual TypeBase? BaseType /// 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 SortedSet DirectlyDerivedTypes + public virtual SortedSet DirectlyDerivedTypes => _directlyDerivedTypes; + /// + /// 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 abstract TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource 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 abstract ConfigurationSource? GetBaseTypeConfigurationSource(); + /// /// 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 @@ -190,7 +206,7 @@ protected virtual IEnumerable GetDerivedTypes() { if (DirectlyDerivedTypes.Count == 0) { - return Enumerable.Empty(); + return []; } var derivedTypes = new List(); @@ -217,7 +233,7 @@ protected virtual IEnumerable GetDerivedTypes() [DebuggerStepThrough] public virtual IEnumerable GetDerivedTypesInclusive() => DirectlyDerivedTypes.Count == 0 - ? new[] { this } + ? [this] : new[] { this }.Concat(GetDerivedTypes()); /// @@ -254,6 +270,16 @@ public virtual bool IsAssignableFrom(TypeBase derivedType) return false; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual TypeBase GetRootType() + => (TypeBase)((IReadOnlyTypeBase)this).GetRootType(); + /// /// 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 @@ -344,6 +370,65 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS return null; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Property? SetDiscriminatorProperty(Property? property, ConfigurationSource configurationSource) + { + if ((string?)this[CoreAnnotationNames.DiscriminatorProperty] == property?.Name) + { + return property; + } + + CheckDiscriminatorProperty(property); + + SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource); + + return Model.ConventionDispatcher.OnDiscriminatorPropertySet(Builder, property?.Name) == property?.Name + ? property + : (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); + } + + private void CheckDiscriminatorProperty(Property? property) + { + if (property != null) + { + if (BaseType != null) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyMustBeOnRoot(DisplayName())); + } + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyNotFound(property.Name, DisplayName())); + } + } + } + + /// + /// Returns the name of the property that will be used for storing a discriminator value. + /// + /// The name of the property that will be used for storing a discriminator value. + public virtual string? GetDiscriminatorPropertyName() + => BaseType is null + ? (string?)this[CoreAnnotationNames.DiscriminatorProperty] + : ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual ConfigurationSource? GetDiscriminatorPropertyConfigurationSource() + => FindAnnotation(CoreAnnotationNames.DiscriminatorProperty)?.GetConfigurationSource(); + #region Properties /// @@ -1447,6 +1532,54 @@ Type IReadOnlyTypeBase.ClrType get => ClrType; } + /// + /// 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. + /// + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IMutableTypeBase? IMutableTypeBase.BaseType + { + get => BaseType; + set => SetBaseType((TypeBase?)value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionTypeBase? IConventionTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + ITypeBase? ITypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + /// /// 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 @@ -1479,6 +1612,71 @@ IConventionTypeBaseBuilder IConventionTypeBase.Builder string? IConventionTypeBase.AddIgnored(string name, bool fromDataAnnotation) => AddIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBase? IConventionTypeBase.SetBaseType(IConventionTypeBase? structuralType, bool fromDataAnnotation) + => SetBaseType( + (TypeBase?)structuralType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableTypeBase.SetDiscriminatorProperty(IReadOnlyProperty? property) + => SetDiscriminatorProperty((Property?)property, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.SetDiscriminatorProperty( + IReadOnlyProperty? property, + bool fromDataAnnotation) + => SetDiscriminatorProperty( + (Property?)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs index e8b95bbeafb..d00da612c07 100644 --- a/src/EFCore/Metadata/RuntimeComplexProperty.cs +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -37,6 +37,8 @@ public RuntimeComplexProperty( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int propertyCount, int complexPropertyCount) : base(name, propertyInfo, fieldInfo, propertyAccessMode) @@ -47,6 +49,7 @@ public RuntimeComplexProperty( _isCollection = collection; ComplexType = new RuntimeComplexType( targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount); } diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index 0fd2c553b67..0c769ff8ede 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -34,10 +34,13 @@ public RuntimeComplexType( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int propertyCount, int complexPropertyCount) : base( - name, type, complexProperty.DeclaringType.Model, null, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + name, type, complexProperty.DeclaringType.Model, baseType: null, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: 0, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount) @@ -51,6 +54,9 @@ public RuntimeComplexType( }; } + private new RuntimeComplexType? BaseType + => (RuntimeComplexType?)base.BaseType; + /// /// 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 @@ -241,4 +247,39 @@ IEntityType ITypeBase.ContainingEntityType [DebuggerStepThrough] get => ContainingEntityType; } + + /// + IReadOnlyComplexType? IReadOnlyComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + IComplexType? IComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDerivedTypes() + => GetDerivedTypes(); + + /// + IEnumerable IReadOnlyComplexType.GetDerivedTypesInclusive() + => !HasDirectlyDerivedTypes + ? [this] + : new[] { this }.Concat(GetDerivedTypes()); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); + + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); } diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index e9516734cea..d6d9c18e7e4 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -25,7 +25,6 @@ public class RuntimeEntityType : RuntimeTypeBase, IRuntimeEntityType private Utilities.OrderedDictionary? _namedIndexes; private readonly Utilities.OrderedDictionary, RuntimeKey> _keys; private Utilities.OrderedDictionary? _triggers; - private readonly object? _discriminatorValue; private readonly bool _hasSharedClrType; private RuntimeKey? _primaryKey; private InstantiationBinding? _constructorBinding; @@ -58,10 +57,10 @@ public RuntimeEntityType( bool sharedClrType, RuntimeModel model, RuntimeEntityType? baseType, - string? discriminatorProperty, ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, object? discriminatorValue, int derivedTypesCount, int propertyCount, @@ -76,14 +75,12 @@ public RuntimeEntityType( int triggerCount) : base( name, type, model, baseType, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: derivedTypesCount, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount) { _hasSharedClrType = sharedClrType; - - SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); - _discriminatorValue = discriminatorValue; _foreignKeys = new List(foreignKeyCount); _navigations = new Utilities.OrderedDictionary(navigationCount, StringComparer.Ordinal); if (skipNavigationCount > 0) @@ -327,7 +324,7 @@ public virtual IEnumerable FindDeclaredForeignKeys(IReadOnlyL private RuntimeForeignKey? FindDeclaredForeignKey( IReadOnlyList properties, IReadOnlyKey principalKey, - IReadOnlyEntityType principalEntityType) + IReadOnlyTypeBase principalEntityType) { if (_foreignKeys.Count == 0) { @@ -949,23 +946,6 @@ public virtual DebugView DebugView LambdaExpression? IReadOnlyEntityType.GetQueryFilter() => (LambdaExpression?)this[CoreAnnotationNames.QueryFilter]; - /// - [DebuggerStepThrough] - string? IReadOnlyEntityType.GetDiscriminatorPropertyName() - { - if (BaseType != null) - { - return ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); - } - - return (string?)this[CoreAnnotationNames.DiscriminatorProperty]; - } - - /// - [DebuggerStepThrough] - object? IReadOnlyEntityType.GetDiscriminatorValue() - => _discriminatorValue; - /// bool IReadOnlyTypeBase.HasSharedClrType { @@ -1009,7 +989,7 @@ IEnumerable IReadOnlyEntityType.GetDerivedTypes() /// IEnumerable IReadOnlyEntityType.GetDerivedTypesInclusive() => !HasDirectlyDerivedTypes - ? new[] { this } + ? [this] : new[] { this }.Concat(GetDerivedTypes()); /// diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 3b52e587521..bab0de72d9f 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -98,13 +98,13 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// The CLR class that is used to represent instances of this type. /// Whether this entity type can share its ClrType with other entities. /// The base type of this entity type. - /// The name of the property that will be used for storing a discriminator value. /// The change tracking strategy for this entity type. /// The for the indexer on the associated CLR type if one exists. /// /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// + /// The name of the property that will be used for storing a discriminator value. /// The discriminator value for this entity type. /// The expected number of directly derived entity types. /// The expected number of declared properties for this entity type. @@ -123,10 +123,10 @@ public virtual RuntimeEntityType AddEntityType( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, RuntimeEntityType? baseType = null, bool sharedClrType = false, - string? discriminatorProperty = null, ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, bool propertyBag = false, + string? discriminatorProperty = null, object? discriminatorValue = null, int derivedTypesCount = 0, int propertyCount = 0, @@ -146,10 +146,10 @@ public virtual RuntimeEntityType AddEntityType( sharedClrType, this, baseType, - discriminatorProperty, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: derivedTypesCount, propertyCount: propertyCount, diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index c3c6676699a..64fbb264fb6 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -19,6 +19,7 @@ public abstract class RuntimeTypeBase : RuntimeAnnotatableBase, IRuntimeTypeBase private RuntimeModel _model; private readonly RuntimeTypeBase? _baseType; private SortedSet? _directlyDerivedTypes; + private readonly object? _discriminatorValue; private readonly Utilities.OrderedDictionary _properties; private Utilities.OrderedDictionary? _complexProperties; private readonly PropertyInfo? _indexerPropertyInfo; @@ -45,6 +46,8 @@ protected RuntimeTypeBase( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int derivedTypesCount, int propertyCount, int complexPropertyCount) @@ -60,7 +63,11 @@ protected RuntimeTypeBase( _changeTrackingStrategy = changeTrackingStrategy; _indexerPropertyInfo = indexerPropertyInfo; + _discriminatorValue = discriminatorValue; _isPropertyBag = propertyBag; + if(discriminatorProperty != null) { + SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); + } _properties = new Utilities.OrderedDictionary(propertyCount, new PropertyNameComparer(this)); if (complexPropertyCount > 0) { @@ -129,7 +136,7 @@ protected virtual IEnumerable GetDerivedTypes() { if (!HasDirectlyDerivedTypes) { - return Enumerable.Empty(); + return []; } var derivedTypes = new List(); @@ -363,6 +370,8 @@ protected virtual IEnumerable GetProperties() /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// + /// The name of the property that will be used for storing a discriminator value. + /// The discriminator value for this complex type. /// The expected number of declared properties for this complex type. /// The expected number of declared complex properties for this complex type. /// The newly created property. @@ -379,6 +388,8 @@ public virtual RuntimeComplexProperty AddComplexProperty( ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, bool propertyBag = false, + string? discriminatorProperty = null, + object? discriminatorValue = null, int propertyCount = 0, int complexPropertyCount = 0) { @@ -396,6 +407,8 @@ public virtual RuntimeComplexProperty AddComplexProperty( changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, + discriminatorValue, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount); @@ -428,8 +441,8 @@ public virtual IEnumerable GetDeclaredComplexProperties( private IEnumerable GetDerivedComplexProperties() => !HasDirectlyDerivedTypes - ? Enumerable.Empty() - : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredComplexProperties()); + ? [] + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); /// /// Gets the complex properties defined on this type. @@ -459,7 +472,7 @@ private IEnumerable FindDerivedComplexProperties(string Check.NotNull(propertyName, nameof(propertyName)); return !HasDirectlyDerivedTypes - ? Enumerable.Empty() + ? [] : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); } @@ -607,8 +620,43 @@ public virtual void FinalizeType() protected static IEnumerable ToEnumerable(T? element) where T : class => element == null - ? Enumerable.Empty() - : new[] { element }; + ? [] + : [element]; + + /// + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + ITypeBase? ITypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + /// + IEnumerable IReadOnlyTypeBase.GetDerivedTypesInclusive() + => !HasDirectlyDerivedTypes + ? [this] + : new[] { this }.Concat(GetDerivedTypes()); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); /// bool IReadOnlyTypeBase.HasSharedClrType @@ -638,6 +686,23 @@ IModel ITypeBase.Model get => Model; } + /// + [DebuggerStepThrough] + string? IReadOnlyTypeBase.GetDiscriminatorPropertyName() + { + if (BaseType != null) + { + return ((IReadOnlyTypeBase)this).GetRootType().GetDiscriminatorPropertyName(); + } + + return (string?)this[CoreAnnotationNames.DiscriminatorProperty]; + } + + /// + [DebuggerStepThrough] + object? IReadOnlyTypeBase.GetDiscriminatorValue() + => _discriminatorValue; + /// [DebuggerStepThrough] IReadOnlyProperty? IReadOnlyTypeBase.FindDeclaredProperty(string name) diff --git a/src/EFCore/Query/StructuralTypeShaperExpression.cs b/src/EFCore/Query/StructuralTypeShaperExpression.cs index 5a41c40663e..10c73ba14c0 100644 --- a/src/EFCore/Query/StructuralTypeShaperExpression.cs +++ b/src/EFCore/Query/StructuralTypeShaperExpression.cs @@ -21,10 +21,10 @@ namespace Microsoft.EntityFrameworkCore.Query; public class StructuralTypeShaperExpression : Expression, IPrintableExpression { private static readonly MethodInfo CreateUnableToDiscriminateExceptionMethod - = typeof(StructuralTypeShaperExpression).GetTypeInfo().GetDeclaredMethod(nameof(CreateUnableToDiscriminateException))!; + = typeof(StructuralTypeShaperExpression).GetMethod(nameof(CreateUnableToDiscriminateException))!; private static readonly MethodInfo GetDiscriminatorValueMethod - = typeof(IReadOnlyEntityType).GetTypeInfo().GetDeclaredMethod(nameof(IReadOnlyEntityType.GetDiscriminatorValue))!; + = typeof(IReadOnlyTypeBase).GetMethod(nameof(IReadOnlyTypeBase.GetDiscriminatorValue))!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -176,7 +176,7 @@ protected virtual LambdaExpression GenerateMaterializationCondition(ITypeBase ty expressions.Add(conditions); } - body = Block(new[] { discriminatorValueVariable }, expressions); + body = Block([discriminatorValueVariable], expressions); } else { diff --git a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs index cb55586cfb1..4b4f38d82ff 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -776,7 +776,7 @@ public override void Can_set_complex_property_annotation() Assert.Equal("bar2", complexProperty["foo2"]); Assert.Equal(typeof(Customer).Name, complexProperty.Name); Assert.Equal( - @"Customer (Customer) Required + @"Customer (Customer) ComplexType: ComplexProperties.Customer#Customer Properties: " + @" diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index ee2cc478e48..1f1de4a2750 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -33,9 +33,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(Dictionary), baseEntityType, sharedClrType: true, - discriminatorProperty: "$type", indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, + discriminatorProperty: "$type", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index 00293d478e1..4d81b493b23 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -24,9 +24,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(Dictionary), baseEntityType, sharedClrType: true, - discriminatorProperty: "$type", indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, + discriminatorProperty: "$type", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index c889a991c64..9ce0af3a15a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -676,6 +676,12 @@ public InstantiationBinding ServiceOnlyConstructorBinding IReadOnlyEntityType IReadOnlyEntityType.BaseType => null!; + ITypeBase? ITypeBase.BaseType + => BaseType; + + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + => BaseType; + IReadOnlyModel IReadOnlyTypeBase.Model => throw new NotImplementedException(); @@ -1005,5 +1011,14 @@ IReadOnlyPropertyBase IReadOnlyTypeBase.FindMember(string name) IEnumerable IReadOnlyTypeBase.FindMembersInHierarchy(string name) => throw new NotImplementedException(); + + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); } } diff --git a/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs b/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs index 7a54b4ffc81..b3f37cbfe92 100644 --- a/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs +++ b/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs @@ -19,7 +19,7 @@ public class DesignApiConsistencyFixture : ApiConsistencyFixtureBase { public override HashSet FluentApiTypes { get; } = [typeof(DesignTimeServiceCollectionExtensions)]; - public override HashSet NonVirtualMethods { get; } = + public override HashSet VirtualMethodExceptions { get; } = [ typeof(CSharpEntityTypeGeneratorBase.ToStringInstanceHelper) .GetProperty(nameof(CSharpEntityTypeGeneratorBase.ToStringInstanceHelper.FormatProvider)).GetMethod, diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index a9c0c44c61d..b55c4c17cfb 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -6028,9 +6028,12 @@ public virtual void Complex_properties_are_stored_in_snapshot() eb.PrimitiveCollection>("List") .HasColumnType("nvarchar(max)") .IsSparse(); - eb.ComplexProperty(e => e.EntityWithStringKey) - .Ignore(e => e.Properties) - .Property(e => e.Id).IsRequired(); + eb.ComplexProperty(e => e.EntityWithStringKey, cb => + { + cb.Ignore(e => e.Properties); + cb.Property(e => e.Id).IsRequired(); + cb.HasDiscriminator(); + }); eb.HasPropertyAnnotation("PropertyAnnotation", 1); eb.HasTypeAnnotation("TypeAnnotation", 2); }); @@ -6067,9 +6070,15 @@ public virtual void Complex_properties_are_stored_in_snapshot() b1.ComplexProperty>("EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 => { + b2.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b2.Property("Id") .IsRequired() .HasColumnType("nvarchar(max)"); + + b2.HasDiscriminator().HasValue("EntityWithStringKey"); }); b1.HasPropertyAnnotation("PropertyAnnotation", 1); diff --git a/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs index 2be53109a1f..f0f0ede033e 100644 --- a/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs @@ -5,6 +5,7 @@ using System.Data; using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; namespace Microsoft.EntityFrameworkCore.Scaffolding; diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index d6040f9efd0..7936924eaa6 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -8644,9 +8644,8 @@ public void Split_out_subtype_with_seed_data() { x.Property("Id"); x.Property("Name"); - x.Property("Discriminator"); - x.HasDiscriminator() + x.HasDiscriminator("Discriminator") .HasValue(1) .HasValue(2); diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 3916535c30d..fb1a22acf44 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -16,15 +16,6 @@ protected override void AddServices(ServiceCollection serviceCollection) protected override Assembly TargetAssembly => typeof(RelationalDatabase).Assembly; - protected override HashSet NonCancellableAsyncMethods - { - get - { - var methods = base.NonCancellableAsyncMethods; - methods.Add(typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionDisposedAsync))); - return methods; - } - } [ConditionalFact] public void Readonly_relational_metadata_methods_have_expected_name() @@ -302,7 +293,7 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase } }; - public override HashSet NonVirtualMethods { get; } + public override HashSet VirtualMethodExceptions { get; } = [ typeof(RelationalCompiledQueryCacheKeyGenerator) @@ -570,32 +561,6 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IMutableStoredProcedure).GetMethod(nameof(IMutableStoredProcedure.AddResultColumn)) ]; - public override HashSet VirtualMethodExceptions { get; } = - [ - // non-sealed record -#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("get_RelationalDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("set_RelationalDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("get_CommandBuilderDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("set_CommandBuilderDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", [typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType()]), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", - [ - typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType() - ]), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", - [ - typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalCommandBuilderDependencies).MakeByRefType() - ]), -#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - ]; - public List> RelationalMetadataMethods { get; } = []; protected override void Initialize() @@ -650,6 +615,8 @@ protected override void Initialize() MirrorTypes.Add( typeof(RelationalComplexTypePrimitiveCollectionBuilderExtensions), typeof(RelationalComplexTypePropertyBuilderExtensions)); + NonCancellableAsyncMethods.Add(typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionDisposedAsync))); + base.Initialize(); } } diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 531aaeccc5f..f27d8e102fa 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -32,6 +32,22 @@ protected static bool IsCompilerSynthesizedMethod(MethodBase method) protected virtual TFixture Fixture { get; } = fixture; + [ConditionalFact] + public void Exceptions_are_valid() + { + Assert.DoesNotContain(null, Fixture.AsyncMethodExceptions); + Assert.DoesNotContain(null, Fixture.MetadataMethodExceptions); + Assert.DoesNotContain(null, Fixture.UnmatchedMetadataMethods); + Assert.DoesNotContain(null, Fixture.VirtualMethodExceptions); + Assert.DoesNotContain(null, Fixture.NotAnnotatedMethods); + Assert.DoesNotContain(null, Fixture.NonCancellableAsyncMethods); + + foreach (var unmatched in Fixture.UnmatchedMirrorMethods) + { + Assert.DoesNotContain(null, unmatched.Value); + } + } + [ConditionalFact] public void Fluent_api_methods_should_not_return_void() { @@ -940,7 +956,7 @@ public void Convention_metadata_methods_have_expected_shape() { var errors = Fixture.MetadataMethods - .SelectMany(m => m.Convention.Select(ValidateConventionMethod)) + .SelectMany(m => m.Convention.Select(mi => ValidateConventionMethod(mi, m.Convention[0].DeclaringType))) .Where(e => e != null) .ToList(); @@ -949,7 +965,7 @@ public void Convention_metadata_methods_have_expected_shape() "\r\n-- Errors: --\r\n" + string.Join(Environment.NewLine, errors)); } - private string ValidateConventionMethod(MethodInfo conventionMethod) + private string ValidateConventionMethod(MethodInfo conventionMethod, Type type) { var message = ValidateMethodName(conventionMethod); if (message != null) @@ -1069,9 +1085,10 @@ var nonVirtualMethods where type.IsVisible && !type.IsSealed && !type.GetCustomAttributes().Any() - from method in type.GetMethods(AnyInstance) + && type.GetMethod("$") == null // Exclude records + from method in type.GetMethods(AnyInstance) where method.DeclaringType == type - && !Fixture.NonVirtualMethods.Contains(method) + && !Fixture.VirtualMethodExceptions.Contains(method) && !Fixture.VirtualMethodExceptions.Contains(method) && !method.IsVirtual && !method.Name.StartsWith("add_", StringComparison.Ordinal) @@ -1086,11 +1103,6 @@ from method in type.GetMethods(AnyInstance) "\r\n-- Missing virtual APIs --\r\n" + string.Join(Environment.NewLine, nonVirtualMethods)); } - private static readonly HashSet _nonCancellableAsyncMethods = []; - - protected virtual HashSet NonCancellableAsyncMethods - => _nonCancellableAsyncMethods; - [ConditionalFact] public virtual void Async_methods_should_have_overload_with_cancellation_token_and_end_with_async_suffix() { @@ -1110,7 +1122,7 @@ where method.GetParameters().Any(pi => pi.ParameterType == typeof(CancellationTo var asyncMethodsWithoutToken = (from method in asyncMethods - where !NonCancellableAsyncMethods.Contains(method) + where !Fixture.NonCancellableAsyncMethods.Contains(method) && method.GetParameters().All(pi => pi.ParameterType != typeof(CancellationToken)) select method).ToList(); @@ -1275,7 +1287,7 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary MirrorTypes { get; } = new(); - public virtual HashSet NonVirtualMethods { get; } = []; + public virtual HashSet VirtualMethodExceptions { get; } = []; public virtual HashSet NotAnnotatedMethods { get; } = []; public virtual HashSet AsyncMethodExceptions { get; } = []; public virtual HashSet UnmatchedMetadataMethods { get; } = []; @@ -1283,16 +1295,6 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary MetadataMethodNameTransformers { get; } = new(); public virtual HashSet MetadataMethodExceptions { get; } = []; - public virtual HashSet VirtualMethodExceptions { get; } = - [ - // un-sealed record -#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - typeof(MaterializerLiftableConstantContext).GetMethod("get_Dependencies"), - typeof(MaterializerLiftableConstantContext).GetMethod("set_Dependencies"), - typeof(MaterializerLiftableConstantContext).GetMethod("Deconstruct"), -#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - ]; - public virtual HashSet ComputedDependencyProperties { get; } = [ typeof(ProviderConventionSetBuilderDependencies).GetProperty( @@ -1418,6 +1420,7 @@ protected ApiConsistencyFixtureBase() public Dictionary MutableMetadataTypes { get; } = new(); public Dictionary ConventionMetadataTypes { get; } = new(); + public virtual HashSet NonCancellableAsyncMethods { get; } = new(); public virtual Dictionary !IsObsolete(m)).ToArray(); - var mutableMethods = typeTuple.Value.Mutable.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray(); - var conventionMethods = typeTuple.Value.Convention.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray(); - var conventionBuilderMethods = typeTuple.Value.ConventionBuilder?.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray() - ?? []; - var runtimeMethods = typeTuple.Value.Runtime?.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray() - ?? []; + var readOnlyMethods = GetMethods(typeTuple.Key).ToArray(); + var mutableMethods = GetMethods(typeTuple.Value.Mutable).ToArray(); + var conventionMethods = GetMethods(typeTuple.Value.Convention).ToArray(); + var conventionBuilderMethods = GetMethods(typeTuple.Value.ConventionBuilder).ToArray(); + var runtimeMethods = GetMethods(typeTuple.Value.Runtime).ToArray(); MetadataMethods.Add((readOnlyMethods, mutableMethods, conventionMethods, conventionBuilderMethods, runtimeMethods)); } + + static IEnumerable GetMethods(Type type) + => type == null ? [] : (IEnumerable)type.GetMethods(PublicInstance); } public bool IsObsolete(MethodInfo method) diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs index a8fbc2dcbf0..4598eecfa42 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs @@ -5,7 +5,7 @@ using System.Dynamic; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -38,7 +38,7 @@ public virtual void Can_set_complex_property_annotation() Assert.Equal("bar2", complexProperty["foo2"]); Assert.Equal(typeof(Customer).Name, complexProperty.Name); Assert.Equal( - @"Customer (Customer) Required + @"Customer (Customer) ComplexType: ComplexProperties.Customer#Customer Properties: " + @" @@ -2224,5 +2224,51 @@ public virtual void Can_call_PrimitiveCollection_on_a_field() Assert.Null(property.PropertyInfo); Assert.NotNull(property.FieldInfo); } + + [ConditionalFact] + public virtual void Can_specify_discriminator_without_explicit_value() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty( + e => e.Quarks, + b => b.HasDiscriminator("Discriminator")); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(nameof(Quarks), complexType.GetDiscriminatorValue()); + + var discriminator = complexType.FindDiscriminatorProperty()!; + Assert.False(discriminator.IsNullable); + Assert.Equal(PropertySaveBehavior.Throw, discriminator.GetAfterSaveBehavior()); + Assert.NotNull(discriminator.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void Can_specify_discriminator_value() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty( + e => e.Quarks, + b => + { + b.HasDiscriminator("EnumType").HasValue(BasicEnum.Two); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(BasicEnum.Two, complexType.GetDiscriminatorValue()); + } } } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs index abacffdbb5c..f24233a95cc 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs @@ -3,6 +3,8 @@ // ReSharper disable InconsistentNaming +using Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public abstract partial class ModelBuilderTest @@ -453,6 +455,15 @@ public override TestComplexPropertyBuilder UsePropertyAccessMode(Prope public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(Expression> propertyExpression) + => new GenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyExpression)); + + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName) + => new GenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyName)); + + public override TestComplexPropertyBuilder HasNoDiscriminator() + => Wrap(PropertyBuilder.HasNoDiscriminator()); + public ComplexPropertyBuilder Instance => PropertyBuilder; } @@ -481,6 +492,18 @@ public override TestDiscriminatorBuilder HasValue(string entityT => Wrap(DiscriminatorBuilder.HasValue(entityTypeName, value)); } + protected class GenericTestComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + : TestComplexTypeDiscriminatorBuilder + { + protected ComplexTypeDiscriminatorBuilder DiscriminatorBuilder { get; } = discriminatorBuilder; + + protected virtual TestComplexTypeDiscriminatorBuilder Wrap(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + => new GenericTestComplexTypeDiscriminatorBuilder(discriminatorBuilder); + + public override TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => Wrap(DiscriminatorBuilder.HasValue(value)); + } + protected class GenericTestOwnedEntityTypeBuilder(OwnedEntityTypeBuilder ownedEntityTypeBuilder) : TestOwnedEntityTypeBuilder, IInfrastructure> diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs index ccd94bd3435..b98a21b9418 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs @@ -521,6 +521,16 @@ public override TestComplexPropertyBuilder UsePropertyAccessMode(Prope public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(Expression> propertyExpression) + => new NonGenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator( + propertyExpression.GetMemberAccess().GetSimpleMemberName(), propertyExpression.GetMemberAccess().GetMemberType())); + + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName) + => new NonGenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyName, typeof(TDiscriminator))); + + public override TestComplexPropertyBuilder HasNoDiscriminator() + => Wrap(PropertyBuilder.HasNoDiscriminator()); + public ComplexPropertyBuilder Instance => PropertyBuilder; } @@ -549,6 +559,18 @@ public override TestDiscriminatorBuilder HasValue(string entityT => Wrap(DiscriminatorBuilder.HasValue(entityTypeName, value)); } + protected class NonGenericTestComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + : TestComplexTypeDiscriminatorBuilder + { + protected ComplexTypeDiscriminatorBuilder DiscriminatorBuilder { get; } = discriminatorBuilder; + + protected virtual TestComplexTypeDiscriminatorBuilder Wrap(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + => new NonGenericTestComplexTypeDiscriminatorBuilder(discriminatorBuilder); + + public override TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => Wrap(DiscriminatorBuilder.HasValue(value)); + } + protected class NonGenericTestOwnedEntityTypeBuilder(OwnedEntityTypeBuilder ownedEntityTypeBuilder) : TestOwnedEntityTypeBuilder, IInfrastructure diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs index 29aa3cff881..d363138f6e6 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs @@ -886,7 +886,7 @@ protected class ComplexPropertiesBase protected class ComplexProperties : ComplexPropertiesBase { - public required Customer Customer { get; set; } + public Customer? Customer { get; set; } public required DoubleProperty DoubleProperty { get; set; } public required IndexedClass IndexedClass { get; set; } public required Quarks Quarks { get; set; } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs index 1664aa0fb16..4dbd03b3f51 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs @@ -376,6 +376,12 @@ public abstract TestComplexPropertyBuilder Ignore( public abstract TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy); public abstract TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); public abstract TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode); + + public abstract TestComplexTypeDiscriminatorBuilder HasDiscriminator( + Expression> propertyExpression); + + public abstract TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName); + public abstract TestComplexPropertyBuilder HasNoDiscriminator(); } public abstract class TestDiscriminatorBuilder @@ -391,6 +397,11 @@ public abstract class TestDiscriminatorBuilder public abstract TestDiscriminatorBuilder HasValue(string entityTypeName, TDiscriminator value); } + public abstract class TestComplexTypeDiscriminatorBuilder + { + public abstract TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value); + } + public abstract class TestOwnedEntityTypeBuilder where TEntity : class; diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs index 3ff4faabd17..20818023af9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs @@ -86,26 +86,28 @@ public async Task InitializeSqlServerAsync( protected override async Task InitializeAsync(Func createContext, Func? seed, Func? clean) { - if (await CreateDatabaseAsync(clean)) + if (!await CleanDatabaseAsync(clean)) { - if (_scriptPath != null) + return; + } + + if (_scriptPath != null) + { + ExecuteScript(await File.ReadAllTextAsync(_scriptPath)); + } + else + { + using var context = createContext(); + await context.Database.EnsureCreatedResilientlyAsync(); + + if (_initScript != null) { - ExecuteScript(await File.ReadAllTextAsync(_scriptPath)); + ExecuteScript(_initScript); } - else - { - using var context = createContext(); - await context.Database.EnsureCreatedResilientlyAsync(); - - if (_initScript != null) - { - ExecuteScript(_initScript); - } - if (seed != null) - { - await seed(context); - } + if (seed != null) + { + await seed(context); } } } @@ -116,7 +118,7 @@ public override DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuild : builder.UseSqlServer(Connection, b => b.ApplyConfiguration())) .ConfigureWarnings(b => b.Ignore(SqlServerEventId.SavepointsDisabledBecauseOfMARS)); - private async Task CreateDatabaseAsync(Func? clean) + private async Task CleanDatabaseAsync(Func? clean) { await using var master = new SqlConnection(CreateConnectionString("master", fileName: null, multipleActiveResultSets: false)); diff --git a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs index f53602725cf..dc63c4f3290 100644 --- a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs +++ b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs @@ -72,7 +72,9 @@ protected override async Task InitializeAsync(Func createContext, Fun } using var context = createContext(); - if (!await context.Database.EnsureCreatedResilientlyAsync()) + + var databaseCreator = context.GetService(); + if (await databaseCreator.ExistsAsync()) { if (clean != null) { @@ -80,11 +82,11 @@ protected override async Task InitializeAsync(Func createContext, Fun } await CleanAsync(context); - - // Run context seeding - await context.Database.EnsureCreatedResilientlyAsync(); } + // Run context seeding + await context.Database.EnsureCreatedResilientlyAsync(); + if (seed != null) { await seed(context); diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index c01f993d370..df6a8f5b112 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -79,7 +79,7 @@ protected override void Initialize() typeof(EntityFrameworkServiceCollectionExtensions) ]; - public override HashSet NonVirtualMethods { get; } = + public override HashSet VirtualMethodExceptions { get; } = [ typeof(CompiledQueryCacheKeyGenerator).GetMethod("GenerateCacheKeyCore", AnyInstance), typeof(InternalEntityEntry).GetMethod("get_Item"), @@ -168,37 +168,14 @@ protected override void Initialize() typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.ComplexType)), typeof(IReadOnlyEntityType).GetMethod(nameof(IReadOnlyEntityType.GetConcreteDerivedTypesInclusive)), typeof(IMutableEntityType).GetMethod(nameof(IMutableEntityType.AddData)), + typeof(IConventionEntityType).GetMethod(nameof(IConventionEntityType.SetBaseType)), typeof(IReadOnlyNavigationBase).GetMethod("get_DeclaringEntityType"), typeof(IReadOnlyNavigationBase).GetMethod("get_TargetEntityType"), typeof(IReadOnlyNavigationBase).GetMethod("get_Inverse"), typeof(IConventionAnnotatableBuilder).GetMethod(nameof(IConventionAnnotatableBuilder.HasNonNullAnnotation)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties)), typeof(IConventionTypeBaseBuilder).GetMethod(nameof(IConventionTypeBaseBuilder.RemoveUnusedImplicitProperties)), - typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]) + typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)) ]; public override HashSet MetadataMethodExceptions { get; } = @@ -208,7 +185,6 @@ protected override void Initialize() typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.AddAnnotations)), typeof(IMutableAnnotatable).GetMethod(nameof(IMutableAnnotatable.AddAnnotations)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsIgnoredType)), - typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsShared)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddOwned)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddShared)), typeof(IMutableModel).GetMethod(nameof(IMutableModel.AddOwned)), diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 643ca9cc5df..cab44b3e42e 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -1919,6 +1919,28 @@ public virtual void Detects_missing_non_string_discriminator_values() VerifyError(CoreStrings.NoDiscriminatorValue(typeof(C).Name), modelBuilder); } + [ConditionalFact] + public virtual void Detects_missing_complex_type_discriminator_values() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity().ComplexProperty(b => b.A) + .HasDiscriminator("Type"); + + VerifyError(CoreStrings.NoDiscriminatorValue("B.A#A"), modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_incompatible_complex_type_discriminator_value() + { + var modelBuilder = CreateConventionModelBuilder(); + var complexPropertyBuilder = modelBuilder.Entity().ComplexProperty(b => b.A); + complexPropertyBuilder.HasDiscriminator("Type"); + + complexPropertyBuilder.Metadata.ComplexType.SetDiscriminatorValue("1"); + + VerifyError(CoreStrings.DiscriminatorValueIncompatible("1", "B.A#A", "byte"), modelBuilder); + } + [ConditionalFact] public virtual void Detects_duplicate_discriminator_values() { diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index c6236e957ae..de9b13c99df 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -762,11 +762,11 @@ private class DiscriminatorPropertySetConvention(bool terminate) : IDiscriminato public readonly List Calls = []; public void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string name, IConventionContext context) { - Assert.True(entityTypeBuilder.Metadata.IsInModel); + Assert.True(structuralTypeBuilder.Metadata.IsInModel); Calls.Add(name);