Skip to content

Commit 86dd7df

Browse files
authored
Merge pull request #103 from AutoMapper/ImprovingAnonymousTypeExpressionMapping
Mapping MemberInit and Expression.New for anonymous types.
2 parents 9f1dc09 + 65dc62f commit 86dd7df

File tree

3 files changed

+160
-2
lines changed

3 files changed

+160
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Reflection.Emit;
6+
7+
namespace AutoMapper.Extensions.ExpressionMapping
8+
{
9+
internal static class AnonymousTypeFactory
10+
{
11+
private static int classCount;
12+
13+
public static Type CreateAnonymousType(IEnumerable<MemberInfo> memberDetails)
14+
=> CreateAnonymousType(memberDetails.ToDictionary(key => key.Name, element => element.GetMemberType()));
15+
16+
public static Type CreateAnonymousType(IDictionary<string, Type> memberDetails)
17+
{
18+
AssemblyName dynamicAssemblyName = new AssemblyName("TempAssembly.AutoMapper.Extensions.ExpressionMapping");
19+
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
20+
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("TempAssembly.AutoMapper.Extensions.ExpressionMapping");
21+
TypeBuilder typeBuilder = dynamicModule.DefineType(GetAnonymousTypeName(), TypeAttributes.Public);
22+
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
23+
24+
var builders = memberDetails.Select
25+
(
26+
info =>
27+
{
28+
Type memberType = info.Value;
29+
string memberName = info.Key;
30+
return new
31+
{
32+
FieldBuilder = typeBuilder.DefineField(string.Concat("_", memberName), memberType, FieldAttributes.Private),
33+
PropertyBuilder = typeBuilder.DefineProperty(memberName, PropertyAttributes.HasDefault, memberType, null),
34+
GetMethodBuilder = typeBuilder.DefineMethod(string.Concat("get_", memberName), getSetAttr, memberType, Type.EmptyTypes),
35+
SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, new Type[] { memberType })
36+
};
37+
}
38+
);
39+
40+
builders.ToList().ForEach(builder =>
41+
{
42+
ILGenerator getMethodIL = builder.GetMethodBuilder.GetILGenerator();
43+
getMethodIL.Emit(OpCodes.Ldarg_0);
44+
getMethodIL.Emit(OpCodes.Ldfld, builder.FieldBuilder);
45+
getMethodIL.Emit(OpCodes.Ret);
46+
47+
ILGenerator setMethodIL = builder.SetMethodBuilder.GetILGenerator();
48+
setMethodIL.Emit(OpCodes.Ldarg_0);
49+
setMethodIL.Emit(OpCodes.Ldarg_1);
50+
setMethodIL.Emit(OpCodes.Stfld, builder.FieldBuilder);
51+
setMethodIL.Emit(OpCodes.Ret);
52+
53+
builder.PropertyBuilder.SetGetMethod(builder.GetMethodBuilder);
54+
builder.PropertyBuilder.SetSetMethod(builder.SetMethodBuilder);
55+
});
56+
57+
return typeBuilder.CreateTypeInfo().AsType();
58+
}
59+
60+
private static string GetAnonymousTypeName()
61+
=> $"AnonymousType{++classCount}";
62+
}
63+
}

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

+57-2
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,37 @@ protected override Expression VisitLambda<T>(Expression<T> node)
161161
protected override Expression VisitNew(NewExpression node)
162162
{
163163
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
164+
{
164165
return Expression.New(newType);
166+
}
167+
else if (node.Arguments.Count > 0 && IsAnonymousType(node.Type))
168+
{
169+
ParameterInfo[] parameters = node.Type.GetConstructors()[0].GetParameters();
170+
Dictionary<string, Expression> bindingExpressions = new Dictionary<string, Expression>();
171+
172+
for (int i = 0; i < parameters.Length; i++)
173+
bindingExpressions.Add(parameters[i].Name, this.Visit(node.Arguments[i]));
174+
175+
return GetMemberInitExpression(bindingExpressions, node.Type);
176+
}
165177

166178
return base.VisitNew(node);
167179
}
168180

181+
private static bool IsAnonymousType(Type type)
182+
=> type.Name.Contains("AnonymousType")
183+
&&
184+
(
185+
Attribute.IsDefined
186+
(
187+
type,
188+
typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
189+
false
190+
)
191+
||
192+
type.Assembly.IsDynamic
193+
);
194+
169195
protected override Expression VisitMemberInit(MemberInitExpression node)
170196
{
171197
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
@@ -193,9 +219,36 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
193219
)
194220
);
195221
}
222+
else if (IsAnonymousType(node.Type))
223+
{
224+
return GetMemberInitExpression
225+
(
226+
node.Bindings
227+
.OfType<MemberAssignment>()
228+
.ToDictionary
229+
(
230+
binding => binding.Member.Name,
231+
binding => this.Visit(binding.Expression)
232+
),
233+
node.Type
234+
);
235+
}
196236

197237
return base.VisitMemberInit(node);
238+
}
198239

240+
private MemberInitExpression GetMemberInitExpression(Dictionary<string, Expression> bindingExpressions, Type oldType)
241+
{
242+
Type newAnonymousType = AnonymousTypeFactory.CreateAnonymousType(bindingExpressions.ToDictionary(a => a.Key, a => a.Value.Type));
243+
TypeMappings.AddTypeMapping(ConfigurationProvider, oldType, newAnonymousType);
244+
245+
return Expression.MemberInit
246+
(
247+
Expression.New(newAnonymousType),
248+
bindingExpressions
249+
.ToDictionary(be => be.Key, be => newAnonymousType.GetMember(be.Key)[0])
250+
.Select(member => Expression.Bind(member.Value, bindingExpressions[member.Key]))
251+
);
199252
}
200253

201254
private MemberInitExpression GetMemberInit(MemberBindingGroup memberBindingGroup)
@@ -537,8 +590,10 @@ private bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDesti
537590
protected void FindDestinationFullName(Type typeSource, Type typeDestination, string sourceFullName, List<PropertyMapInfo> propertyMapInfoList)
538591
{
539592
const string period = ".";
540-
541-
if (typeSource == typeDestination)
593+
bool BothTypesAreAnonymous()
594+
=> IsAnonymousType(typeSource) && IsAnonymousType(typeDestination);
595+
596+
if (typeSource == typeDestination || BothTypesAreAnonymous())
542597
{
543598
var sourceFullNameArray = sourceFullName.Split(new[] { period[0] }, StringSplitOptions.RemoveEmptyEntries);
544599
sourceFullNameArray.Aggregate(propertyMapInfoList, (list, next) =>

tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs

+40
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,46 @@ public void Map_orderBy_thenBy_To_Dictionary_Select_expression_without_generic_t
635635
Assert.True(users[0].Id == 11);
636636
}
637637

638+
[Fact]
639+
public void Map_to_anonymous_type_when_init_member_is_not_a_literal()
640+
{
641+
//Arrange
642+
Expression<Func<IQueryable<UserModel>, IEnumerable<object>>> expression = q => q.OrderBy(s => s.Id).Select(u => new { UserId = u.Id, Account = u.AccountModel });
643+
644+
//Act
645+
Expression<Func<IQueryable<User>, IEnumerable<object>>> expMapped = (Expression<Func<IQueryable<User>, IEnumerable<object>>>)mapper.MapExpression
646+
(
647+
expression,
648+
typeof(Expression<Func<IQueryable<UserModel>, IEnumerable<object>>>),
649+
typeof(Expression<Func<IQueryable<User>, IEnumerable<object>>>)
650+
);
651+
652+
List<dynamic> users = expMapped.Compile().Invoke(Users).ToList();
653+
654+
Assert.True(users[0].UserId == 11);
655+
Assert.True(users[0].Account.Balance == 150000);
656+
}
657+
658+
[Fact]
659+
public void Map_to_anonymous_type_when_init_member_is_not_a_literal_with_navigation_property()
660+
{
661+
//Arrange
662+
Expression<Func<IQueryable<UserModel>, IEnumerable<object>>> expression = q => q.OrderBy(s => s.Id).Select(u => new { UserId = u.Id, Branch = u.AccountModel.Branch });
663+
664+
//Act
665+
Expression<Func<IQueryable<User>, IEnumerable<object>>> expMapped = (Expression<Func<IQueryable<User>, IEnumerable<object>>>)mapper.MapExpression
666+
(
667+
expression,
668+
typeof(Expression<Func<IQueryable<UserModel>, IEnumerable<object>>>),
669+
typeof(Expression<Func<IQueryable<User>, IEnumerable<object>>>)
670+
);
671+
672+
List<dynamic> users = expMapped.Compile().Invoke(Users).ToList();
673+
674+
Assert.True(users[0].UserId == 11);
675+
Assert.True(users[0].Branch.Name == "Head Row");
676+
}
677+
638678
[Fact]
639679
public void Map_dynamic_return_type()
640680
{

0 commit comments

Comments
 (0)