Skip to content

Commit 2bde2af

Browse files
committed
Default to ownership for Cosmos
Fixes #24803
1 parent b44b5cc commit 2bde2af

20 files changed

+384
-179
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,60 @@
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.Linq;
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 relationships between entity types based on the navigation properties
15+
/// as long as there is no ambiguity as to which is the corresponding inverse navigation.
16+
/// All navigations are assumed to be targeting owned entity types for Cosmos.
17+
/// </summary>
18+
public class CosmosRelationshipDiscoveryConvention : RelationshipDiscoveryConvention
19+
{
20+
/// <summary>
21+
/// Creates a new instance of <see cref="RelationshipDiscoveryConvention" />.
22+
/// </summary>
23+
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
24+
public CosmosRelationshipDiscoveryConvention(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.Convention : null,
48+
targetShouldBeOwned: true);
49+
#pragma warning restore EF1001 // Internal EF Core API usage.
50+
51+
/// <summary>
52+
/// Returns a value indicating whether the given entity type should be owned.
53+
/// </summary>
54+
/// <param name="targetEntityType"> Target entity type. </param>
55+
/// <returns> <see langword="true"/> if the given entity type should be owned. </returns>
56+
protected override bool ShouldBeOwned(IConventionEntityType targetEntityType)
57+
=> targetEntityType.GetConfigurationSource() == ConfigurationSource.Convention
58+
|| base.ShouldBeOwned(targetEntityType);
59+
}
60+
}

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

src/EFCore.SqlServer.NTS/Properties/SqlServerNTSStrings.Designer.cs

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore/ChangeTracking/EntityEntry.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ public virtual PropertyValues CurrentValues
325325
/// </para>
326326
/// <para>
327327
/// Note that whenever real original property values are not available (e.g. entity was not yet
328-
/// persisted to the database) this will default to the current property values of this entity.
328+
/// persisted to the database or was retrieved in a non-tracking query) this will default to the
329+
/// current property values of this entity.
329330
/// </para>
330331
/// </summary>
331332
/// <value> The original values. </value>
@@ -338,14 +339,14 @@ public virtual PropertyValues OriginalValues
338339
/// <summary>
339340
/// <para>
340341
/// Queries the database for copies of the values of the tracked entity as they currently
341-
/// exist in the database. If the entity is not found in the database, then null is returned.
342+
/// exist in the database. If the entity is not found in the database, then <see langword="null"/> is returned.
342343
/// </para>
343344
/// <para>
344345
/// Note that changing the values in the returned dictionary will not update the values
345346
/// in the database.
346347
/// </para>
347348
/// </summary>
348-
/// <returns> The store values, or null if the entity does not exist in the database. </returns>
349+
/// <returns> The store values, or <see langword="null"/> if the entity does not exist in the database. </returns>
349350
public virtual PropertyValues? GetDatabaseValues()
350351
{
351352
var values = Finder.GetDatabaseValues(InternalEntry);

src/EFCore/Diagnostics/CoreEventId.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private static EventId MakeUpdateId(Id id)
146146
public static readonly EventId SaveChangesFailed = MakeUpdateId(Id.SaveChangesFailed);
147147

148148
/// <summary>
149-
/// The same entity is being tracked as a different weak entity type.
149+
/// The same entity is being tracked as a different shared entity entity type.
150150
/// This event is in the <see cref="DbLoggerCategory.Update" /> category.
151151
/// </summary>
152152
public static readonly EventId DuplicateDependentEntityTypeInstanceWarning =

src/EFCore/Infrastructure/ModelValidator.cs

+14-29
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ protected virtual void ValidatePropertyMapping(
141141
{
142142
Check.NotNull(model, nameof(model));
143143

144-
if (!(model is IConventionModel conventionModel))
144+
if (model is not IConventionModel conventionModel)
145145
{
146146
return;
147147
}
@@ -167,10 +167,8 @@ protected virtual void ValidatePropertyMapping(
167167
continue;
168168
}
169169

170-
var clrProperties = new HashSet<string>(StringComparer.Ordinal);
171-
172170
var runtimeProperties = entityType.GetRuntimeProperties();
173-
171+
var clrProperties = new HashSet<string>(StringComparer.Ordinal);
174172
clrProperties.UnionWith(
175173
runtimeProperties.Values
176174
.Where(pi => pi.IsCandidateProperty(needsWrite: false))
@@ -181,6 +179,7 @@ protected virtual void ValidatePropertyMapping(
181179
.Concat(entityType.GetNavigations())
182180
.Concat(entityType.GetSkipNavigations())
183181
.Concat(entityType.GetServiceProperties()).Select(p => p.Name));
182+
184183
if (entityType.IsPropertyBag)
185184
{
186185
clrProperties.ExceptWith(_dictionaryProperties);
@@ -191,7 +190,6 @@ protected virtual void ValidatePropertyMapping(
191190
continue;
192191
}
193192

194-
var configuration = ((Model)entityType.Model).Configuration;
195193
foreach (var clrPropertyName in clrProperties)
196194
{
197195
if (entityType.FindIgnoredConfigurationSource(clrPropertyName) != null)
@@ -212,53 +210,40 @@ protected virtual void ValidatePropertyMapping(
212210
continue;
213211
}
214212

215-
Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(clrProperty, out var targetType);
213+
var targetType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(
214+
clrProperty, conventionModel, out var targetOwned);
216215
if (targetType == null
217-
|| targetSequenceType == null)
216+
&& clrProperty.FindSetterProperty() == null)
218217
{
219-
if (clrProperty.FindSetterProperty() == null)
220-
{
221-
continue;
222-
}
223-
224-
var sharedType = clrProperty.GetMemberType();
225-
if (conventionModel.IsShared(sharedType))
226-
{
227-
targetType = sharedType;
228-
}
218+
continue;
229219
}
230220

231-
var isTargetSharedOrOwned = targetType != null
232-
&& (conventionModel.IsShared(targetType)
233-
|| conventionModel.IsOwned(targetType));
234-
235-
if (targetType?.IsValidEntityType() == true
236-
&& (isTargetSharedOrOwned
237-
|| conventionModel.FindEntityType(targetType) != null
238-
|| targetType.GetRuntimeProperties().Any(p => p.IsCandidateProperty())))
221+
if (targetType != null)
239222
{
223+
var targetShared = conventionModel.IsShared(targetType);
224+
targetOwned ??= conventionModel.IsOwned(targetType);
240225
// ReSharper disable CheckForReferenceEqualityInstead.1
241226
// ReSharper disable CheckForReferenceEqualityInstead.3
242227
if ((!entityType.IsKeyless
243228
|| targetSequenceType == null)
244229
&& entityType.GetDerivedTypes().All(
245230
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
246231
== null)
247-
&& (!isTargetSharedOrOwned
232+
&& (!(targetShared || targetOwned.Value)
248233
|| (!targetType.Equals(entityType.ClrType)
249234
&& (!entityType.IsInOwnershipPath(targetType)
250235
|| (entityType.FindOwnership()!.PrincipalEntityType.ClrType.Equals(targetType)
251236
&& targetSequenceType == null)))))
252237
{
253238
if (conventionModel.IsOwned(entityType.ClrType)
254-
&& conventionModel.IsOwned(targetType))
239+
&& targetOwned.Value)
255240
{
256241
throw new InvalidOperationException(
257242
CoreStrings.AmbiguousOwnedNavigation(
258243
entityType.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName()));
259244
}
260245

261-
if (model.IsShared(targetType))
246+
if (targetShared)
262247
{
263248
throw new InvalidOperationException(
264249
CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, entityType.DisplayName()));
@@ -742,7 +727,7 @@ protected virtual void ValidateOwnership(
742727
ownership.PrincipalEntityType.DisplayName()));
743728
}
744729
}
745-
else if (((IMutableModel)model).IsOwned(entityType.ClrType))
730+
else if (((IConventionModel)model).IsOwned(entityType.ClrType))
746731
{
747732
throw new InvalidOperationException(CoreStrings.OwnerlessOwnedType(entityType.DisplayName()));
748733
}

0 commit comments

Comments
 (0)