Skip to content

Commit

Permalink
Default relationships to ownership for Cosmos
Browse files Browse the repository at this point in the history
Allow to reconfigure STETs as regular entity types

Fixes #24803
  • Loading branch information
AndriySvyryd committed Jul 17, 2021
1 parent 4507cc3 commit 5e26c54
Show file tree
Hide file tree
Showing 37 changed files with 1,146 additions and 283 deletions.
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static class CosmosEntityTypeExtensions
?? GetDefaultContainer(entityType);

private static string? GetDefaultContainer(IReadOnlyEntityType entityType)
=> entityType.IsOwned()
=> entityType.FindOwnership() != null
? null
: entityType.Model.GetDefaultContainer()
?? entityType.ShortName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// 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.Linq;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
Expand All @@ -16,7 +15,8 @@ public class CosmosDiscriminatorConvention :
DiscriminatorConvention,
IForeignKeyOwnershipChangedConvention,
IForeignKeyRemovedConvention,
IEntityTypeAddedConvention
IEntityTypeAddedConvention,
IEntityTypeAnnotationChangedConvention
{
/// <summary>
/// Creates a new instance of <see cref="CosmosDiscriminatorConvention" />.
Expand All @@ -36,16 +36,7 @@ public virtual void ProcessEntityTypeAdded(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionContext<IConventionEntityTypeBuilder> context)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotNull(context, nameof(context));

var entityType = entityTypeBuilder.Metadata;
if (entityType.BaseType == null
&& entityType.IsDocumentRoot())
{
entityTypeBuilder.HasDiscriminator(typeof(string))
?.HasValue(entityType, entityType.ShortName());
}
ProcessEntityType(entityTypeBuilder);
}

/// <summary>
Expand All @@ -57,17 +48,9 @@ public virtual void ProcessForeignKeyOwnershipChanged(
IConventionForeignKeyBuilder relationshipBuilder,
IConventionContext<bool?> context)
{
Check.NotNull(relationshipBuilder, nameof(relationshipBuilder));
Check.NotNull(context, nameof(context));

var entityType = relationshipBuilder.Metadata.DeclaringEntityType;
if (relationshipBuilder.Metadata.IsOwnership
&& !entityType.IsDocumentRoot()
&& entityType.BaseType == null
&& !entityType.GetDerivedTypes().Any())
{
entityType.Builder.HasNoDiscriminator();
}

ProcessEntityType(entityType.Builder);
}

/// <summary>
Expand All @@ -81,13 +64,52 @@ public virtual void ProcessForeignKeyRemoved(
IConventionForeignKey foreignKey,
IConventionContext<IConventionForeignKey> context)
{
var entityType = foreignKey.DeclaringEntityType;
if (foreignKey.IsOwnership
&& !entityType.IsDocumentRoot()
&& entityType.BaseType == null
&& !entityType.GetDerivedTypes().Any())
if (foreignKey.IsOwnership)
{
entityType.Builder.HasNoDiscriminator();
ProcessEntityType(entityTypeBuilder);
}
}

/// <summary>
/// Called after an annotation is changed on an entity type.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type. </param>
/// <param name="name"> The annotation name. </param>
/// <param name="annotation"> The new annotation. </param>
/// <param name="oldAnnotation"> The old annotation. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
public void ProcessEntityTypeAnnotationChanged(
IConventionEntityTypeBuilder entityTypeBuilder,
string name,
IConventionAnnotation? annotation,
IConventionAnnotation? oldAnnotation,
IConventionContext<IConventionAnnotation> context)
{
if (name != CosmosAnnotationNames.ContainerName
|| (annotation == null) == (oldAnnotation == null))
{
return;
}

ProcessEntityType(entityTypeBuilder);
}

private void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBuilder)
{
var entityType = entityTypeBuilder.Metadata;
if (entityType.BaseType != null)
{
return;
}

if (!entityType.IsDocumentRoot())
{
entityTypeBuilder.HasNoDiscriminator();
}
else
{
entityTypeBuilder.HasDiscriminator(typeof(string))
?.HasValue(entityType, entityType.ShortName());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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;

// ReSharper disable once CheckNamespace
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,35 @@
// 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 Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;

// ReSharper disable once CheckNamespace
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>
/// Returns a value indicating whether the given entity type should be added as owned if it isn't currently in the model.
/// </summary>
/// <param name="targetType"> Target entity type. </param>
/// <param name="model"> The model. </param>
/// <returns> <see langword="true"/> if the given entity type should be owned. </returns>
protected override bool? ShouldBeOwned(Type targetType, IConventionModel model)
=> true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,47 +44,72 @@ 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, (KeyDiscoveryConvention)keyDiscoveryConvention);
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, keyDiscoveryConvention);
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);

ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, relationshipDiscoveryConvention);
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.ForeignKeyNullNavigationSetConventions, relationshipDiscoveryConvention);

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

ReplaceConvention(conventionSet.NavigationRemovedConventions, relationshipDiscoveryConvention);

conventionSet.EntityTypeAnnotationChangedConventions.Add(discriminatorConvention);
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Linq;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration;
using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static class CosmosEntityTypeExtensions
/// </summary>
public static bool IsDocumentRoot(this IReadOnlyEntityType entityType)
=> entityType.BaseType?.IsDocumentRoot()
?? (!entityType.IsOwned()
?? (entityType.FindOwnership() == null
|| entityType[CosmosAnnotationNames.ContainerName] != null);
}
}
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue)
return false;
}

var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter;
var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter;

if (converter != null)
{
Expand Down
19 changes: 14 additions & 5 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ protected virtual void ValidatePropertyMapping(
if (targetType != null)
{
var targetShared = conventionModel.IsShared(targetType);
targetOwned ??= conventionModel.IsOwned(targetType);
targetOwned ??= IsOwned(targetType, conventionModel);
// ReSharper disable CheckForReferenceEqualityInstead.1
// ReSharper disable CheckForReferenceEqualityInstead.3
if ((!entityType.IsKeyless
Expand All @@ -230,10 +230,9 @@ protected virtual void ValidatePropertyMapping(
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
== null)
&& (!(targetShared || targetOwned.Value)
|| (!targetType.Equals(entityType.ClrType)
&& (!entityType.IsInOwnershipPath(targetType)
|| (entityType.FindOwnership()!.PrincipalEntityType.ClrType.Equals(targetType)
&& targetSequenceType == null)))))
|| !targetType.Equals(entityType.ClrType))
&& (!entityType.IsInOwnershipPath(targetType)
|| targetSequenceType == null))
{
if (entityType.IsOwned()
&& targetOwned.Value)
Expand Down Expand Up @@ -274,6 +273,16 @@ protected virtual void ValidatePropertyMapping(
}
}

/// <summary>
/// Returns a value indicating whether that target CLR type would correspond to an owned entity type.
/// </summary>
/// <param name="targetType"> The target CLR type. </param>
/// <param name="conventionModel"> The model. </param>
/// <returns> <see langword="true"/> if the given CLR type corresponds to an owned entity type. </returns>
protected virtual bool IsOwned(Type targetType, IConventionModel conventionModel)
=> conventionModel.FindIsOwnedConfigurationSource(targetType) != null
|| conventionModel.FindEntityTypes(targetType).Any(t => t.IsOwned());

/// <summary>
/// Validates that no attempt is made to ignore inherited properties.
/// </summary>
Expand Down
Loading

0 comments on commit 5e26c54

Please sign in to comment.