Skip to content

Commit d71d914

Browse files
committed
Default relationships to ownership for Cosmos
Allow to reconfigure STETs as regular entity types Fixes #24803
1 parent 0e53027 commit d71d914

File tree

4 files changed

+147
-11
lines changed

4 files changed

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

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

+33-11
Original file line numberDiff line numberDiff line change
@@ -44,45 +44,67 @@ 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);
68+
69+
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, inversePropertyAttributeConvention);
70+
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, relationshipDiscoveryConvention);
5771

5872
conventionSet.EntityTypePrimaryKeyChangedConventions.Add(storeKeyConvention);
5973

6074
conventionSet.KeyAddedConventions.Add(storeKeyConvention);
6175

6276
conventionSet.KeyRemovedConventions.Add(storeKeyConvention);
63-
ReplaceConvention(conventionSet.KeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
77+
ReplaceConvention(conventionSet.KeyRemovedConventions, keyDiscoveryConvention);
6478

65-
ReplaceConvention(conventionSet.ForeignKeyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
79+
ReplaceConvention(conventionSet.ForeignKeyAddedConventions, keyDiscoveryConvention);
6680

6781
conventionSet.ForeignKeyRemovedConventions.Add(discriminatorConvention);
6882
conventionSet.ForeignKeyRemovedConventions.Add(storeKeyConvention);
69-
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
83+
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, keyDiscoveryConvention);
7084

71-
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
85+
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, keyDiscoveryConvention);
7286

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

7589
conventionSet.ForeignKeyOwnershipChangedConventions.Add(discriminatorConvention);
7690
conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention);
77-
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
91+
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, keyDiscoveryConvention);
92+
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, relationshipDiscoveryConvention);
93+
94+
ReplaceConvention(conventionSet.NavigationAddedConventions, inversePropertyAttributeConvention);
95+
ReplaceConvention(conventionSet.NavigationAddedConventions, relationshipDiscoveryConvention);
96+
97+
ReplaceConvention(conventionSet.NavigationRemovedConventions, relationshipDiscoveryConvention);
7898

7999
conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention);
80-
conventionSet.EntityTypeAnnotationChangedConventions.Add(keyDiscoveryConvention);
100+
conventionSet.EntityTypeAnnotationChangedConventions.Add((CosmosKeyDiscoveryConvention)keyDiscoveryConvention);
81101

82-
ReplaceConvention(conventionSet.PropertyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
102+
ReplaceConvention(conventionSet.PropertyAddedConventions, keyDiscoveryConvention);
83103

84104
conventionSet.PropertyAnnotationChangedConventions.Add(storeKeyConvention);
85105

106+
ReplaceConvention(conventionSet.ModelFinalizingConventions, inversePropertyAttributeConvention);
107+
86108
return conventionSet;
87109
}
88110

test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs

+29
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,35 @@ protected override TestModelBuilder CreateModelBuilder(Action<ModelConfiguration
309309

310310
public class CosmosGenericOwnedTypes : GenericOwnedTypes
311311
{
312+
[ConditionalFact]
313+
public virtual void Reference_type_is_discovered_as_owned()
314+
{
315+
var modelBuilder = CreateModelBuilder();
316+
317+
modelBuilder.Entity<OneToOneOwnerWithField>(
318+
e =>
319+
{
320+
e.Property(p => p.Id);
321+
e.Property(p => p.AlternateKey);
322+
e.Property(p => p.Description);
323+
e.HasKey(p => p.Id);
324+
});
325+
326+
var model = modelBuilder.FinalizeModel();
327+
328+
var owner = model.FindEntityType(typeof(OneToOneOwnerWithField));
329+
Assert.Equal(typeof(OneToOneOwnerWithField).FullName, owner.Name);
330+
var ownership = owner.FindNavigation(nameof(OneToOneOwnerWithField.OwnedDependent)).ForeignKey;
331+
Assert.True(ownership.IsOwnership);
332+
Assert.Equal(nameof(OneToOneOwnerWithField.OwnedDependent), ownership.PrincipalToDependent.Name);
333+
Assert.Equal(nameof(OneToOneOwnedWithField.OneToOneOwner), ownership.DependentToPrincipal.Name);
334+
Assert.Equal(nameof(OneToOneOwnerWithField.Id), ownership.PrincipalKey.Properties.Single().Name);
335+
var owned = ownership.DeclaringEntityType;
336+
Assert.Single(owned.GetForeignKeys());
337+
Assert.NotNull(model.FindEntityType(typeof(OneToOneOwnedWithField)));
338+
Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(OneToOneOwnedWithField)));
339+
}
340+
312341
protected override TestModelBuilder CreateModelBuilder(Action<ModelConfigurationBuilder> configure = null)
313342
=> CreateTestModelBuilder(CosmosTestHelpers.Instance, configure);
314343
}

0 commit comments

Comments
 (0)