枚举及其整数值之间的 AutoMapper 映射因 ReverseMap 而失败

AutoMapper mapping between enum and its integer values fails with ReverseMap

该应用程序是使用 DDD 方法构建的,具有一组单独的持久性模型。我调用了数据库对象,或 dbo:

public class ParentDbo
{
    public int ParentId { get; set; }
    public int TypeId { get; set; }
}

public class ChildDbo
{
    public int ChildId { get; set; }
    public ParentDbo Parent { get; set; }
    public int RetryNumber { get; set; }
}

我们有一个简单的模型可供查看:父子关系。 RetryNumber 表示数据库中的枚举值。


在检索数据时,它首先使用 Dapper 查询数据库,然后使用其 splitOn 功能将数据映射到其中。这部分无关紧要,但为了完整起见,我还是会展示它:

const string sql = "SELECT * FROM XXX ....";

using (var cnt = _dbConnectionFactory.CreateConnection())
{
    var childDbos = await cnt.QueryAsync<ChildDbo, ParentDbo, ChildDbo>(
        sql: sql,
        map: (childDbo, parentDbo) =>
        {
            childDbo.Parent = parentDbo;
            return childDbo;
        },
        splitOn: "ParentId"
    );
}

Dapper 的局限性在于它无法将数据映射到 私有复杂 对象。这就是为什么我必须拥有 2 套模型的主要原因。我想用私有设置器和其他技术将数据和逻辑封装在域模型中。

这是我的领域模型:

public class Parent
{
    public int Id { get; private set; }
    public int TypeId { get; private set; }
    
    public Parent(int parentId, int typeId)
    {
        // Validations
        
        this.Id = parentId;
        this.TypeId = typeId;
    }
}

public class Child
{
    public int Id { get; private set; }
    public Parent Parent { get; private set; }
    public Attempt Attempt { get; private set; }
    
    public Child(int childId, Parent parent, Attempt attempt)
    {
        // Validations
        
        this.Id = childId;
        this.Parent = parent;
        this.Attempt = attempt;
    }
}

对于域模型,我不想要 public setter 和无参数构造函数。

Attempt 是具有整数支持值的枚举:

public enum Attempt
{
    Original = 1,
    FirstRetry = 2,
    SecondRetry = 3,
    LastRetry = 4
}

最后,我想使用AutoMapper 在Dbos 和领域模型之间进行映射。这是映射:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Child, ChildDbo>()
            .ForMember(dest => dest.ChildId, opts => opts.MapFrom(src => src.Id))
            .ForMember(dest => dest.RetryNumber, opts => opts.MapFrom(src => (int)src.Attempt))
            .ReverseMap();
        
        CreateMap<Parent, ParentDbo>()
            .ForMember(dest => dest.ParentId, opts => opts.MapFrom(src => src.Id))
            .ReverseMap();
    }
}

我想要双向映射,所以我使用 ReverseMap()


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

它毫无问题地将域模型映射到 dbos:

但从 dbos 到域模型的反向映射会抛出异常:

Unhandled exception. System.ArgumentException: Program+Child needs to have a constructor with 0 args or only optional args. (Parameter 'type') at lambda_method18(Closure , Object , Child , 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) at Program.Main()


我已经尝试删除枚举 属性 并且一切正常所以我很确定是枚举映射有问题。

据我在您的 fiddle 中看到的,您正在尝试从 ChildDbo 映射到 Parent,但没有针对它的映射设置。将映射代码更改为:

var child2 = mapper.Map<Child>(childDbo);

并且由于第三个 Child 的 ctor 参数和源 属性 名称不匹配,将映射更改为:

CreateMap<Child, ChildDbo>()
    .ForMember(dest => dest.ChildId, opts => opts.MapFrom(src => src.Id))
    .ForMember(dest => dest.RetryNumber, opts => opts.MapFrom(src => (int)src.Attempt))
    .ReverseMap()
    .ConstructUsing((dbo, ctx) => new Child(dbo.ChildId, ctx.Mapper.Map<Parent>(dbo.Parent), (Attempt)dbo.RetryNumber));

here

或者将第三个Child的ctor参数重命名为retryNumber:

public Child(int childId, Parent parent, Attempt retryNumber)

参见 here

或使用ForCtorParam:

CreateMap<Child, ChildDbo>()
    .ForMember(dest => dest.ChildId, opts => opts.MapFrom(src => src.Id))
    .ForMember(dest => dest.RetryNumber, opts => opts.MapFrom(src => (int)src.Attempt))
    .ReverseMap()
    .ForCtorParam("attempt", opt => opt.MapFrom(dbo => dbo.RetryNumber))

Here.