Skip to content

Commit 36b2be2

Browse files
authored
Merge pull request #2940 from AutoMapper/flow-mapper-and-context
Flow resolution context to nested mappings
2 parents bdb8eec + 5361f44 commit 36b2be2

File tree

3 files changed

+229
-46
lines changed

3 files changed

+229
-46
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<VersionPrefix>8.0.1</VersionPrefix>
4+
<VersionPrefix>8.1.0</VersionPrefix>
55
<Authors>Jimmy Bogard</Authors>
66
<LangVersion>latest</LangVersion>
77
<WarningsAsErrors>true</WarningsAsErrors>

src/AutoMapper/ResolutionContext.cs

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using AutoMapper.Execution;
36

47
namespace AutoMapper
58
{
69
/// <summary>
710
/// Context information regarding resolution of a destination value
811
/// </summary>
9-
public class ResolutionContext
12+
public class ResolutionContext : IRuntimeMapper
1013
{
1114
private Dictionary<ContextCacheKey, object> _instanceCache;
1215
private Dictionary<TypePair, int> _typeDepth;
16+
private readonly IRuntimeMapper _inner;
17+
18+
public ResolutionContext(IMappingOperationOptions options, IRuntimeMapper mapper)
19+
{
20+
Options = options;
21+
_inner = mapper;
22+
}
1323

1424
/// <summary>
1525
/// Mapping operation options
1626
/// </summary>
1727
public IMappingOperationOptions Options { get; }
1828

19-
internal object GetDestination(object source, Type destinationType)
20-
{
21-
InstanceCache.TryGetValue(new ContextCacheKey(source, destinationType), out object destination);
22-
return destination;
23-
}
29+
/// <summary>
30+
/// Context items from <see cref="Options"/>
31+
/// </summary>
32+
public IDictionary<string, object> Items => Options.Items;
2433

25-
internal void CacheDestination(object source, Type destinationType, object destination)
26-
{
27-
InstanceCache[new ContextCacheKey(source, destinationType)] = destination;
28-
}
34+
/// <summary>
35+
/// Current mapper
36+
/// </summary>
37+
public IRuntimeMapper Mapper => this;
38+
39+
public IConfigurationProvider ConfigurationProvider => _inner.ConfigurationProvider;
40+
41+
Func<Type, object> IMapper.ServiceCtor => _inner.ServiceCtor;
42+
43+
ResolutionContext IRuntimeMapper.DefaultContext => _inner.DefaultContext;
2944

3045
/// <summary>
3146
/// Instance cache for resolving circular references
@@ -44,14 +59,6 @@ public Dictionary<ContextCacheKey, object> InstanceCache
4459
}
4560
}
4661

47-
private void CheckDefault()
48-
{
49-
if(IsDefault)
50-
{
51-
throw new InvalidOperationException();
52-
}
53-
}
54-
5562
/// <summary>
5663
/// Instance cache for resolving keeping track of depth
5764
/// </summary>
@@ -69,6 +76,99 @@ private Dictionary<TypePair, int> TypeDepth
6976
}
7077
}
7178

79+
TDestination IMapper.Map<TDestination>(object source)
80+
=> (TDestination)_inner.Map(source, null, source.GetType(), typeof(TDestination), this);
81+
82+
TDestination IMapper.Map<TDestination>(object source, Action<IMappingOperationOptions> opts)
83+
{
84+
opts(Options);
85+
86+
return ((IMapper)this).Map<TDestination>(source);
87+
}
88+
89+
TDestination IMapper.Map<TSource, TDestination>(TSource source)
90+
=> _inner.Map(source, default(TDestination), this);
91+
92+
TDestination IMapper.Map<TSource, TDestination>(TSource source, Action<IMappingOperationOptions<TSource, TDestination>> opts)
93+
{
94+
var typedOptions = new MappingOperationOptions<TSource, TDestination>(_inner.ServiceCtor);
95+
96+
opts(typedOptions);
97+
98+
var destination = default(TDestination);
99+
100+
typedOptions.BeforeMapAction(source, destination);
101+
102+
destination = _inner.Map(source, destination, this);
103+
104+
typedOptions.AfterMapAction(source, destination);
105+
106+
return destination;
107+
}
108+
109+
TDestination IMapper.Map<TSource, TDestination>(TSource source, TDestination destination)
110+
=> _inner.Map(source, destination, this);
111+
112+
TDestination IMapper.Map<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions<TSource, TDestination>> opts)
113+
{
114+
var typedOptions = new MappingOperationOptions<TSource, TDestination>(_inner.ServiceCtor);
115+
116+
opts(typedOptions);
117+
118+
typedOptions.BeforeMapAction(source, destination);
119+
120+
destination = _inner.Map(source, destination, this);
121+
122+
typedOptions.AfterMapAction(source, destination);
123+
124+
return destination;
125+
}
126+
127+
object IMapper.Map(object source, Type sourceType, Type destinationType)
128+
=> _inner.Map(source, null, sourceType, destinationType, this);
129+
130+
object IMapper.Map(object source, Type sourceType, Type destinationType, Action<IMappingOperationOptions> opts)
131+
{
132+
opts(Options);
133+
134+
return ((IMapper)this).Map(source, sourceType, destinationType);
135+
}
136+
137+
object IMapper.Map(object source, object destination, Type sourceType, Type destinationType)
138+
=> _inner.Map(source, destination, sourceType, destinationType, this);
139+
140+
object IMapper.Map(object source, object destination, Type sourceType, Type destinationType, Action<IMappingOperationOptions> opts)
141+
{
142+
opts(Options);
143+
144+
return ((IMapper)this).Map(source, destination, sourceType, destinationType);
145+
}
146+
147+
object IRuntimeMapper.Map(object source, object destination, Type sourceType, Type destinationType, ResolutionContext context,
148+
IMemberMap memberMap)
149+
=> _inner.Map(source, destination, sourceType, destinationType, context, memberMap);
150+
151+
TDestination IRuntimeMapper.Map<TSource, TDestination>(TSource source, TDestination destination, ResolutionContext context,
152+
IMemberMap memberMap)
153+
=> _inner.Map(source, destination, context, memberMap);
154+
155+
IQueryable<TDestination> IMapper.ProjectTo<TDestination>(IQueryable source, object parameters, params Expression<Func<TDestination, object>>[] membersToExpand)
156+
=> _inner.ProjectTo(source, parameters, membersToExpand);
157+
158+
IQueryable<TDestination> IMapper.ProjectTo<TDestination>(IQueryable source, IDictionary<string, object> parameters, params string[] membersToExpand)
159+
=> _inner.ProjectTo<TDestination>(source, parameters, membersToExpand);
160+
161+
internal object GetDestination(object source, Type destinationType)
162+
{
163+
InstanceCache.TryGetValue(new ContextCacheKey(source, destinationType), out object destination);
164+
return destination;
165+
}
166+
167+
internal void CacheDestination(object source, Type destinationType, object destination)
168+
{
169+
InstanceCache[new ContextCacheKey(source, destinationType)] = destination;
170+
}
171+
72172
internal void IncrementTypeDepth(TypePair types)
73173
{
74174
TypeDepth[types]++;
@@ -87,37 +187,24 @@ internal int GetTypeDepth(TypePair types)
87187
return TypeDepth[types];
88188
}
89189

90-
/// <summary>
91-
/// Current mapper
92-
/// </summary>
93-
public IRuntimeMapper Mapper { get; }
94-
95-
/// <summary>
96-
/// Current configuration
97-
/// </summary>
98-
public IConfigurationProvider ConfigurationProvider => Mapper.ConfigurationProvider;
99-
100-
/// <summary>
101-
/// Context items from <see cref="Options"/>
102-
/// </summary>
103-
public IDictionary<string, object> Items => Options.Items;
104-
105-
public ResolutionContext(IMappingOperationOptions options, IRuntimeMapper mapper)
106-
{
107-
Options = options;
108-
Mapper = mapper;
109-
}
190+
internal void ValidateMap(TypeMap typeMap)
191+
=> ConfigurationProvider.AssertConfigurationIsValid(typeMap);
110192

111-
internal bool IsDefault => this == Mapper.DefaultContext;
193+
internal bool IsDefault => this == _inner.DefaultContext;
112194

113195
internal TDestination Map<TSource, TDestination>(TSource source, TDestination destination, IMemberMap memberMap)
114-
=> Mapper.Map(source, destination, this, memberMap);
196+
=> _inner.Map(source, destination, this, memberMap);
115197

116-
internal object Map(object source, object destination, Type sourceType, Type destinationType, IMemberMap memberMap)
117-
=> Mapper.Map(source, destination, sourceType, destinationType, this, memberMap);
198+
internal object Map(object source, object destination, Type sourceType, Type destinationType, IMemberMap memberMap)
199+
=> _inner.Map(source, destination, sourceType, destinationType, this, memberMap);
118200

119-
internal void ValidateMap(TypeMap typeMap)
120-
=> ConfigurationProvider.AssertConfigurationIsValid(typeMap);
201+
private void CheckDefault()
202+
{
203+
if (IsDefault)
204+
{
205+
throw new InvalidOperationException();
206+
}
207+
}
121208
}
122209

123210
public struct ContextCacheKey : IEquatable<ContextCacheKey>

src/UnitTests/ContextItems.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
namespace AutoMapper.UnitTests
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace AutoMapper.UnitTests
25
{
36
namespace ContextItems
47
{
@@ -93,5 +96,98 @@ public void Should_use_value_passed_in()
9396
dest.Value1.ShouldBe(15);
9497
}
9598
}
99+
100+
public class When_mapping_nested_context_items : AutoMapperSpecBase
101+
{
102+
public class Door { }
103+
104+
public class FromGarage
105+
{
106+
public List<FromCar> FromCars { get; set; }
107+
}
108+
109+
public class ToGarage
110+
{
111+
public List<ToCar> ToCars { get; set; }
112+
}
113+
114+
public class FromCar
115+
{
116+
public int Id { get; set; }
117+
public string Name { get; set; }
118+
public Door Door { get; set; }
119+
}
120+
121+
public class ToCar
122+
{
123+
public int Id { get; set; }
124+
public string Name { get; set; }
125+
public Door Door { get; set; }
126+
}
127+
128+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
129+
{
130+
cfg.CreateMap<FromGarage, ToGarage>()
131+
.ForMember(dest => dest.ToCars, opts => opts.MapFrom((src, dest, destVal, ctx) =>
132+
{
133+
var toCars = new List<ToCar>();
134+
135+
ToCar toCar;
136+
foreach (var fromCar in src.FromCars)
137+
{
138+
toCar = ctx.Mapper.Map<ToCar>(fromCar);
139+
if (toCar == null)
140+
continue;
141+
142+
toCars.Add(toCar);
143+
}
144+
145+
return toCars;
146+
}));
147+
148+
cfg.CreateMap<FromCar, ToCar>()
149+
.ConvertUsing((src, dest, ctx) =>
150+
{
151+
ToCar toCar = null;
152+
FromCar fromCar = src;
153+
154+
if (fromCar.Name != null)
155+
{
156+
toCar = new ToCar
157+
{
158+
Id = fromCar.Id,
159+
Name = fromCar.Name,
160+
Door = (Door) ctx.Items["Door"]
161+
};
162+
}
163+
164+
return toCar;
165+
});
166+
});
167+
168+
[Fact]
169+
public void Should_flow_context_items_to_nested_mappings()
170+
{
171+
var door = new Door();
172+
var fromGarage = new FromGarage
173+
{
174+
FromCars = new List<FromCar>
175+
{
176+
new FromCar {Door = door, Id = 2, Name = "Volvo"},
177+
new FromCar {Door = door, Id = 3, Name = "Hyundai"},
178+
}
179+
};
180+
181+
var toGarage = Mapper.Map<ToGarage>(fromGarage, opts =>
182+
{
183+
opts.Items.Add("Door", door);
184+
});
185+
186+
foreach (var d in toGarage.ToCars.Select(c => c.Door))
187+
{
188+
d.ShouldBeSameAs(door);
189+
}
190+
}
191+
}
96192
}
97193
}

0 commit comments

Comments
 (0)