Skip to content

Commit

Permalink
Default to ownership for Cosmos
Browse files Browse the repository at this point in the history
Fixes #24803
  • Loading branch information
AndriySvyryd committed Jun 15, 2021
1 parent b44b5cc commit 2bde2af
Show file tree
Hide file tree
Showing 20 changed files with 384 additions and 179 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// A convention that configures the inverse navigation property based on the <see cref="InversePropertyAttribute" />
/// specified on the other navigation property.
/// All navigations are assumed to be targeting owned entity types for Cosmos.
/// </summary>
public class CosmosInversePropertyAttributeConvention : InversePropertyAttributeConvention
{
/// <summary>
/// Creates a new instance of <see cref="InversePropertyAttributeConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
public CosmosInversePropertyAttributeConvention(ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// Finds or tries to create an entity type target for the given navigation member.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the referencing entity type. </param>
/// <param name="targetClrType"> The CLR type of the target entity type. </param>
/// <param name="navigationMemberInfo"> The navigation member. </param>
/// <param name="shouldCreate"> Whether an entity type should be created if one doesn't currently exist. </param>
/// <returns> The builder for the target entity type or <see langword="null"/> if it can't be created. </returns>
protected override IConventionEntityTypeBuilder? TryGetTargetEntityTypeBuilder(
IConventionEntityTypeBuilder entityTypeBuilder,
Type targetClrType,
MemberInfo navigationMemberInfo,
bool shouldCreate = true)
=> ((InternalEntityTypeBuilder)entityTypeBuilder)
#pragma warning disable EF1001 // Internal EF Core API usage.
.GetTargetEntityTypeBuilder(
targetClrType,
navigationMemberInfo,
shouldCreate ? ConfigurationSource.DataAnnotation : null,
targetShouldBeOwned: true);
#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// A convention that configures relationships between entity types based on the navigation properties
/// as long as there is no ambiguity as to which is the corresponding inverse navigation.
/// All navigations are assumed to be targeting owned entity types for Cosmos.
/// </summary>
public class CosmosRelationshipDiscoveryConvention : RelationshipDiscoveryConvention
{
/// <summary>
/// Creates a new instance of <see cref="RelationshipDiscoveryConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
public CosmosRelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// Finds or tries to create an entity type target for the given navigation member.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the referencing entity type. </param>
/// <param name="targetClrType"> The CLR type of the target entity type. </param>
/// <param name="navigationMemberInfo"> The navigation member. </param>
/// <param name="shouldCreate"> Whether an entity type should be created if one doesn't currently exist. </param>
/// <returns> The builder for the target entity type or <see langword="null"/> if it can't be created. </returns>
protected override IConventionEntityTypeBuilder? TryGetTargetEntityTypeBuilder(
IConventionEntityTypeBuilder entityTypeBuilder,
Type targetClrType,
MemberInfo navigationMemberInfo,
bool shouldCreate = true)
=> ((InternalEntityTypeBuilder)entityTypeBuilder)
#pragma warning disable EF1001 // Internal EF Core API usage.
.GetTargetEntityTypeBuilder(
targetClrType,
navigationMemberInfo,
shouldCreate ? ConfigurationSource.Convention : null,
targetShouldBeOwned: true);
#pragma warning restore EF1001 // Internal EF Core API usage.

/// <summary>
/// Returns a value indicating whether the given entity type should be owned.
/// </summary>
/// <param name="targetEntityType"> Target entity type. </param>
/// <returns> <see langword="true"/> if the given entity type should be owned. </returns>
protected override bool ShouldBeOwned(IConventionEntityType targetEntityType)
=> targetEntityType.GetConfigurationSource() == ConfigurationSource.Convention
|| base.ShouldBeOwned(targetEntityType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,45 +44,67 @@ public override ConventionSet CreateConventionSet()

var storeKeyConvention = new StoreKeyConvention(Dependencies);
var discriminatorConvention = new CosmosDiscriminatorConvention(Dependencies);
var keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies);
KeyDiscoveryConvention keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies);
InversePropertyAttributeConvention inversePropertyAttributeConvention =
new CosmosInversePropertyAttributeConvention(Dependencies);
RelationshipDiscoveryConvention relationshipDiscoveryConvention =
new CosmosRelationshipDiscoveryConvention(Dependencies);
conventionSet.EntityTypeAddedConventions.Add(storeKeyConvention);
conventionSet.EntityTypeAddedConventions.Add(discriminatorConvention);
ReplaceConvention(conventionSet.EntityTypeAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.EntityTypeAddedConventions, keyDiscoveryConvention);
ReplaceConvention(conventionSet.EntityTypeAddedConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.EntityTypeAddedConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.EntityTypeIgnoredConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.EntityTypeRemovedConventions, (DiscriminatorConvention)discriminatorConvention);
ReplaceConvention(conventionSet.EntityTypeRemovedConventions, inversePropertyAttributeConvention);

conventionSet.EntityTypeBaseTypeChangedConventions.Add(storeKeyConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (DiscriminatorConvention)discriminatorConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, keyDiscoveryConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, relationshipDiscoveryConvention);

conventionSet.EntityTypePrimaryKeyChangedConventions.Add(storeKeyConvention);

conventionSet.KeyAddedConventions.Add(storeKeyConvention);

conventionSet.KeyRemovedConventions.Add(storeKeyConvention);
ReplaceConvention(conventionSet.KeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.KeyRemovedConventions, keyDiscoveryConvention);

ReplaceConvention(conventionSet.ForeignKeyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyAddedConventions, keyDiscoveryConvention);

conventionSet.ForeignKeyRemovedConventions.Add(discriminatorConvention);
conventionSet.ForeignKeyRemovedConventions.Add(storeKeyConvention);
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, keyDiscoveryConvention);

ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, keyDiscoveryConvention);

ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, keyDiscoveryConvention);

conventionSet.ForeignKeyOwnershipChangedConventions.Add(discriminatorConvention);
conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention);
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, keyDiscoveryConvention);
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.NavigationAddedConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.NavigationAddedConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.NavigationRemovedConventions, relationshipDiscoveryConvention);

conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention);
conventionSet.EntityTypeAnnotationChangedConventions.Add(keyDiscoveryConvention);
conventionSet.EntityTypeAnnotationChangedConventions.Add((CosmosKeyDiscoveryConvention)keyDiscoveryConvention);

ReplaceConvention(conventionSet.PropertyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.PropertyAddedConventions, keyDiscoveryConvention);

conventionSet.PropertyAnnotationChangedConventions.Add(storeKeyConvention);

ReplaceConvention(conventionSet.ModelFinalizingConventions, inversePropertyAttributeConvention);

return conventionSet;
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/EFCore/ChangeTracking/EntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ public virtual PropertyValues CurrentValues
/// </para>
/// <para>
/// Note that whenever real original property values are not available (e.g. entity was not yet
/// persisted to the database) this will default to the current property values of this entity.
/// persisted to the database or was retrieved in a non-tracking query) this will default to the
/// current property values of this entity.
/// </para>
/// </summary>
/// <value> The original values. </value>
Expand All @@ -338,14 +339,14 @@ public virtual PropertyValues OriginalValues
/// <summary>
/// <para>
/// Queries the database for copies of the values of the tracked entity as they currently
/// exist in the database. If the entity is not found in the database, then null is returned.
/// exist in the database. If the entity is not found in the database, then <see langword="null"/> is returned.
/// </para>
/// <para>
/// Note that changing the values in the returned dictionary will not update the values
/// in the database.
/// </para>
/// </summary>
/// <returns> The store values, or null if the entity does not exist in the database. </returns>
/// <returns> The store values, or <see langword="null"/> if the entity does not exist in the database. </returns>
public virtual PropertyValues? GetDatabaseValues()
{
var values = Finder.GetDatabaseValues(InternalEntry);
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Diagnostics/CoreEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private static EventId MakeUpdateId(Id id)
public static readonly EventId SaveChangesFailed = MakeUpdateId(Id.SaveChangesFailed);

/// <summary>
/// The same entity is being tracked as a different weak entity type.
/// The same entity is being tracked as a different shared entity entity type.
/// This event is in the <see cref="DbLoggerCategory.Update" /> category.
/// </summary>
public static readonly EventId DuplicateDependentEntityTypeInstanceWarning =
Expand Down
43 changes: 14 additions & 29 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ protected virtual void ValidatePropertyMapping(
{
Check.NotNull(model, nameof(model));

if (!(model is IConventionModel conventionModel))
if (model is not IConventionModel conventionModel)
{
return;
}
Expand All @@ -167,10 +167,8 @@ protected virtual void ValidatePropertyMapping(
continue;
}

var clrProperties = new HashSet<string>(StringComparer.Ordinal);

var runtimeProperties = entityType.GetRuntimeProperties();

var clrProperties = new HashSet<string>(StringComparer.Ordinal);
clrProperties.UnionWith(
runtimeProperties.Values
.Where(pi => pi.IsCandidateProperty(needsWrite: false))
Expand All @@ -181,6 +179,7 @@ protected virtual void ValidatePropertyMapping(
.Concat(entityType.GetNavigations())
.Concat(entityType.GetSkipNavigations())
.Concat(entityType.GetServiceProperties()).Select(p => p.Name));

if (entityType.IsPropertyBag)
{
clrProperties.ExceptWith(_dictionaryProperties);
Expand All @@ -191,7 +190,6 @@ protected virtual void ValidatePropertyMapping(
continue;
}

var configuration = ((Model)entityType.Model).Configuration;
foreach (var clrPropertyName in clrProperties)
{
if (entityType.FindIgnoredConfigurationSource(clrPropertyName) != null)
Expand All @@ -212,53 +210,40 @@ protected virtual void ValidatePropertyMapping(
continue;
}

Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(clrProperty, out var targetType);
var targetType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(
clrProperty, conventionModel, out var targetOwned);
if (targetType == null
|| targetSequenceType == null)
&& clrProperty.FindSetterProperty() == null)
{
if (clrProperty.FindSetterProperty() == null)
{
continue;
}

var sharedType = clrProperty.GetMemberType();
if (conventionModel.IsShared(sharedType))
{
targetType = sharedType;
}
continue;
}

var isTargetSharedOrOwned = targetType != null
&& (conventionModel.IsShared(targetType)
|| conventionModel.IsOwned(targetType));

if (targetType?.IsValidEntityType() == true
&& (isTargetSharedOrOwned
|| conventionModel.FindEntityType(targetType) != null
|| targetType.GetRuntimeProperties().Any(p => p.IsCandidateProperty())))
if (targetType != null)
{
var targetShared = conventionModel.IsShared(targetType);
targetOwned ??= conventionModel.IsOwned(targetType);
// ReSharper disable CheckForReferenceEqualityInstead.1
// ReSharper disable CheckForReferenceEqualityInstead.3
if ((!entityType.IsKeyless
|| targetSequenceType == null)
&& entityType.GetDerivedTypes().All(
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
== null)
&& (!isTargetSharedOrOwned
&& (!(targetShared || targetOwned.Value)
|| (!targetType.Equals(entityType.ClrType)
&& (!entityType.IsInOwnershipPath(targetType)
|| (entityType.FindOwnership()!.PrincipalEntityType.ClrType.Equals(targetType)
&& targetSequenceType == null)))))
{
if (conventionModel.IsOwned(entityType.ClrType)
&& conventionModel.IsOwned(targetType))
&& targetOwned.Value)
{
throw new InvalidOperationException(
CoreStrings.AmbiguousOwnedNavigation(
entityType.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName()));
}

if (model.IsShared(targetType))
if (targetShared)
{
throw new InvalidOperationException(
CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, entityType.DisplayName()));
Expand Down Expand Up @@ -742,7 +727,7 @@ protected virtual void ValidateOwnership(
ownership.PrincipalEntityType.DisplayName()));
}
}
else if (((IMutableModel)model).IsOwned(entityType.ClrType))
else if (((IConventionModel)model).IsOwned(entityType.ClrType))
{
throw new InvalidOperationException(CoreStrings.OwnerlessOwnedType(entityType.DisplayName()));
}
Expand Down
Loading

0 comments on commit 2bde2af

Please sign in to comment.