Skip to content

Commit 877d917

Browse files
authored
Merge pull request #2450 from AutoMapper/CollectionBug
Collection bug
2 parents 605941b + 6deb4c1 commit 877d917

File tree

4 files changed

+81
-13
lines changed

4 files changed

+81
-13
lines changed

src/AutoMapper/AutoMapper.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Summary>A convention-based object-object mapper</Summary>
55
<Description>A convention-based object-object mapper. AutoMapper uses a fluent configuration API to define an object-object mapping strategy. AutoMapper uses a convention-based matching algorithm to match up source to destination values. Currently, AutoMapper is designed for model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer.</Description>
6-
<VersionPrefix>6.2.1</VersionPrefix>
6+
<VersionPrefix>6.2.2</VersionPrefix>
77
<Authors>Jimmy Bogard</Authors>
88
<TargetFrameworks>netstandard1.3;netstandard1.1;net45;net40</TargetFrameworks>
99
<NoWarn>$(NoWarn);1591</NoWarn>

src/AutoMapper/Execution/DelegateFactory.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Linq.Expressions;
5+
using AutoMapper.Configuration;
6+
using AutoMapper.Mappers.Internal;
47

58
namespace AutoMapper.Execution
69
{
710
using static Expression;
8-
using static ExpressionBuilder;
11+
using static Internal.ExpressionFactory;
12+
using static ElementTypeHelper;
913

1014
public static class DelegateFactory
1115
{
@@ -44,6 +48,15 @@ public static Expression GenerateConstructorExpression(Type type)
4448
return Constant(null, typeof(string));
4549
}
4650

51+
if (type.IsInterface())
52+
{
53+
return type.ImplementsGenericInterface(typeof(IDictionary<,>))
54+
? CreateCollection(type, typeof(Dictionary<,>))
55+
: (type.ImplementsGenericInterface(typeof(ICollection<>))
56+
? CreateCollection(type, typeof(List<>))
57+
: InvalidType(type, $"Cannot create an instance of interface type {type}."));
58+
}
59+
4760
if (type.IsAbstract())
4861
{
4962
return InvalidType(type, $"Cannot create an instance of abstract type {type}.");
@@ -68,6 +81,15 @@ public static Expression GenerateConstructorExpression(Type type)
6881
return New(ctorWithOptionalArgs, args);
6982
}
7083

84+
private static Expression CreateCollection(Type type, Type collectionType)
85+
{
86+
var listType = collectionType.MakeGenericType(GetElementTypes(type, ElementTypeFlags.BreakKeyValuePair));
87+
if (type.IsAssignableFrom(listType))
88+
return ToType(New(listType), type);
89+
90+
return InvalidType(type, $"Cannot create an instance of interface type {type}.");
91+
}
92+
7193
private static Expression InvalidType(Type type, string message)
7294
{
7395
var ex = new ArgumentException(message, "type");

src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
namespace AutoMapper.Mappers.Internal
1010
{
1111
using static Expression;
12-
using static AutoMapper.Execution.ExpressionBuilder;
12+
using static ExpressionBuilder;
1313
using static ExpressionFactory;
14+
using static ElementTypeHelper;
1415

1516
public static class CollectionMapperExpressionFactory
1617
{
@@ -22,7 +23,7 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur
2223
{
2324
var passedDestination = Variable(destExpression.Type, "passedDestination");
2425
var newExpression = Variable(passedDestination.Type, "collectionDestination");
25-
var sourceElementType = ElementTypeHelper.GetElementType(sourceExpression.Type);
26+
var sourceElementType = GetElementType(sourceExpression.Type);
2627

2728
var itemExpr = mapItem(configurationProvider, profileMap, sourceExpression.Type, passedDestination.Type,
2829
contextExpression, out ParameterExpression itemParam);
@@ -33,7 +34,7 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur
3334
destinationCollectionType = typeof(IList);
3435
var addMethod = destinationCollectionType.GetDeclaredMethod("Add");
3536

36-
Expression destination, createInstance, assignNewExpression;
37+
Expression destination, assignNewExpression;
3738

3839
UseDestinationValue();
3940

@@ -46,7 +47,7 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur
4647
Assign(passedDestination, destExpression),
4748
assignNewExpression,
4849
Call(destination, clearMethod),
49-
ToType(mapExpr, createInstance.Type)
50+
mapExpr
5051
);
5152
if (propertyMap != null)
5253
return checkNull;
@@ -61,14 +62,13 @@ void UseDestinationValue()
6162
{
6263
if(propertyMap?.UseDestinationValue == true)
6364
{
64-
createInstance = passedDestination;
6565
destination = passedDestination;
6666
assignNewExpression = Empty();
6767
}
6868
else
6969
{
7070
destination = newExpression;
71-
createInstance = passedDestination.Type.NewExpr(ifInterfaceType);
71+
Expression createInstance = passedDestination.Type.NewExpr(ifInterfaceType);
7272
var isReadOnly = Property(ToType(passedDestination, destinationCollectionType), "IsReadOnly");
7373
assignNewExpression = Assign(newExpression,
7474
Condition(OrElse(Equal(passedDestination, Constant(null)), isReadOnly), ToType(createInstance, passedDestination.Type), passedDestination));
@@ -80,16 +80,16 @@ private static Expression NewExpr(this Type baseType, Type ifInterfaceType)
8080
{
8181
var newExpr = baseType.IsInterface()
8282
? New(
83-
ifInterfaceType.MakeGenericType(ElementTypeHelper.GetElementTypes(baseType,
83+
ifInterfaceType.MakeGenericType(GetElementTypes(baseType,
8484
ElementTypeFlags.BreakKeyValuePair)))
8585
: DelegateFactory.GenerateConstructorExpression(baseType);
8686
return newExpr;
8787
}
8888

8989
public static Expression MapItemExpr(IConfigurationProvider configurationProvider, ProfileMap profileMap, Type sourceType, Type destType, Expression contextParam, out ParameterExpression itemParam)
9090
{
91-
var sourceElementType = ElementTypeHelper.GetElementType(sourceType);
92-
var destElementType = ElementTypeHelper.GetElementType(destType);
91+
var sourceElementType = GetElementType(sourceType);
92+
var destElementType = GetElementType(destType);
9393
itemParam = Parameter(sourceElementType, "item");
9494

9595
var typePair = new TypePair(sourceElementType, destElementType);
@@ -100,8 +100,8 @@ public static Expression MapItemExpr(IConfigurationProvider configurationProvide
100100

101101
public static Expression MapKeyPairValueExpr(IConfigurationProvider configurationProvider, ProfileMap profileMap, Type sourceType, Type destType, Expression contextParam, out ParameterExpression itemParam)
102102
{
103-
var sourceElementTypes = ElementTypeHelper.GetElementTypes(sourceType, ElementTypeFlags.BreakKeyValuePair);
104-
var destElementTypes = ElementTypeHelper.GetElementTypes(destType, ElementTypeFlags.BreakKeyValuePair);
103+
var sourceElementTypes = GetElementTypes(sourceType, ElementTypeFlags.BreakKeyValuePair);
104+
var destElementTypes = GetElementTypes(destType, ElementTypeFlags.BreakKeyValuePair);
105105

106106
var typePairKey = new TypePair(sourceElementTypes[0], destElementTypes[0]);
107107
var typePairValue = new TypePair(sourceElementTypes[1], destElementTypes[1]);

src/UnitTests/CollectionMapping.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,52 @@ public void Should_map_to_NameValueCollection() {
616616
}
617617
}
618618

619+
public class When_mapping_from_ICollection_types_but_implementations_are_different : AutoMapperSpecBase
620+
{
621+
public class Source
622+
{
623+
public ICollection<Item> Items { get; set; }
624+
625+
public class Item
626+
{
627+
public int Value { get; set; }
628+
}
629+
}
630+
public class Dest
631+
{
632+
public ICollection<Item> Items { get; set; } = new HashSet<Item>();
633+
634+
public class Item
635+
{
636+
public int Value { get; set; }
637+
}
638+
}
639+
640+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
641+
{
642+
cfg.CreateMap<Source, Dest>();
643+
cfg.CreateMap<Source.Item, Dest.Item>();
644+
});
645+
646+
[Fact]
647+
public void Should_map_items()
648+
{
649+
var source = new Source
650+
{
651+
Items = new List<Source.Item>
652+
{
653+
new Source.Item { Value = 5 }
654+
}
655+
};
656+
var dest = new Dest();
657+
658+
Mapper.Map(source, dest);
659+
660+
dest.Items.Count.ShouldBe(1);
661+
dest.Items.First().Value.ShouldBe(5);
662+
}
663+
}
664+
619665
public class When_mapping_enumerable_to_array : AutoMapperSpecBase
620666
{
621667
public class Source

0 commit comments

Comments
 (0)