自动映射器 - 多对象源和一个目标

本文关键字:目标 一个 映射 多对象 | 更新日期: 2023-09-27 18:36:45

我正在使用自动映射器将多个对象(db class映射到uu对象)。

地图 1:

Mapper.CreateMap<sourceone, destination>().ForMember(sss => sss.one, m => m.MapFrom(source => source.abc));

地图 2:

Mapper.CreateMap<sourcetwo, destination>().ForMember(sss => sss.two, m => m.MapFrom(source => source.xyz));
destination d = new destination();

地图 1

d = AutoMapper.Mapper.Map<sourceone, destination>(sourceone);

地图 2

d = AutoMapper.Mapper.Map<sourcetwo, destination>(sourcetwo);

一旦我调用"地图 2",使用地图 1 填充的值就会丢失。(即目的地一变为空)。我该如何解决这个问题?

自动映射器 - 多对象源和一个目标

Map有一个重载,它采用源和目标对象:

d = AutoMapper.Mapper.Map<sourceone, destination>(sourceone);
/* Pass the created destination to the second map call: */
AutoMapper.Mapper.Map<sourcetwo, destination>(sourcetwo, d);
mapper.MergeInto<PersonCar>(person, car)

以公认的答案作为扩展方法,简单而通用的版本:

public static TResult MergeInto<TResult>(this IMapper mapper, object item1, object item2)
{
    return mapper.Map(item2, mapper.Map<TResult>(item1));
}
public static TResult MergeInto<TResult>(this IMapper mapper, params object[] objects)
{
    var res = mapper.Map<TResult>(objects.First());
    return objects.Skip(1).Aggregate(res, (r, obj) => mapper.Map(obj, r));
}

为每种输入类型配置映射后:

IMapper mapper = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonCar>();
    cfg.CreateMap<Car, PersonCar>();
}).CreateMapper();
public class Person
{
    public string Name { get; set; }
    public string PhNo { get; set; }
}
public class Company
{
    public int EmpNo { get; set; }
    public string Title { get; set; }
}
public class PersonCompany
{
    public string Name { get; set; }
    public string PhNo { get; set; }
    public int EmpNo { get; set; }
    public string Title { get; set; }
}
//you can test as below
        var pMap = Mapper.CreateMap<Person,PersonCompany>();
        pMap.ForAllMembers(d => d.Ignore()); 
        pMap.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Name))
            .ForMember(d => d.PhNo, opt => opt.MapFrom(s => s.PhNo));
        var cMap = Mapper.CreateMap<Company, PersonCompany>();
        cMap.ForAllMembers(d => d.Ignore());
        cMap.ForMember(d => d.EmpNo, opt => opt.MapFrom(s => s.EmpNo))
            .ForMember(d => d.Title, opt => opt.MapFrom(s => s.Title));

        var person = new Person { Name = "PersonName", PhNo = "212-000-0000" };
        var personCompany = Mapper.Map<Person,PersonCompany>(person);
        var company = new Company { Title = "Associate Director", EmpNo = 10001 };
        personCompany = Mapper.Map(company, personCompany);
        Console.WriteLine("personCompany.Name={0}", personCompany.Name);
        Console.WriteLine("personCompany.PhNo={0}", personCompany.PhNo);
        Console.WriteLine("personCompany.EmpNo={0}", personCompany.EmpNo);
        Console.WriteLine("personCompany.Title={0}", personCompany.Title);

根据我的说法,您应该避免调用重载的 Map 方法,该方法采用接受答案中所述的目标对象的实例。这不会让你测试/验证你的映射配置(Mapper.Configuration.AssertConfigurationIsValid()),或者这样做你会在你的映射中添加很多"忽略"。

一个非常简单的解决方案是创建一个保存源引用的复合类型,并基于该复合类型定义到目标的映射。

像这样:

    public class SourceOneTwo
    {
        public SourceOne SourceOne { get; set; }
        public SourceTwo SourceTwo { get; set; }
    }
    static void Main(string[] args)
    {
        var config = new MapperConfiguration(cfg => 
            cfg.CreateMap<SourceOneTwo, Destination>()
            .ForMember(dest => dest.one, m => m.MapFrom(source => source.SourceOne.abc))
            .ForMember(dest => dest.two, m => m.MapFrom(source => source.SourceTwo.xyz)));
        config.AssertConfigurationIsValid();
    }

只是想补充一点,现在你可以使用元组语法来定义来自多种类型的映射。

// configuring
Mapper
  .CreateMap<(SourceType1 Source1, SourceType2 Source2), DestinationType>()
    .ForMember(sss => sss.one, m => m.MapFrom(source => source.Source1.abc))
    .ForMember(sss => sss.two, m => m.MapFrom(source => source.Source2.xyz));
// using
var result = Mapper.Map<DestinationType>((source1, source2));

优点:

  • 第一种类型映射后不必记住从第二种类型映射
  • 所有映射都是在一个地方一次性定义的(只要您不需要单独的映射以及组合映射)
  • 从 2 个以上的源类型映射时不那么笨拙
  • 本质上是@asidis的想法,但不需要单独的类型

缺点:

  • 好吧,与一般的自动映射器一样,您必须知道在哪里查找,以找出用于映射的类型
  • 当涉及多种类型时,可能看起来有点笨拙

现在它看起来像这样:

DestinationDto = _mapper.Map(source2, _mapper.Map<source1type, destinationType>(source1));

通过扩展的解决方案:

public static class MapperExtension
{
    public static T MergeInto<T>(this IMapper mapper, params object[] sources)
    {
        return (T)sources
            .Aggregate(Activator.CreateInstance(typeof(T)), (dest, source) => mapper.Map(source, dest));
    }
}

用法:

_mapper.MergeInto<TypeOut>(new object[] { type1, type2, type3 });

笔记:

ITypeConverter不仅应支持返回新类型,还应支持返回已创建的目标。

样本:

public class TypeInToTypeOut : ITypeConverter<TypeIn, TypeOut>
{
    public TypeOut Convert(TypeIn source, TypeOut destination, ResolutionContext context)
    {
        var response = destination ?? new();
        response.Field1 = source?.FielderA;
        response.Field2 = source?.FielderB;
        return response;
    }
}
相关文章: