IT:AD:AutoMapper
Summary
Although MS's architecture document on DDD recommended AutoMapper, I was…underwhelmed by the end of the project. Too verbose, too little documentation for the hard edge cases, too time consuming.
The general consensus (even by its creator) that it's deemed to be ok for Domain Entity up to DTO, but not so hot going back.
Anyway, These are the notes developed while working with it – but next time, I'll try a different mapping strategy.
Create A Map
SS:Intro to AutoMapper 1.1 SS:AutoMapper as Operator
//Examples of creating custom maps between values: Mapper.CreateMap<Source3, Destination3>(); Mapper.CreateMap<NestedSource3, NestedDestination3>() .ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Val));
For ones you have to ignore, use:
.ForMember(dest => dest.Value, opt => opt.Ignore());
Example:
public class TestInvoiceB { public int Id { get; set; } public DateTime DateCreated { get; set; } public ICollection<TestLineItemB> LineItems { get; private set; } public TestInvoiceB() { LineItems = new Collection<TestLineItemB>(); } } public class TestLineItemB { public int Id { get; set; } public int TestInvoiceBId {get;set;} public TestInvoiceB Invoice {get;set;} public string Description { get; set; } public int Count { get; set; } public decimal ItemAmount { get; set; } public decimal Amount { get { return Count*ItemAmount; } set {}} } public class TestInvoiceBDTO { public int Id { get; private set; } public DateTime DateCreated { get; set; } public ICollection<TestLineItemBDTO> SomeLineItems { get { return _SomeLineItems; } private set { _SomeLineItems = value;}} private ICollection<TestLineItemBDTO> _SomeLineItems = new Collection<TestLineItemBDTO>(); } public class TestLineItemBDTO { public int Id { get; private set; } public int TestInvoiceBId { get; private set; } public TestInvoiceB Invoice { get; set; } public string XDescription { get; set; } public int Count { get; set; } public decimal ItemBmount { get; set; } public decimal Amount { get;set;} }
XLib Implementation
The above gets mapped as follows:
public class TestInvoiceBMapper : AutoMapperTypeMapperBase<TestInvoiceB, TestInvoiceBDTO> { protected override void BeforeInternalMap(ref TestInvoiceB source) { base.CreateMap<TestInvoiceB, TestInvoiceBDTO>() .ForMember(d => d.SomeLineItems, opt => opt.MapFrom(s => s.LineItems)) ; base.CreateMap<TestLineItemB, TestLineItemBDTO>() .ForMember(d => d.TestInvoiceBId, opt => opt.MapFrom(s => s.TestInvoiceBId)) .ForMember(d => d.XDescription, opt => opt.MapFrom(s => s.Description)). ForMember(d => d.ItemBmount, opt => opt.MapFrom(s => s.ItemAmount)); //Mapper.CreateMap<ICollection<TestLineItemA>, ICollection<TestLineItemADTO>>(); } protected override TestInvoiceBDTO InternalMap(TestInvoiceB source) { return base.Map<TestInvoiceB, TestInvoiceBDTO>(source); } }
Checking Configuration
After registering your mappings, you can check to see if all Target properties have been filled in with:
Mapper.AssertConfigurationIsValid();
Conditional Mapping
Working with EF CodeFirst
EF has built in change tracking, as well as integrity checks. You can't just ask Automapper to do what it normally does, and recreate an object. You have to use the objects that are there…
In other words, you'll have to state that you will be using whatever subject is there (rather than create a new one) by using the opt => opt.UseDestinationObject
See AutoMapperModifiers Example:
BeforeMap and AfterMap
There's some hooks to do some pre-work.
http://stackoverflow.com/questions/2806664/automapper-using-beforemap-and-aftermap configure.CreateMap<IFrom, ITo>() .BeforeMap((x,y) => x.PrepareForMapping()) .etc .AfterMap((x,y) => x.PostMapping())