0

A project I'm working on (still in .NET Framework, in case that matters) uses AutoMapper for two separate projects that use the same Database and share a LOT of the same view model classes from a Common class library project, e.g.:

// in Project 1
Mapper.Initialize(cfg =>
{
    cfg.CreateMap<A, AModel>()
        .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
        .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName));

    cfg.CreateMap<B2, BModel>()
        .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
        .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName))
        .ForMember(dest => dest.Prop3, opt => opt.MapFrom(src => src.OtherOTHERPropertyName));
}

// in Project 2
Mapper.Initialize(cfg =>
{
    cfg.CreateMap<A, AModel>()
        .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
        .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName));

    cfg.CreateMap<B2, BModel>()
        .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
        .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName))
        .ForMember(dest => dest.Prop3, opt => opt.Ignore());
}

I'd like to move all of the duplicate mappings (which is most of them) into a profile or something in the Common project:

public class CommonAutoMapperProfile : Profile
{
    public CommonAutoMapperProfile()
    {
        CreateMap<A, AModel>()
            .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
            .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName));

        CreateMap<B, BModel>()
            .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.PropertyName))
            .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.OtherPropertyName));
    }
}

This is no problem for the COMPLETELY identical mappings like A->AModel, but I'm struggling with ones like B->BModel that differ by even ONE ForMember call (Prop3 in this case), which Project 1 sets and Project 2 ignores. It DOES apply that rule, but it seems that when the second CreateMap is called, ALL mappings set by CommonAutoMapperProfile get thrown out entirely:

// in project 1
Mapper.Initialize(cfg =>
{
    cfg.AddProfile<CommonAutoMapperProfile>();
    cfg.CreateMap<B, BModel>()
        .ForMember(dest => dest.Prop3, opt => opt.MapFrom(src => src.OtherOTHERPropertyName));
    // BModel.Prop1 and BModel.Prop2 are still null; BModel.Prop3 is the only one being set
}

I'd LIKE it to "merge" the mappings together, i.e. retain the property mappings defined in CommonAutoMapperProfile and add the project-specific ones on top. Is that something AutoMapper just... CAN'T do?

I've also attempted creating ANOTHER Profile class that is specific to each project, with unfortunately the same results, whether I'm adding it with another cfg.AddProfile call:

public class Project1AutoMapperProfile : Profile
{
    public Project1AutoMapperProfile()
    {
        CreateMap<B, BModel>()
            .ForMember(dest => dest.Prop3, opt => opt.MapFrom(src => src.OtherOTHERPropertyName));
    }
}

// elsewhere in Project 1...
Mapper.Initialize(cfg =>
{
    cfg.AddProfile<CommonAutoMapperProfile>();
    cfg.AddProfile<Project1AutoMapperProfile>();
}

or having it inherit from CommonAutoMapperProfile:

public class Project1AutoMapperProfile : CommonAutoMapperProfile
{
    public Project1AutoMapperProfile()
    {
        CreateMap<B, BModel>()
            .ForMember(dest => dest.Prop3, opt => opt.MapFrom(src => src.OtherOTHERPropertyName));
    }
}

// elsewhere in Project 1...
Mapper.Initialize(cfg =>
{
    cfg.AddProfile<Project1AutoMapperProfile>();
}

Both cases had the exact same results as the first case: ONLY the mappings in Project1AutoMapperProfile were being applied

1
  • 2
    Opinion: If there are two different mappings, the destination model is probably being used for two different logical purposes. It should be split into two classes. Commented Mar 7, 2024 at 15:33

1 Answer 1

1

As mentioned in the comments, what you have is not an ideal design. But I understand that sometimes we have to work with the cards we're dealt (e.g. you have no access to modify the models).

As the name implies, calling CreateMap multiple times for the same mapping will just keep creating a new one. If you want to build up from an existing one, you have to append/modify. There's no built-in facility to get and existing map and modify it, but we can be a little sneaky and implement it ourselves.


NOTE: I still do not recommend doing this. If you can break up your models, do that and use the recommended model inheritance.


That said, the code below works. For the current version of AutoMapper. For this specific case. Make sure you have good test coverage, in case it breaks in the future, or for some of your more complex models.

public class B { public int Prop1; public int Prop2; }
public class BModel { public int PropB1; public int PropB2; }

public class CommonProfile : Profile
{
  public CommonProfile() 
  {
    // Map Prop1, ignore Prop2
    CreateMap<B, BModel>()
      .ForMember(d => d.PropB1, o => o.MapFrom(s => s.Prop1))
      .ForMember(d => d.PropB2, o => o.Ignore());
  }
  
  protected IMappingExpression<T,TT> CreateOrGetMap<T, TT>() 
  {
    // Sneaky access the un-exposed existing configs.
    var existing = ((IProfileConfiguration)this).TypeMapConfigs
      .Where(c => c.SourceType == typeof(T) && c.DestinationType == typeof(TT))
      .FirstOrDefault();
      
    // If found existing map, return it. Otherwise, create new one.
    return (existing as IMappingExpression<T,TT>) ?? CreateMap<T, TT>();
  }
}

We then create a derived profile which will modify the mapping in the Common one.

public class Proj2Profile : CommonProfile {
  public Proj2Profile() 
  {
    // Since there's already a map configured in the parent profile, 
    // this will modify it.
    CreateOrGetMap<B, BModel>()
      .ForMember(d => d.PropB2, o => o.MapFrom(s => s.Prop2));
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

thanks for the Mapping Inheritance suggestion! for clarification, by "break up my models", do you mean to have separate BModel classes for Project 1, Project 2, and the Common project, even if these three classes are 100% identical property-wise? or am I misunderstanding?
ViewModels should be specific to the needs. It looks like your Project1 may not need some of the properties (since you're Ignore()-ing them). So really your Common will hold the database Models (with all the properties matching your DB), and your child projects will maintain their own ViewModels containing the properties you need from the main Model.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.