繁体   English   中英

从多个属性到复杂对象的 AutoMapper 映射失败,ReverseMap 和自定义值解析器

[英]AutoMapper mapping from multiple properties to complex objects fails with ReverseMap and custom value resolver

我在将多个属性反向映射回复杂对象时遇到问题,即使使用自定义值解析器也是如此。

以下是持久性 model:

public class EmailDbo
{
    public int EmailId { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime? DateSent { get; set; }
    public string SendTo { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public bool DownloadAvailable { get; set; }
    public DateTime? AdminDateSent { get; set; }
    public string AdminEmail { get; set; }
    public string AdminSubject { get; set; }
    public string AdminBody { get; set; }
    public int StatusId { get; set; }
}

我有来自数据库的 Dapper map 数据并填写此 model。


以下是我想要 map 与持久性 model 来回的域模型:

public class Email
{
    public string SendTo { get; private set; }
    public string Subject { get; private set; }
    public string Body { get; private set; }
    public DateTime? DateSent { get; private set; }
    
    public Email(string sendTo, string subject, string body, DateTime? dateSent = null)
    {
        // Validations
        this.SendTo = sendTo;
        this.Subject = subject;
        this.Body = body;
        this.DateSent = dateSent;
    }
}

public enum EmailTaskStatus
{
    Sent = 1,
    Unsent = 2
}

public class EmailTask
{
    public int Id { get; private set; }
    public DateTime DateCreated { get; private set; }
    public Email PayerEmail { get; private set; }
    public Email AdminEmail { get; private set; }
    public bool DownloadAvailableForAdmin { get; private set; }
    public EmailTaskStatus Status { get; private set; }
    
    public EmailTask(int emailTaskId, DateTime dateCreated, Email payerEmail, Email adminEmail,
        bool downloadAvailable, EmailTaskStatus status)
    {
        // Validations
        this.Id = emailTaskId;
        this.DateCreated = dateCreated;
        this.PayerEmail = payerEmail;
        this.AdminEmail = adminEmail;
        this.DownloadAvailableForAdmin = downloadAvailable;
        this.Status = status;
    }
}

我想为付款人和管理员 email 使用一个名为Email的值 object。 您可以看出它们只是扁平地存储在数据库/持久性 model 中。 付款人 email 是必需的,但不是管理员 email。


我的映射配置如下:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<EmailTask, EmailDbo>()
            .ForMember(dest => dest.EmailId, opts => opts.MapFrom(src => src.Id))
            .ForMember(dest => dest.SendTo, opts => opts.MapFrom(src => src.PayerEmail.SendTo))
            .ForMember(dest => dest.Subject, opts => opts.MapFrom(src => src.PayerEmail.Subject))
            .ForMember(dest => dest.Body, opts => opts.MapFrom(src => src.PayerEmail.Body))
            .ForMember(dest => dest.DateSent, opts => opts.MapFrom(src => src.PayerEmail.DateSent))
            .ForMember(dest => dest.DownloadAvailable, opts => opts.MapFrom(src => src.DownloadAvailableForAdmin))
            .ForMember(dest => dest.AdminEmail, opts => 
            {
                opts.PreCondition(src => (src.AdminEmail != null));
                opts.MapFrom(src => src.AdminEmail.SendTo);
            })
            .ForMember(dest => dest.AdminSubject, opts =>
            {
                opts.PreCondition(src => (src.AdminEmail != null));
                opts.MapFrom(src => src.AdminEmail.Subject);
            })
            .ForMember(dest => dest.AdminBody, opts =>
            {
                opts.PreCondition(src => (src.AdminEmail != null));
                opts.MapFrom(src => src.AdminEmail.Body);   
            })
            .ForMember(dest => dest.AdminDateSent, opts =>
            {
                opts.PreCondition(src => (src.AdminEmail != null)); 
                opts.MapFrom(src => src.AdminEmail.DateSent);   
            })
            .ForMember(dest => dest.StatusId, opts => opts.MapFrom(src => (int)src.Status))
            .ReverseMap()
                .ForCtorParam("status", opts => opts.MapFrom(src => src.StatusId))
                .ForMember(dest => dest.PayerEmail, opts => opts.MapFrom<PayerEmailValueResolver>())
                .ForMember(dest => dest.AdminEmail, opts => opts.MapFrom<AdminEmailValueResolver>());
    }
}

ReverseMap()之后,我想获取多个属性并构造复杂的 object Email 因此,我为此定义了两个自定义值解析器

public class PayerEmailValueResolver : IValueResolver<EmailDbo, EmailTask, Email>
{
    public Email Resolve(EmailDbo emailDbo, EmailTask emailTask, Email email, ResolutionContext context)
    {
        return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);  
    }
}

public class AdminEmailValueResolver : IValueResolver<EmailDbo, EmailTask, Email>
{
    public Email Resolve(EmailDbo emailDbo, EmailTask emailTask, Email email, ResolutionContext context)
    {
        if (String.IsNullOrWhiteSpace(emailDbo.AdminEmail) &&
            String.IsNullOrWhiteSpace(emailDbo.AdminSubject) &&
            String.IsNullOrWhiteSpace(emailDbo.AdminBody) &&
            !emailDbo.AdminDateSent.HasValue)
        {
            return null;
        }
        
        return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);  
    }
}

与往常一样,从域 model 到 Dbo 的映射工作正常:

在此处输入图像描述

但不是其他方式,从 Dbo 到域 model。 它抛出异常:

未处理的异常。 System.ArgumentException:Program+EmailTask 需要有一个带有 0 个参数或只有可选参数的构造函数。 AutoMapper.Mapper.MapCore[TSource,Tdestination](TSource source, Tdestination destination, ResolutionContext context, Type sourceType, Type destinationType, IMemberMap memberMap) 中的 lambda_method32(Closure, Object, EmailTask, ResolutionContext ) 中的(参数“类型”)。 Mapper.Map[TSource,Tdestination](TSource source, Tdestination destination) at AutoMapper.Mapper.Map[TDestination](Object source)

.Net Fiddle 演示: https://dotnetfiddle.net/DcTsPG


我想知道 AutoMapper 是否混淆了这两个Email对象:付款人 email 和管理员 email,因为它们都是Email类型。

相反 map AutoMapper无法创建EmailTask 的实例。

将无参数构造函数添加到您的EmailTask class -

public EmailTask()
{
    // AutoMapper use only
}

此外,由于您的值解析器正在创建Email的实例,因此也向您的Email class 添加一个无参数构造函数 -

public Email()
{
    // AutoMapper use only
}

最后,修改EmailTask class 中的PayerEmailAdminEmail属性,以便可以公开设置它们 -

public Email PayerEmail { get; set; }
public Email AdminEmail { get; set; }

那应该可以解决您的问题。

编辑:
@David Liang,在阅读了您的评论后,我想说,根据 DDD 来适应您的场景,您可能需要修改当前的映射方法。

问题是,当您从EmailTask 映射EmailDbo时,该过程更容易,因为EmailDbo是 DTO 类型 class 没有参数化构造函数。 因此,仅属性映射就足以完成这项工作。

但是,当您尝试从EmailDbo执行EmailTask EmailTask 时,您正在尝试实例化一个域 model class ,它具有非常严格的定义,无法从外部尝试保护它的复杂类型作为参数的参数化构造函数,并且无法访问。 因此,您当前使用的.ReverseMap()方法不会很有帮助,因为仅属性映射不足以为您提供实例化 class 所需的所有构造函数参数。 剧中还有 AutoMapper 的命名约定。

以下是来自EmailDboEmailTask 的映射配置,其中分离出反向映射,并将值解析器重构为帮助器 class。 前向映射保持不变。

CreateMap<EmailDbo, EmailTask>()
    .ConstructUsing((s, d) =>
        new EmailTask(
                s.EmailId,
                s.DateCreated,
                Helper.GetPayerEmail(s),
                Helper.GetAdminEmail(s),
                s.DownloadAvailable,
                (EmailTaskStatus)s.StatusId))
    .IgnoreAllPropertiesWithAnInaccessibleSetter();

Helper class -

public class Helper
{
    public static Email GetPayerEmail(EmailDbo emailDbo)
    {
        return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
    }

    public static Email GetAdminEmail(EmailDbo emailDbo)
    {
        if (string.IsNullOrWhiteSpace(emailDbo.AdminEmail) && string.IsNullOrWhiteSpace(emailDbo.AdminSubject)
            && string.IsNullOrWhiteSpace(emailDbo.AdminBody) && !emailDbo.AdminDateSent.HasValue)
        {
            return null;
        }
        return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
    }
}

这是完整的小提琴 - https://dotnetfiddle.net/2MxSdt

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM