Skip to content

Commit 5e26c54

Browse files
committed
Default relationships to ownership for Cosmos
Allow to reconfigure STETs as regular entity types Fixes #24803
1 parent 4507cc3 commit 5e26c54

37 files changed

+1146
-283
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 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)