Skip to content

Commit c9bdff2

Browse files
authored
Merge pull request #2602 from KSid/project-extension-methods-#2593
Fix projection to destinations using extension methods #2593
2 parents 84ee9f8 + e963b4f commit c9bdff2

File tree

6 files changed

+277
-5
lines changed

6 files changed

+277
-5
lines changed

src/AutoMapper/QueryableExtensions/Impl/MemberGetterExpressionResultConverter.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ private static ExpressionResolutionResult ExpressionResolutionResult(
2121
private static ExpressionResolutionResult ExpressionResolutionResult(
2222
ExpressionResolutionResult expressionResolutionResult, MemberInfo getter)
2323
{
24-
var member = Expression.MakeMemberAccess(expressionResolutionResult.ResolutionExpression, getter);
24+
var member = (getter is MethodInfo method)
25+
? (Expression)Expression.Call(method, expressionResolutionResult.ResolutionExpression)
26+
: Expression.MakeMemberAccess(expressionResolutionResult.ResolutionExpression, getter);
2527
return new ExpressionResolutionResult(member, member.Type);
2628
}
2729

src/AutoMapper/TypeDetails.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,22 @@ private IEnumerable<MethodInfo> BuildPublicNoArgExtensionMethods(IEnumerable<Met
107107
{
108108
genericInterfaces = genericInterfaces.Union(new[] { Type });
109109
}
110+
110111
return explicitExtensionMethods.Union
111112
(
112-
from genericMethod in sourceExtensionMethodSearch
113-
where genericMethod.IsGenericMethodDefinition
114113
from genericInterface in genericInterfaces
115114
let genericInterfaceArguments = genericInterface.GetTypeInfo().GenericTypeArguments
116-
where genericMethod.GetGenericArguments().Length == genericInterfaceArguments.Length
117-
let methodMatch = genericMethod.MakeGenericMethod(genericInterfaceArguments)
115+
let matchedMethods = (
116+
from extensionMethod in sourceExtensionMethodSearch
117+
where !extensionMethod.IsGenericMethodDefinition
118+
select extensionMethod
119+
).Concat(
120+
from extensionMethod in sourceExtensionMethodSearch
121+
where extensionMethod.IsGenericMethodDefinition
122+
&& extensionMethod.GetGenericArguments().Length == genericInterfaceArguments.Length
123+
select extensionMethod.MakeGenericMethod(genericInterfaceArguments)
124+
)
125+
from methodMatch in matchedMethods
118126
where methodMatch.GetParameters()[0].ParameterType.GetTypeInfo().IsAssignableFrom(genericInterface.GetTypeInfo())
119127
select methodMatch
120128
).ToArray();
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Data.Entity;
3+
using System.Linq;
4+
using Shouldly;
5+
using Xunit;
6+
7+
namespace AutoMapper.IntegrationTests
8+
{
9+
using UnitTests;
10+
using QueryableExtensions;
11+
using System.Collections.Generic;
12+
13+
public class ICollectionAggregateProjections : AutoMapperSpecBase
14+
{
15+
public class Customer
16+
{
17+
[Key]
18+
public int Id { get; set; }
19+
public string FirstName { get; set; }
20+
public string LastName { get; set; }
21+
public ICollection<Item> Items { get; set; }
22+
}
23+
24+
public class Item
25+
{
26+
public int Id { get; set; }
27+
public int Code { get; set; }
28+
}
29+
30+
public class CustomerViewModel
31+
{
32+
public int ItemCodesCount { get; set; }
33+
public int ItemCodesMin { get; set; }
34+
public int ItemCodesMax { get; set; }
35+
public int ItemCodesSum { get; set; }
36+
}
37+
38+
public class Context : DbContext
39+
{
40+
public Context()
41+
{
42+
Database.SetInitializer<Context>(new DatabaseInitializer());
43+
}
44+
45+
public DbSet<Customer> Customers { get; set; }
46+
}
47+
48+
public class DatabaseInitializer : DropCreateDatabaseAlways<Context>
49+
{
50+
protected override void Seed(Context context)
51+
{
52+
context.Customers.Add(new Customer
53+
{
54+
Id = 1,
55+
FirstName = "Bob",
56+
LastName = "Smith",
57+
Items = new[] { new Item { Code = 1 }, new Item { Code = 3 }, new Item { Code = 5 } }
58+
});
59+
60+
base.Seed(context);
61+
}
62+
}
63+
64+
protected override MapperConfiguration Configuration => new MapperConfiguration(_ => { });
65+
66+
[Fact]
67+
public void Can_map_with_projection()
68+
{
69+
using (var context = new Context())
70+
{
71+
var result = context.Customers.Select(customer => new
72+
{
73+
ItemCodes = (ICollection<int>)customer.Items.Select(item => item.Code).ToList()
74+
}).ProjectTo<CustomerViewModel>(Configuration).Single();
75+
76+
result.ItemCodesCount.ShouldBe(3);
77+
result.ItemCodesMin.ShouldBe(1);
78+
result.ItemCodesMax.ShouldBe(5);
79+
result.ItemCodesSum.ShouldBe(9);
80+
}
81+
}
82+
}
83+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Data.Entity;
3+
using System.Linq;
4+
using Shouldly;
5+
using Xunit;
6+
7+
namespace AutoMapper.IntegrationTests
8+
{
9+
using UnitTests;
10+
using QueryableExtensions;
11+
using System.Collections.Generic;
12+
13+
public class IEnumerableAggregateProjections : AutoMapperSpecBase
14+
{
15+
public class Customer
16+
{
17+
[Key]
18+
public int Id { get; set; }
19+
public string FirstName { get; set; }
20+
public string LastName { get; set; }
21+
public ICollection<Item> Items { get; set; }
22+
}
23+
24+
public class Item
25+
{
26+
public int Id { get; set; }
27+
public int Code { get; set; }
28+
}
29+
30+
public class CustomerViewModel
31+
{
32+
public int ItemCodesCount { get; set; }
33+
public int ItemCodesMin { get; set; }
34+
public int ItemCodesMax { get; set; }
35+
public int ItemCodesSum { get; set; }
36+
}
37+
38+
public class Context : DbContext
39+
{
40+
public Context()
41+
{
42+
Database.SetInitializer<Context>(new DatabaseInitializer());
43+
}
44+
45+
public DbSet<Customer> Customers { get; set; }
46+
}
47+
48+
public class DatabaseInitializer : DropCreateDatabaseAlways<Context>
49+
{
50+
protected override void Seed(Context context)
51+
{
52+
context.Customers.Add(new Customer
53+
{
54+
Id = 1,
55+
FirstName = "Bob",
56+
LastName = "Smith",
57+
Items = new[] { new Item { Code = 1 }, new Item { Code = 3 }, new Item { Code = 5 } }
58+
});
59+
60+
base.Seed(context);
61+
}
62+
}
63+
64+
protected override MapperConfiguration Configuration => new MapperConfiguration(_ => { });
65+
66+
[Fact]
67+
public void Can_map_with_projection()
68+
{
69+
using (var context = new Context())
70+
{
71+
var result = context.Customers.Select(customer => new
72+
{
73+
ItemCodes = customer.Items.Select(item => item.Code)
74+
}).ProjectTo<CustomerViewModel>(Configuration).Single();
75+
76+
result.ItemCodesCount.ShouldBe(3);
77+
result.ItemCodesMin.ShouldBe(1);
78+
result.ItemCodesMax.ShouldBe(5);
79+
result.ItemCodesSum.ShouldBe(9);
80+
}
81+
}
82+
}
83+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Shouldly;
2+
using Xunit;
3+
using System.Linq;
4+
using AutoMapper.QueryableExtensions;
5+
using System.Collections.Generic;
6+
7+
namespace AutoMapper.UnitTests.Bug
8+
{
9+
public class CannotMapICollectionToAggregateSumDestination
10+
{
11+
class DummySource
12+
{
13+
public ICollection<int> DummyCollection { get; set; }
14+
}
15+
16+
class DummyDestination
17+
{
18+
public int DummyCollectionSum { get; set; }
19+
}
20+
21+
[Fact]
22+
public void Should_map_icollection_to_aggregate_sum_destination()
23+
{
24+
// arrange
25+
var config = new MapperConfiguration(cfg =>
26+
{
27+
cfg.CreateMap<DummySource, DummyDestination>();
28+
});
29+
30+
// act
31+
// do nothing
32+
33+
// assert
34+
config.AssertConfigurationIsValid();
35+
}
36+
37+
[Fact]
38+
public void Should_project_icollection_to_aggregate_sum_destination()
39+
{
40+
// arrange
41+
var config = new MapperConfiguration(_ => { });
42+
var source = new DummySource() { DummyCollection = new[] { 1, 4, 5 } };
43+
44+
// act
45+
var destination = new[] { source }.AsQueryable()
46+
.ProjectTo<DummyDestination>(config)
47+
.Single();
48+
49+
// assert
50+
destination.DummyCollectionSum.ShouldBe(10);
51+
}
52+
}
53+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Shouldly;
2+
using Xunit;
3+
using System.Linq;
4+
using AutoMapper.QueryableExtensions;
5+
using System.Collections.Generic;
6+
7+
namespace AutoMapper.UnitTests.Bug
8+
{
9+
public class CannotProjectIEnumerableToAggregateDestinations
10+
{
11+
class DummySource
12+
{
13+
public IEnumerable<int> DummyEnumerable { get; set; }
14+
}
15+
16+
class DummyDestination
17+
{
18+
public int DummyEnumerableCount { get; set; }
19+
public int DummyEnumerableSum { get; set; }
20+
public int DummyEnumerableMin { get; set; }
21+
public int DummyEnumerableMax { get; set; }
22+
}
23+
24+
[Fact]
25+
public void Should_project_ienumerable_to_aggregate_destinations()
26+
{
27+
// arrange
28+
var config = new MapperConfiguration(_ => { });
29+
var source = new DummySource() { DummyEnumerable = new[] { 1, 4, 5 } };
30+
31+
// act
32+
var destination = new[] { source }.AsQueryable()
33+
.ProjectTo<DummyDestination>(config)
34+
.Single();
35+
36+
// assert
37+
destination.DummyEnumerableCount.ShouldBe(3);
38+
destination.DummyEnumerableSum.ShouldBe(10);
39+
destination.DummyEnumerableMin.ShouldBe(1);
40+
destination.DummyEnumerableMax.ShouldBe(5);
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)