从多个属性到复杂对象的 AutoMapper 映射因 ReverseMap 和自定义值解析器而失败
AutoMapper mapping from multiple properties to complex objects fails with ReverseMap and custom value resolver
我在将多个属性反向映射回复杂对象时遇到问题,即使使用自定义值解析器也是如此。
持久性模型如下:
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 地图数据并填写这个模型。
以下是我想要与持久性模型来回映射的领域模型:
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
的值对象。您可以看出它们只是平铺存储在 database/persistence 模型中。并且需要付款人电子邮件,但不需要管理员电子邮件。
我的映射配置如下:
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()
之后,我想抓取多个属性并构造复杂对象Email
。因此,我为此定义了两个 custom value resolvers:
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);
}
}
一如既往,从域模型到 Dbo 的映射工作正常:
但不是相反,从 Dbo 到域模型。它抛出异常:
Unhandled exception. System.ArgumentException: Program+EmailTask needs to have a constructor with 0 args or only optional args. (Parameter 'type')
at lambda_method32(Closure , Object , EmailTask , ResolutionContext )
at AutoMapper.Mapper.MapCore[TSource,TDestination](TSource source, TDestination destination, ResolutionContext context, Type sourceType, Type destinationType, IMemberMap memberMap)
at AutoMapper.Mapper.Map[TSource,TDestination](TSource source, TDestination destination)
at AutoMapper.Mapper.Map[TDestination](Object source)
.Net Fiddle 演示: https://dotnetfiddle.net/DcTsPG
我想知道 AutoMapper 是否混淆了这两个 Email
对象:付款人电子邮件和管理员电子邮件,因为它们都是 Email
类型。
反向映射 AutoMapper
无法创建 EmailTask
的实例。
为你的 EmailTask
class -
添加一个无参数的构造函数
public EmailTask()
{
// AutoMapper use only
}
此外,由于您的值解析器正在创建 Email
的实例,因此也将无参数构造函数添加到您的 Email
class -
public Email()
{
// AutoMapper use only
}
最后,修改EmailTask
中的PayerEmail
和AdminEmail
属性class使其可以公开设置-
public Email PayerEmail { get; set; }
public Email AdminEmail { get; set; }
这应该可以解决您的问题。
编辑:
@David Liang,在阅读您的评论后我会说,为了适应 DDD 的场景,您可能需要修改当前的映射方法。
事实是,当您从 EmailTask
映射 EmailDbo
时,过程更容易,因为 EmailDbo
是一个没有参数化构造函数的 DTO 类型 class。因此,仅 属性 映射就足以完成这项工作。
但是当您尝试从 EmailDbo
映射 EmailTask
时,您正在尝试实例化域模型 class,它具有非常严格定义的参数化构造函数,该构造函数将复杂类型作为参数,并试图保护它的属性如何可以和不能从外部访问。因此,您当前使用的 .ReverseMap()
方法不会很有帮助,因为仅 属性 映射不足以为您提供实例化 class 所需的所有构造函数参数。剧中还有AutoMapper的命名规范
以下是 EmailDbo
中 EmailTask
的映射配置,其中反向映射被分离出来,值解析器被重构为助手 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);
}
}
这是完整的 fiddle - https://dotnetfiddle.net/2MxSdt
我在将多个属性反向映射回复杂对象时遇到问题,即使使用自定义值解析器也是如此。
持久性模型如下:
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 地图数据并填写这个模型。
以下是我想要与持久性模型来回映射的领域模型:
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
的值对象。您可以看出它们只是平铺存储在 database/persistence 模型中。并且需要付款人电子邮件,但不需要管理员电子邮件。
我的映射配置如下:
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()
之后,我想抓取多个属性并构造复杂对象Email
。因此,我为此定义了两个 custom value resolvers:
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);
}
}
一如既往,从域模型到 Dbo 的映射工作正常:
但不是相反,从 Dbo 到域模型。它抛出异常:
Unhandled exception. System.ArgumentException: Program+EmailTask needs to have a constructor with 0 args or only optional args. (Parameter 'type') at lambda_method32(Closure , Object , EmailTask , ResolutionContext ) at AutoMapper.Mapper.MapCore[TSource,TDestination](TSource source, TDestination destination, ResolutionContext context, Type sourceType, Type destinationType, IMemberMap memberMap) at AutoMapper.Mapper.Map[TSource,TDestination](TSource source, TDestination destination) at AutoMapper.Mapper.Map[TDestination](Object source)
.Net Fiddle 演示: https://dotnetfiddle.net/DcTsPG
我想知道 AutoMapper 是否混淆了这两个 Email
对象:付款人电子邮件和管理员电子邮件,因为它们都是 Email
类型。
反向映射 AutoMapper
无法创建 EmailTask
的实例。
为你的 EmailTask
class -
public EmailTask()
{
// AutoMapper use only
}
此外,由于您的值解析器正在创建 Email
的实例,因此也将无参数构造函数添加到您的 Email
class -
public Email()
{
// AutoMapper use only
}
最后,修改EmailTask
中的PayerEmail
和AdminEmail
属性class使其可以公开设置-
public Email PayerEmail { get; set; }
public Email AdminEmail { get; set; }
这应该可以解决您的问题。
编辑:
@David Liang,在阅读您的评论后我会说,为了适应 DDD 的场景,您可能需要修改当前的映射方法。
事实是,当您从 EmailTask
映射 EmailDbo
时,过程更容易,因为 EmailDbo
是一个没有参数化构造函数的 DTO 类型 class。因此,仅 属性 映射就足以完成这项工作。
但是当您尝试从 EmailDbo
映射 EmailTask
时,您正在尝试实例化域模型 class,它具有非常严格定义的参数化构造函数,该构造函数将复杂类型作为参数,并试图保护它的属性如何可以和不能从外部访问。因此,您当前使用的 .ReverseMap()
方法不会很有帮助,因为仅 属性 映射不足以为您提供实例化 class 所需的所有构造函数参数。剧中还有AutoMapper的命名规范
以下是 EmailDbo
中 EmailTask
的映射配置,其中反向映射被分离出来,值解析器被重构为助手 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);
}
}
这是完整的 fiddle - https://dotnetfiddle.net/2MxSdt