使用AutoMapper取消DTO

时间:2010-06-29 21:55:15

标签: mapping automapper

我一直在尝试使用AutoMapper来节省从我的DTO到我的域对象的时间,但是我在配置地图时遇到了麻烦,以至于它可以工作,我开始怀疑AutoMapper是否可能是错误的工作工具。

考虑这个域对象的例子(一个实体和一个值):

public class Person
{
    public string Name { get; set; }
    public StreetAddress Address { get; set; }
}

public class StreetAddress
{
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

我的DTO(来自Linq-to-SQL对象)看起来大概是这样的:

public class PersonDTO
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

我希望能够在我的存储库中执行此操作:

return Mapper.Map<PersonDTO, Person>(result);

我尝试过各种方式配置AutoMapper,但我不断收到通用的缺少类型映射配置或不支持的映射错误,没有详细信息告诉我我在哪里失败。< / p>

我尝试了很多不同的配置,但这里有几个:

Mapper.CreateMap<PersonDTO, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
    .ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
    .ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));

我已经读过使用AutoMapper的展平对象很简单,但 unflattening 它们并不容易......甚至可能。任何人都可以告诉我,我是否正在尝试做不可能的事情,如果不是我做错了什么?

请注意,我的实际对象有点复杂,所以我可能会遗漏错误的关键信息...如果我正在做的事情看起来正确我可以提供更多信息或开始简化我的用于测试的对象。

7 个答案:

答案 0 :(得分:63)

这似乎对我有用:

Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

基本上,创建从dto到两个对象的映射,然后将其用作子对象的源。

答案 1 :(得分:9)

无法发表评论,因此发布了答案。我想AutoMapper实现有一些变化,所以HansoS提出的答案https://stackoverflow.com/a/5154321/2164198不再可编译。虽然还有另一种方法可以在这种情况下使用 - ResolveUsing

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))

答案 2 :(得分:9)

除了sydneyos回答并根据Trevor de Koekkoek评论,双向映射是可能的这种方式

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

public class PersonViewModel
{
    public string Name { get; set; }
    public string AddressStreet { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
}

自动映射

Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

如果你实现NameOf类,你可以摆脱前缀魔术字符串

Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));

编辑:在C#6

Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));

答案 3 :(得分:8)

使用https://github.com/omuleanu/ValueInjecter展平 unflattening ,以及您需要的任何其他内容,还有 asp.net mvc示例下载中的应用程序 ,其中演示了所有功能(也是单元测试)

答案 4 :(得分:5)

这可能会迟到但你可以通过使用lambda表达式来创建这样的对象来解决这个问题:

Mapper.CreateMap<Person, Domain.Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))

答案 5 :(得分:1)

我正在使用此

public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var prefix = opt.DestinationMember.Name;
    var memberProps = typeof(TMember).GetProperties();
    var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
        .Select(sourceProp => new
        {
            SourceProp = sourceProp,
            MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
        })
        .Where(x => x.MemberProp != null);
    var parameter = Expression.Parameter(typeof(TSource));

    var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
    var resolver = Expression.Lambda<Func<TSource, TMember>>(
        Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
        parameter);

    opt.ResolveUsing(resolver.Compile());
}

配置

new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonDTO>();
    cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});

模型

public class Person
{
    public string Name { get; set; }
    public Address HomeAddress { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

遵循AutoMapper展平惯例

public class PersonDTO
{
    public string Name { get; set; }
    public string HomeAddressLine1 { get; set; }
    public string HomeAddressLine2 { get; set; }
    public string HomeAddressCity { get; set; }
    public string HomeAddressState { get; set; }
    public string HomeAddressZipCode { get; set; }
}

可能需要很多改进,但它有效...

答案 6 :(得分:0)

我有另一个解决方案。主要思想是 AutoMapper know如何在拼合对象中正确命名属性时如何拼合嵌套对象:添加嵌套对象属性名称作为前缀。对于您的情况,地址是前缀:

public class PersonDTO
{
    public string Name { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
    ...
}

因此创建从嵌套到展平的熟悉的映射,然后然后使用ReverseMap方法使AutomMapper了解如何展平嵌套的对象

CreateMap<Person, PersonDTO>()
   .ReverseMap();

仅此而已!