AutoMapper从多个来源转换

Bar*_*xto 59 c# automapper

假设我有两个模型类:

public class People {
   public string FirstName {get;set;}
   public string LastName {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

还有一类电话:

public class Phone {
   public string Number {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

我想转换为PeoplePhoneDto,如下所示:

public class PeoplePhoneDto {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string PhoneNumber {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

让我们在我的控制器中说:

var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);

// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;
Run Code Online (Sandbox Code Playgroud)

我似乎找不到这个场景的任何例子.这可能吗 ?

注意:示例不是真实的,仅针对此问题.

Ser*_*kiy 90

您无法直接将多个来源映射到单个目的地 - 您应该逐个应用地图,如Andrew Whitaker的回答所述.因此,您必须定义所有映射:

Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
        .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));
Run Code Online (Sandbox Code Playgroud)

然后通过任何这些映射创建目标对象,并将其他映射应用于创建的对象.这个步骤可以通过非常简单的扩展方法简化:

public static TDestination Map<TSource, TDestination>(
    this TDestination destination, TSource source)
{
    return Mapper.Map(source, destination);
}
Run Code Online (Sandbox Code Playgroud)

用法很简单:

var dto = Mapper.Map<PeoplePhoneDto>(people)
                .Map(phone);
Run Code Online (Sandbox Code Playgroud)

  • 正是我在寻找的!谢谢 ! (2认同)
  • AutoMapper 有一个抽象 [IMapper](https://gist.github.com/ilyapalkin/8822638),用于将多个源映射到我使用的单个目的地。 (2认同)
  • 扩展方法对我不起作用(AutoMapper v9 Mapper.Map 不是静态的)替代方案只是使用您的映射器实例,例如 var foo = mapper.Map&lt;PeoplePhoneDto&gt;(people); 映射器.Map(电话, foo); (2认同)

Lui*_*eia 29

如果您使用的是 C# 7+,请尝试此操作(@Pawe\xc5\x82 Bejgerthat 的答案略有不同,这将使事情变得更加简单):

\n
Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>()\n    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.people.FirstName))\n    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.people.LastName))\n    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.phone.Number ));\n
Run Code Online (Sandbox Code Playgroud)\n

然后像这样使用它:

\n
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>((people, phone));\n
Run Code Online (Sandbox Code Playgroud)\n

是的,你需要在参数周围加上几个括号,这不是一个错误。其背后的原因是您传递的是一个(而不是两个)单个源,而该源恰好是一个(人员,电话)元组。

\n

  • 发现我的问题 - 我的视图没有设置器:facepalm: (2认同)
  • 不管怎样,这样做的一个缺点是在使用上——由于它不是类型安全的,很容易弄错元组。在使用中获得类型安全的一种选择是定义一个扩展方法:`public static PeoplePhoneDto MapPeoplePhone(this IMapper mapper, People people, Phone phone) =&gt; mapper.Map&lt;PeoplePhoneDto&gt;((people,phone));` 然后这只是想出好的扩展方法名称的问题,但我们都喜欢命名,对吧?;) (2认同)

Paw*_*ger 17

你可以使用一个Tuple:

Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));
Run Code Online (Sandbox Code Playgroud)

如果您有更多的源模型,您可以使用不同的表示(列表,字典或其他),将所有这些模型作为源集合在一起.

上面的代码应该优先放在一些AutoMapperConfiguration文件中,设置一次并全局,然后在适用时使用.

默认情况下,AutoMapper仅支持单个数据源.所以不可能直接设置多个源(没有将它包装在一个集合中),因为那么如果两个源模型具有相同名称的属性,我们怎么知道呢?

虽然有一些解决方法可以实现这一目标:

public static class EntityMapper
{
    public static T Map<T>(params object[] sources) where T : class
    {
        if (!sources.Any())
        {
            return default(T);
        }

        var initialSource = sources[0];

        var mappingResult = Map<T>(initialSource);

        // Now map the remaining source objects
        if (sources.Count() > 1)
        {
            Map(mappingResult, sources.Skip(1).ToArray());
        }

        return mappingResult;
    }

    private static void Map(object destination, params object[] sources)
    {
        if (!sources.Any())
        {
            return;
        }

        var destinationType = destination.GetType();

        foreach (var source in sources)
        {
            var sourceType = source.GetType();
            Mapper.Map(source, destination, sourceType, destinationType);
        }
    }

    private static T Map<T>(object source) where T : class
    {
        var destinationType = typeof(T);
        var sourceType = source.GetType();

        var mappingResult = Mapper.Map(source, sourceType, destinationType);

        return mappingResult as T;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);
Run Code Online (Sandbox Code Playgroud)

但说实话,即使我已经使用AutoMapper几年了,我也从未需要使用来自多个来源的映射.例如,当我在单视图模型中需要多个业务模型时,我只是将这些模型嵌入到视图模型类中.

所以在你的情况下,它看起来像这样:

public class PeoplePhoneDto {
    public People People { get; set; }
    public Phone Phone { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

  • 所以我必须在做映射之前创建一个元组,我想知道自动播放器的真正好处是什么......听起来有点过分.有没有办法避免创建另一种类型(元组,dic等)? (3认同)
  • 您建议的“PeoplePhoneDto”看起来不错,但我仍然认为来自多个源的映射很有用,尤其是在映射视图模型中。我认为大多数现实世界场景需要多个源来构建视图模型。我想您可以创建不扁平化的视图模型来解决这个问题,但我认为创建视图模型而不关心业务模式是什么样子是一个好主意。 (2认同)
  • @TheMuffinMan`Tuple`将第一个类型的参数暴露为`Item1`,第二个参数暴露为`Item2`等.在这个意义上,顺序很重要. (2认同)

Ran*_*nga 6

我会编写一个扩展方法,如下所示:

    public static TDestination Map<TSource1, TSource2, TDestination>(
        this IMapper mapper, TSource1 source1, TSource2 source2)
    {
        var destination = mapper.Map<TSource1, TDestination>(source1);
        return mapper.Map(source2, destination);
    }
Run Code Online (Sandbox Code Playgroud)

那么用法将是:

    mapper.Map<People, Phone, PeoplePhoneDto>(people, phone);
Run Code Online (Sandbox Code Playgroud)