Using Non-static AutoMapper Configuration

December 22nd 2014 AutoMapper Entity Framework

We've all heard a lot about object-relational mapping (ORM), but in multitier applications there's another type of mapping we have to deal with: object-object mapping. If you're wondering, what that is: mapping between the domain model objects and data transfer objects (DTOs) is a common example of it.

In contrast to ORM, there's a lot less libraries available for object-object mapping. In .NET space, one of the most popular ones is AutoMapper. In simple scenarios it can replace all your trivial error-prone mapping code:

public class SourceClass
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class DestinationClass
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public DestinationClass MapManually(SourceClass source)
{
    return new DestinationClass
    {
        Id = source.Id,
        Name = source.Name
    };
}

public void InitAutoMapper()
{
    // called during application initialization
    Mapper.CreateMap<SourceClass, DestinationClass>();
}

public DestinationClass MapAutomatically(SourceClass source)
{
    return Mapper.Map<DestinationClass>(source);
}

The more properties there are in objects, the more code you save. Of course AutoMapper is not limited to cases where properties map one to one. Thanks to built-in conventions, it can handle more complex scenarios, as well. When even that's not enough, mapping for individual properties can be configured manually. By just browsing through the documentation it becomes obvious, that there's no lack of different configuration options. Just be careful, your AutoMapper configuration doesn't become more complex than your manual mapping code would be.

All that being said; based on the information so far, AutoMapper imposes a very serious limitation: both the configuration and the mapping function are static. This will cause problems when your mapping configuration depends on external object instances:

public class SourceClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<int> NestedIds { get; set; }
}

public class NestedClass
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class DestinationClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<NestedClass> Nesteds { get; set; }
}

public DestinationClass MapAutomatically(SourceClass source)
{
    using (var context = new TestDbContext())
    {
        Mapper.CreateMap<SourceClass, DestinationClass>()
            .ForMember(dest => dest.Nesteds, opt => opt.Ignore())
            .AfterMap((src, dest) =>
            {
                dest.Nesteds.Clear();
                foreach (var nested in context.NestedClasses
                    .Where(n => src.NestedIds.Contains(n.Id)))
                {
                    dest.Nesteds.Add(nested);
                }
            });

        return Mapper.Map<DestinationClass>(source);
    }
}

This sample assumes that nested collection of objects will be represented in DTOs with their ids only. To map them back to objects, Entity Framework DbContext instance is required. Not only that; it must be the same instance as used for saving changes, otherwise SaveChanges() will throw an exception. Although I moved the mapper initialization to the mapping function, the above code still won't work in multithreaded scenarios because there is always just one common static mapping used by all threads. Hence, all threads will use the context from the thread that last initialized the mapping.

Fortunately, AutoMapper allows non-static mapper configurations, as well. I don't think that's actually documented, but it's being used in AutoMapper's unit tests. With this technique, my mapping function can be fixed to also work correctly in multithreaded scenarios (notice the use of ConfigurationStore and MappingEngine instead of the static Mapper class):

public DestinationClass MapAutomatically(SourceClass source)
{
    using (var context = new TestDbContext())
    {
        var config = new ConfigurationStore(
            new TypeMapFactory(), MapperRegistry.Mappers);
        config.CreateMap<SourceClass, DestinationClass>()
            .ForMember(dest => dest.Nesteds, opt => opt.Ignore())
            .AfterMap((src, dest) =>
            {
                dest.Nesteds.Clear();
                foreach (var nested in context.NestedClasses
                    .Where(n => src.NestedIds.Contains(n.Id)))
                {
                    dest.Nesteds.Add(nested);
                }
            });

        var mappingEngine = new MappingEngine(config);
        return mappingEngine.Map<DestinationClass>(source);
    }
}

As you can see, my mapping function is becoming more and more complicated, although it still needs further improvements before it will correctly map to entity objects in all cases. If you're interested in the subject of using AutoMapper with Entity Framework, you should read Roger Alsing's excellent blogpost about it. He makes a great point: don't blindly stick to AutoMapper when it starts getting in the way; use it only, when the resulting code is actually simpler to write and easier to understand.

Copyright
Creative Commons License