Skip to content

Commit 9fa54fe

Browse files
authored
Default relationships to ownership for Cosmos
Allow to reconfigure STETs as regular entity types Rerun CosmosDiscriminatorConvention and BaseTypeDiscoveryConvention when ownership changes Keep OwnedNavigationBuilder.DependentEntityType updated Fixes #24803
1 parent 731490d commit 9fa54fe

39 files changed

+1150
-284
lines changed

src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static class CosmosEntityTypeExtensions
2525
?? GetDefaultContainer(entityType);
2626

2727
private static string? GetDefaultContainer(IReadOnlyEntityType entityType)
28-
=> entityType.IsOwned()
28+
=> entityType.FindOwnership() != null
2929
? null
3030
: entityType.Model.GetDefaultContainer()
3131
?? entityType.ShortName();

src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal;
66
using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
7-
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
87
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal;
98
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
109
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;

src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs

+51-29
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Linq;
54
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
65
using Microsoft.EntityFrameworkCore.Metadata.Builders;
76
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
8-
using Microsoft.EntityFrameworkCore.Utilities;
97

8+
// ReSharper disable once CheckNamespace
109
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
1110
{
1211
/// <summary>
@@ -16,7 +15,8 @@ public class CosmosDiscriminatorConvention :
1615
DiscriminatorConvention,
1716
IForeignKeyOwnershipChangedConvention,
1817
IForeignKeyRemovedConvention,
19-
IEntityTypeAddedConvention
18+
IEntityTypeAddedConvention,
19+
IEntityTypeAnnotationChangedConvention
2020
{
2121
/// <summary>
2222
/// Creates a new instance of <see cref="CosmosDiscriminatorConvention" />.
@@ -36,16 +36,7 @@ public virtual void ProcessEntityTypeAdded(
3636
IConventionEntityTypeBuilder entityTypeBuilder,
3737
IConventionContext<IConventionEntityTypeBuilder> context)
3838
{
39-
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
40-
Check.NotNull(context, nameof(context));
41-
42-
var entityType = entityTypeBuilder.Metadata;
43-
if (entityType.BaseType == null
44-
&& entityType.IsDocumentRoot())
45-
{
46-
entityTypeBuilder.HasDiscriminator(typeof(string))
47-
?.HasValue(entityType, entityType.ShortName());
48-
}
39+
ProcessEntityType(entityTypeBuilder);
4940
}
5041

5142
/// <summary>
@@ -57,17 +48,9 @@ public virtual void ProcessForeignKeyOwnershipChanged(
5748
IConventionForeignKeyBuilder relationshipBuilder,
5849
IConventionContext<bool?> context)
5950
{
60-
Check.NotNull(relationshipBuilder, nameof(relationshipBuilder));
61-
Check.NotNull(context, nameof(context));
62-
6351
var entityType = relationshipBuilder.Metadata.DeclaringEntityType;
64-
if (relationshipBuilder.Metadata.IsOwnership
65-
&& !entityType.IsDocumentRoot()
66-
&& entityType.BaseType == null
67-
&& !entityType.GetDerivedTypes().Any())
68-
{
69-
entityType.Builder.HasNoDiscriminator();
70-
}
52+
53+
ProcessEntityType(entityType.Builder);
7154
}
7255

7356
/// <summary>
@@ -81,13 +64,52 @@ public virtual void ProcessForeignKeyRemoved(
8164
IConventionForeignKey foreignKey,
8265
IConventionContext<IConventionForeignKey> context)
8366
{
84-
var entityType = foreignKey.DeclaringEntityType;
85-
if (foreignKey.IsOwnership
86-
&& !entityType.IsDocumentRoot()
87-
&& entityType.BaseType == null
88-
&& !entityType.GetDerivedTypes().Any())
67+
if (foreignKey.IsOwnership)
8968
{
90-
entityType.Builder.HasNoDiscriminator();
69+
ProcessEntityType(entityTypeBuilder);
70+
}
71+
}
72+
73+
/// <summary>
74+
/// Called after an annotation is changed on an entity type.
75+
/// </summary>
76+
/// <param name="entityTypeBuilder"> The builder for the entity type. </param>
77+
/// <param name="name"> The annotation name. </param>
78+
/// <param name="annotation"> The new annotation. </param>
79+
/// <param name="oldAnnotation"> The old annotation. </param>
80+
/// <param name="context"> Additional information associated with convention execution. </param>
81+
public virtual void ProcessEntityTypeAnnotationChanged(
82+
IConventionEntityTypeBuilder entityTypeBuilder,
83+
string name,
84+
IConventionAnnotation? annotation,
85+
IConventionAnnotation? oldAnnotation,
86+
IConventionContext<IConventionAnnotation> context)
87+
{
88+
if (name != CosmosAnnotationNames.ContainerName
89+
|| (annotation == null) == (oldAnnotation == null))
90+
{
91+
return;
92+
}
93+
94+
ProcessEntityType(entityTypeBuilder);
95+
}
96+
97+
private void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBuilder)
98+
{
99+
var entityType = entityTypeBuilder.Metadata;
100+
if (entityType.BaseType != null)
101+
{
102+
return;
103+
}
104+
105+
if (!entityType.IsDocumentRoot())
106+
{
107+
entityTypeBuilder.HasNoDiscriminator();
108+
}
109+
else
110+
{
111+
entityTypeBuilder.HasDiscriminator(typeof(string))
112+
?.HasValue(entityType, entityType.ShortName());
91113
}
92114
}
93115

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.ComponentModel.DataAnnotations.Schema;
6+
using System.Reflection;
7+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
8+
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
9+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
10+
11+
// ReSharper disable once CheckNamespace
12+
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
13+
{
14+
/// <summary>
15+
/// A convention that configures the inverse navigation property based on the <see cref="InversePropertyAttribute" />
16+
/// specified on the other navigation property.
17+
/// All navigations are assumed to be targeting owned entity types for Cosmos.
18+
/// </summary>
19+
public class CosmosInversePropertyAttributeConvention : InversePropertyAttributeConvention
20+
{
21+
/// <summary>
22+
/// Creates a new instance of <see cref="InversePropertyAttributeConvention" />.
23+
/// </summary>
24+
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
25+
public CosmosInversePropertyAttributeConvention(ProviderConventionSetBuilderDependencies dependencies)
26+
: base(dependencies)
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Finds or tries to create an entity type target for the given navigation member.
32+
/// </summary>
33+
/// <param name="entityTypeBuilder"> The builder for the referencing entity type. </param>
34+
/// <param name="targetClrType"> The CLR type of the target entity type. </param>
35+
/// <param name="navigationMemberInfo"> The navigation member. </param>
36+
/// <param name="shouldCreate"> Whether an entity type should be created if one doesn't currently exist. </param>
37+
/// <returns> The builder for the target entity type or <see langword="null"/> if it can't be created. </returns>
38+
protected override IConventionEntityTypeBuilder? TryGetTargetEntityTypeBuilder(
39+
IConventionEntityTypeBuilder entityTypeBuilder,
40+
Type targetClrType,
41+
MemberInfo navigationMemberInfo,
42+
bool shouldCreate = true)
43+
=> ((InternalEntityTypeBuilder)entityTypeBuilder)
44+
#pragma warning disable EF1001 // Internal EF Core API usage.
45+
.GetTargetEntityTypeBuilder(
46+
targetClrType,
47+
navigationMemberInfo,
48+
shouldCreate ? ConfigurationSource.DataAnnotation : null,
49+
targetShouldBeOwned: true);
50+
#pragma warning restore EF1001 // Internal EF Core API usage.
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
6+
7+
// ReSharper disable once CheckNamespace
8+
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
9+
{
10+
/// <summary>
11+
/// A convention that configures relationships between entity types based on the navigation properties
12+
/// as long as there is no ambiguity as to which is the corresponding inverse navigation.
13+
/// All navigations are assumed to be targeting owned entity types for Cosmos.
14+
/// </summary>
15+
public class CosmosRelationshipDiscoveryConvention : RelationshipDiscoveryConvention
16+
{
17+
/// <summary>
18+
/// Creates a new instance of <see cref="RelationshipDiscoveryConvention" />.
19+
/// </summary>
20+
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
21+
public CosmosRelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
22+
: base(dependencies)
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Returns a value indicating whether the given entity type should be added as owned if it isn't currently in the model.
28+
/// </summary>
29+
/// <param name="targetType"> Target entity type. </param>
30+
/// <param name="model"> The model. </param>
31+
/// <returns> <see langword="true"/> if the given entity type should be owned. </returns>
32+
protected override bool? ShouldBeOwned(Type targetType, IConventionModel model)
33+
=> true;
34+
}
35+
}

src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs

+37-12
Original file line numberDiff line numberDiff line change
@@ -44,47 +44,72 @@ public override ConventionSet CreateConventionSet()
4444

4545
var storeKeyConvention = new StoreKeyConvention(Dependencies);
4646
var discriminatorConvention = new CosmosDiscriminatorConvention(Dependencies);
47-
var keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies);
47+
KeyDiscoveryConvention keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies);
48+
InversePropertyAttributeConvention inversePropertyAttributeConvention =
49+
new CosmosInversePropertyAttributeConvention(Dependencies);
50+
RelationshipDiscoveryConvention relationshipDiscoveryConvention =
51+
new CosmosRelationshipDiscoveryConvention(Dependencies);
4852
conventionSet.EntityTypeAddedConventions.Add(storeKeyConvention);
4953
conventionSet.EntityTypeAddedConventions.Add(discriminatorConvention);
50-
ReplaceConvention(conventionSet.EntityTypeAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
54+
ReplaceConvention(conventionSet.EntityTypeAddedConventions, keyDiscoveryConvention);
55+
ReplaceConvention(conventionSet.EntityTypeAddedConventions, inversePropertyAttributeConvention);
56+
ReplaceConvention(conventionSet.EntityTypeAddedConventions, relationshipDiscoveryConvention);
57+
58+
ReplaceConvention(conventionSet.EntityTypeIgnoredConventions, relationshipDiscoveryConvention);
5159

5260
ReplaceConvention(conventionSet.EntityTypeRemovedConventions, (DiscriminatorConvention)discriminatorConvention);
61+
ReplaceConvention(conventionSet.EntityTypeRemovedConventions, inversePropertyAttributeConvention);
5362

5463
conventionSet.EntityTypeBaseTypeChangedConventions.Add(storeKeyConvention);
5564
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (DiscriminatorConvention)discriminatorConvention);
56-
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
65+
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, keyDiscoveryConvention);
66+
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, inversePropertyAttributeConvention);
67+
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, relationshipDiscoveryConvention);
5768

58-
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
69+
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, keyDiscoveryConvention);
70+
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, inversePropertyAttributeConvention);
71+
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, relationshipDiscoveryConvention);
5972

6073
conventionSet.EntityTypePrimaryKeyChangedConventions.Add(storeKeyConvention);
6174

6275
conventionSet.KeyAddedConventions.Add(storeKeyConvention);
6376

6477
conventionSet.KeyRemovedConventions.Add(storeKeyConvention);
65-
ReplaceConvention(conventionSet.KeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
78+
ReplaceConvention(conventionSet.KeyRemovedConventions, keyDiscoveryConvention);
6679

67-
ReplaceConvention(conventionSet.ForeignKeyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
80+
ReplaceConvention(conventionSet.ForeignKeyAddedConventions, keyDiscoveryConvention);
6881

82+
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, relationshipDiscoveryConvention);
6983
conventionSet.ForeignKeyRemovedConventions.Add(discriminatorConvention);
7084
conventionSet.ForeignKeyRemovedConventions.Add(storeKeyConvention);
71-
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
85+
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, keyDiscoveryConvention);
7286

73-
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
87+
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, keyDiscoveryConvention);
7488

75-
ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
89+
ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, keyDiscoveryConvention);
7690

7791
conventionSet.ForeignKeyOwnershipChangedConventions.Add(discriminatorConvention);
7892
conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention);
79-
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
93+
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, keyDiscoveryConvention);
94+
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, relationshipDiscoveryConvention);
95+
96+
ReplaceConvention(conventionSet.ForeignKeyNullNavigationSetConventions, relationshipDiscoveryConvention);
97+
98+
ReplaceConvention(conventionSet.NavigationAddedConventions, inversePropertyAttributeConvention);
99+
ReplaceConvention(conventionSet.NavigationAddedConventions, relationshipDiscoveryConvention);
80100

101+
ReplaceConvention(conventionSet.NavigationRemovedConventions, relationshipDiscoveryConvention);
102+
103+
conventionSet.EntityTypeAnnotationChangedConventions.Add(discriminatorConvention);
81104
conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention);
82-
conventionSet.EntityTypeAnnotationChangedConventions.Add(keyDiscoveryConvention);
105+
conventionSet.EntityTypeAnnotationChangedConventions.Add((CosmosKeyDiscoveryConvention)keyDiscoveryConvention);
83106

84-
ReplaceConvention(conventionSet.PropertyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
107+
ReplaceConvention(conventionSet.PropertyAddedConventions, keyDiscoveryConvention);
85108

86109
conventionSet.PropertyAnnotationChangedConventions.Add(storeKeyConvention);
87110

111+
ReplaceConvention(conventionSet.ModelFinalizingConventions, inversePropertyAttributeConvention);
112+
88113
return conventionSet;
89114
}
90115

src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Linq;
66
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
77
using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration;
8-
using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal;
98
using Microsoft.EntityFrameworkCore.Infrastructure;
109
using Microsoft.EntityFrameworkCore.Metadata.Builders;
1110
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;

src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static class CosmosEntityTypeExtensions
2121
/// </summary>
2222
public static bool IsDocumentRoot(this IReadOnlyEntityType entityType)
2323
=> entityType.BaseType?.IsDocumentRoot()
24-
?? (!entityType.IsOwned()
24+
?? (entityType.FindOwnership() == null
2525
|| entityType[CosmosAnnotationNames.ContainerName] != null);
2626
}
2727
}

src/EFCore.Relational/Metadata/IColumn.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue)
9090
return false;
9191
}
9292

93-
var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter;
93+
var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter;
9494

9595
if (converter != null)
9696
{

src/EFCore/Infrastructure/ModelValidator.cs

+14-5
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ protected virtual void ValidatePropertyMapping(
221221
if (targetType != null)
222222
{
223223
var targetShared = conventionModel.IsShared(targetType);
224-
targetOwned ??= conventionModel.IsOwned(targetType);
224+
targetOwned ??= IsOwned(targetType, conventionModel);
225225
// ReSharper disable CheckForReferenceEqualityInstead.1
226226
// ReSharper disable CheckForReferenceEqualityInstead.3
227227
if ((!entityType.IsKeyless
@@ -230,10 +230,9 @@ protected virtual void ValidatePropertyMapping(
230230
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
231231
== null)
232232
&& (!(targetShared || targetOwned.Value)
233-
|| (!targetType.Equals(entityType.ClrType)
234-
&& (!entityType.IsInOwnershipPath(targetType)
235-
|| (entityType.FindOwnership()!.PrincipalEntityType.ClrType.Equals(targetType)
236-
&& targetSequenceType == null)))))
233+
|| !targetType.Equals(entityType.ClrType))
234+
&& (!entityType.IsInOwnershipPath(targetType)
235+
|| targetSequenceType == null))
237236
{
238237
if (entityType.IsOwned()
239238
&& targetOwned.Value)
@@ -274,6 +273,16 @@ protected virtual void ValidatePropertyMapping(
274273
}
275274
}
276275

276+
/// <summary>
277+
/// Returns a value indicating whether that target CLR type would correspond to an owned entity type.
278+
/// </summary>
279+
/// <param name="targetType"> The target CLR type. </param>
280+
/// <param name="conventionModel"> The model. </param>
281+
/// <returns> <see langword="true"/> if the given CLR type corresponds to an owned entity type. </returns>
282+
protected virtual bool IsOwned(Type targetType, IConventionModel conventionModel)
283+
=> conventionModel.FindIsOwnedConfigurationSource(targetType) != null
284+
|| conventionModel.FindEntityTypes(targetType).Any(t => t.IsOwned());
285+
277286
/// <summary>
278287
/// Validates that no attempt is made to ignore inherited properties.
279288
/// </summary>

0 commit comments

Comments
 (0)