AutoMapper:域模型和视图模型之间的双向深度映射

时间:2014-04-04 01:21:32

标签: c# asp.net-mvc viewmodel automapper automapper-3

我需要在平面ViewModel和深层结构域模型之间映射两种方式。这将是我们解决方案中的常见情况。

我的模特是:

public class Client 
{
   ...
   public NotificationSettings NotificationSettings { get; set; }
   public ContactDetails ContactDetails { get; set; }
   ...
}

public class NotificationSettings
{
    ...
    public bool ReceiveActivityEmails { get; set; }
    public bool ReceiveActivitySms { get; set; }
    ...
}

public class ContactDetails
{
    ...
    public string Email { get; set }
    public string MobileNumber { get; set; }
    ...
}

public class ClientNotificationOptionsViewModel
{
    public string Email { get; set }
    public string MobileNumber { get; set; }
    public bool ReceiveActivityEmails { get; set; }
    public bool ReceiveActivitySms { get; set; }
}

映射代码:

Mapper.CreateMap<Client, ClientNotificationOptionsViewModel>()
    .ForMember(x => x.ReceiveActivityEmails, opt => opt.MapFrom(x => x.NotificationSettings.ReceiveActivityEmails))
    .ForMember(x => x.ReceiveActivitySms, opt => opt.MapFrom(x => x.NotificationSettings.ReceiveActivitySms))
    .ForMember(x => x.Email, opt => opt.MapFrom(x => x.ContactDetails.Email))
    .ForMember(x => x.MobileNumber, opt => opt.MapFrom(x => x.ContactDetails.MobileNumber));

// Have to use AfterMap because ForMember(x => x.NotificationSettings.ReceiveActivityEmail) generates "expression must resolve to top-level member" error
Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
    .IgnoreUnmapped()
    .AfterMap((from, to) =>
    {
        to.NotificationSettings.ReceiveActivityEmail = from.ReceiveActivityEmail;
        to.NotificationSettings.ReceiveActivitySms = from.ReceiveActivitySms;
        to.ContactDetails.Email = from.Email;
        to.ContactDetails.MobileNumber = from.MobileNumber;
    });


...

// Hack as ForAllMembers() returns void instead of fluent API syntax
public static IMappingExpression<TSource, TDest> IgnoreUnmapped<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
    expression.ForAllMembers(opt => opt.Ignore());
    return expression;
}

我不喜欢它,因为:

1)这很麻烦

2)第二个映射几乎消除了Automapper的功能并手动实现了工作 - 它的唯一优势是在整个代码中引用Automapper的一致性

任何人都可以建议:

a)将Automapper用于深层属性的更好方法是什么?

b)像这样执行双向映射的更好方法是什么?

c)在这种情况下我是否应该使用Automapper的建议?是否有令人信服的理由不回归到手动编码的简单方法?例如:

void MapManually(Client client, ClientNotificationOptionsViewModel viewModel)
{
    viewModel.Email = client.ContactDetails.Email;
    // etc
}  

void MapManually(ClientNotificationOptionsViewModel viewModel, Client client)
{
    client.ContactDetails.Email = viewModel.Email;
    // etc
}

-Brendan

P.S。重组领域模型不是解决方案。

P.P.S可以通过扩展方法来清理上述代码。设置深度属性的一些时髦的反射......但如果可能的话,我宁愿使用automapper功能。

2 个答案:

答案 0 :(得分:4)

这也可以这样做:

Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
    .ForMember(x => x.NotificationSettings, opt => opt.MapFrom(x => new NotificationSettings() { ReceiveActivityEmails = x.ReceiveActivityEmails, ReceiveActivitySms = x.ReceiveActivitySms}))
    .ForMember(x => x.ContactDetails, opt => opt.MapFrom(x => new ContactDetails() { Email = x.Email, MobileNumber = x.MobileNumber }));

但与你的解决方案没什么不同。

此外,您可以通过从模型创建地图到内部类来实现:

Mapper.CreateMap<ClientNotificationOptionsViewModel, ContactDetails>();

Mapper.CreateMap<ClientNotificationOptionsViewModel, NotificationSettings>();

Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
    .ForMember(x => x.NotificationSettings, opt => opt.MapFrom(x => x))
    .ForMember(x => x.ContactDetails, opt => opt.MapFrom(x => x));

您不需要在新映射中指定 ForMember ,因为这两个类中的属性具有相同的名称。

答案 1 :(得分:-3)

最后我发现AutoMapper不适合我的场景。

相反,我构建了一个自定义实用程序来提供双向映射&amp;深度属性映射允许配置如下。鉴于我们项目的范围,我认为这是合理的。

BiMapper.CreateProfile<Client, ClientNotificationsViewModel>()
    .Map(x => x.NotificationSettings.ReceiveActivityEmail, x => x.ReceiveActivityEmail)
    .Map(x => x.NotificationSettings.ReceiveActivitySms, x => x.ReceiveActivitySms)
    .Map(x => x.ContactDetails.Email, x => x.Email)
    .Map(x => x.ContactDetails.MobileNumber, x => x.MobileNumber);

BiMapper.PerformMap(client, viewModel);
BiMapper.PerformMap(viewModel, client);

道歉我无法分享其实施的商业工作。但是我希望它能帮助其他人知道构建自己的东西是不可能的,并且可以提供优于AutoMapper的优势或手动完成。