diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs index 131d5d45b91..0c181f15b4e 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs @@ -209,7 +209,7 @@ public static EntityTypeBuilder HasPartitionKey( { Check.NotNull(propertyExpression, nameof(propertyExpression)); - entityTypeBuilder.Metadata.SetPartitionKeyPropertyName(propertyExpression.GetPropertyAccess().GetSimpleMemberName()); + entityTypeBuilder.Metadata.SetPartitionKeyPropertyName(propertyExpression.GetMemberAccess().GetSimpleMemberName()); return entityTypeBuilder; } diff --git a/src/EFCore.Relational/Migrations/MigrationBuilder.cs b/src/EFCore.Relational/Migrations/MigrationBuilder.cs index 34fc0362777..7b16999e236 100644 --- a/src/EFCore.Relational/Migrations/MigrationBuilder.cs +++ b/src/EFCore.Relational/Migrations/MigrationBuilder.cs @@ -755,7 +755,7 @@ public virtual CreateTableBuilder CreateTable( var columnsBuilder = new ColumnsBuilder(createTableOperation); var columnsObject = columns(columnsBuilder); - var columnMap = new Dictionary(); + var columnMap = new Dictionary(); foreach (var property in typeof(TColumns).GetTypeInfo().DeclaredProperties) { var addColumnOperation = ((IInfrastructure)property.GetMethod.Invoke(columnsObject, null)).Instance; diff --git a/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs b/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs index 47ff13ea456..a224ce12af0 100644 --- a/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs +++ b/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Operations.Builders /// Type of a typically anonymous type for building columns. public class CreateTableBuilder : OperationBuilder { - private readonly IReadOnlyDictionary _columnMap; + private readonly IReadOnlyDictionary _columnMap; /// /// Constructs a new builder for the given and @@ -28,7 +28,7 @@ public class CreateTableBuilder : OperationBuilder The map of CLR properties to s. public CreateTableBuilder( [NotNull] CreateTableOperation operation, - [NotNull] IReadOnlyDictionary columnMap) + [NotNull] IReadOnlyDictionary columnMap) : base(operation) { Check.NotNull(columnMap, nameof(columnMap)); @@ -191,6 +191,6 @@ public virtual OperationBuilder CheckConstraint( => (CreateTableBuilder)base.Annotation(name, value); private string[] Map(LambdaExpression columns) - => columns.GetPropertyAccessList().Select(c => _columnMap[c].Name).ToArray(); + => columns.GetMemberAccessList().Select(c => _columnMap[c].Name).ToArray(); } } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs index ac986c35c13..dc89d908c14 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs @@ -126,7 +126,7 @@ public static IndexBuilder IncludeProperties( IncludeProperties( indexBuilder, - includeExpression.GetPropertyAccessList().Select(MemberInfoExtensions.GetSimpleMemberName).ToArray()); + includeExpression.GetMemberAccessList().Select(MemberInfoExtensions.GetSimpleMemberName).ToArray()); return indexBuilder; } diff --git a/src/EFCore/ChangeTracking/EntityEntry`.cs b/src/EFCore/ChangeTracking/EntityEntry`.cs index ef0cd954b8a..050e3a09b72 100644 --- a/src/EFCore/ChangeTracking/EntityEntry`.cs +++ b/src/EFCore/ChangeTracking/EntityEntry`.cs @@ -58,7 +58,7 @@ public virtual PropertyEntry Property( { Check.NotNull(propertyExpression, nameof(propertyExpression)); - return new PropertyEntry(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName()); + return new PropertyEntry(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName()); } /// @@ -79,7 +79,7 @@ public virtual ReferenceEntry Reference( { Check.NotNull(propertyExpression, nameof(propertyExpression)); - return new ReferenceEntry(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName()); + return new ReferenceEntry(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName()); } /// @@ -100,7 +100,7 @@ public virtual CollectionEntry Collection( { Check.NotNull(propertyExpression, nameof(propertyExpression)); - return new CollectionEntry(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName()); + return new CollectionEntry(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName()); } /// diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs index 089d60a728b..94fc2c4b6b1 100644 --- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs +++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs @@ -19,7 +19,6 @@ namespace Microsoft.EntityFrameworkCore.Internal /// 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. /// - [DebuggerStepThrough] public static class ExpressionExtensions { /// @@ -38,28 +37,30 @@ public static bool IsNullConstantExpression([NotNull] this Expression expression /// 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. /// - public static IReadOnlyList MatchPropertyAccessList( - [NotNull] this LambdaExpression lambdaExpression, [NotNull] Func propertyMatcher) + public static IReadOnlyList MatchMemberAccessList( + [NotNull] this LambdaExpression lambdaExpression, [NotNull] Func memberMatcher) + where TMemberInfo : MemberInfo { Check.DebugAssert(lambdaExpression.Body != null, "lambdaExpression.Body is null"); + Check.DebugAssert(lambdaExpression.Parameters.Count == 1, "lambdaExpression.Parameters.Count is " + lambdaExpression.Parameters.Count + ". Should be 1."); - var parameterExpression = lambdaExpression.Parameters.Single(); + var parameterExpression = lambdaExpression.Parameters[0]; if (RemoveConvert(lambdaExpression.Body) is NewExpression newExpression) { - var propertyInfos + var memberInfos = newExpression .Arguments - .Select(a => propertyMatcher(a, parameterExpression)) + .Select(a => memberMatcher(a, parameterExpression)) .Where(p => p != null) .ToList(); - return propertyInfos.Count != newExpression.Arguments.Count ? null : propertyInfos; + return memberInfos.Count != newExpression.Arguments.Count ? null : memberInfos; } - var propertyPath = propertyMatcher(lambdaExpression.Body, parameterExpression); + var memberPath = memberMatcher(lambdaExpression.Body, parameterExpression); - return propertyPath != null ? new[] { propertyPath } : null; + return memberPath != null ? new[] { memberPath } : null; } /// @@ -68,37 +69,39 @@ var propertyInfos /// 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. /// - public static PropertyInfo MatchSimplePropertyAccess( - [NotNull] this Expression parameterExpression, [NotNull] Expression propertyAccessExpression) + public static TMemberInfo MatchSimpleMemberAccess( + [NotNull] this Expression parameterExpression, [NotNull] Expression memberAccessExpression) + where TMemberInfo : MemberInfo { - var propertyInfos = MatchPropertyAccess(parameterExpression, propertyAccessExpression); + var memberInfos = MatchMemberAccess(parameterExpression, memberAccessExpression); - return propertyInfos?.Count == 1 ? propertyInfos[0] : null; + return memberInfos?.Count == 1 ? memberInfos[0] as TMemberInfo : null; } - private static IReadOnlyList MatchPropertyAccess( - this Expression parameterExpression, Expression propertyAccessExpression) + private static IReadOnlyList MatchMemberAccess( + this Expression parameterExpression, Expression memberAccessExpression) + where TMemberInfo : MemberInfo { - var propertyInfos = new List(); + var memberInfos = new List(); MemberExpression memberExpression; do { - memberExpression = RemoveTypeAs(RemoveConvert(propertyAccessExpression)) as MemberExpression; + memberExpression = RemoveTypeAs(RemoveConvert(memberAccessExpression)) as MemberExpression; - if (!(memberExpression?.Member is PropertyInfo propertyInfo)) + if (!(memberExpression?.Member is TMemberInfo memberInfo)) { return null; } - propertyInfos.Insert(0, propertyInfo); + memberInfos.Insert(0, memberInfo); - propertyAccessExpression = memberExpression.Expression; + memberAccessExpression = memberExpression.Expression; } while (RemoveTypeAs(RemoveConvert(memberExpression.Expression)) != parameterExpression); - return propertyInfos; + return memberInfos; } /// diff --git a/src/EFCore/Infrastructure/ExpressionExtensions.cs b/src/EFCore/Infrastructure/ExpressionExtensions.cs index eb98e98bd29..95492c715ba 100644 --- a/src/EFCore/Infrastructure/ExpressionExtensions.cs +++ b/src/EFCore/Infrastructure/ExpressionExtensions.cs @@ -149,28 +149,46 @@ public static bool TryGetIndexerArguments( /// The expression. /// The . public static PropertyInfo GetPropertyAccess([NotNull] this LambdaExpression propertyAccessExpression) + => GetInternalMemberAccess(propertyAccessExpression); + + /// + /// + /// Gets the represented by a simple member-access expression. + /// + /// + /// This method is typically used to parse member access lambdas from fluent APIs. + /// + /// + /// The expression. + /// The . + public static MemberInfo GetMemberAccess([NotNull] this LambdaExpression memberAccessExpression) + => GetInternalMemberAccess(memberAccessExpression); + + private static TMemberInfo GetInternalMemberAccess([NotNull] this LambdaExpression memberAccessExpression) + where TMemberInfo : MemberInfo { Check.DebugAssert( - propertyAccessExpression.Parameters.Count == 1, - $"Parameters.Count is {propertyAccessExpression.Parameters.Count}"); + memberAccessExpression.Parameters.Count == 1, + $"Parameters.Count is {memberAccessExpression.Parameters.Count}"); - var parameterExpression = propertyAccessExpression.Parameters.Single(); - var propertyInfo = parameterExpression.MatchSimplePropertyAccess(propertyAccessExpression.Body); + var parameterExpression = memberAccessExpression.Parameters[0]; + var memberInfo = parameterExpression.MatchSimpleMemberAccess(memberAccessExpression.Body); - if (propertyInfo == null) + if (memberInfo == null) { throw new ArgumentException( - CoreStrings.InvalidPropertyExpression(propertyAccessExpression), - nameof(propertyAccessExpression)); + CoreStrings.InvalidMemberExpression(memberAccessExpression), + nameof(memberAccessExpression)); } - var declaringType = propertyInfo.DeclaringType; + var declaringType = memberInfo.DeclaringType; var parameterType = parameterExpression.Type; if (declaringType != null && declaringType != parameterType && declaringType.IsInterface - && declaringType.IsAssignableFrom(parameterType)) + && declaringType.IsAssignableFrom(parameterType) + && memberInfo is PropertyInfo propertyInfo) { var propertyGetter = propertyInfo.GetMethod; var interfaceMapping = parameterType.GetTypeInfo().GetRuntimeInterfaceMap(declaringType); @@ -180,12 +198,12 @@ public static PropertyInfo GetPropertyAccess([NotNull] this LambdaExpression pro { if (targetMethod.Equals(runtimeProperty.GetMethod)) { - return runtimeProperty; + return runtimeProperty as TMemberInfo; } } } - return propertyInfo; + return memberInfo; } /// @@ -195,8 +213,6 @@ public static PropertyInfo GetPropertyAccess([NotNull] this LambdaExpression pro /// /// /// Only simple expressions are supported, such as those used to reference a property. - /// This type is typically used by database providers (and other extensions). It is generally - /// not used in application code. /// /// /// This method is typically used by database providers (and other extensions). It is generally @@ -212,23 +228,55 @@ public static IReadOnlyList GetPropertyAccessList([NotNull] this L if (propertyAccessExpression.Parameters.Count != 1) { throw new ArgumentException( - CoreStrings.InvalidPropertiesExpression(propertyAccessExpression), + CoreStrings.InvalidMembersExpression(propertyAccessExpression), nameof(propertyAccessExpression)); } - var propertyPaths - = propertyAccessExpression.MatchPropertyAccessList((p, e) => e.MatchSimplePropertyAccess(p)); + var propertyPaths = propertyAccessExpression + .MatchMemberAccessList((p, e) => e.MatchSimpleMemberAccess(p)); if (propertyPaths == null) { throw new ArgumentException( - CoreStrings.InvalidPropertiesExpression(propertyAccessExpression), + CoreStrings.InvalidMembersExpression(propertyAccessExpression), nameof(propertyAccessExpression)); } return propertyPaths; } + /// + /// + /// Returns a list of extracted from the given simple + /// . + /// + /// + /// Only simple expressions are supported, such as those used to reference a member. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The expression. + /// The list of referenced members. + public static IReadOnlyList GetMemberAccessList([NotNull] this LambdaExpression memberAccessExpression) + { + Check.NotNull(memberAccessExpression, nameof(memberAccessExpression)); + + var memberPaths = memberAccessExpression + .MatchMemberAccessList((p, e) => e.MatchSimpleMemberAccess(p)); + + if (memberPaths == null) + { + throw new ArgumentException( + CoreStrings.InvalidMembersExpression(memberAccessExpression), + nameof(memberAccessExpression)); + } + + return memberPaths; + } + /// /// /// Creates an tree representing reading a value from a diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs index 0ba284d99fb..c5abc27f5a6 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs @@ -83,7 +83,7 @@ public virtual ReferenceCollectionBuilder WithOne( => new ReferenceCollectionBuilder( DeclaringEntityType, RelatedEntityType, - WithOneBuilder(navigationExpression?.GetPropertyAccess()).Metadata); + WithOneBuilder(navigationExpression?.GetMemberAccess()).Metadata); /// /// Configures this as a many-to-many relationship. @@ -129,8 +129,8 @@ public virtual CollectionCollectionBuilder WithMany( return new CollectionCollectionBuilder( RelatedEntityType, DeclaringEntityType, - WithLeftManyNavigation(navigationExpression.GetPropertyAccess()), - WithRightManyNavigation(navigationExpression.GetPropertyAccess(), leftName)); + WithLeftManyNavigation(navigationExpression.GetMemberAccess()), + WithRightManyNavigation(navigationExpression.GetMemberAccess(), leftName)); } } } diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 181fcab044c..9026956f659 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -88,7 +88,7 @@ public virtual EntityTypeBuilder HasBaseType() public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) => new KeyBuilder( Builder.PrimaryKey( - Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), + Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList(), ConfigurationSource.Explicit).Metadata); /// @@ -120,7 +120,7 @@ public virtual KeyBuilder HasKey([NotNull] Expression> key public virtual KeyBuilder HasAlternateKey([NotNull] Expression> keyExpression) => new KeyBuilder( Builder.HasKey( - Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), + Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList(), ConfigurationSource.Explicit).Metadata); /// @@ -155,7 +155,7 @@ public virtual KeyBuilder HasAlternateKey([NotNull] Expression Property([NotNull] Expression> propertyExpression) => new PropertyBuilder( Builder.Property( - Check.NotNull(propertyExpression, nameof(propertyExpression)).GetPropertyAccess(), ConfigurationSource.Explicit) + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit) .Metadata); /// @@ -170,7 +170,7 @@ public virtual PropertyBuilder Property([NotNull] Expressi public virtual NavigationBuilder Navigation([NotNull] Expression> navigationExpression) => new NavigationBuilder( Builder.Navigation( - Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// Excludes the given property from the entity type. This method is typically used to remove properties @@ -182,7 +182,7 @@ public virtual NavigationBuilder Navigation([NotNull] Expression public virtual EntityTypeBuilder Ignore([NotNull] Expression> propertyExpression) => (EntityTypeBuilder)base.Ignore( - Check.NotNull(propertyExpression, nameof(propertyExpression)).GetPropertyAccess().GetSimpleMemberName()); + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess().GetSimpleMemberName()); /// /// Excludes the given property from the entity type. This method is typically used to remove properties @@ -233,7 +233,7 @@ public virtual EntityTypeBuilder ToQuery([NotNull] Expression HasIndex([NotNull] Expression> indexExpression) => new IndexBuilder( Builder.HasIndex( - Check.NotNull(indexExpression, nameof(indexExpression)).GetPropertyAccessList(), + Check.NotNull(indexExpression, nameof(indexExpression)).GetMemberAccessList(), ConfigurationSource.Explicit).Metadata); /// @@ -302,7 +302,7 @@ public virtual OwnedNavigationBuilder OwnsOne> navigationExpression) where TRelatedEntity : class => OwnsOneBuilder( - new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// @@ -371,7 +371,7 @@ public virtual EntityTypeBuilder OwnsOne( Check.NotNull(navigationExpression, nameof(navigationExpression)); Check.NotNull(buildAction, nameof(buildAction)); - buildAction.Invoke(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetPropertyAccess()))); + buildAction.Invoke(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } @@ -383,7 +383,7 @@ private OwnedNavigationBuilder OwnsOneBuilder OwnsMany>> navigationExpression) where TRelatedEntity : class => OwnsManyBuilder( - new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// @@ -517,7 +517,7 @@ public virtual EntityTypeBuilder OwnsMany( Check.NotNull(navigationExpression, nameof(navigationExpression)); Check.NotNull(buildAction, nameof(buildAction)); - buildAction.Invoke(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetPropertyAccess()))); + buildAction.Invoke(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } @@ -613,7 +613,7 @@ public virtual ReferenceNavigationBuilder HasOne> navigationExpression = null) where TRelatedEntity : class { - var navigationMember = navigationExpression?.GetPropertyAccess(); + var navigationMember = navigationExpression?.GetMemberAccess(); var relatedEntityType = FindRelatedEntityType(typeof(TRelatedEntity), navigationMember?.GetSimpleMemberName()); var foreignKey = HasOneBuilder( MemberIdentity.Create(navigationMember), relatedEntityType); @@ -704,7 +704,7 @@ public virtual CollectionNavigationBuilder HasMany>> navigationExpression = null) where TRelatedEntity : class { - var navigationMember = navigationExpression?.GetPropertyAccess(); + var navigationMember = navigationExpression?.GetMemberAccess(); var relatedEntityType = FindRelatedEntityType(typeof(TRelatedEntity), navigationMember?.GetSimpleMemberName()); var skipNavigation = navigationMember != null ? Builder.Metadata.FindSkipNavigation(navigationMember) : null; @@ -812,7 +812,7 @@ public virtual DiscriminatorBuilder HasDiscriminator( - Builder.HasDiscriminator(propertyExpression.GetPropertyAccess(), ConfigurationSource.Explicit)); + Builder.HasDiscriminator(propertyExpression.GetMemberAccess(), ConfigurationSource.Explicit)); } /// diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index 42b0b024f03..33b4177954a 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -62,7 +62,7 @@ public OwnedNavigationBuilder( public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) => new KeyBuilder( DependentEntityType.Builder.PrimaryKey( - Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), ConfigurationSource.Explicit).Metadata); + Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList(), ConfigurationSource.Explicit).Metadata); /// /// Sets the properties that make up the primary key for this owned entity type. @@ -98,7 +98,7 @@ public virtual PropertyBuilder Property( => UpdateBuilder( () => new PropertyBuilder( DependentEntityType.Builder.Property( - Check.NotNull(propertyExpression, nameof(propertyExpression)).GetPropertyAccess(), + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit).Metadata)); /// @@ -116,7 +116,7 @@ public virtual PropertyBuilder Property( public virtual NavigationBuilder Navigation( [NotNull] Expression> navigationExpression) => new NavigationBuilder(DependentEntityType.Builder.Navigation( - Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// Excludes the given property from the entity type. This method is typically used to remove properties @@ -139,7 +139,7 @@ public virtual OwnedNavigationBuilder Ignore( => (OwnedNavigationBuilder) base.Ignore( Check.NotNull(propertyExpression, nameof(propertyExpression)) - .GetPropertyAccess().GetSimpleMemberName()); + .GetMemberAccess().GetSimpleMemberName()); /// /// Configures an index on the specified properties. If there is an existing index on the given @@ -159,7 +159,7 @@ public virtual OwnedNavigationBuilder Ignore( public virtual IndexBuilder HasIndex([NotNull] Expression> indexExpression) => new IndexBuilder( DependentEntityType.Builder.HasIndex( - Check.NotNull(indexExpression, nameof(indexExpression)).GetPropertyAccessList(), ConfigurationSource.Explicit) + Check.NotNull(indexExpression, nameof(indexExpression)).GetMemberAccessList(), ConfigurationSource.Explicit) .Metadata); /// @@ -224,7 +224,7 @@ public virtual OwnershipBuilder WithOwner( PrincipalEntityType, DependentEntityType, Builder.HasNavigation( - referenceExpression?.GetPropertyAccess(), + referenceExpression?.GetMemberAccess(), pointsToPrincipal: true, ConfigurationSource.Explicit).Metadata); @@ -285,7 +285,7 @@ public virtual OwnedNavigationBuilder Own where TNewDependentEntity : class => OwnsOneBuilder( new MemberIdentity( - Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// @@ -356,7 +356,7 @@ public virtual OwnedNavigationBuilder OwnsOne(new MemberIdentity(navigationExpression.GetPropertyAccess()))); + buildAction.Invoke(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } @@ -435,7 +435,7 @@ public virtual OwnedNavigationBuilder Own [NotNull] Expression>> navigationExpression) where TNewDependentEntity : class => OwnsManyBuilder( - new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess())); /// /// @@ -509,7 +509,7 @@ public virtual OwnedNavigationBuilder OwnsMany(new MemberIdentity(navigationExpression.GetPropertyAccess()))); + buildAction.Invoke(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetMemberAccess()))); return this; } } @@ -607,7 +607,7 @@ public virtual ReferenceNavigationBuilder H [CanBeNull] Expression> navigationExpression = null) where TNewRelatedEntity : class { - var navigation = navigationExpression?.GetPropertyAccess(); + var navigation = navigationExpression?.GetMemberAccess(); var relatedEntityType = FindRelatedEntityType(typeof(TNewRelatedEntity), navigation?.GetSimpleMemberName()); return new ReferenceNavigationBuilder( diff --git a/src/EFCore/Metadata/Builders/OwnershipBuilder`.cs b/src/EFCore/Metadata/Builders/OwnershipBuilder`.cs index 57746ac5635..75c86ea0d29 100644 --- a/src/EFCore/Metadata/Builders/OwnershipBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnershipBuilder`.cs @@ -131,7 +131,7 @@ public virtual OwnershipBuilder HasForeignKey( [NotNull] Expression> foreignKeyExpression) { Builder = Builder.HasForeignKey( - Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetPropertyAccessList(), + Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetMemberAccessList(), (EntityType)DependentEntityType, ConfigurationSource.Explicit); return new OwnershipBuilder( @@ -181,7 +181,7 @@ public virtual OwnershipBuilder HasPrincipalKey( [NotNull] Expression> keyExpression) { Builder = Builder.HasPrincipalKey( - Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), + Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList(), ConfigurationSource.Explicit); return new OwnershipBuilder( Builder, diff --git a/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder.cs b/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder.cs index 62e61a324f0..9df354290d1 100644 --- a/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder.cs @@ -117,8 +117,8 @@ protected virtual InternalForeignKeyBuilder HasForeignKeyBuilder([NotNull] IRead /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual InternalForeignKeyBuilder HasForeignKeyBuilder([NotNull] IReadOnlyList foreignKeyProperties) - => Builder.HasForeignKey(foreignKeyProperties, (EntityType)DependentEntityType, ConfigurationSource.Explicit); + protected virtual InternalForeignKeyBuilder HasForeignKeyBuilder([NotNull] IReadOnlyList foreignKeyMembers) + => Builder.HasForeignKey(foreignKeyMembers, (EntityType)DependentEntityType, ConfigurationSource.Explicit); /// /// Configures the unique property(s) that this relationship targets. Typically you would only call this @@ -151,8 +151,8 @@ protected virtual InternalForeignKeyBuilder HasPrincipalKeyBuilder([NotNull] IRe /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual InternalForeignKeyBuilder HasPrincipalKeyBuilder([NotNull] IReadOnlyList keyProperties) - => Builder.HasPrincipalKey(keyProperties, ConfigurationSource.Explicit); + protected virtual InternalForeignKeyBuilder HasPrincipalKeyBuilder([NotNull] IReadOnlyList keyMembers) + => Builder.HasPrincipalKey(keyMembers, ConfigurationSource.Explicit); /// /// Configures whether this is a required relationship (i.e. whether the foreign key property(s) can diff --git a/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder`.cs index e4138e1365a..12c52a06eea 100644 --- a/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ReferenceCollectionBuilder`.cs @@ -115,7 +115,7 @@ protected ReferenceCollectionBuilder( public virtual ReferenceCollectionBuilder HasForeignKey( [NotNull] Expression> foreignKeyExpression) => new ReferenceCollectionBuilder( - HasForeignKeyBuilder(Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetPropertyAccessList()), + HasForeignKeyBuilder(Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetMemberAccessList()), this, foreignKeySet: true); @@ -154,7 +154,7 @@ public virtual ReferenceCollectionBuilder Ha public virtual ReferenceCollectionBuilder HasPrincipalKey( [NotNull] Expression> keyExpression) => new ReferenceCollectionBuilder( - HasPrincipalKeyBuilder(Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList()), + HasPrincipalKeyBuilder(Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList()), this, principalKeySet: true); diff --git a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder`.cs index 24a6f9fb1ea..6a081e3d27c 100644 --- a/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ReferenceNavigationBuilder`.cs @@ -106,7 +106,7 @@ public virtual ReferenceCollectionBuilder WithMany( return new ReferenceCollectionBuilder( RelatedEntityType, DeclaringEntityType, - WithManyBuilder(navigationExpression?.GetPropertyAccess()).Metadata); + WithManyBuilder(navigationExpression?.GetMemberAccess()).Metadata); } /// @@ -153,6 +153,6 @@ public virtual ReferenceReferenceBuilder WithOne( => new ReferenceReferenceBuilder( DeclaringEntityType, RelatedEntityType, - WithOneBuilder(navigationExpression?.GetPropertyAccess()).Metadata); + WithOneBuilder(navigationExpression?.GetMemberAccess()).Metadata); } } diff --git a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs index 2c3bed65358..f1a9c081aa2 100644 --- a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs +++ b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs @@ -172,10 +172,10 @@ protected virtual InternalForeignKeyBuilder HasForeignKeyBuilder( protected virtual InternalForeignKeyBuilder HasForeignKeyBuilder( [NotNull] EntityType dependentEntityType, [NotNull] string dependentEntityTypeName, - [NotNull] IReadOnlyList foreignKeyProperties) + [NotNull] IReadOnlyList foreignKeyMembers) => HasForeignKeyBuilder( dependentEntityType, dependentEntityTypeName, - (b, d) => b.HasForeignKey(foreignKeyProperties, d, ConfigurationSource.Explicit)); + (b, d) => b.HasForeignKey(foreignKeyMembers, d, ConfigurationSource.Explicit)); private InternalForeignKeyBuilder HasForeignKeyBuilder( EntityType dependentEntityType, @@ -282,10 +282,10 @@ protected virtual InternalForeignKeyBuilder HasPrincipalKeyBuilder( protected virtual InternalForeignKeyBuilder HasPrincipalKeyBuilder( [NotNull] EntityType principalEntityType, [NotNull] string principalEntityTypeName, - [NotNull] IReadOnlyList foreignKeyProperties) + [NotNull] IReadOnlyList foreignKeyMembers) => HasPrincipalKeyBuilder( principalEntityType, principalEntityTypeName, - b => b.HasPrincipalKey(foreignKeyProperties, ConfigurationSource.Explicit)); + b => b.HasPrincipalKey(foreignKeyMembers, ConfigurationSource.Explicit)); private InternalForeignKeyBuilder HasPrincipalKeyBuilder( EntityType principalEntityType, diff --git a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder`.cs b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder`.cs index 24eb472e472..56c716c1545 100644 --- a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder`.cs @@ -211,7 +211,7 @@ public virtual ReferenceReferenceBuilder HasForeignKey< HasForeignKeyBuilder( ResolveEntityType(typeof(TDependentEntity)), typeof(TDependentEntity).ShortDisplayName(), - Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetPropertyAccessList()), + Check.NotNull(foreignKeyExpression, nameof(foreignKeyExpression)).GetMemberAccessList()), this, inverted: Builder.Metadata.DeclaringEntityType.ClrType != typeof(TDependentEntity), foreignKeySet: true); @@ -314,7 +314,7 @@ public virtual ReferenceReferenceBuilder HasPrincipalKe HasPrincipalKeyBuilder( ResolveEntityType(typeof(TPrincipalEntity)), typeof(TPrincipalEntity).ShortDisplayName(), - Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList()), + Check.NotNull(keyExpression, nameof(keyExpression)).GetMemberAccessList()), this, inverted: Builder.Metadata.PrincipalEntityType.ClrType != typeof(TPrincipalEntity), principalKeySet: true); diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 45d5a6d1305..830e3256782 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -173,8 +173,8 @@ public virtual InternalKeyBuilder HasKey([NotNull] IReadOnlyList propert /// 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. /// - public virtual InternalKeyBuilder HasKey([NotNull] IReadOnlyList clrProperties, ConfigurationSource configurationSource) - => HasKeyInternal(GetOrCreateProperties(clrProperties, configurationSource), configurationSource); + public virtual InternalKeyBuilder HasKey([NotNull] IReadOnlyList clrMembers, ConfigurationSource configurationSource) + => HasKeyInternal(GetOrCreateProperties(clrMembers, configurationSource), configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2475,12 +2475,12 @@ public virtual InternalForeignKeyBuilder HasRelationship( /// public virtual InternalForeignKeyBuilder HasRelationship( [NotNull] EntityType targetEntityType, - [CanBeNull] MemberInfo navigationProperty, + [CanBeNull] MemberInfo navigationMember, ConfigurationSource configurationSource, bool? targetIsPrincipal = null) => HasRelationship( Check.NotNull(targetEntityType, nameof(targetEntityType)), - MemberIdentity.Create(navigationProperty), + MemberIdentity.Create(navigationMember), null, targetIsPrincipal, configurationSource); @@ -2888,10 +2888,10 @@ public virtual InternalForeignKeyBuilder HasOwnership( /// public virtual InternalForeignKeyBuilder HasOwnership( [NotNull] Type targetEntityType, - [NotNull] MemberInfo navigationProperty, + [NotNull] MemberInfo navigationMember, ConfigurationSource configurationSource) => HasOwnership( - new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationProperty), + new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationMember), inverse: null, configurationSource); /// @@ -2919,13 +2919,13 @@ public virtual InternalForeignKeyBuilder HasOwnership( /// public virtual InternalForeignKeyBuilder HasOwnership( [NotNull] Type targetEntityType, - [NotNull] MemberInfo navigationProperty, - [CanBeNull] MemberInfo inverseProperty, + [NotNull] MemberInfo navigationMember, + [CanBeNull] MemberInfo inverseMember, ConfigurationSource configurationSource) => HasOwnership( new TypeIdentity(targetEntityType, Metadata.Model), - MemberIdentity.Create(navigationProperty), - MemberIdentity.Create(inverseProperty), + MemberIdentity.Create(navigationMember), + MemberIdentity.Create(inverseMember), configurationSource); private InternalForeignKeyBuilder HasOwnership( diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index f82b30abcc9..f227dd9df45 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -867,7 +867,7 @@ private bool CanRemoveNavigation(bool pointsToPrincipal, ConfigurationSource? co && (overrideSameSource || configurationSource != Metadata.GetPrincipalToDependentConfigurationSource())); private static bool IsCompatible( - [NotNull] MemberInfo navigationProperty, + [NotNull] MemberInfo navigationMember, bool pointsToPrincipal, [NotNull] Type dependentType, [NotNull] Type principalType, @@ -878,13 +878,13 @@ private static bool IsCompatible( if (!pointsToPrincipal) { var canBeUnique = Navigation.IsCompatible( - navigationProperty, + navigationMember, principalType, dependentType, shouldBeCollection: false, shouldThrow: false); var canBeNonUnique = Navigation.IsCompatible( - navigationProperty, + navigationMember, principalType, dependentType, shouldBeCollection: true, @@ -899,7 +899,7 @@ private static bool IsCompatible( if (shouldThrow) { Navigation.IsCompatible( - navigationProperty, + navigationMember, principalType, dependentType, shouldBeCollection: null, @@ -910,7 +910,7 @@ private static bool IsCompatible( } } else if (!Navigation.IsCompatible( - navigationProperty, + navigationMember, dependentType, principalType, shouldBeCollection: false, @@ -2061,10 +2061,10 @@ private bool CanSetForeignKey( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalForeignKeyBuilder HasPrincipalKey( - [NotNull] IReadOnlyList properties, + [NotNull] IReadOnlyList members, ConfigurationSource configurationSource) => HasPrincipalKey( - Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(properties, configurationSource), + Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(members, configurationSource), configurationSource); /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 18c71b162ac..2fd40222e8b 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -100,6 +100,22 @@ public static string InvalidPropertyExpression([CanBeNull] object expression) GetString("InvalidPropertyExpression", nameof(expression)), expression); + /// + /// The expression '{expression}' is not a valid members access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields use an anonymous type: 't => new {{ t.MyProperty, t.MyField }}'. + /// + public static string InvalidMembersExpression([CanBeNull] object expression) + => string.Format( + GetString("InvalidMembersExpression", nameof(expression)), + expression); + + /// + /// The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. + /// + public static string InvalidMemberExpression([CanBeNull] object expression) + => string.Format( + GetString("InvalidMemberExpression", nameof(expression)), + expression); + /// /// The instance of entity type '{entityType}' cannot be tracked because another instance with the same key value for {keyProperties} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index d1b642ca953..c9cbb00e898 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -143,9 +143,17 @@ The properties expression '{expression}' is not valid. The expression should represent a simple property access: 't => t.MyProperty'. When specifying multiple properties use an anonymous type: 't => new {{ t.MyProperty1, t.MyProperty2 }}'. + Obsolete The expression '{expression}' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'. + Obsolete + + + The expression '{expression}' is not a valid members access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields use an anonymous type: 't => new {{ t.MyProperty, t.MyField }}'. + + + The expression '{expression}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. The instance of entity type '{entityType}' cannot be tracked because another instance with the same key value for {keyProperties} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values. @@ -904,7 +912,7 @@ Invalid include path '{navigationChain}', couldn't find navigation for '{navigationName}'. - Error CoreEventId.InvalidIncludePathError object object + Error CoreEventId.InvalidIncludePathError object object The same entity is being tracked as different weak entity types '{dependent1}' and '{dependent2}'. If a property value changes it will result in two store changes, which might not be the desired outcome. diff --git a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs index 2c8b0e7c9ad..d8303fd0814 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs @@ -59,6 +59,69 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); } + [ConditionalFact] + public virtual void Finds_existing_navigations_and_uses_associated_FK_with_fields() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.Name); + e.HasKey(p => p.Id); + }); + modelBuilder.Entity(e => + { + e.Property(d => d.DependentWithFieldId); + e.Property(d => d.AnotherOneToManyPrincipalId); + e.Ignore(d => d.OneToManyPrincipal); + e.Ignore(d => d.OneToOnePrincipal); + e.HasKey(d => d.DependentWithFieldId); + }); + + modelBuilder.Entity() + .HasMany(p => p.Dependents) + .WithMany(d => d.ManyToManyPrincipals) + .UsingEntity( + jwf => jwf.HasOne(j => j.DependentWithField) + .WithMany(), + jwf => jwf.HasOne(j => j.ManyToManyPrincipalWithField) + .WithMany()) + .HasKey(j => new { j.DependentWithFieldId, j.ManyToManyPrincipalWithFieldId }); + + var principalEntityType = model.FindEntityType(typeof(ManyToManyPrincipalWithField)); + var dependentEntityType = model.FindEntityType(typeof(DependentWithField)); + var joinEntityType = model.FindEntityType(typeof(ManyToManyJoinWithFields)); + + var principalToJoinNav = principalEntityType.GetSkipNavigations().Single(); + var dependentToJoinNav = dependentEntityType.GetSkipNavigations().Single(); + + var principalToDependentFk = principalToJoinNav.ForeignKey; + var dependentToPrincipalFk = dependentToJoinNav.ForeignKey; + + Assert.Equal(2, joinEntityType.GetForeignKeys().Count()); + Assert.Same(principalToDependentFk, joinEntityType.GetForeignKeys().Last()); + Assert.Same(dependentToPrincipalFk, joinEntityType.GetForeignKeys().First()); + + modelBuilder.Entity() + .HasMany(p => p.Dependents) + .WithMany(d => d.ManyToManyPrincipals) + .UsingEntity( + jwf => jwf.HasOne(j => j.DependentWithField) + .WithMany(), + jwf => jwf.HasOne(j => j.ManyToManyPrincipalWithField) + .WithMany()); + + modelBuilder.FinalizeModel(); + + Assert.Same(principalToJoinNav, principalEntityType.GetSkipNavigations().Single()); + Assert.Same(dependentToJoinNav, dependentEntityType.GetSkipNavigations().Single()); + Assert.Equal(2, joinEntityType.GetForeignKeys().Count()); + Assert.Same(principalToDependentFk, joinEntityType.GetForeignKeys().Last()); + Assert.Same(dependentToPrincipalFk, joinEntityType.GetForeignKeys().First()); + } + [ConditionalFact] public virtual void Configures_association_type() { diff --git a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs index 1b4b5d48d8e..a73fa80eff8 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs @@ -59,6 +59,71 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() Assert.Empty(principalType.GetIndexes()); } + [ConditionalFact] + public virtual void Finds_existing_navigations_and_uses_associated_FK_with_fields() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.AlternateKey); + e.Property(p => p.Name); + e.HasKey(p => p.Id); + }); + modelBuilder.Entity(e => + { + e.Property(d => d.DependentWithFieldId); + e.Property(d => d.OneToManyPrincipalId); + e.Property(d => d.AnotherOneToManyPrincipalId); + e.Ignore(d => d.ManyToManyPrincipals); + e.Ignore(d => d.OneToOnePrincipal); + e.HasKey(d => d.DependentWithFieldId); + }); + + modelBuilder.Entity() + .HasMany(p => p.Dependents) + .WithOne(d => d.OneToManyPrincipal) + .HasForeignKey(d => d.OneToManyPrincipalId); + + var dependentType = model.FindEntityType(typeof(DependentWithField)); + var principalType = model.FindEntityType(typeof(OneToManyPrincipalWithField)); + var fk = dependentType.GetForeignKeys().Single(); + + var navToPrincipal = dependentType.FindNavigation("OneToManyPrincipal"); + var navToDependent = principalType.FindNavigation("Dependents"); + + var principalKey = principalType.GetKeys().Single(); + var dependentKey = dependentType.GetKeys().Single(); + + modelBuilder.Entity() + .HasMany(p => p.Dependents) + .WithOne(d => d.OneToManyPrincipal); + + modelBuilder.FinalizeModel(); + + Assert.Same(fk, dependentType.GetForeignKeys().Single()); + Assert.Equal(navToPrincipal.Name, dependentType.GetNavigations().Single().Name); + Assert.Same(navToDependent, principalType.GetNavigations().Single()); + Assert.Same(fk, dependentType.GetNavigations().Single().ForeignKey); + Assert.Same(fk, principalType.GetNavigations().Single().ForeignKey); + AssertEqual( + new[] { "AlternateKey", principalKey.Properties.Single().Name, "Name" }, + principalType.GetProperties().Select(p => p.Name)); + AssertEqual( + new[] { "AnotherOneToManyPrincipalId", fk.Properties.Single().Name, dependentKey.Properties.Single().Name }, + dependentType.GetProperties().Select(p => p.Name)); + Assert.Empty(principalType.GetForeignKeys()); + Assert.Same(principalKey, principalType.GetKeys().Single()); + Assert.Same(dependentKey, dependentType.GetKeys().Single()); + Assert.Same(principalKey, principalType.FindPrimaryKey()); + Assert.Same(dependentKey, dependentType.FindPrimaryKey()); + Assert.Equal(dependentType.GetForeignKeys().Count(), dependentType.GetIndexes().Count()); + Assert.False(fk.DeclaringEntityType.FindIndex(fk.Properties).IsUnique); + Assert.Empty(principalType.GetIndexes()); + } + [ConditionalFact] public virtual void Finds_existing_navigation_to_principal_and_uses_associated_FK() { diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs index 5b15b3e82b6..a26a57a3d29 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs @@ -82,40 +82,40 @@ protected override TestEntityTypeBuilder Wrap(EntityTypeBuilder OwnsOne( Expression> navigationExpression) => new GenericStringTestOwnedNavigationBuilder( - EntityTypeBuilder.OwnsOne(navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName())); + EntityTypeBuilder.OwnsOne(navigationExpression?.GetMemberAccess()?.GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsOne( Expression> navigationExpression, Action> buildAction) => Wrap( EntityTypeBuilder.OwnsOne( - navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName(), + navigationExpression?.GetMemberAccess()?.GetSimpleMemberName(), r => buildAction(new GenericStringTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) => new GenericStringTestOwnedNavigationBuilder( - EntityTypeBuilder.OwnsMany(navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName())); + EntityTypeBuilder.OwnsMany(navigationExpression?.GetMemberAccess()?.GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsMany( Expression>> navigationExpression, Action> buildAction) => Wrap( EntityTypeBuilder.OwnsMany( - navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName(), + navigationExpression?.GetMemberAccess()?.GetSimpleMemberName(), r => buildAction(new GenericStringTestOwnedNavigationBuilder(r)))); public override TestReferenceNavigationBuilder HasOne( Expression> navigationExpression = null) => new GenericStringTestReferenceNavigationBuilder( EntityTypeBuilder.HasOne( - navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName())); + navigationExpression?.GetMemberAccess()?.GetSimpleMemberName())); public override TestCollectionNavigationBuilder HasMany( Expression>> navigationExpression = null) => new GenericStringTestCollectionNavigationBuilder( EntityTypeBuilder.HasMany( - navigationExpression?.GetPropertyAccess()?.GetSimpleMemberName())); + navigationExpression?.GetMemberAccess()?.GetSimpleMemberName())); } private class GenericStringTestReferenceNavigationBuilder : @@ -133,13 +133,13 @@ public override TestReferenceCollectionBuilder WithMany Expression>> navigationExpression = null) => new GenericStringTestReferenceCollectionBuilder( ReferenceNavigationBuilder.WithMany( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestReferenceReferenceBuilder WithOne( Expression> navigationExpression = null) => new GenericStringTestReferenceReferenceBuilder( ReferenceNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } private class GenericStringTestCollectionNavigationBuilder @@ -157,7 +157,7 @@ public override TestReferenceCollectionBuilder WithOne( Expression> navigationExpression = null) => new GenericStringTestReferenceCollectionBuilder( CollectionNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } private class GenericStringTestReferenceCollectionBuilder @@ -179,13 +179,13 @@ public override TestReferenceCollectionBuilder HasForei Expression> foreignKeyExpression) => Wrap( ReferenceCollectionBuilder.HasForeignKey( - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceCollectionBuilder HasPrincipalKey( Expression> keyExpression) => Wrap( ReferenceCollectionBuilder.HasPrincipalKey( - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); } private class GenericStringTestReferenceReferenceBuilder @@ -207,7 +207,7 @@ public override TestReferenceReferenceBuilder HasForeig => Wrap( ReferenceReferenceBuilder.HasForeignKey( typeof(TDependentEntity).FullName, - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasForeignKey( params string[] foreignKeyPropertyNames) @@ -220,7 +220,7 @@ public override TestReferenceReferenceBuilder HasPrinci => Wrap( ReferenceReferenceBuilder.HasPrincipalKey( typeof(TPrincipalEntity).FullName, - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasPrincipalKey( params string[] keyPropertyNames) @@ -246,38 +246,38 @@ protected override GenericTestOwnedNavigationBuilder WithOwner( Expression> referenceExpression) => new GenericTestOwnershipBuilder( - OwnedNavigationBuilder.WithOwner(referenceExpression?.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.WithOwner(referenceExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) => Wrap( - OwnedNavigationBuilder.OwnsOne(navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.OwnsOne(navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression, Action> buildAction) => Wrap( OwnedNavigationBuilder.OwnsOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression?.GetMemberAccess().GetSimpleMemberName(), r => buildAction(Wrap(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) => Wrap( - OwnedNavigationBuilder.OwnsMany(navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.OwnsMany(navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression, Action> buildAction) => Wrap( OwnedNavigationBuilder.OwnsMany( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression?.GetMemberAccess().GetSimpleMemberName(), r => buildAction(Wrap(r)))); public override TestReferenceNavigationBuilder HasOne( Expression> navigationExpression = null) => new GenericStringTestReferenceNavigationBuilder( - OwnedNavigationBuilder.HasOne(navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.HasOne(navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } } } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs index 1c6e13d7eaa..d3b08524a5b 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs @@ -111,7 +111,7 @@ public override TestReferenceReferenceBuilder HasForeig => Wrap( ReferenceReferenceBuilder.HasForeignKey( typeof(TDependentEntity), - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasForeignKey( params string[] foreignKeyPropertyNames) @@ -122,7 +122,7 @@ public override TestReferenceReferenceBuilder HasPrinci => Wrap( ReferenceReferenceBuilder.HasPrincipalKey( typeof(TPrincipalEntity), - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasPrincipalKey( params string[] keyPropertyNames) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs index 59b762fbd75..4c491d10c51 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs @@ -116,7 +116,7 @@ public override TestOwnedNavigationBuilder OwnsOne> navigationExpression) => new NonGenericStringTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsOne( - typeof(TRelatedEntity).FullName, navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TRelatedEntity).FullName, navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsOne( Expression> navigationExpression, @@ -124,7 +124,7 @@ public override TestEntityTypeBuilder OwnsOne( => Wrap( EntityTypeBuilder.OwnsOne( typeof(TRelatedEntity).FullName, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericStringTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( @@ -132,7 +132,7 @@ public override TestOwnedNavigationBuilder OwnsMany new NonGenericTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsMany( typeof(TRelatedEntity).FullName, - navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsMany( Expression>> navigationExpression, @@ -140,7 +140,7 @@ public override TestEntityTypeBuilder OwnsMany( => Wrap( EntityTypeBuilder.OwnsMany( typeof(TRelatedEntity).FullName, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestReferenceNavigationBuilder HasOne( @@ -148,14 +148,14 @@ public override TestReferenceNavigationBuilder HasOne new NonGenericStringTestReferenceNavigationBuilder( EntityTypeBuilder.HasOne( typeof(TRelatedEntity).FullName, - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestCollectionNavigationBuilder HasMany( Expression>> navigationExpression = null) => new NonGenericTestCollectionNavigationBuilder( EntityTypeBuilder.HasMany( typeof(TRelatedEntity).FullName, - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } private class NonGenericStringTestReferenceNavigationBuilder @@ -172,7 +172,7 @@ public override TestReferenceReferenceBuilder WithOne( Expression> navigationExpression = null) => new NonGenericStringTestReferenceReferenceBuilder( ReferenceNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } private class NonGenericStringTestReferenceReferenceBuilder @@ -194,14 +194,14 @@ public override TestReferenceReferenceBuilder HasForeig => Wrap( ReferenceReferenceBuilder.HasForeignKey( typeof(TDependentEntity).FullName, - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasPrincipalKey( Expression> keyExpression) => Wrap( ReferenceReferenceBuilder.HasPrincipalKey( typeof(TPrincipalEntity).FullName, - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasForeignKey( params string[] foreignKeyPropertyNames) @@ -262,7 +262,7 @@ public override TestOwnedNavigationBuilder> navigationExpression) => new NonGenericStringTestOwnedNavigationBuilder( OwnedNavigationBuilder.OwnsOne( - typeof(TNewDependentEntity).FullName, navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity).FullName, navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression, @@ -270,14 +270,14 @@ public override TestOwnedNavigationBuilder OwnsOne Wrap( OwnedNavigationBuilder.OwnsOne( typeof(TNewDependentEntity).FullName, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericStringTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) => Wrap( OwnedNavigationBuilder.OwnsMany( - typeof(TNewDependentEntity).FullName, navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity).FullName, navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression, @@ -285,14 +285,14 @@ public override TestOwnedNavigationBuilder OwnsMany Wrap( OwnedNavigationBuilder.OwnsMany( typeof(TNewDependentEntity).FullName, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(Wrap(r)))); public override TestReferenceNavigationBuilder HasOne( Expression> navigationExpression = null) => new NonGenericStringTestReferenceNavigationBuilder( OwnedNavigationBuilder.HasOne( - typeof(TNewDependentEntity).FullName, navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity).FullName, navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } } } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index e7227f57446..83e521a7d42 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -118,7 +118,7 @@ public override TestEntityTypeBuilder HasBaseType(string baseEntityType public override TestKeyBuilder HasKey(Expression> keyExpression) => new NonGenericTestKeyBuilder( - EntityTypeBuilder.HasKey(keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + EntityTypeBuilder.HasKey(keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestKeyBuilder HasKey(params string[] propertyNames) => new NonGenericTestKeyBuilder(EntityTypeBuilder.HasKey(propertyNames)); @@ -126,7 +126,7 @@ public override TestKeyBuilder HasKey(params string[] propertyNames) public override TestKeyBuilder HasAlternateKey(Expression> keyExpression) => new NonGenericTestKeyBuilder( EntityTypeBuilder.HasAlternateKey( - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestKeyBuilder HasAlternateKey(params string[] propertyNames) => new NonGenericTestKeyBuilder(EntityTypeBuilder.HasAlternateKey(propertyNames)); @@ -136,9 +136,9 @@ public override TestEntityTypeBuilder HasNoKey() public override TestPropertyBuilder Property(Expression> propertyExpression) { - var propertyInfo = propertyExpression.GetPropertyAccess(); + var memberInfo = propertyExpression.GetMemberAccess(); return new NonGenericTestPropertyBuilder( - EntityTypeBuilder.Property(propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName())); + EntityTypeBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); } public override TestPropertyBuilder Property(string propertyName) @@ -148,24 +148,21 @@ public override TestPropertyBuilder IndexerProperty(string => new NonGenericTestPropertyBuilder(EntityTypeBuilder.IndexerProperty(propertyName)); public override TestNavigationBuilder Navigation(Expression> navigationExpression) - { - var navigationInfo = navigationExpression.GetPropertyAccess(); - return new NonGenericTestNavigationBuilder( - EntityTypeBuilder.Navigation(navigationInfo.GetSimpleMemberName())); - } + => new NonGenericTestNavigationBuilder( + EntityTypeBuilder.Navigation(navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestNavigationBuilder Navigation(string propertyName) => new NonGenericTestNavigationBuilder(EntityTypeBuilder.Navigation(propertyName)); public override TestEntityTypeBuilder Ignore(Expression> propertyExpression) - => Wrap(EntityTypeBuilder.Ignore(propertyExpression.GetPropertyAccess().GetSimpleMemberName())); + => Wrap(EntityTypeBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder Ignore(string propertyName) => Wrap(EntityTypeBuilder.Ignore(propertyName)); public override TestIndexBuilder HasIndex(Expression> indexExpression) => new NonGenericTestIndexBuilder( - EntityTypeBuilder.HasIndex(indexExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + EntityTypeBuilder.HasIndex(indexExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestIndexBuilder HasIndex(params string[] propertyNames) => new NonGenericTestIndexBuilder(EntityTypeBuilder.HasIndex(propertyNames)); @@ -185,7 +182,7 @@ public override TestEntityTypeBuilder OwnsOne( public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) => new NonGenericTestOwnedNavigationBuilder( - EntityTypeBuilder.OwnsOne(typeof(TRelatedEntity), navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + EntityTypeBuilder.OwnsOne(typeof(TRelatedEntity), navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsOne( Expression> navigationExpression, @@ -193,7 +190,7 @@ public override TestEntityTypeBuilder OwnsOne( => Wrap( EntityTypeBuilder.OwnsOne( typeof(TRelatedEntity), - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany(string navigationName) @@ -211,7 +208,7 @@ public override TestEntityTypeBuilder OwnsMany( public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) => new NonGenericTestOwnedNavigationBuilder( - EntityTypeBuilder.OwnsMany(typeof(TRelatedEntity), navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + EntityTypeBuilder.OwnsMany(typeof(TRelatedEntity), navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsMany( Expression>> navigationExpression, @@ -219,7 +216,7 @@ public override TestEntityTypeBuilder OwnsMany( => Wrap( EntityTypeBuilder.OwnsMany( typeof(TRelatedEntity), - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestReferenceNavigationBuilder HasOne(string navigationName) @@ -231,7 +228,7 @@ public override TestReferenceNavigationBuilder HasOne new NonGenericTestReferenceNavigationBuilder( EntityTypeBuilder.HasOne( typeof(TRelatedEntity), - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestCollectionNavigationBuilder HasMany(string navigationName) => new NonGenericTestCollectionNavigationBuilder( @@ -242,7 +239,7 @@ public override TestCollectionNavigationBuilder HasMany => new NonGenericTestCollectionNavigationBuilder( EntityTypeBuilder.HasMany( typeof(TRelatedEntity), - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder HasQueryFilter(Expression> filter) => Wrap(EntityTypeBuilder.HasQueryFilter(filter)); @@ -281,7 +278,7 @@ public override TestDiscriminatorBuilder HasDiscriminator> propertyExpression) => new NonGenericTestDiscriminatorBuilder( EntityTypeBuilder.HasDiscriminator( - propertyExpression?.GetPropertyAccess().GetSimpleMemberName(), typeof(TDiscriminator))); + propertyExpression?.GetMemberAccess().GetSimpleMemberName(), typeof(TDiscriminator))); public override TestDiscriminatorBuilder HasDiscriminator(string propertyName) => new NonGenericTestDiscriminatorBuilder( @@ -485,7 +482,7 @@ public override TestReferenceCollectionBuilder WithMany Expression>> navigationExpression = null) => new NonGenericTestReferenceCollectionBuilder( ReferenceNavigationBuilder.WithMany( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestReferenceReferenceBuilder WithOne(string navigationName) => new NonGenericTestReferenceReferenceBuilder( @@ -495,7 +492,7 @@ public override TestReferenceReferenceBuilder WithOne( Expression> navigationExpression = null) => new NonGenericTestReferenceReferenceBuilder( ReferenceNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } protected class NonGenericTestCollectionNavigationBuilder @@ -519,7 +516,7 @@ public override TestReferenceCollectionBuilder WithOne( Expression> navigationExpression = null) => new NonGenericTestReferenceCollectionBuilder( CollectionNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestCollectionCollectionBuilder WithMany( string navigationName) @@ -529,7 +526,7 @@ public override TestCollectionCollectionBuilder WithMan public override TestCollectionCollectionBuilder WithMany( Expression>> navigationExpression) => new NonGenericTestCollectionCollectionBuilder( - CollectionNavigationBuilder.WithMany(navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + CollectionNavigationBuilder.WithMany(navigationExpression.GetMemberAccess().GetSimpleMemberName())); } protected class NonGenericTestReferenceCollectionBuilder @@ -550,13 +547,13 @@ public override TestReferenceCollectionBuilder HasForei Expression> foreignKeyExpression) => new NonGenericTestReferenceCollectionBuilder( ReferenceCollectionBuilder.HasForeignKey( - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceCollectionBuilder HasPrincipalKey( Expression> keyExpression) => new NonGenericTestReferenceCollectionBuilder( ReferenceCollectionBuilder.HasPrincipalKey( - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceCollectionBuilder HasForeignKey(params string[] foreignKeyPropertyNames) => new NonGenericTestReferenceCollectionBuilder( @@ -603,12 +600,12 @@ public override TestReferenceReferenceBuilder HasForeig Expression> foreignKeyExpression) => Wrap(ReferenceReferenceBuilder.HasForeignKey( typeof(TDependentEntity), - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasPrincipalKey( Expression> keyExpression) => Wrap(ReferenceReferenceBuilder.HasPrincipalKey( - typeof(TPrincipalEntity), keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + typeof(TPrincipalEntity), keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasForeignKey( params string[] foreignKeyPropertyNames) @@ -696,7 +693,7 @@ public override TestOwnershipBuilder HasForeignKey( Expression> foreignKeyExpression) => Wrap( OwnershipBuilder.HasForeignKey( - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestOwnershipBuilder HasPrincipalKey( params string[] keyPropertyNames) @@ -706,7 +703,7 @@ public override TestOwnershipBuilder HasPrincipalKey( Expression> keyExpression) => Wrap( OwnershipBuilder.HasPrincipalKey( - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); OwnershipBuilder IInfrastructure.Instance => OwnershipBuilder; } @@ -739,7 +736,7 @@ public override TestOwnedNavigationBuilder HasAnnotat public override TestKeyBuilder HasKey(Expression> keyExpression) => new NonGenericTestKeyBuilder( OwnedNavigationBuilder.HasKey( - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestKeyBuilder HasKey(params string[] propertyNames) => new NonGenericTestKeyBuilder(OwnedNavigationBuilder.HasKey(propertyNames)); @@ -753,9 +750,9 @@ public override TestPropertyBuilder IndexerProperty(string public override TestPropertyBuilder Property( Expression> propertyExpression) { - var propertyInfo = propertyExpression.GetPropertyAccess(); + var memberInfo = propertyExpression.GetMemberAccess(); return new NonGenericTestPropertyBuilder( - OwnedNavigationBuilder.Property(propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName())); + OwnedNavigationBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); } public override TestNavigationBuilder Navigation(string navigationName) @@ -763,7 +760,7 @@ public override TestNavigationBuilder Navigation(string navigationN public override TestNavigationBuilder Navigation( Expression> navigationExpression) => new NonGenericTestNavigationBuilder( - OwnedNavigationBuilder.Navigation(navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.Navigation(navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder Ignore(string propertyName) => Wrap(OwnedNavigationBuilder.Ignore(propertyName)); @@ -771,7 +768,7 @@ public override TestOwnedNavigationBuilder Ignore(str public override TestOwnedNavigationBuilder Ignore( Expression> propertyExpression) => Wrap( - OwnedNavigationBuilder.Ignore(propertyExpression.GetPropertyAccess().GetSimpleMemberName())); + OwnedNavigationBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); public override TestIndexBuilder HasIndex(params string[] propertyNames) => new NonGenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(propertyNames)); @@ -779,7 +776,7 @@ public override TestIndexBuilder HasIndex(params string[] propertyNames public override TestIndexBuilder HasIndex(Expression> indexExpression) => new NonGenericTestIndexBuilder( OwnedNavigationBuilder.HasIndex( - indexExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + indexExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestOwnershipBuilder WithOwner(string ownerReference) => new NonGenericTestOwnershipBuilder( @@ -788,13 +785,13 @@ public override TestOwnershipBuilder WithOwner(string public override TestOwnershipBuilder WithOwner( Expression> referenceExpression) => new NonGenericTestOwnershipBuilder( - OwnedNavigationBuilder.WithOwner(referenceExpression?.GetPropertyAccess()?.GetSimpleMemberName())); + OwnedNavigationBuilder.WithOwner(referenceExpression?.GetMemberAccess()?.GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) => Wrap( OwnedNavigationBuilder.OwnsOne( - typeof(TNewDependentEntity), navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity), navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression, @@ -802,14 +799,14 @@ public override TestOwnedNavigationBuilder OwnsOne Wrap( OwnedNavigationBuilder.OwnsOne( typeof(TNewDependentEntity), - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) => Wrap( OwnedNavigationBuilder.OwnsMany( - typeof(TNewDependentEntity), navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity), navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression, @@ -817,14 +814,14 @@ public override TestOwnedNavigationBuilder OwnsMany Wrap( OwnedNavigationBuilder.OwnsMany( typeof(TNewDependentEntity), - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(Wrap(r)))); public override TestReferenceNavigationBuilder HasOne( Expression> navigationExpression = null) => new NonGenericTestReferenceNavigationBuilder( OwnedNavigationBuilder.HasOne( - typeof(TNewDependentEntity), navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + typeof(TNewDependentEntity), navigationExpression?.GetMemberAccess().GetSimpleMemberName())); public override TestOwnedNavigationBuilder HasChangeTrackingStrategy( ChangeTrackingStrategy changeTrackingStrategy) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericUnqualifiedStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericUnqualifiedStringTest.cs index e362f9c8672..bba78ade724 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericUnqualifiedStringTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericUnqualifiedStringTest.cs @@ -63,7 +63,7 @@ public override TestOwnedNavigationBuilder OwnsOne> navigationExpression) => new NonGenericStringTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsOne( - typeof(TRelatedEntity).Name, navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + typeof(TRelatedEntity).Name, navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsOne( Expression> navigationExpression, @@ -71,7 +71,7 @@ public override TestEntityTypeBuilder OwnsOne( => Wrap( EntityTypeBuilder.OwnsOne( typeof(TRelatedEntity).Name, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericStringTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( @@ -79,7 +79,7 @@ public override TestOwnedNavigationBuilder OwnsMany new NonGenericTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsMany( typeof(TRelatedEntity).Name, - navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression.GetMemberAccess().GetSimpleMemberName())); public override TestEntityTypeBuilder OwnsMany( Expression>> navigationExpression, @@ -87,13 +87,13 @@ public override TestEntityTypeBuilder OwnsMany( => Wrap( EntityTypeBuilder.OwnsMany( typeof(TRelatedEntity).Name, - navigationExpression.GetPropertyAccess().GetSimpleMemberName(), + navigationExpression.GetMemberAccess().GetSimpleMemberName(), r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestReferenceNavigationBuilder HasOne( Expression> navigationExpression = null) { - var navigationName = navigationExpression?.GetPropertyAccess().GetSimpleMemberName(); + var navigationName = navigationExpression?.GetMemberAccess().GetSimpleMemberName(); return new NonGenericStringTestReferenceNavigationBuilder( navigationName == null @@ -104,7 +104,7 @@ public override TestReferenceNavigationBuilder HasOne HasMany( Expression>> navigationExpression = null) { - var navigationName = navigationExpression?.GetPropertyAccess().GetSimpleMemberName(); + var navigationName = navigationExpression?.GetMemberAccess().GetSimpleMemberName(); return new NonGenericTestCollectionNavigationBuilder( navigationName == null @@ -127,7 +127,7 @@ public override TestReferenceReferenceBuilder WithOne( Expression> navigationExpression = null) => new NonGenericStringTestReferenceReferenceBuilder( ReferenceNavigationBuilder.WithOne( - navigationExpression?.GetPropertyAccess().GetSimpleMemberName())); + navigationExpression?.GetMemberAccess().GetSimpleMemberName())); } private class NonGenericStringTestReferenceReferenceBuilder : NonGenericTestReferenceReferenceBuilder< @@ -149,14 +149,14 @@ public override TestReferenceReferenceBuilder HasForeig => Wrap( ReferenceReferenceBuilder.HasForeignKey( typeof(TDependentEntity).Name, - foreignKeyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + foreignKeyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasPrincipalKey( Expression> keyExpression) => Wrap( ReferenceReferenceBuilder.HasPrincipalKey( typeof(TPrincipalEntity).Name, - keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + keyExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); public override TestReferenceReferenceBuilder HasForeignKey( params string[] foreignKeyPropertyNames) @@ -184,7 +184,7 @@ protected override NonGenericTestOwnedNavigationBuilder HasOne( Expression> navigationExpression = null) { - var navigationName = navigationExpression?.GetPropertyAccess().GetSimpleMemberName(); + var navigationName = navigationExpression?.GetMemberAccess().GetSimpleMemberName(); return new NonGenericStringTestReferenceNavigationBuilder( navigationName == null diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 39c04562486..454716c3898 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using Xunit; @@ -1250,6 +1251,160 @@ public virtual void Can_ignore_explicit_interface_implementation() Assert.Empty(modelBuilder.Model.FindEntityType(typeof(EntityBase)).GetProperties()); } + [ConditionalFact] + public virtual void Can_set_key_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasKey(e => e.Id); + + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + var primaryKey = entity.FindPrimaryKey(); + Assert.NotNull(primaryKey); + var property = Assert.Single(primaryKey.Properties); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_set_composite_key_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); + + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + var primaryKeyProperties = entity.FindPrimaryKey().Properties; + Assert.Equal(2, primaryKeyProperties.Count); + var first = primaryKeyProperties[0]; + var second = primaryKeyProperties[1]; + Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + Assert.Null(first.PropertyInfo); + Assert.NotNull(first.FieldInfo); + Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + Assert.Null(second.PropertyInfo); + Assert.NotNull(second.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_set_alternate_key_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasAlternateKey(e => e.CompanyId); + + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + var properties = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetProperties(); + Assert.Single(properties); + var property = properties.Single(); + Assert.Equal(nameof(EntityWithFields.CompanyId), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + var keys = entity.GetKeys(); + var key = Assert.Single(keys); + Assert.Equal(properties, key.Properties); + } + + [ConditionalFact] + public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasAlternateKey(e => new { e.TenantId, e.CompanyId }); + + var keys = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetKeys(); + Assert.Single(keys); + var properties = keys.Single().Properties; + Assert.Equal(2, properties.Count); + var first = properties[0]; + var second = properties[1]; + Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + Assert.Null(first.PropertyInfo); + Assert.NotNull(first.FieldInfo); + Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + Assert.Null(second.PropertyInfo); + Assert.NotNull(second.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_call_Property_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().Property(e => e.Id); + + var properties = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetProperties(); + var property = Assert.Single(properties); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_set_index_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasIndex(e => e.CompanyId); + + var indexes = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + var index = Assert.Single(indexes); + var property = Assert.Single(index.Properties); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_set_composite_index_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity().HasIndex(e => new { e.TenantId, e.CompanyId }); + + var indexes = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)).GetIndexes(); + var index = Assert.Single(indexes); + Assert.Equal(2, index.Properties.Count); + var properties = index.Properties; + var first = properties[0]; + var second = properties[1]; + Assert.Equal(nameof(EntityWithFields.TenantId), first.Name); + Assert.Null(first.PropertyInfo); + Assert.NotNull(first.FieldInfo); + Assert.Equal(nameof(EntityWithFields.CompanyId), second.Name); + Assert.Null(second.PropertyInfo); + Assert.NotNull(second.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_ignore_a_field_on_an_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity() + .Ignore(e => e.CompanyId) + .HasKey(e => e.Id); + + var entity = modelBuilder.Model.FindEntityType(typeof(EntityWithFields)); + var property = Assert.Single(entity.GetProperties()); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + } + + [ConditionalFact] + public virtual void Can_ignore_a_field_on_a_keyless_entity_with_fields() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity() + .HasNoKey() + .Ignore(e => e.FirstName) + .Property(e => e.LastName); + + var entity = modelBuilder.Model.FindEntityType(typeof(KeylessEntityWithFields)); + var property = Assert.Single(entity.GetProperties()); + Assert.Equal(nameof(KeylessEntityWithFields.LastName), property.Name); + } + [ConditionalFact] public virtual void Can_add_seed_data_objects() { diff --git a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs index 8cda9bfa1a9..efb9c21c5ff 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs @@ -67,6 +67,71 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() Assert.Empty(principalType.GetIndexes()); } + [ConditionalFact] + public virtual void Finds_existing_navigations_and_uses_associated_FK_with_fields() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.AlternateKey); + e.Property(p => p.Name); + e.HasKey(p => p.Id); + }); + modelBuilder.Entity(e => + { + e.Property(d => d.DependentWithFieldId); + e.Property(d => d.OneToManyPrincipalId); + e.Property(d => d.AnotherOneToManyPrincipalId); + e.Ignore(d => d.ManyToManyPrincipals); + e.Ignore(d => d.OneToOnePrincipal); + e.HasKey(d => d.DependentWithFieldId); + }); + + modelBuilder.Entity() + .HasOne(d => d.OneToManyPrincipal) + .WithMany(p => p.Dependents) + .HasForeignKey(d => d.OneToManyPrincipalId); + + var dependentType = model.FindEntityType(typeof(DependentWithField)); + var principalType = model.FindEntityType(typeof(OneToManyPrincipalWithField)); + var fk = dependentType.GetForeignKeys().Single(); + + var navToPrincipal = dependentType.FindNavigation("OneToManyPrincipal"); + var navToDependent = principalType.FindNavigation("Dependents"); + + var principalKey = principalType.GetKeys().Single(); + var dependentKey = dependentType.GetKeys().Single(); + + modelBuilder.Entity() + .HasOne(d => d.OneToManyPrincipal) + .WithMany(p => p.Dependents); + + modelBuilder.FinalizeModel(); + + Assert.Same(fk, dependentType.GetForeignKeys().Single()); + Assert.Equal(navToPrincipal.Name, dependentType.GetNavigations().Single().Name); + Assert.Same(navToDependent, principalType.GetNavigations().Single()); + Assert.Same(fk, dependentType.GetNavigations().Single().ForeignKey); + Assert.Same(fk, principalType.GetNavigations().Single().ForeignKey); + AssertEqual( + new[] { "AlternateKey", principalKey.Properties.Single().Name, "Name" }, + principalType.GetProperties().Select(p => p.Name)); + AssertEqual( + new[] { "AnotherOneToManyPrincipalId", fk.Properties.Single().Name, dependentKey.Properties.Single().Name }, + dependentType.GetProperties().Select(p => p.Name)); + Assert.Empty(principalType.GetForeignKeys()); + Assert.Same(principalKey, principalType.GetKeys().Single()); + Assert.Same(dependentKey, dependentType.GetKeys().Single()); + Assert.Same(principalKey, principalType.FindPrimaryKey()); + Assert.Same(dependentKey, dependentType.FindPrimaryKey()); + Assert.Equal(dependentType.GetForeignKeys().Count(), dependentType.GetIndexes().Count()); + Assert.False(fk.DeclaringEntityType.FindIndex(fk.Properties).IsUnique); + Assert.Empty(principalType.GetIndexes()); + } + [ConditionalFact] public virtual void Finds_existing_navigation_to_principal_and_uses_associated_FK() { diff --git a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs index c842f900e44..237e8c722be 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs @@ -56,6 +56,67 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() Assert.Empty(principalType.GetIndexes()); } + [ConditionalFact] + public virtual void Finds_existing_navigations_and_uses_associated_FK_with_fields() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.Name); + e.HasKey(p => p.Id); + }); + modelBuilder.Entity(e => + { + e.Property(d => d.DependentWithFieldId); + e.Property(d => d.OneToOnePrincipalId); + e.Ignore(d => d.ManyToManyPrincipals); + e.Ignore(d => d.OneToManyPrincipal); + e.HasKey(d => d.DependentWithFieldId); + }); + + modelBuilder.Entity() + .HasOne(d => d.OneToOnePrincipal) + .WithOne(p => p.Dependent) + .HasForeignKey(d => d.DependentWithFieldId); + + var dependentType = model.FindEntityType(typeof(DependentWithField)); + var principalType = model.FindEntityType(typeof(OneToOnePrincipalWithField)); + var fk = dependentType.GetForeignKeys().Single(); + + var navToPrincipal = dependentType.FindNavigation("OneToOnePrincipal"); + var navToDependent = principalType.FindNavigation("Dependent"); + + var principalKey = principalType.GetKeys().Single(); + var dependentKey = dependentType.GetKeys().Single(); + + modelBuilder.Entity() + .HasOne(d => d.OneToOnePrincipal) + .WithOne(p => p.Dependent); + + modelBuilder.FinalizeModel(); + + Assert.Single(dependentType.GetForeignKeys()); + Assert.Same(navToPrincipal, dependentType.GetNavigations().Single()); + Assert.Same(navToDependent, principalType.GetNavigations().Single()); + Assert.Same(fk.PrincipalKey, principalType.GetNavigations().Single().ForeignKey.PrincipalKey); + AssertEqual( + new[] { principalKey.Properties.Single().Name, "Name" }, + principalType.GetProperties().Select(p => p.Name)); + AssertEqual( + new[] { dependentKey.Properties.Single().Name, "OneToOnePrincipalId" }, + dependentType.GetProperties().Select(p => p.Name)); + Assert.Empty(principalType.GetForeignKeys()); + Assert.Same(principalKey, principalType.GetKeys().Single()); + Assert.Same(dependentKey, dependentType.GetKeys().Single()); + Assert.Same(principalKey, principalType.FindPrimaryKey()); + Assert.Same(dependentKey, dependentType.FindPrimaryKey()); + Assert.Empty(dependentType.GetIndexes()); + Assert.Empty(principalType.GetIndexes()); + } + [ConditionalFact] public virtual void Can_create_two_FKs_using_the_same_property() { diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index fa235ef3239..2c352cd70d4 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -70,6 +70,101 @@ public virtual void Can_configure_owned_type_using_nested_closure() Assert.Single(owned.GetForeignKeys()); } + [ConditionalFact] + public virtual void Can_configure_one_to_one_owned_type_with_fields() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Owned(); + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.AlternateKey); + e.Property(p => p.Description); + e.HasKey(p => p.Id); + }); + + modelBuilder.Entity() + .OwnsOne( + owner => owner.OwnedDependent, + db => + { + db.WithOwner(d => d.OneToOneOwner); + db.Property(d => d.OneToOneOwnerId); + db.HasIndex(d => d.OneToOneOwnerId); + db.Navigation(owned => owned.OneToOneOwner); + }); + + modelBuilder.Entity() + .Navigation(owner => owner.OwnedDependent); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(OneToOneOwnerWithField)); + Assert.Equal(typeof(OneToOneOwnerWithField).FullName, owner.Name); + var ownership = owner.FindNavigation(nameof(OneToOneOwnerWithField.OwnedDependent)).ForeignKey; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(OneToOneOwnerWithField.OwnedDependent), ownership.PrincipalToDependent.Name); + Assert.Equal(nameof(OneToOneOwnedWithField.OneToOneOwner), ownership.DependentToPrincipal.Name); + Assert.Equal(nameof(OneToOneOwnedWithField.OneToOneOwnerId), ownership.Properties.Single().Name); + Assert.Equal(nameof(OneToOneOwnerWithField.Id), ownership.PrincipalKey.Properties.Single().Name); + var owned = ownership.DeclaringEntityType; + Assert.Single(owned.GetForeignKeys()); + Assert.Equal(nameof(OneToOneOwnedWithField.OneToOneOwnerId), owned.GetIndexes().Single().Properties.Single().Name); + Assert.Equal( + new[] { nameof(OneToOneOwnedWithField.OneToOneOwnerId) }, owned.GetProperties().Select(p => p.Name)); + Assert.NotNull(model.FindEntityType(typeof(OneToOneOwnedWithField))); + Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(OneToOneOwnedWithField))); + } + + [ConditionalFact] + public virtual void Can_configure_one_to_many_owned_type_with_fields() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Owned(); + modelBuilder.Entity(e => + { + e.Property(p => p.Id); + e.Property(p => p.AlternateKey); + e.Property(p => p.Description); + e.HasKey(p => p.Id); + }); + + modelBuilder.Entity() + .OwnsMany( + owner => owner.OwnedDependents, + db => + { + db.WithOwner(d => d.OneToManyOwner); + db.Property(d => d.OneToManyOwnerId); + db.HasIndex(d => d.OneToManyOwnerId); + db.Navigation(owned => owned.OneToManyOwner); + }); + + modelBuilder.Entity() + .Navigation(owner => owner.OwnedDependents); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(OneToManyOwnerWithField)); + Assert.Equal(typeof(OneToManyOwnerWithField).FullName, owner.Name); + var ownership = owner.FindNavigation(nameof(OneToManyOwnerWithField.OwnedDependents)).ForeignKey; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(OneToManyOwnerWithField.OwnedDependents), ownership.PrincipalToDependent.Name); + Assert.Equal(nameof(OneToManyOwnedWithField.OneToManyOwner), ownership.DependentToPrincipal.Name); + Assert.Equal(nameof(OneToManyOwnedWithField.OneToManyOwnerId), ownership.Properties.Single().Name); + Assert.Equal(nameof(OneToManyOwnerWithField.Id), ownership.PrincipalKey.Properties.Single().Name); + var owned = ownership.DeclaringEntityType; + Assert.Single(owned.GetForeignKeys()); + Assert.Equal(nameof(OneToManyOwnedWithField.OneToManyOwnerId), owned.GetIndexes().Single().Properties.Single().Name); + Assert.Equal( + new[] { nameof(OneToManyOwnedWithField.OneToManyOwnerId), nameof(OneToManyOwnerWithField.Id) }, + owned.GetProperties().Select(p => p.Name)); + Assert.NotNull(model.FindEntityType(typeof(OneToManyOwnedWithField))); + Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(OneToManyOwnedWithField))); + } + [ConditionalFact] public virtual void Can_configure_owned_type_inverse() { diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 0ddbd69e2f1..4b8e1f61615 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -687,6 +687,20 @@ protected class ApplicationUser public IList Friendships { get; set; } } + public class EntityWithFields + { + public long Id; + public int CompanyId; + public int TenantId; + public KeylessEntityWithFields KeylessEntity; + } + + public class KeylessEntityWithFields + { + public string FirstName; + public string LastName; + } + protected class QueryResult { public int ValueFk { get; set; } @@ -938,5 +952,88 @@ public class Ownee3 { public string Name { get; private set; } } + + public class OneToManyPrincipalWithField + { + public int Id; + public Guid AlternateKey; + public string Name; + + public IEnumerable Dependents; + } + + public class OneToOnePrincipalWithField + { + public int Id; + public string Name; + + public DependentWithField Dependent; + } + + public class ManyToManyPrincipalWithField + { + public int Id; + public string Name; + + public List Dependents; + } + + protected class ManyToManyJoinWithFields + { + public int ManyToManyPrincipalWithFieldId; + public int DependentWithFieldId; + + public ManyToManyPrincipalWithField ManyToManyPrincipalWithField { get; set; } + public DependentWithField DependentWithField { get; set; } + } + + public class DependentWithField + { + public int DependentWithFieldId; + + public int? OneToManyPrincipalId; + public Guid AnotherOneToManyPrincipalId; + public OneToManyPrincipalWithField OneToManyPrincipal { get; set; } + public int OneToOnePrincipalId; + public OneToOnePrincipalWithField OneToOnePrincipal { get; set; } + public List ManyToManyPrincipals { get; set; } + } + + public class OneToManyOwnerWithField + { + public int Id; + public Guid AlternateKey; + public string Description; + + public List OwnedDependents { get; set; } + } + + public class OneToManyOwnedWithField + { + public string FirstName; + public string LastName; + + public int OneToManyOwnerId; + public OneToManyOwnerWithField OneToManyOwner { get; set; } + } + + public class OneToOneOwnerWithField + { + public int Id; + public Guid AlternateKey; + public string Description; + + public OneToOneOwnedWithField OwnedDependent { get; set; } + } + + public class OneToOneOwnedWithField + { + public string FirstName; + public string LastName; + + public int OneToOneOwnerId; + public OneToOneOwnerWithField OneToOneOwner { get; set; } + + } } } diff --git a/test/EFCore.Tests/Utilities/ExpressionExtensionsTest.cs b/test/EFCore.Tests/Utilities/ExpressionExtensionsTest.cs index 8d6de36ffc5..18c0edfbb9a 100644 --- a/test/EFCore.Tests/Utilities/ExpressionExtensionsTest.cs +++ b/test/EFCore.Tests/Utilities/ExpressionExtensionsTest.cs @@ -4,8 +4,10 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.ModelBuilding; using Xunit; // ReSharper disable InconsistentNaming @@ -30,7 +32,7 @@ public void Get_property_access_should_throw_when_not_property_access() Expression> expression = d => 123; Assert.Contains( - CoreStrings.InvalidPropertyExpression(expression), + CoreStrings.InvalidMemberExpression(expression), Assert.Throws(() => expression.GetPropertyAccess()).Message); } @@ -41,7 +43,7 @@ public void Get_property_access_should_throw_when_not_property_access_on_the_pro Expression> expression = d => closure.Hour; Assert.Contains( - CoreStrings.InvalidPropertyExpression(expression), + CoreStrings.InvalidMemberExpression(expression), Assert.Throws(() => expression.GetPropertyAccess()).Message); } @@ -98,7 +100,7 @@ public void Get_property_access_list_should_throw_when_invalid_expression() Expression> expression = d => new { P = d.AddTicks(23) }; Assert.Contains( - CoreStrings.InvalidPropertiesExpression(expression), + CoreStrings.InvalidMembersExpression(expression), Assert.Throws(() => expression.GetPropertyAccessList()).Message); } @@ -110,8 +112,109 @@ public void Get_property_access_list_should_throw_when_property_access_not_on_th Expression> expression = d => new { d.Date, closure.Day }; Assert.Contains( - CoreStrings.InvalidPropertiesExpression(expression), + CoreStrings.InvalidMembersExpression(expression), Assert.Throws(() => expression.GetPropertyAccessList()).Message); } + + [ConditionalFact] + public void Get_member_access_should_return_property_info_when_valid_property_access_expression() + { + Expression> propertyExpression = d => d.Hour; + var memberInfo = propertyExpression.GetMemberAccess(); + + Assert.NotNull(memberInfo); + Assert.IsAssignableFrom(memberInfo); + Assert.Equal("Hour", memberInfo.Name); + } + + [ConditionalFact] + public void Get_member_access_should_return_field_info_when_valid_field_access_expression() + { + Expression> fieldExpression = e => e.CompanyId; + var memberInfo = fieldExpression.GetMemberAccess(); + + Assert.NotNull(memberInfo); + Assert.IsAssignableFrom(memberInfo); + Assert.Equal("CompanyId", memberInfo.Name); + } + + [ConditionalFact] + public void Get_member_access_should_throw_when_not_member_access() + { + Expression> expression = e => 123; + + Assert.Contains( + CoreStrings.InvalidMemberExpression(expression), + Assert.Throws(() => expression.GetMemberAccess()).Message); + } + + [ConditionalFact] + public void Get_member_access_should_throw_when_not_member_access_on_the_provided_argument() + { + var closure = new ModelBuilderTest.EntityWithFields + { + Id = 1, + CompanyId = 100, + TenantId = 200 + }; + + Expression> expression = e => closure.CompanyId; + + Assert.Contains( + CoreStrings.InvalidMemberExpression(expression), + Assert.Throws(() => expression.GetMemberAccess()).Message); + } + + [ConditionalFact] + public void Get_member_access_should_handle_convert() + { + // Note: CompanyId is an int, so we are converting int -> long + Expression> fieldExpression = e => e.CompanyId; + + var memberInfo = fieldExpression.GetMemberAccess(); + + Assert.NotNull(memberInfo); + Assert.Equal("CompanyId", memberInfo.Name); + } + + [ConditionalFact] + public void Get_member_access_list_should_handle_convert() + { + Expression> expression = e => new { e.Id, e.CompanyId }; + + var memberInfos = expression.GetMemberAccessList(); + + Assert.NotNull(memberInfos); + Assert.Equal(2, memberInfos.Count); + Assert.Equal("Id", memberInfos.First().Name); + Assert.Equal("CompanyId", memberInfos.Last().Name); + } + + [ConditionalFact] + public void Get_member_access_list_should_throw_when_invalid_expression() + { + Expression> expression = e => new { P = e.Id + e.CompanyId }; + + Assert.Contains( + CoreStrings.InvalidMembersExpression(expression), + Assert.Throws(() => expression.GetMemberAccessList()).Message); + } + + [ConditionalFact] + public void Get_member_access_list_should_throw_when_member_access_not_on_the_provided_argument() + { + var closure = new ModelBuilderTest.EntityWithFields + { + Id = 1, + CompanyId = 100, + TenantId = 200 + }; + + Expression> expression = e => new { e.Id, closure.CompanyId }; + + Assert.Contains( + CoreStrings.InvalidMembersExpression(expression), + Assert.Throws(() => expression.GetMemberAccessList()).Message); + } } }