Skip to content

Commit 43494bd

Browse files
authored
Merge pull request #2494 from lbargaoanu/let_clause_explicit_expansion
Ignore properties that are not expanded
2 parents 741bd66 + 25c2806 commit 43494bd

File tree

2 files changed

+118
-37
lines changed

2 files changed

+118
-37
lines changed

src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,11 @@ protected override Expression VisitNew(NewExpression node)
204204
}
205205
}
206206

207-
private List<MemberBinding> CreateMemberBindings(ExpressionRequest request,
208-
TypeMap typeMap,
209-
Expression instanceParameter, TypePairCount typePairCount, LetPropertyMaps letPropertyMaps)
207+
private List<MemberBinding> CreateMemberBindings(ExpressionRequest request, TypeMap typeMap, Expression instanceParameter, TypePairCount typePairCount, LetPropertyMaps letPropertyMaps)
210208
{
211209
var bindings = new List<MemberBinding>();
212-
213-
foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm => pm.CanResolveValue() && ReflectionHelper.CanBeSet(pm.DestinationProperty)))
210+
foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm =>
211+
(!pm.ExplicitExpansion || request.MembersToExpand.Contains(pm.DestinationProperty)) && pm.CanResolveValue() && ReflectionHelper.CanBeSet(pm.DestinationProperty)))
214212
{
215213
letPropertyMaps.Push(propertyMap);
216214

@@ -222,44 +220,33 @@ private List<MemberBinding> CreateMemberBindings(ExpressionRequest request,
222220
void CreateMemberBinding(PropertyMap propertyMap)
223221
{
224222
var result = ResolveExpression(propertyMap, request.SourceType, instanceParameter, letPropertyMaps);
225-
226-
if(propertyMap.ExplicitExpansion && !request.MembersToExpand.Contains(propertyMap.DestinationProperty))
223+
var propertyTypeMap = _configurationProvider.ResolveTypeMap(result.Type, propertyMap.DestinationPropertyType);
224+
var propertyRequest = new ExpressionRequest(result.Type, propertyMap.DestinationPropertyType, request.MembersToExpand, request);
225+
if(propertyRequest.AlreadyExists)
227226
{
228227
return;
229228
}
230-
var propertyTypeMap = _configurationProvider.ResolveTypeMap(result.Type,
231-
propertyMap.DestinationPropertyType);
232-
var propertyRequest = new ExpressionRequest(result.Type, propertyMap.DestinationPropertyType, request.MembersToExpand, request);
233-
234-
if(!propertyRequest.AlreadyExists)
229+
var binder = Binders.FirstOrDefault(b => b.IsMatch(propertyMap, propertyTypeMap, result));
230+
if(binder == null)
235231
{
236-
var binder = Binders.FirstOrDefault(b => b.IsMatch(propertyMap, propertyTypeMap, result));
237-
238-
if(binder == null)
239-
{
240-
var message =
241-
$"Unable to create a map expression from {propertyMap.SourceMember?.DeclaringType?.Name}.{propertyMap.SourceMember?.Name} ({result.Type}) to {propertyMap.DestinationProperty.DeclaringType?.Name}.{propertyMap.DestinationProperty.Name} ({propertyMap.DestinationPropertyType})";
242-
243-
throw new AutoMapperMappingException(message, null, typeMap.Types, typeMap, propertyMap);
244-
}
245-
246-
var bindExpression = binder.Build(_configurationProvider, propertyMap, propertyTypeMap,
247-
propertyRequest, result, typePairCount, letPropertyMaps);
248-
249-
250-
if (bindExpression != null)
251-
{
252-
var rhs = propertyMap.ValueTransformers
253-
.Concat(typeMap.ValueTransformers)
254-
.Concat(typeMap.Profile.ValueTransformers)
255-
.Where(vt => vt.IsMatch(propertyMap))
256-
.Aggregate(bindExpression.Expression, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));
232+
var message =
233+
$"Unable to create a map expression from {propertyMap.SourceMember?.DeclaringType?.Name}.{propertyMap.SourceMember?.Name} ({result.Type}) to {propertyMap.DestinationProperty.DeclaringType?.Name}.{propertyMap.DestinationProperty.Name} ({propertyMap.DestinationPropertyType})";
234+
throw new AutoMapperMappingException(message, null, typeMap.Types, typeMap, propertyMap);
235+
}
236+
var bindExpression = binder.Build(_configurationProvider, propertyMap, propertyTypeMap, propertyRequest, result, typePairCount, letPropertyMaps);
237+
if(bindExpression == null)
238+
{
239+
return;
240+
}
241+
var rhs = propertyMap.ValueTransformers
242+
.Concat(typeMap.ValueTransformers)
243+
.Concat(typeMap.Profile.ValueTransformers)
244+
.Where(vt => vt.IsMatch(propertyMap))
245+
.Aggregate(bindExpression.Expression, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));
257246

258-
bindExpression = bindExpression.Update(rhs);
247+
bindExpression = bindExpression.Update(rhs);
259248

260-
bindings.Add(bindExpression);
261-
}
262-
}
249+
bindings.Add(bindExpression);
263250
}
264251
}
265252

src/IntegrationTests/CustomMapFrom/MapObjectPropertyFromSubQuery.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,100 @@ namespace AutoMapper.IntegrationTests
1111
using System.Linq.Expressions;
1212
using QueryableExtensions;
1313

14+
public class MapObjectPropertyFromSubQueryExplicitExpansion : AutoMapperSpecBase
15+
{
16+
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
17+
{
18+
cfg.CreateMap<Product, ProductModel>()
19+
.ForMember(d => d.Price, o =>
20+
{
21+
o.MapFrom(source => source.Articles.Where(x => x.IsDefault && x.NationId == 1 && source.ECommercePublished).FirstOrDefault());
22+
o.ExplicitExpansion();
23+
});
24+
cfg.CreateMap<Article, PriceModel>()
25+
.ForMember(d => d.RegionId, o => o.MapFrom(s => s.NationId));
26+
});
27+
28+
[Fact]
29+
public void Should_map_ok()
30+
{
31+
using(var context = new ClientContext())
32+
{
33+
var projection = context.Products.ProjectTo<ProductModel>(Configuration);
34+
var counter = new FirstOrDefaultCounter();
35+
counter.Visit(projection.Expression);
36+
counter.Count.ShouldBe(0);
37+
var productModel = projection.First();
38+
productModel.Price.ShouldBeNull();
39+
productModel.Id.ShouldBe(1);
40+
}
41+
}
42+
43+
class FirstOrDefaultCounter : ExpressionVisitor
44+
{
45+
public int Count;
46+
47+
protected override Expression VisitMethodCall(MethodCallExpression node)
48+
{
49+
if(node.Method.Name == "FirstOrDefault")
50+
{
51+
Count++;
52+
}
53+
return base.VisitMethodCall(node);
54+
}
55+
}
56+
57+
public partial class Article
58+
{
59+
public int Id { get; set; }
60+
public int ProductId { get; set; }
61+
public bool IsDefault { get; set; }
62+
public short NationId { get; set; }
63+
public virtual Product Product { get; set; }
64+
}
65+
66+
public partial class Product
67+
{
68+
public int Id { get; set; }
69+
public string Name { get; set; }
70+
public bool ECommercePublished { get; set; }
71+
public virtual ICollection<Article> Articles { get; set; }
72+
public int Value { get; }
73+
public virtual List<Article> OtherArticles { get; }
74+
}
75+
76+
public class PriceModel
77+
{
78+
public int Id { get; set; }
79+
public short RegionId { get; set; }
80+
public bool IsDefault { get; set; }
81+
}
82+
83+
public class ProductModel
84+
{
85+
public int Id { get; set; }
86+
public PriceModel Price { get; set; }
87+
}
88+
89+
class Initializer : DropCreateDatabaseAlways<ClientContext>
90+
{
91+
protected override void Seed(ClientContext context)
92+
{
93+
context.Products.Add(new Product { ECommercePublished = true, Articles = new[] { new Article { IsDefault = true, NationId = 1, ProductId = 1 } } });
94+
}
95+
}
96+
97+
class ClientContext : DbContext
98+
{
99+
protected override void OnModelCreating(DbModelBuilder modelBuilder)
100+
{
101+
Database.SetInitializer(new Initializer());
102+
}
103+
104+
public DbSet<Product> Products { get; set; }
105+
}
106+
}
107+
14108
public class MapObjectPropertyFromSubQuery : AutoMapperSpecBase
15109
{
16110
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg=>

0 commit comments

Comments
 (0)