From bac5b2d111a4d47d9ad8bed5a765b52472c44cdf Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 13 Aug 2020 11:44:17 -0700 Subject: [PATCH] Fix an OwnsOne overload not passing through the type. Implement IEquatable on TypeIdentity and MemberIdentity Fixes #21838 --- .../Metadata/Builders/EntityTypeBuilder`.cs | 22 ++++++------- .../Internal/InternalForeignKeyBuilder.cs | 4 +++ src/EFCore/Metadata/Internal/TypeIdentity.cs | 33 ++++++++++++++++++- src/EFCore/Metadata/MemberIdentity.cs | 32 +++++++++++++++++- .../ModelBuilding/OwnedTypesTestBase.cs | 10 ++++-- 5 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 58e8bc0e25d..04ea567d391 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -370,17 +370,17 @@ public virtual OwnedNavigationBuilder OwnsOne /// /// The entity type that this relationship targets. - /// The name of the target entity. + /// The name of the entity type that this relationship targets. /// /// The name of the reference navigation property on this entity type that represents the relationship. /// /// An object that can be used to configure the owned type and the relationship. public virtual OwnedNavigationBuilder OwnsOne( - [NotNull] string entityTypeName, + [NotNull] string ownedTypeName, [NotNull] string navigationName) where TRelatedEntity : class => OwnsOneBuilder( - new TypeIdentity(Check.NotEmpty(entityTypeName, nameof(entityTypeName)), typeof(TRelatedEntity)), + new TypeIdentity(Check.NotEmpty(ownedTypeName, nameof(ownedTypeName)), typeof(TRelatedEntity)), new MemberIdentity(Check.NotEmpty(navigationName, nameof(navigationName)))); /// @@ -431,18 +431,18 @@ public virtual OwnedNavigationBuilder OwnsOne /// /// The entity type that this relationship targets. - /// The name of the target entity. + /// The name of the entity type that this relationship targets. /// /// A lambda expression representing the reference navigation property on this entity type that represents /// the relationship (customer => customer.Address). /// /// An object that can be used to configure the owned type and the relationship. public virtual OwnedNavigationBuilder OwnsOne( - [NotNull] string entityTypeName, + [NotNull] string ownedTypeName, [NotNull] Expression> navigationExpression) where TRelatedEntity : class => OwnsOneBuilder( - new TypeIdentity(Check.NotEmpty(entityTypeName, nameof(entityTypeName)), typeof(TRelatedEntity)), + new TypeIdentity(Check.NotEmpty(ownedTypeName, nameof(ownedTypeName)), typeof(TRelatedEntity)), new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// @@ -682,7 +682,7 @@ public virtual EntityTypeBuilder OwnsOne( Check.NotNull(buildAction, nameof(buildAction)); buildAction(OwnsOneBuilder( - new TypeIdentity(ownedTypeName), new MemberIdentity(navigationExpression.GetMemberAccess()))); + new TypeIdentity(ownedTypeName, typeof(TRelatedEntity)), new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } @@ -1045,7 +1045,7 @@ public virtual EntityTypeBuilder OwnsMany( /// /// /// The entity type that this relationship targets. - /// The name of the target entity. + /// The name of the entity type that this relationship targets. /// /// A lambda expression representing the reference navigation property on this entity type that represents /// the relationship (customer => customer.Address). @@ -1053,17 +1053,17 @@ public virtual EntityTypeBuilder OwnsMany( /// An action that performs configuration of the owned type and the relationship. /// An object that can be used to configure the entity type. public virtual EntityTypeBuilder OwnsMany( - [NotNull] string entityTypeName, + [NotNull] string ownedTypeName, [NotNull] Expression>> navigationExpression, [NotNull] Action> buildAction) where TRelatedEntity : class { - Check.NotEmpty(entityTypeName, nameof(entityTypeName)); + Check.NotEmpty(ownedTypeName, nameof(ownedTypeName)); Check.NotNull(navigationExpression, nameof(navigationExpression)); Check.NotNull(buildAction, nameof(buildAction)); buildAction(OwnsManyBuilder( - new TypeIdentity(entityTypeName, typeof(TRelatedEntity)), new MemberIdentity(navigationExpression.GetMemberAccess()))); + new TypeIdentity(ownedTypeName, typeof(TRelatedEntity)), new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index 7bf060a8d52..f20f358e7f0 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -746,6 +746,8 @@ private bool CanSetNavigations( } if (navigationToPrincipalProperty != null + && dependentEntityType.ClrType != null + && principalEntityType.ClrType != null && !IsCompatible( navigationToPrincipalProperty, pointsToPrincipal: true, @@ -763,6 +765,8 @@ private bool CanSetNavigations( } if (navigationToDependentProperty != null + && dependentEntityType.ClrType != null + && principalEntityType.ClrType != null && !IsCompatible( navigationToDependentProperty, pointsToPrincipal: false, diff --git a/src/EFCore/Metadata/Internal/TypeIdentity.cs b/src/EFCore/Metadata/Internal/TypeIdentity.cs index abcfa12fcff..33ca3288e29 100644 --- a/src/EFCore/Metadata/Internal/TypeIdentity.cs +++ b/src/EFCore/Metadata/Internal/TypeIdentity.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using JetBrains.Annotations; @@ -14,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerDisplay("{DebuggerDisplay(),nq}")] - public readonly struct TypeIdentity + public readonly struct TypeIdentity : IEquatable { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -83,5 +84,35 @@ public TypeIdentity([NotNull] Type type, [NotNull] Model model) public bool IsNamed { [DebuggerStepThrough] get; } private string DebuggerDisplay() => Name; + + /// + public override bool Equals(object obj) => obj is TypeIdentity identity && Equals(identity); + + /// + public bool Equals(TypeIdentity other) + => Name == other.Name + && EqualityComparer.Default.Equals(Type, other.Type) + && IsNamed == other.IsNamed; + + /// + public override int GetHashCode() => HashCode.Combine(Name, Type, IsNamed); + + /// + /// Compares one id to another id to see if they represent the same type. + /// + /// The first id. + /// The second id. + /// if they represent the same type; otherwise. + public static bool operator ==(TypeIdentity left, TypeIdentity right) + => left.Equals(right); + + /// + /// Compares one id to another id to see if they represent different types. + /// + /// The first id. + /// The second id. + /// if they represent different types; otherwise. + public static bool operator !=(TypeIdentity left, TypeIdentity right) + => !(left == right); } } diff --git a/src/EFCore/Metadata/MemberIdentity.cs b/src/EFCore/Metadata/MemberIdentity.cs index 1b899d12f30..03b9d034415 100644 --- a/src/EFCore/Metadata/MemberIdentity.cs +++ b/src/EFCore/Metadata/MemberIdentity.cs @@ -1,6 +1,8 @@ // 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.Generic; using System.Diagnostics; using System.Reflection; using JetBrains.Annotations; @@ -11,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// Represents the identity of an entity type member, can be based on or just the name. /// [DebuggerDisplay("{DebuggerDisplay(),nq}")] - public readonly struct MemberIdentity + public readonly struct MemberIdentity : IEquatable { private readonly object _nameOrMember; @@ -88,5 +90,33 @@ public MemberInfo MemberInfo private string DebuggerDisplay() => Name ?? "NONE"; + + /// + public override bool Equals(object obj) => obj is MemberIdentity identity && Equals(identity); + + /// + public bool Equals(MemberIdentity other) + => EqualityComparer.Default.Equals(_nameOrMember, other._nameOrMember); + + /// + public override int GetHashCode() => HashCode.Combine(_nameOrMember); + + /// + /// Compares one id to another id to see if they represent the same member. + /// + /// The first id. + /// The second id. + /// if they represent the same member; otherwise. + public static bool operator ==(MemberIdentity left, MemberIdentity right) + => left.Equals(right); + + /// + /// Compares one id to another id to see if they represent different members. + /// + /// The first id. + /// The second id. + /// if they represent different members; otherwise. + public static bool operator !=(MemberIdentity left, MemberIdentity right) + => !(left == right); } } diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 7661cfe8c74..a1b9616cffd 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -1521,12 +1521,18 @@ public virtual void Shared_type_can_be_used_as_owned_type() modelBuilder.Entity( b => { - b.OwnsOne>("Shared1", e => e.Reference).IndexerProperty("Value"); + b.OwnsOne>("Shared1", e => e.Reference, sb => + { + sb.IndexerProperty("Value"); + }); b.OwnsMany("Shared2", e => e.Collection).IndexerProperty("IsDeleted"); b.OwnsOne(e => e.OwnedNavigation, o => { - o.OwnsOne>("Shared3", e => e.Reference).IndexerProperty("NestedValue"); + o.OwnsOne>("Shared3", e => e.Reference, sb => + { + sb.IndexerProperty("NestedValue"); + }); o.OwnsMany("Shared4", e => e.Collection).IndexerProperty("NestedLong"); }); });