Jose Maluco
Jose Maluco

Reputation: 23

Forgetting to map classes with AutoMapper

The application I'm working on has several places where we use AutoMapper to map entities.

The problem is if I had a model entity from one side to the other of the project, many times I forget to add the mapping for the new entity (I just need a copy paste from other entities), ending up that the solution compiles and I get no exception.

It just launches the application without full functionality and no debugging messages, which makes difficult to figure out what I've missed.

Is there any way to force the compiler at compile time to give me an error in case I forget to do a mapping?

Upvotes: 1

Views: 813

Answers (2)

Andy Vaal
Andy Vaal

Reputation: 493

I had the same problem and decided to solve it by wrapping up AutoMapper. For each source-destination map I provide a method that I create after I've added it to my AutoMapper profile.

This may take away some of the ease of implementing AutoMapper but I find the compile time checking worth it.

public class MyType {
    public string SomeProperty { get;set; }
}

public class MyOtherType {
    public string SomeProperty { get;set; }
}

public class MyAlternateType {
    public string AlternateProperty {get;set;}
}

public class AutoMapperProfile : Profile {
    public AutoMapperProfile() {
        CreateMap<MyType, MyOtherType>();
        CreateMap<MyAlternateType, MyOtherType>()
            .ForMember(ot => ot.SomeProperty, options => options.MapFrom(at => at.AlternateProperty));
    }
}

public interface IMyMappingProvider {
    // Uncomment below for Queryable Extensions
    //IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand);
    //IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand);

    /*
     * Add your mapping declarations below
     */

    MyOtherType MapToMyOtherType(MyType source);
    MyOtherType MapToMyOtherType(MyAlternateType source);
}

public class MyMappingProvider : IMyMappingProvider {
    private IMapper Mapper { get; set; }

    public MyMappingProvider(IMapper mapper) {
        Mapper = mapper;
    }

    /* Uncomment this for Queryable Extensions
    public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand) {
        return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(null, membersToExpand);
    }

    public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand) {
        return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(parameters, membersToExpand);
    }
    */

    /*
     * Implement your mapping methods below
     */

    public MyOtherType MapToMyOtherType(MyType source) {
        return Mapper.Map<MyType, MyOtherType>(source);
    }

    public MyOtherType MapToMyOtherType(MyAlternateType source) {
        return Mapper.Map<MyAlternateType, MyOtherType>(source);
    }
}

If you are using the AutoMapper's Queryable extensions you can add the following class and uncomment the Queryable Extensions code above.

public static class QueryableExtensions {
    /*
     * Implement your extension methods below
     */
    public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
    {
        return mapper.ProjectTo<MyType, MyOtherType>(source, membersToExpand);
    }

    public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyAlternateType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
    {
        return mapper.ProjectTo<MyAlternateType, MyOtherType>(source, membersToExpand);
    }
}

Tested with AutoMapper 6.1.1 using LinqPad:

var autoMapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); });

IMyMappingProvider mapper = new MyMappingProvider(autoMapperConfig.CreateMapper());

var myTypes = new List<MyType>()
{
    new MyType() {SomeProperty = "Test1"},
    new MyType() {SomeProperty = "Test2"},
    new MyType() {SomeProperty = "Test3"}
};

myTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();  

var myAlternateTypes = new List<MyAlternateType>()
{
    new MyAlternateType() {AlternateProperty = "AlternateTest1"},
    new MyAlternateType() {AlternateProperty = "AlternateTest2"},
    new MyAlternateType() {AlternateProperty = "AlternateTest3"}
};

myAlternateTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump(); 

mapper.MapToMyOtherType(myTypes[0]).Dump();

As @serge.karalenka said, don't forget to still test your mapping configuration by calling AssertConfigurationIsValid().

Upvotes: 0

serge.karalenka
serge.karalenka

Reputation: 990

AFAIK, there isn't a possibility to force compile-time checking for Automapper. Nevertheless, there is a possibility to verify the correctness of your mappings: After you've defined all your mappings, call the AssertConfigurationIsValid method which will throws an AutoMapperConfigurationException exception if the defined mappings are broken.

You can make this a part of your unit or integration test suite.

Upvotes: 1

Related Questions