If you need to convert object types from one to
another, writing it by hand could be a pain, check out AutoMapper.
There's a good getting started on the project's wiki.
The basic idea is to define a map between the source and the destination:
Mapper.CreateMap<source, destination>();
To make sure your mappings are correct:
Mapper.AssertConfigurationIsValid();
You can flatten hierarchies if you name the
destination properties as such:
Source:
Element.Name
Destination:
ElementName
You can define conditions in which a certain property will be mapped or not:
Mapper.CreateMap<SourceEntity, DestinationModel>()
.ForMember(dest => dest.Name, opt=>opt.Condition(i=>((SourceEntity)i.Parent.SourceValue).Id == 2));
You can define custom mappings - for example,
we'll switch between the name and the description:
Mapper.CreateMap<SourceEntity, DestinationModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(source => source.Description))
.ForMember(dest => dest.Description, opt => opt.MapFrom(source => source.Name));
You can have custom mapping transformations:
For Example with lambda expressions:
Mapper.CreateMap<SourceEntity, FlatDestinationModel>()
.ForMember(i => i.AttributesSomeValue, opt => opt.ResolveUsing(i=>string.Join(", ", i.Attributes.Select(a=>a.Value).ToArray())));
Or with a ValueResolver class:
class
AttributeValueResolver : ValueResolver<SourceEntity,string>
{
protected override string ResolveCore(SourceEntity source)
{
return string.Join(", ", source.Attributes.Select(i=>i.Value).ToArray());
}
}
Mapper.CreateMap<SourceEntity, FlatDestinationModel>().ForMember(i => i.AttributesSomeValue, opt => opt.ResolveUsing<AttributeValueResolver>());
You can ignore certain properties:
Mapper.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Description, opt => opt.Ignore());
You can set specific order in which the
properties are mapped:
Mapper.CreateMap<SourceEntity, ChangeAwareDestinationModel>()
.ForMember(dest => dest.Description, opt => opt.SetMappingOrder(3))
.ForMember(dest => dest.Name, opt => opt.SetMappingOrder(2));
You can set a specific value for all mapping operations:
Mapper.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Description, opt => opt.UseValue("No Description"));
You can execute code before/after mapping (like putting the object into a tracking list):
Mapper.CreateMap<SourceEntity, DestinationModel>()
.BeforeMap((source, destination) =>
{
Console.WriteLine("Before Map");
})
.AfterMap((source, destination) =>
{
Console.WriteLine("After Map");
});
You can create custom type converters:
With lambda:
Mapper.CreateMap<SourceEntity, DestinationModel>().ConvertUsing(s =>
{
return new DestinationModel
{
Description = s.Description,
Name = s.Name
};
});
or with ITypeConverter:
class CustomTypeConverter : ITypeConverter<SourceEntity,DestinationModel>
{
#region ITypeConverter<SourceEntity,DestinationModel> Members
public DestinationModel
Convert(ResolutionContext context)
{
var src = (SourceEntity)context.SourceValue;
return new DestinationModel
{
Description = src.Description,
Name = src.Name
};
}
#endregion
}
Mapper.CreateMap<SourceEntity, DestinationModel>().ConvertUsing<CustomTypeConverter>();
You can create custom
object initialization (if you want to use a different constructor):
Mapper.CreateMap<SourceEntity, ChangeAwareDestinationModel>().ConstructUsing(c => new ChangeAwareDestinationModel(((SourceEntity)c.SourceValue).Name));
Note that the difference between ConvertUsing
and ConstructUsing is that ConvertUsing is invoking the method you provided and
exits the conversion procedure while ConstructUsing only instantiates
the object and continues execution on the rest of the mapping rules.
You can map lists to array and vice
versa (including the normal mapping capabilities):
Mapper.CreateMap<SourceEntity, DestinationModel>();
var dst = Mapper.Map<SourceEntity[], List<DestinationModel>>(src);
Dynamic Map - Creates a proxy from a known
interface and populates it based on the interface map (Note that the interface
must be public):
Mapper.CreateMap<SourceEntity, IDestinationModel>();
var dst = Mapper.DynamicMap<IDestinationModel>(src);
You can also use LINQ (in
AutoMapper.QueryableExtensions namespace):
var dst = src.Project().To<DestinationModel>();
src must be an IQueryable to use Linq
You can replace null with a value:
Mapper.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Description, opt => opt.NullSubstitute("Value is null"));
You can leave a property alone:
Mapper.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Description, opt => opt.UseDestinationValue());
DestinationModel dst = new DestinationModel
{
Description = "prepopulated
description"
};
dst = Mapper.Map<SourceEntity, DestinationModel>(src,dst);
Use Custom Formatter (implementing
IValueFormatter):
Mapper.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Description, opt => { opt.ResolveUsing<AttributeCountResolver>(); opt.AddFormatter<CustomFormatter>(); });
class CustomFormatter : IValueFormatter
{
#region
IValueFormatter Members
public string FormatValue(ResolutionContext
context)
{
int value = (int)context.SourceValue;
if (value == 0)
return "No Items";
else if (value == 1)
return "1 Item";
return string.Format("{0} Items", value);
}
#endregion
}
You can convert Dictionaries:
Mapper.CreateMap<SourceEntity, DestinationModel>();
var dst = Mapper.Map<Dictionary<int,SourceEntity>, Dictionary<int,DestinationModel>>(src);
Multiple Mapping Engines for multiple mapping
profiles:
ConfigurationStore enginestore1 = new ConfigurationStore(new TypeMapFactory(),MapperRegistry.AllMappers());
ConfigurationStore enginestore2 = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.AllMappers());
MappingEngine engine1 = new MappingEngine(enginestore1);
MappingEngine engine2 = new MappingEngine(enginestore2);
enginestore1.CreateMap<SourceEntity,DestinationModel>().ForMember(dest => dest.Description, opt=>opt.Ignore());
enginestore2.CreateMap<SourceEntity, DestinationModel>().ForMember(dest => dest.Name, opt => opt.Ignore());
var dst1 = engine1.Map<SourceEntity,DestinationModel>(srcdata[0]);
var dst2 = engine2.Map<SourceEntity, DestinationModel>(srcdata[1]);
The way it works is by having two
ConfigurationStores, each for each engine, you create a map on the store and
execute the mapping on the engine.
The only negative thing I have to say about this
project is that I haven't found any inline documentation.
Many thanks - succinct and comprehensive
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteExcellent blog!
ReplyDeleteExcellent article - thank you so much!
ReplyDeleteI was looking for how to map between abstract classes and this article explained everything I needed to know.
Thank you.