Skip to content

Commit af60a49

Browse files
authored
Merge pull request #2874 from AutoMapper/simplify-include-base
Simplify include base with IncludeAllDerived
2 parents 8f8794e + 89ba3f5 commit af60a49

File tree

6 files changed

+88
-0
lines changed

6 files changed

+88
-0
lines changed

docs/Mapping-inheritance.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ CreateMap<DerivedEntity, DerivedDto>()
2727

2828
In each case above, the derived mapping inherits the custom mapping configuration from the base mapping configuration.
2929

30+
To include all derived maps, from the base type map configuration:
31+
32+
```c#
33+
CreateMap<BaseEntity, BaseDto>()
34+
.IncludeAllDerived();
35+
36+
CreateMap<DerivedEntity, DerivedDto>();
37+
```
38+
3039
### Runtime polymorphism
3140

3241
Take:

src/AutoMapper/Configuration/MappingExpression.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public IMappingExpression ForMember(string name, Action<IMemberConfigurationExpr
8181

8282
public new IMappingExpression PreserveReferences() => (IMappingExpression)base.PreserveReferences();
8383

84+
public new IMappingExpression IncludeAllDerived() => (IMappingExpression) base.IncludeAllDerived();
85+
8486
protected override IPropertyMapConfiguration CreateMemberConfigurationExpression<TMember>(MemberInfo member, Type sourceType)
8587
=> new MemberConfigurationExpression(member, sourceType);
8688

@@ -532,6 +534,12 @@ public IMappingExpression<TSource, TDestination> ValidateMemberList(MemberList m
532534
});
533535
return this;
534536
}
537+
538+
public IMappingExpression<TSource, TDestination> IncludeAllDerived()
539+
{
540+
TypeMapActions.Add(tm => tm.IncludeAllDerivedTypes = true);
541+
return this;
542+
}
535543

536544
private IPropertyMapConfiguration GetDestinationMemberConfiguration(MemberInfo destinationMember) =>
537545
_memberConfigurations.FirstOrDefault(m => m.DestinationMember == destinationMember);

src/AutoMapper/IMappingExpression.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ IMappingExpression AfterMap<TMappingAction>()
177177
/// <param name="memberList">Member list to validate</param>
178178
/// <returns>Itself</returns>
179179
IMappingExpression ValidateMemberList(MemberList memberList);
180+
181+
/// <summary>
182+
/// Include this configuration in all derived types' maps. Works by scanning all type maps for matches during configuration.
183+
/// </summary>
184+
/// <returns>Itself</returns>
185+
IMappingExpression IncludeAllDerived();
180186
}
181187

182188
/// <summary>
@@ -444,5 +450,10 @@ IMappingExpression<TSource, TDestination> ForSourceMember(string sourceMemberNam
444450
/// <returns>Itself</returns>
445451
IMappingExpression<TSource, TDestination> ValidateMemberList(MemberList memberList);
446452

453+
/// <summary>
454+
/// Include this configuration in all derived types' maps. Works by scanning all type maps for matches during configuration.
455+
/// </summary>
456+
/// <returns>Itself</returns>
457+
IMappingExpression<TSource, TDestination> IncludeAllDerived();
447458
}
448459
}

src/AutoMapper/MapperConfiguration.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,19 @@ private void Seal()
327327
profile.Register(this);
328328
}
329329

330+
foreach (var typeMap in _typeMapRegistry.Values.Where(tm => tm.IncludeAllDerivedTypes))
331+
{
332+
foreach (var derivedMap in _typeMapRegistry
333+
.Where(tm =>
334+
typeMap.SourceType.IsAssignableFrom(tm.Key.SourceType) &&
335+
typeMap.DestinationType.IsAssignableFrom(tm.Key.DestinationType) &&
336+
typeMap != tm.Value)
337+
.Select(tm => tm.Value))
338+
{
339+
typeMap.IncludeDerivedTypes(derivedMap.SourceType, derivedMap.DestinationType);
340+
}
341+
}
342+
330343
foreach (var profile in Profiles)
331344
{
332345
profile.Configure(this);

src/AutoMapper/TypeMap.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public PathMap FindPathMapByDestinationPath(string destinationFullPath) =>
7777

7878
public bool ConstructDestinationUsingServiceLocator { get; set; }
7979

80+
public bool IncludeAllDerivedTypes { get; set; }
81+
8082
public MemberList ConfiguredMemberList { get; set; }
8183

8284
public IEnumerable<TypePair> IncludedDerivedTypes => _includedDerivedTypes;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Shouldly;
2+
using Xunit;
3+
4+
namespace AutoMapper.UnitTests.MappingInheritance
5+
{
6+
public class IncludeAllDerived : AutoMapperSpecBase
7+
{
8+
public class A
9+
{
10+
public int Value { get; set; }
11+
}
12+
public class B : A { }
13+
public class C : B { }
14+
public class D : A { }
15+
16+
public class ADto
17+
{
18+
public int Value { get; set; }
19+
}
20+
21+
public class BDto : ADto { }
22+
public class CDto : BDto { }
23+
public class DDto : ADto { }
24+
25+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
26+
{
27+
cfg.CreateMap<A, ADto>()
28+
.ForMember(d => d.Value, opt => opt.MapFrom(src => 5))
29+
.IncludeAllDerived();
30+
31+
cfg.CreateMap<B, BDto>();
32+
cfg.CreateMap<C, CDto>();
33+
cfg.CreateMap<D, DDto>();
34+
});
35+
36+
[Fact]
37+
public void Should_apply_configuration_to_all_derived()
38+
{
39+
Mapper.Map<ADto>(new A {Value = 10}).Value.ShouldBe(5);
40+
Mapper.Map<BDto>(new B {Value = 10}).Value.ShouldBe(5);
41+
Mapper.Map<CDto>(new C {Value = 10}).Value.ShouldBe(5);
42+
Mapper.Map<DDto>(new D {Value = 10}).Value.ShouldBe(5);
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)