Skip to content

Fix to map members of the generic type definition during expression mapping #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AutoMapper.Extensions.ExpressionMapping.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
VisualStudioVersion = 15.0.27130.2020
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Extensions.ExpressionMapping", "src\AutoMapper.Extensions.ExpressionMapping\AutoMapper.Extensions.ExpressionMapping.csproj", "{24DF305C-EE59-460A-BA97-4B7CD5505434}"
EndProject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.0" />
<PackageReference Include="AutoMapper" Version="7.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace AutoMapper
{
using AutoMapper.Extensions.ExpressionMapping;
using static Expression;

internal static class ExpressionExtensions
Expand Down Expand Up @@ -56,4 +57,23 @@ public static Expression NullCheck(this Expression expression, Type destinationT
public static Expression IfNullElse(this Expression expression, Expression then, Expression @else = null)
=> ExpressionFactory.IfNullElse(expression, then, @else);
}

internal static class ExpressionHelpers
{
public static MemberExpression MemberAccesses(string members, Expression obj) =>
(MemberExpression)GetMemberPath(obj.Type, members).MemberAccesses(obj);

private static IEnumerable<MemberInfo> GetMemberPath(Type type, string fullMemberName)
{
MemberInfo property = null;
foreach (var memberName in fullMemberName.Split('.'))
{
var currentType = GetCurrentType(property, type);
yield return property = currentType.GetFieldOrProperty(memberName);
}
}

private static Type GetCurrentType(MemberInfo member, Type type)
=> member?.GetMemberType() ?? type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public MemberExpression Result
return result;
});

return ExpressionFactory.MemberAccesses(member, _newParameter);
return ExpressionHelpers.MemberAccesses(member, _newParameter);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected override Expression VisitMember(MemberExpression node)
return v.Result;
}
fullName = BuildFullName(propertyMapInfoList);
var me = ExpressionFactory.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);
var me = ExpressionHelpers.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);
if (me.Expression.NodeType == ExpressionType.MemberAccess && (me.Type == typeof(string) || me.Type.GetTypeInfo().IsValueType || (me.Type.GetTypeInfo().IsGenericType
&& me.Type.GetGenericTypeDefinition() == typeof(Nullable<>)
&& Nullable.GetUnderlyingType(me.Type).GetTypeInfo().IsValueType)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected override Expression VisitMember(MemberExpression node)
? sourcePath
: string.Concat(ParentFullName, ".", sourcePath);

var me = ExpressionFactory.MemberAccesses(fullName, NewParameter);
var me = ExpressionHelpers.MemberAccesses(fullName, NewParameter);

return me;
}
Expand Down
5 changes: 0 additions & 5 deletions src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ internal static class TypeExtensions
public static MethodInfo GetRemoveMethod(this EventInfo eventInfo) => eventInfo.RemoveMethod;
#endif

public static Type CreateType(this TypeBuilder type)
{
return type.CreateTypeInfo().AsType();
}

public static IEnumerable<MemberInfo> GetDeclaredMembers(this Type type) => type.GetTypeInfo().DeclaredMembers;

public static IEnumerable<Type> GetTypeInheritance(this Type type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,21 @@ protected override Expression VisitMember(MemberExpression node)
return ex;
}
fullName = BuildFullName(propertyMapInfoList);
var me = ExpressionFactory.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);
var me = ExpressionHelpers.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);

this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, me.Type);
return me;
}

protected override Expression VisitLambda<T>(Expression<T> node)
{
var ex = this.Visit(node.Body);

var mapped = Expression.Lambda(ex, node.GetDestinationParameterExpressions(this.InfoDictionary, this.TypeMappings));
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type);
return mapped;
}

protected override Expression VisitUnary(UnaryExpression node)
{
switch (node.NodeType)
Expand All @@ -109,6 +118,13 @@ protected override Expression VisitUnary(UnaryExpression node)
default:
return base.VisitUnary(node);
}
case ExpressionType.Lambda:
var lambdaExpression = (LambdaExpression)node.Operand;
var ex = this.Visit(lambdaExpression.Body);

var mapped = Expression.Quote(Expression.Lambda(ex, lambdaExpression.GetDestinationParameterExpressions(this.InfoDictionary, this.TypeMappings)));
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type);
return mapped;
default:
return base.VisitUnary(node);
}
Expand Down Expand Up @@ -137,7 +153,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node)

var listOfArgumentsForNewMethod = node.Arguments.Aggregate(new List<Expression>(), (lst, next) =>
{
var mappedNext = ArgumentMapper.Create(this, next).MappedArgumentExpression;
//var mappedNext = ArgumentMapper.Create(this, next).MappedArgumentExpression;
//Using VisitUnary and VisitLambda instead of ArgumentMappers
var mappedNext = this.Visit(next);
TypeMappings.AddTypeMapping(ConfigurationProvider, next.Type, mappedNext.Type);

lst.Add(mappedNext);
Expand All @@ -151,9 +169,14 @@ protected override Expression VisitMethodCall(MethodCallExpression node)

ConvertTypesIfNecessary(node.Method.GetParameters(), listOfArgumentsForNewMethod, node.Method);

/*Using VisitUnary and VisitLambda instead of ArgumentMappers
* return node.Method.IsStatic
? GetStaticExpression()
: GetInstanceExpression(ArgumentMapper.Create(this, node.Object).MappedArgumentExpression);*/

return node.Method.IsStatic
? GetStaticExpression()
: GetInstanceExpression(ArgumentMapper.Create(this, node.Object).MappedArgumentExpression);
: GetInstanceExpression(this.Visit(node.Object));

MethodCallExpression GetInstanceExpression(Expression instance)
=> node.Method.IsGenericMethod
Expand Down Expand Up @@ -225,15 +248,18 @@ private static void AddPropertyMapInfo(Type parentType, string name, List<Proper
}
}

private bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDestination)
=> typeSource.IsGenericType() && typeDestination.IsGenericType() && typeSource.GetGenericTypeDefinition() == typeDestination.GetGenericTypeDefinition();

protected void FindDestinationFullName(Type typeSource, Type typeDestination, string sourceFullName, List<PropertyMapInfo> propertyMapInfoList)
{
const string period = ".";

if (typeSource == typeDestination)
{
var sourceFullNameArray = sourceFullName.Split(new[] { period[0] }, StringSplitOptions.RemoveEmptyEntries);
sourceFullNameArray.Aggregate(propertyMapInfoList, (list, next) =>
{

if (list.Count == 0)
{
AddPropertyMapInfo(typeSource, next, list);
Expand All @@ -250,6 +276,36 @@ protected void FindDestinationFullName(Type typeSource, Type typeDestination, st
return;
}

if (GenericTypeDefinitionsAreEquivalent(typeSource, typeDestination))
{
if (sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) < 0)
{
//sourceFullName is a member of the generic type definition so just add the members PropertyMapInfo
AddPropertyMapInfo(typeDestination, sourceFullName, propertyMapInfoList);
var sourceType = typeSource.GetFieldOrProperty(sourceFullName).GetMemberType();
var destType = typeDestination.GetFieldOrProperty(sourceFullName).GetMemberType();

TypeMappings.AddTypeMapping(ConfigurationProvider, sourceType, destType);

return;
}
else
{
//propertyName is a member of the generic type definition so just add the members PropertyMapInfo
var propertyName = sourceFullName.Substring(0, sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase));
AddPropertyMapInfo(typeDestination, propertyName, propertyMapInfoList);

var sourceType = typeSource.GetFieldOrProperty(propertyName).GetMemberType();
var destType = typeDestination.GetFieldOrProperty(propertyName).GetMemberType();

TypeMappings.AddTypeMapping(ConfigurationProvider, sourceType, destType);

var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1);
FindDestinationFullName(sourceType, destType, childFullName, propertyMapInfoList);
return;
}
}

var typeMap = ConfigurationProvider.CheckIfMapExists(sourceType: typeDestination, destinationType: typeSource);//The destination becomes the source because to map a source expression to a destination expression,
//we need the expressions used to create the source from the destination

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using Shouldly;
using Xunit;
using AutoMapper;

namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
{
Expand Down Expand Up @@ -411,6 +412,32 @@ public void Map_orderBy_thenBy_GroupBy_expression()
Assert.True(users[0].Count() == 2);
}

[Fact]
public void Map_orderBy_thenBy_GroupBy_Select_expression()
{
//Arrange
Expression<Func<IQueryable<UserModel>, IQueryable<object>>> grouped = q => q.OrderBy(s => s.Id).ThenBy(s => s.FullName).GroupBy(s => s.AgeInYears).Select(grp => new { Id = grp.Key, Count = grp.Count() });

//Act
Expression<Func<IQueryable<User>, IQueryable<object>>> expMapped = mapper.MapExpression<Expression<Func<IQueryable<User>, IQueryable<object>>>>(grouped);
List<dynamic> users = expMapped.Compile().Invoke(Users).ToList();

Assert.True(users[0].Count == 2);
}

[Fact]
public void Map_orderBy_thenBy_To_Dictionary_Select_expression()
{
//Arrange
Expression<Func<IQueryable<UserModel>, IEnumerable<object>>> grouped = q => q.OrderBy(s => s.Id).ThenBy(s => s.FullName).ToDictionary(kvp => kvp.Id).Select(grp => new { Id = grp.Key, Name = grp.Value.FullName });

//Act
Expression<Func<IQueryable<User>, IEnumerable<object>>> expMapped = mapper.MapExpression<Expression<Func<IQueryable<User>, IEnumerable<object>>>>(grouped);
List<dynamic> users = expMapped.Compile().Invoke(Users).ToList();

Assert.True(users[0].Id == 11);
}

[Fact]
public void Map_dynamic_return_type()
{
Expand Down