Skip to content

Commit

Permalink
Don't add discriminators for embedded entities
Browse files Browse the repository at this point in the history
Allow to update entities without discriminators
Don't try to add the default discriminator if a conflicting property already exists

Fixes #17764
Fixes #17302
Fixes #17814
  • Loading branch information
AndriySvyryd committed Sep 17, 2019
1 parent 130fe39 commit f44ba0d
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 274 deletions.
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ dotnet_naming_rule.interface_naming.symbols = interface_symbol
dotnet_naming_rule.interface_naming.style = interface_style
dotnet_naming_rule.interface_naming.severity = suggestion

# Classes, Structs, Enums, Properties, Methods, Events, Namespaces
dotnet_naming_symbols.class_symbol.applicable_kinds = class, struct, enum, property, method, event, namespace
# Classes, Structs, Enums, Properties, Methods, Local Functions, Events, Namespaces
dotnet_naming_symbols.class_symbol.applicable_kinds = class, struct, enum, property, method, local_function, event, namespace
dotnet_naming_symbols.class_symbol.applicable_accessibilities = *

dotnet_naming_rule.class_naming.symbols = class_symbol
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// 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.Collections;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Utilities;
Expand All @@ -12,7 +13,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
/// <summary>
/// A convention that configures the discriminator value for entity types as the entity type name.
/// </summary>
public class CosmosDiscriminatorConvention : DiscriminatorConvention, IEntityTypeAddedConvention
public class CosmosDiscriminatorConvention :
DiscriminatorConvention,
IForeignKeyOwnershipChangedConvention,
IForeignKeyRemovedConvention,
IEntityTypeAddedConvention
{
/// <summary>
/// Creates a new instance of <see cref="CosmosDiscriminatorConvention" />.
Expand All @@ -36,22 +41,56 @@ public CosmosDiscriminatorConvention([NotNull] ProviderConventionSetBuilderDepen
Check.NotNull(context, nameof(context));

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

private static bool Any([NotNull] IEnumerable source)
/// <summary>
/// Called after the ownership value for a foreign key is changed.
/// </summary>
/// <param name="relationshipBuilder"> The builder for the foreign key. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
public virtual void ProcessForeignKeyOwnershipChanged(
IConventionRelationshipBuilder relationshipBuilder,
IConventionContext<IConventionRelationshipBuilder> context)
{
foreach (var _ in source)
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())
{
return true;
entityType.Builder.HasNoDeclaredDiscriminator();
}
}

return false;
/// <summary>
/// Called after a foreign key is removed.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type. </param>
/// <param name="foreignKey"> The removed foreign key. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
public void ProcessForeignKeyRemoved(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionForeignKey foreignKey,
IConventionContext<IConventionForeignKey> context)
{
var entityType = foreignKey.DeclaringEntityType;
if (foreignKey.IsOwnership
&& !entityType.IsDocumentRoot()
&& entityType.BaseType == null
&& !entityType.GetDerivedTypes().Any())
{
entityType.Builder.HasNoDeclaredDiscriminator();
}
}

/// <summary>
Expand All @@ -72,11 +111,14 @@ private static bool Any([NotNull] IEnumerable source)
return;
}

IConventionDiscriminatorBuilder discriminator;
IConventionDiscriminatorBuilder discriminator = null;
var entityType = entityTypeBuilder.Metadata;
if (newBaseType == null)
{
discriminator = entityTypeBuilder.HasDiscriminator(typeof(string));
if (entityType.IsDocumentRoot())
{
discriminator = entityTypeBuilder.HasDiscriminator(typeof(string));
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public override ConventionSet CreateConventionSet()
conventionSet.EntityTypeBaseTypeChangedConventions.Add(storeKeyConvention);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (DiscriminatorConvention)discriminatorConvention);

conventionSet.ForeignKeyRemovedConventions.Add(discriminatorConvention);
conventionSet.ForeignKeyRemovedConventions.Add(storeKeyConvention);

conventionSet.ForeignKeyOwnershipChangedConventions.Add(discriminatorConvention);
conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention);

conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention);
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
public class StoreKeyConvention :
IEntityTypeAddedConvention,
IForeignKeyOwnershipChangedConvention,
IForeignKeyRemovedConvention,
IEntityTypeAnnotationChangedConvention,
IEntityTypeBaseTypeChangedConvention
{
Expand Down Expand Up @@ -113,6 +114,23 @@ private static void Process(IConventionEntityTypeBuilder entityTypeBuilder)
Process(relationshipBuilder.Metadata.DeclaringEntityType.Builder);
}

/// <summary>
/// Called after a foreign key is removed.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type. </param>
/// <param name="foreignKey"> The removed foreign key. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
public void ProcessForeignKeyRemoved(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionForeignKey foreignKey,
IConventionContext<IConventionForeignKey> context)
{
if (foreignKey.IsOwnership)
{
Process(foreignKey.DeclaringEntityType.Builder);
}
}

/// <summary>
/// Called after an annotation is changed on an entity type.
/// </summary>
Expand Down
16 changes: 12 additions & 4 deletions src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,12 @@ private bool Save(IUpdateEntry entry)
{
document = documentSource.CreateDocument(entry);

document[entityType.GetDiscriminatorProperty().GetPropertyName()] =
JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer);
var propertyName = entityType.GetDiscriminatorProperty()?.GetPropertyName();
if (propertyName != null)
{
document[propertyName] =
JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer);
}
}

return _cosmosClient.ReplaceItem(
Expand Down Expand Up @@ -255,8 +259,12 @@ private Task<bool> SaveAsync(IUpdateEntry entry, CancellationToken cancellationT
{
document = documentSource.CreateDocument(entry);

document[entityType.GetDiscriminatorProperty().GetPropertyName()] =
JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer);
var propertyName = entityType.GetDiscriminatorProperty()?.GetPropertyName();
if (propertyName != null)
{
document[propertyName] =
JToken.FromObject(entityType.GetDiscriminatorValue(), CosmosClientWrapper.Serializer);
}
}

return _cosmosClient.ReplaceItemAsync(
Expand Down
3 changes: 0 additions & 3 deletions src/EFCore/Infrastructure/ModelSource.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// 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.Collections.Concurrent;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
Expand Down
46 changes: 44 additions & 2 deletions src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,41 @@ public virtual InternalPropertyBuilder Property([NotNull] MemberInfo memberInfo,
&& (canOverrideSameSource || (configurationSource != currentConfigurationSource));
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool CanAddProperty(Type propertyType, string name,ConfigurationSource? typeConfigurationSource)
{
var conflictingMember = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault();
if (conflictingMember != null
&& conflictingMember.IsShadowProperty()
&& conflictingMember.ClrType != propertyType
&& typeConfigurationSource != null
&& !typeConfigurationSource.Overrides(conflictingMember.GetTypeConfigurationSource()))
{
return false;
}

if (Metadata.ClrType == null)
{
return true;
}

var memberInfo = Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault();
if (memberInfo != null
&& propertyType != memberInfo.GetMemberType()
&& (memberInfo as PropertyInfo)?.IsEFIndexerProperty() != true
&& typeConfigurationSource != null)
{
return false;
}

return true;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -3415,11 +3450,18 @@ private void RemoveUnusedDiscriminatorProperty(Property newDiscriminatorProperty
string name,
Type discriminatorType,
bool fromDataAnnotation)
=> (name == null && discriminatorType == null)
=> ((name == null && discriminatorType == null)
|| ((name == null || discriminatorProperty?.Name == name)
&& (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType))
|| (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource());
.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource()))
&& (discriminatorProperty != null
|| Metadata.RootType().Builder.CanAddProperty(
discriminatorType ?? _defaultDiscriminatorType,
name ?? _defaultDiscriminatorName,
typeConfigurationSource: discriminatorType != null
? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention
: (ConfigurationSource?)null));

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ await using (var testDatabase = CreateTestStore(seed: false))
var addressJson = existingAddressEntry.Property<JObject>("__jObject").CurrentValue;

Assert.Equal("Second", addressJson[nameof(Address.Street)]);
Assert.Equal(4, addressJson.Count);
Assert.Equal(3, addressJson.Count);
Assert.Equal(2, addressJson["unmappedId"]);

addresses = people[2].Addresses.ToList();
Expand Down

0 comments on commit f44ba0d

Please sign in to comment.