9

Can AutoMapper be "persuaded" to temporarily suspend particular mappings?

To illustrate what am trying to accomplish, I will use an illustration. Suppose that I have a repository, StudentRepository, that uses LINQ to interacts with database objects (tables) like Students, Courses, Activities, Clubs etc. On the application side, there are matching domain objects Student, Course, Activity, Club. The Student class contains array members of type Course, Activity, and Club like:

public class Student
{
    // ... more members
    public Course[] Courses { get; set; }
    public Activity[] Activities { get; set; }
    public Club[] Clubs { get; set; }
    // ... even more members
}

AutoMapper is configured to map the database objects to the domain objects where the mappings are defined in a static constructor of StudentRepository like:

public class StudentRepository : IStudentRepository
{
    static class StudentRepository
    {
        // ... other mappings

        Mapper.CreateMap<TableStudent, Student>()
            .ForMember(dest => dest.Courses, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Course>>(src.TableCourses)))
            .ForMember(dest => dest.Activities, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Activity>>(src.TableActivities)))
            .ForMember(dest => dest.Clubs, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Clubs>>(src.TableClubs)))
        // where TableStudents, TableCourses, TableActivities, TableClubs are database entities

        // ... yet more mappings

    }
}

Is it possible to "persuade" AutoMapper to suspend the mappings within one function block? For example:

public Student[] GetStudents()
{
    DataContext dbContext = new StudentDBContext();

    var query = dbContext.Students;
    // => SUSPEND CONFIGURATION MAPPINGS for Subjects, Activities and Clubs WHILE STILL making use of others
    // => The idea here it to take personal charge of 'manually' setting the particular members (*for some specific reasons)

    var students = Mapper.Map<Student>(query); // => Still be able to use AutoMapper to map other members
}

public Student[] OtherStudentRepositoryMethods()
{
    // Other repository methods continue to make use of the mappings configured in the static constructor
}

NOTE "for some specific reasons": One reason one may want to take control away from AutoMapper would be this http://codebetter.com/davidhayden/2007/08/06/linq-to-sql-query-tuning-appears-to-break-down-in-more-advanced-scenarios/ where in the case of a 1:n associations, LINQ to SQL only supports joining-in one 1:n association per query. AutoMapper would be inefficient here - making N calls to load Courses for N students returned, N more calls to load Activities for the same N students returned, and possibly N more calls to load Clubs for the same N students returned.

John Gathogo
  • 4,495
  • 3
  • 32
  • 48
  • I have not tried this but there is a `BeforeMap ` `Mapper.CreateMap().BeforeMap(x => { Mapper.CreateMap().ForMember... })` create the map in the before map. There may be additional checks you could do to not re-create the map multiple times. – Josiah Ruddell May 22 '12 at 14:53

3 Answers3

4

Is it possible to "persuade" AutoMapper to suspend the mappings within one function block?

As I know, the best way to do it - use Ignore() like this

public class StudentRepository : IStudentRepository
{
    static class StudentRepository
    {
        // ... other mappings

        Mapper.CreateMap<TableStudent, Student>()
            .ForMember(dest => dest.Courses, opt => opt.Ignore())
            .ForMember(dest => dest.Activities, opt => opt.Ignore())
            .ForMember(dest => dest.Clubs, opt => opt.Ignore())
        // where TableStudents, TableCourses, TableActivities, TableClubs are database entities

        // ... yet more mappings

    }
}

Also, as it was noticed before, I'd recommend you to use different profiles for each goal you want to achieve.Here is a example

public BaseService()
{
    AutoMapperRegistry.Configure();
}

public class AutoMapperRegistry
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
        {
            x.AddProfile<ServiceProfile1>();
            x.AddProfile<ServiceProfileReverseProfile1>();
        });
    }
}

public class ServiceProfile1 : Profile
{
    protected override string ProfileName
    {
        get
        {
            return "ServiceProfile1";
        }
    }

    protected override void Configure()
    {
        Mapper.CreateMap<DataContract_Sub, DTO_Sub>();
        Mapper.CreateMap<DataContract, DTO>()
            .ForMember(x => x.DataContract_Sub, opt => opt.MapFrom(y => y.DTO_Sub))
    .BeforeMap((s, d) => 
            {
                // your custom logic
            })
    .AfterMap((s, d) => 
            {
                // your custom logic
            });
    }
}
Andriy Zakharko
  • 1,623
  • 2
  • 16
  • 37
2

One way of achieving this would be to create separate mapping engine instances for each scenario, that way you could configure different maps, as suggested in this answer from Jimmy Bogard on wanting to map a single type in different ways.

Community
  • 1
  • 1
Rob West
  • 5,189
  • 1
  • 27
  • 42
0

hmm... Thanks guys for the feedback. I took time to consider all answers and suggestions. None particularly renders exactly well though they provided a lot of food for thought. I thought I should inject something I tried. (Disclaimer: My opinion is that its a dirty approach - many things could go wrong - and Murphy's laws continue to hold). You could leverage the Ignore functionality in the particular instance to "suspend" the mapping. Typically, in a try and catch block as follows:

public Student[] GetStudents()
{
    try
    { // Suspend/Ignore the mappings
        // => SUSPEND CONFIGURATION MAPPINGS for Subjects, Activities and Clubs
        Mapper.CreateMap<TableStudent, Student>()
                    .ForMember(dest => dest.Courses, opt => opt.Ignore())
                    .ForMember(dest => dest.Activities, opt => opt.Ignore())
                    .ForMember(dest => dest.Clubs, opt => opt.Ignore())

        DataContext dbContext = new StudentDBContext();

        var query = dbContext.Students;

        // other logic ...

        var students = Mapper.Map<Student>(query); // => Still be able to use AutoMapper to map other members
        // set the properties you needed to do manually
    }
    finally // Restore back the mappings
    {
        Mapper.CreateMap<TableStudent, Student>()
                    .ForMember(dest => dest.Courses, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Course>>(src.TableCourses)))
                    .ForMember(dest => dest.Activities, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Activity>>(src.TableActivities)))
                    .ForMember(dest => dest.Clubs, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Clubs>>(src.TableClubs)))
    }
}

Like I mentioned, its perhaps dirty. Not the kind of code I would be happy wriing - especially since I don't know what kind of exceptional situations can arise if CreateMap() fails within the finally block, but on a legacy application where you couldn't overhaul the approach - to possibly use different profiles like suggested by @AndriyZakharko above, you could use it to get control back temporarily. I tried it out personally.

John Gathogo
  • 4,495
  • 3
  • 32
  • 48