Skip to content

Commit

Permalink
Fix for 11410. Updating ModelBuilder APIs to use GetMemberAccess() (#…
Browse files Browse the repository at this point in the history
…20726)

Updating ModelBuilder tests to use GetMemberAccess() and add tests for OneToOne, OneToMany, ManyToOne, ManyToMany plus tests for OneToOne and OneToMany Owned Types.
  • Loading branch information
lajones authored Apr 25, 2020
1 parent 45a51cb commit 1d01584
Show file tree
Hide file tree
Showing 33 changed files with 974 additions and 198 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public static EntityTypeBuilder<TEntity> HasPartitionKey<TEntity, TProperty>(
{
Check.NotNull(propertyExpression, nameof(propertyExpression));

entityTypeBuilder.Metadata.SetPartitionKeyPropertyName(propertyExpression.GetPropertyAccess().GetSimpleMemberName());
entityTypeBuilder.Metadata.SetPartitionKeyPropertyName(propertyExpression.GetMemberAccess().GetSimpleMemberName());

return entityTypeBuilder;
}
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Migrations/MigrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ public virtual CreateTableBuilder<TColumns> CreateTable<TColumns>(

var columnsBuilder = new ColumnsBuilder(createTableOperation);
var columnsObject = columns(columnsBuilder);
var columnMap = new Dictionary<PropertyInfo, AddColumnOperation>();
var columnMap = new Dictionary<MemberInfo, AddColumnOperation>();
foreach (var property in typeof(TColumns).GetTypeInfo().DeclaredProperties)
{
var addColumnOperation = ((IInfrastructure<AddColumnOperation>)property.GetMethod.Invoke(columnsObject, null)).Instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Operations.Builders
/// <typeparam name="TColumns"> Type of a typically anonymous type for building columns. </typeparam>
public class CreateTableBuilder<TColumns> : OperationBuilder<CreateTableOperation>
{
private readonly IReadOnlyDictionary<PropertyInfo, AddColumnOperation> _columnMap;
private readonly IReadOnlyDictionary<MemberInfo, AddColumnOperation> _columnMap;

/// <summary>
/// Constructs a new builder for the given <see cref="CreateTableOperation" /> and
Expand All @@ -28,7 +28,7 @@ public class CreateTableBuilder<TColumns> : OperationBuilder<CreateTableOperatio
/// <param name="columnMap"> The map of CLR properties to <see cref="AddColumnOperation" />s. </param>
public CreateTableBuilder(
[NotNull] CreateTableOperation operation,
[NotNull] IReadOnlyDictionary<PropertyInfo, AddColumnOperation> columnMap)
[NotNull] IReadOnlyDictionary<MemberInfo, AddColumnOperation> columnMap)
: base(operation)
{
Check.NotNull(columnMap, nameof(columnMap));
Expand Down Expand Up @@ -191,6 +191,6 @@ public virtual OperationBuilder<CreateCheckConstraintOperation> CheckConstraint(
=> (CreateTableBuilder<TColumns>)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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public static IndexBuilder<TEntity> IncludeProperties<TEntity>(

IncludeProperties(
indexBuilder,
includeExpression.GetPropertyAccessList().Select(MemberInfoExtensions.GetSimpleMemberName).ToArray());
includeExpression.GetMemberAccessList().Select(MemberInfoExtensions.GetSimpleMemberName).ToArray());

return indexBuilder;
}
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/ChangeTracking/EntityEntry`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public virtual PropertyEntry<TEntity, TProperty> Property<TProperty>(
{
Check.NotNull(propertyExpression, nameof(propertyExpression));

return new PropertyEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName());
return new PropertyEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName());
}

/// <summary>
Expand All @@ -79,7 +79,7 @@ public virtual ReferenceEntry<TEntity, TProperty> Reference<TProperty>(
{
Check.NotNull(propertyExpression, nameof(propertyExpression));

return new ReferenceEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName());
return new ReferenceEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName());
}

/// <summary>
Expand All @@ -100,7 +100,7 @@ public virtual CollectionEntry<TEntity, TProperty> Collection<TProperty>(
{
Check.NotNull(propertyExpression, nameof(propertyExpression));

return new CollectionEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetPropertyAccess().GetSimpleMemberName());
return new CollectionEntry<TEntity, TProperty>(InternalEntry, propertyExpression.GetMemberAccess().GetSimpleMemberName());
}

/// <summary>
Expand Down
45 changes: 24 additions & 21 deletions src/EFCore/Extensions/Internal/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
[DebuggerStepThrough]
public static class ExpressionExtensions
{
/// <summary>
Expand All @@ -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.
/// </summary>
public static IReadOnlyList<PropertyInfo> MatchPropertyAccessList(
[NotNull] this LambdaExpression lambdaExpression, [NotNull] Func<Expression, Expression, PropertyInfo> propertyMatcher)
public static IReadOnlyList<TMemberInfo> MatchMemberAccessList<TMemberInfo>(
[NotNull] this LambdaExpression lambdaExpression, [NotNull] Func<Expression, Expression, TMemberInfo> 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;
}

/// <summary>
Expand All @@ -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.
/// </summary>
public static PropertyInfo MatchSimplePropertyAccess(
[NotNull] this Expression parameterExpression, [NotNull] Expression propertyAccessExpression)
public static TMemberInfo MatchSimpleMemberAccess<TMemberInfo>(
[NotNull] this Expression parameterExpression, [NotNull] Expression memberAccessExpression)
where TMemberInfo : MemberInfo
{
var propertyInfos = MatchPropertyAccess(parameterExpression, propertyAccessExpression);
var memberInfos = MatchMemberAccess<TMemberInfo>(parameterExpression, memberAccessExpression);

return propertyInfos?.Count == 1 ? propertyInfos[0] : null;
return memberInfos?.Count == 1 ? memberInfos[0] as TMemberInfo : null;
}

private static IReadOnlyList<PropertyInfo> MatchPropertyAccess(
this Expression parameterExpression, Expression propertyAccessExpression)
private static IReadOnlyList<TMemberInfo> MatchMemberAccess<TMemberInfo>(
this Expression parameterExpression, Expression memberAccessExpression)
where TMemberInfo : MemberInfo
{
var propertyInfos = new List<PropertyInfo>();
var memberInfos = new List<TMemberInfo>();

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;
}

/// <summary>
Expand Down
82 changes: 65 additions & 17 deletions src/EFCore/Infrastructure/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,28 +149,46 @@ public static bool TryGetIndexerArguments(
/// <param name="propertyAccessExpression"> The expression. </param>
/// <returns> The <see cref="PropertyInfo" />. </returns>
public static PropertyInfo GetPropertyAccess([NotNull] this LambdaExpression propertyAccessExpression)
=> GetInternalMemberAccess<PropertyInfo>(propertyAccessExpression);

/// <summary>
/// <para>
/// Gets the <see cref="MemberInfo" /> represented by a simple member-access expression.
/// </para>
/// <para>
/// This method is typically used to parse member access lambdas from fluent APIs.
/// </para>
/// </summary>
/// <param name="memberAccessExpression"> The expression. </param>
/// <returns> The <see cref="MemberInfo" />. </returns>
public static MemberInfo GetMemberAccess([NotNull] this LambdaExpression memberAccessExpression)
=> GetInternalMemberAccess<MemberInfo>(memberAccessExpression);

private static TMemberInfo GetInternalMemberAccess<TMemberInfo>([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<TMemberInfo>(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);
Expand All @@ -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;
}

/// <summary>
Expand All @@ -195,8 +213,6 @@ public static PropertyInfo GetPropertyAccess([NotNull] this LambdaExpression pro
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// This method is typically used by database providers (and other extensions). It is generally
Expand All @@ -212,23 +228,55 @@ public static IReadOnlyList<PropertyInfo> 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<PropertyInfo>(p));

if (propertyPaths == null)
{
throw new ArgumentException(
CoreStrings.InvalidPropertiesExpression(propertyAccessExpression),
CoreStrings.InvalidMembersExpression(propertyAccessExpression),
nameof(propertyAccessExpression));
}

return propertyPaths;
}

/// <summary>
/// <para>
/// Returns a list of <see cref="MemberInfo" /> extracted from the given simple
/// <see cref="LambdaExpression" />.
/// </para>
/// <para>
/// Only simple expressions are supported, such as those used to reference a member.
/// </para>
/// <para>
/// This method is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <param name="memberAccessExpression"> The expression. </param>
/// <returns> The list of referenced members. </returns>
public static IReadOnlyList<MemberInfo> GetMemberAccessList([NotNull] this LambdaExpression memberAccessExpression)
{
Check.NotNull(memberAccessExpression, nameof(memberAccessExpression));

var memberPaths = memberAccessExpression
.MatchMemberAccessList((p, e) => e.MatchSimpleMemberAccess<MemberInfo>(p));

if (memberPaths == null)
{
throw new ArgumentException(
CoreStrings.InvalidMembersExpression(memberAccessExpression),
nameof(memberAccessExpression));
}

return memberPaths;
}

/// <summary>
/// <para>
/// Creates an <see cref="Expression" /> tree representing reading a value from a <see cref="ValueBuffer" />
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne(
=> new ReferenceCollectionBuilder<TEntity, TRelatedEntity>(
DeclaringEntityType,
RelatedEntityType,
WithOneBuilder(navigationExpression?.GetPropertyAccess()).Metadata);
WithOneBuilder(navigationExpression?.GetMemberAccess()).Metadata);

/// <summary>
/// Configures this as a many-to-many relationship.
Expand Down Expand Up @@ -129,8 +129,8 @@ public virtual CollectionCollectionBuilder<TRelatedEntity, TEntity> WithMany(
return new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationExpression.GetPropertyAccess()),
WithRightManyNavigation(navigationExpression.GetPropertyAccess(), leftName));
WithLeftManyNavigation(navigationExpression.GetMemberAccess()),
WithRightManyNavigation(navigationExpression.GetMemberAccess(), leftName));
}
}
}
Loading

0 comments on commit 1d01584

Please sign in to comment.