300x250 AD TOP

Search This Blog

Paling Dilihat

Powered by Blogger.

Friday, October 26, 2012

Translating Objects of One Type to Another with AutoMapper


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.

Tags:

5 comments:

  1. Many thanks - succinct and comprehensive

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Excellent article - thank you so much!

    I was looking for how to map between abstract classes and this article explained everything I needed to know.

    Thank you.

    ReplyDelete