Skip to content

Commit 862c4d7

Browse files
committed
Add support for complex type discriminator.
Breaking change: IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet signature changed Part of #31376
1 parent 8cea586 commit 862c4d7

File tree

50 files changed

+1940
-625
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1940
-625
lines changed

Diff for: src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui
9797

9898
/// <inheritdoc />
9999
public override void ProcessDiscriminatorPropertySet(
100-
IConventionEntityTypeBuilder entityTypeBuilder,
100+
IConventionTypeBaseBuilder structuralTypeBuilder,
101101
string? name,
102102
IConventionContext<string> context)
103103
{
104-
var entityType = entityTypeBuilder.Metadata;
105-
if (entityType.IsDocumentRoot())
104+
var entityType = structuralTypeBuilder.Metadata as IConventionEntityType;
105+
if (entityType != null
106+
&& entityType.IsDocumentRoot())
106107
{
107-
base.ProcessDiscriminatorPropertySet(entityTypeBuilder, name, context);
108+
base.ProcessDiscriminatorPropertySet(structuralTypeBuilder, name, context);
108109
}
109110
}
110111

Diff for: src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs

+10-11
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,10 @@ private void ProcessEntityType(IConventionEntityType entityType, IConventionCont
174174
}
175175

176176
// Don't chain, because each of these could return null if the property has been explicitly configured with some other value.
177-
computedIdPropertyBuilder = computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName)
178-
?? computedIdPropertyBuilder;
179-
180-
computedIdPropertyBuilder = computedIdPropertyBuilder.IsRequired(true)
181-
?? computedIdPropertyBuilder;
182-
183-
computedIdPropertyBuilder = computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory))
184-
?? computedIdPropertyBuilder;
185-
177+
computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName);
178+
computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory));
186179
computedIdPropertyBuilder.AfterSave(PropertySaveBehavior.Throw);
180+
computedIdPropertyBuilder.IsRequired(true);
187181
}
188182

189183
/// <inheritdoc />
@@ -327,8 +321,13 @@ public virtual void ProcessModelAnnotationChanged(
327321

328322
/// <inheritdoc />
329323
public virtual void ProcessDiscriminatorPropertySet(
330-
IConventionEntityTypeBuilder entityTypeBuilder,
324+
IConventionTypeBaseBuilder structuralTypeBuilder,
331325
string? name,
332326
IConventionContext<string?> context)
333-
=> ProcessEntityType(entityTypeBuilder.Metadata, context);
327+
{
328+
if (structuralTypeBuilder is IConventionEntityTypeBuilder entityTypeBuilder)
329+
{
330+
ProcessEntityType(entityTypeBuilder.Metadata, context);
331+
}
332+
}
334333
}

Diff for: src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs

+26-8
Original file line numberDiff line numberDiff line change
@@ -925,14 +925,6 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator
925925
.Append(_code.Literal(entityType.HasSharedClrType));
926926
}
927927

928-
var discriminatorProperty = entityType.GetDiscriminatorPropertyName();
929-
if (discriminatorProperty != null)
930-
{
931-
mainBuilder.AppendLine(",")
932-
.Append("discriminatorProperty: ")
933-
.Append(_code.Literal(discriminatorProperty));
934-
}
935-
936928
var changeTrackingStrategy = entityType.GetChangeTrackingStrategy();
937929
if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot)
938930
{
@@ -959,6 +951,14 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator
959951
.Append(_code.Literal(true));
960952
}
961953

954+
var discriminatorProperty = entityType.GetDiscriminatorPropertyName();
955+
if (discriminatorProperty != null)
956+
{
957+
mainBuilder.AppendLine(",")
958+
.Append("discriminatorProperty: ")
959+
.Append(_code.Literal(discriminatorProperty));
960+
}
961+
962962
var discriminatorValue = entityType.GetDiscriminatorValue();
963963
if (discriminatorValue != null)
964964
{
@@ -2182,6 +2182,24 @@ private void CreateComplexProperty(
21822182
.Append(_code.Literal(true));
21832183
}
21842184

2185+
var discriminatorPropertyName = complexType.GetDiscriminatorPropertyName();
2186+
if (discriminatorPropertyName != null)
2187+
{
2188+
mainBuilder.AppendLine(",")
2189+
.Append("discriminatorProperty: ")
2190+
.Append(_code.Literal(discriminatorPropertyName));
2191+
}
2192+
2193+
var discriminatorValue = complexType.GetDiscriminatorValue();
2194+
if (discriminatorValue != null)
2195+
{
2196+
AddNamespace(discriminatorValue.GetType(), parameters.Namespaces);
2197+
2198+
mainBuilder.AppendLine(",")
2199+
.Append("discriminatorValue: ")
2200+
.Append(_code.UnknownLiteral(discriminatorValue));
2201+
}
2202+
21852203
mainBuilder.AppendLine(",")
21862204
.Append("propertyCount: ")
21872205
.Append(_code.Literal(complexType.GetDeclaredProperties().Count()));

Diff for: src/EFCore/Infrastructure/ModelValidator.cs

+69-2
Original file line numberDiff line numberDiff line change
@@ -640,12 +640,16 @@ protected virtual void ValidateInheritanceMapping(
640640
/// <param name="rootEntityType">The entity type to validate.</param>
641641
protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType)
642642
{
643-
var derivedTypes = rootEntityType.GetDerivedTypesInclusive().ToList();
643+
var derivedTypes = rootEntityType.GetDerivedTypesInclusive();
644644
var discriminatorProperty = rootEntityType.FindDiscriminatorProperty();
645645
if (discriminatorProperty == null)
646646
{
647-
if (derivedTypes.Count == 1)
647+
if (!derivedTypes.Skip(1).Any())
648648
{
649+
foreach (var complexProperty in rootEntityType.GetDeclaredComplexProperties())
650+
{
651+
ValidateDiscriminatorValues(complexProperty.ComplexType);
652+
}
649653
return;
650654
}
651655

@@ -655,6 +659,69 @@ protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType)
655659

656660
var discriminatorValues = new Dictionary<object, IEntityType>(discriminatorProperty.GetKeyValueComparer());
657661

662+
foreach (var derivedType in derivedTypes)
663+
{
664+
foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())
665+
{
666+
ValidateDiscriminatorValues(complexProperty.ComplexType);
667+
}
668+
669+
if (!derivedType.ClrType.IsInstantiable())
670+
{
671+
continue;
672+
}
673+
674+
var discriminatorValue = derivedType[CoreAnnotationNames.DiscriminatorValue];
675+
if (discriminatorValue == null)
676+
{
677+
throw new InvalidOperationException(
678+
CoreStrings.NoDiscriminatorValue(derivedType.DisplayName()));
679+
}
680+
681+
if (!discriminatorProperty.ClrType.IsInstanceOfType(discriminatorValue))
682+
{
683+
throw new InvalidOperationException(
684+
CoreStrings.DiscriminatorValueIncompatible(
685+
discriminatorValue, derivedType.DisplayName(), discriminatorProperty.ClrType.DisplayName()));
686+
}
687+
688+
if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType))
689+
{
690+
throw new InvalidOperationException(
691+
CoreStrings.DuplicateDiscriminatorValue(
692+
derivedType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName()));
693+
}
694+
695+
discriminatorValues[discriminatorValue] = derivedType;
696+
}
697+
}
698+
699+
/// <summary>
700+
/// Validates the discriminator and values for the given complex type and nested ones.
701+
/// </summary>
702+
/// <param name="complexType">The entity type to validate.</param>
703+
protected virtual void ValidateDiscriminatorValues(IComplexType complexType)
704+
{
705+
foreach (var complexProperty in complexType.GetComplexProperties())
706+
{
707+
ValidateDiscriminatorValues(complexProperty.ComplexType);
708+
}
709+
710+
var derivedTypes = complexType.GetDerivedTypesInclusive();
711+
var discriminatorProperty = complexType.FindDiscriminatorProperty();
712+
if (discriminatorProperty == null)
713+
{
714+
if (!derivedTypes.Skip(1).Any())
715+
{
716+
return;
717+
}
718+
719+
throw new InvalidOperationException(
720+
CoreStrings.NoDiscriminatorProperty(complexType.DisplayName()));
721+
}
722+
723+
var discriminatorValues = new Dictionary<object, IComplexType>(discriminatorProperty.GetKeyValueComparer());
724+
658725
foreach (var derivedType in derivedTypes)
659726
{
660727
if (!derivedType.ClrType.IsInstantiable())

Diff for: src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs

+67-20
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,11 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName)
168168
/// If no property with the given name exists, then a new property will be added.
169169
/// </summary>
170170
/// <remarks>
171-
/// When adding a new property, if a property with the same name exists in the entity class
172-
/// then it will be added to the model. If no property exists in the entity class, then
171+
/// When adding a new property, if a property with the same name exists in the complex class
172+
/// then it will be added to the model. If no property exists in the complex class, then
173173
/// a new shadow state property will be added. A shadow state property is one that does not have a
174-
/// corresponding property in the entity class. The current value for the property is stored in
175-
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the entity class.
174+
/// corresponding property in the complex class. The current value for the property is stored in
175+
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
176176
/// </remarks>
177177
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
178178
/// <param name="propertyName">The name of the property to be configured.</param>
@@ -188,11 +188,11 @@ public virtual ComplexTypePropertyBuilder<TProperty> Property<TProperty>(string
188188
/// If no property with the given name exists, then a new property will be added.
189189
/// </summary>
190190
/// <remarks>
191-
/// When adding a new property, if a property with the same name exists in the entity class
192-
/// then it will be added to the model. If no property exists in the entity class, then
191+
/// When adding a new property, if a property with the same name exists in the complex class
192+
/// then it will be added to the model. If no property exists in the complex class, then
193193
/// a new shadow state property will be added. A shadow state property is one that does not have a
194-
/// corresponding property in the entity class. The current value for the property is stored in
195-
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the entity class.
194+
/// corresponding property in the complex class. The current value for the property is stored in
195+
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
196196
/// </remarks>
197197
/// <param name="propertyType">The type of the property to be configured.</param>
198198
/// <param name="propertyName">The name of the property to be configured.</param>
@@ -225,11 +225,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string
225225
/// If no property with the given name exists, then a new property will be added.
226226
/// </summary>
227227
/// <remarks>
228-
/// When adding a new property, if a property with the same name exists in the entity class
229-
/// then it will be added to the model. If no property exists in the entity class, then
228+
/// When adding a new property, if a property with the same name exists in the complex class
229+
/// then it will be added to the model. If no property exists in the complex class, then
230230
/// a new shadow state property will be added. A shadow state property is one that does not have a
231-
/// corresponding property in the entity class. The current value for the property is stored in
232-
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the entity class.
231+
/// corresponding property in the complex class. The current value for the property is stored in
232+
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
233233
/// </remarks>
234234
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
235235
/// <param name="propertyName">The name of the property to be configured.</param>
@@ -245,11 +245,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder<TProperty> PrimitiveCollect
245245
/// If no property with the given name exists, then a new property will be added.
246246
/// </summary>
247247
/// <remarks>
248-
/// When adding a new property, if a property with the same name exists in the entity class
249-
/// then it will be added to the model. If no property exists in the entity class, then
248+
/// When adding a new property, if a property with the same name exists in the complex class
249+
/// then it will be added to the model. If no property exists in the complex class, then
250250
/// a new shadow state property will be added. A shadow state property is one that does not have a
251-
/// corresponding property in the entity class. The current value for the property is stored in
252-
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the entity class.
251+
/// corresponding property in the complex class. The current value for the property is stored in
252+
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
253253
/// </remarks>
254254
/// <param name="propertyType">The type of the property to be configured.</param>
255255
/// <param name="propertyName">The name of the property to be configured.</param>
@@ -265,7 +265,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type pr
265265
/// If no property with the given name exists, then a new property will be added.
266266
/// </summary>
267267
/// <remarks>
268-
/// Indexer properties are stored in the entity using
268+
/// Indexer properties are stored in the complex type using
269269
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
270270
/// supplying the provided property name.
271271
/// </remarks>
@@ -284,7 +284,7 @@ public virtual ComplexTypePropertyBuilder<TProperty> IndexerProperty
284284
/// If no property with the given name exists, then a new property will be added.
285285
/// </summary>
286286
/// <remarks>
287-
/// Indexer properties are stored in the entity using
287+
/// Indexer properties are stored in the complex type using
288288
/// <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/">an indexer</see>
289289
/// supplying the provided property name.
290290
/// </remarks>
@@ -563,8 +563,8 @@ public virtual ComplexPropertyBuilder Ignore(string propertyName)
563563
}
564564

565565
/// <summary>
566-
/// Configures the <see cref="ChangeTrackingStrategy" /> to be used for this entity type.
567-
/// This strategy indicates how the context detects changes to properties for an instance of the entity type.
566+
/// Configures the <see cref="ChangeTrackingStrategy" /> to be used for this complex type.
567+
/// This strategy indicates how the context detects changes to properties for an instance of the complex type.
568568
/// </summary>
569569
/// <param name="changeTrackingStrategy">The change tracking strategy to be used.</param>
570570
/// <returns>The same builder instance so that multiple configuration calls can be chained.</returns>
@@ -623,6 +623,53 @@ public virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAcces
623623
return this;
624624
}
625625

626+
/// <summary>
627+
/// Configures the discriminator property used to identify the complex type in the store.
628+
/// </summary>
629+
/// <returns>A builder that allows the discriminator property to be configured.</returns>
630+
public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator()
631+
=> TypeBuilder.HasDiscriminator(ConfigurationSource.Explicit)!;
632+
633+
/// <summary>
634+
/// Configures the discriminator property used to identify the complex type in the store.
635+
/// </summary>
636+
/// <param name="name">The name of the discriminator property.</param>
637+
/// <param name="type">The type of values stored in the discriminator property.</param>
638+
/// <returns>A builder that allows the discriminator property to be configured.</returns>
639+
public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator(
640+
string name,
641+
Type type)
642+
{
643+
Check.NotEmpty(name, nameof(name));
644+
Check.NotNull(type, nameof(type));
645+
646+
return TypeBuilder.HasDiscriminator(name, type, ConfigurationSource.Explicit)!;
647+
}
648+
649+
/// <summary>
650+
/// Configures the discriminator property used to identify the complex type in the store.
651+
/// </summary>
652+
/// <typeparam name="TDiscriminator">The type of values stored in the discriminator property.</typeparam>
653+
/// <param name="name">The name of the discriminator property.</param>
654+
/// <returns>A builder that allows the discriminator property to be configured.</returns>
655+
public virtual ComplexTypeDiscriminatorBuilder<TDiscriminator> HasDiscriminator<TDiscriminator>(string name)
656+
{
657+
Check.NotEmpty(name, nameof(name));
658+
659+
return new ComplexTypeDiscriminatorBuilder<TDiscriminator>(
660+
TypeBuilder.HasDiscriminator(name, typeof(TDiscriminator), ConfigurationSource.Explicit)!);
661+
}
662+
663+
/// <summary>
664+
/// Configures the complex type as having no discriminator property.
665+
/// </summary>
666+
/// <returns>The same builder instance so that multiple configuration calls can be chained.</returns>
667+
public virtual ComplexPropertyBuilder HasNoDiscriminator()
668+
{
669+
TypeBuilder.HasNoDiscriminator(ConfigurationSource.Explicit);
670+
return this;
671+
}
672+
626673
#region Hidden System.Object members
627674

628675
/// <summary>

0 commit comments

Comments
 (0)