automapper在ORM中的作用,求教?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了automapper在ORM中的作用,求教?相关的知识,希望对你有一定的参考价值。

最近在研究.net的一些orm框架

在用到Entity的时候,发现一些开源代码用到了automapper,说是将数据对象转成DTO
不是很明白这个意思。
想比较直接的问下,这个到底是要达到什么目的呢?

AutoMapper?举个最简单的例子。
比如在ORM中,与数据库交互用的Model模型是具有很多属性变量方法神马的。而当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑),我们不希望直接将这个Model模型传递给它们,这时我们会创建一个贫血模型来保存数据并传递。神马是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性神马的,只能保存必须的数据,木有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个创建的过程中,如果我们手动来进行,就会看到这样的代码:
B b=new B();
b.XXX1=a.XXX1;
b.XXX2=a.XXX2;
...
...
...
return b;

此时,AutoMapper可以发挥的作用就是根据A的模型和B的模型中的定义,自动将A模型映射为一个全新的B模型。从而避免写这样又臭又长又无聊的代码。SO,理解为代码生成器也可以……追问

大概知道贫血模型和充血模型的区别。
但是还是没明白您指的“当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑)”

追答

基于访问性的控制或从模型本身上考虑。对外开放的原则是,尽量降低系统耦合度,否则内部一旦变更外部所有的接口都要跟随发生变更;另一方面,系统内部的一些数据或方法并不希望外部能看到或调用。类似的考虑很多,只是举个例子。

追问

系统设计的原则是高内聚低耦合,尽量依赖抽象而不依赖于具体。
这里感觉automapper就是使数据库实体对一个外部调用实体的转换更简便(不用一个属性一个属性的赋值)。
1.确实没感觉出体现出了什么灵活性。
2.方法具体接口不想让外部看到可以通过一个领域模型来根据业务封装内部的方法,
但是数据结构不想让外部看到,小弟不是很清楚,请问大哥能给描述个场景吗?

追答

理解到转换更方便就行了,实际上它也就是干这事儿的,没那么高深。
具体场景?最简单的一个,数据库里面有用户信息表,供别的系统调用,提供了数据接口。如果直接暴露了数据库层的表结构的话,会对系统本身产生依赖。具体表现在,假定现在因为某种需要,为用户信息增加了十个字段的信息,那么,如果不进行类型映射的话,会导致所有基于此用户数据结构的模块集体挂掉(接口约定变更)。而如果使用了映射的话,我们可以在内部进行转换,保持原有接口不变并提供新的更全面的接口,这是保证系统的可维护性和可迁移性。

参考技术A 自己看吧不抄了~

ASP.NET Core 中的对象映射之 AutoMapper

AutoMapper 简介

AutoMapper是一个对象映射器,它可以将一种类型的对象转换为另一种类型的对象。

它提供了映射规则及操作方法,使我们不用过多配置就可以映射两个类, 可以帮我们免于编写无聊的映射代码. 在代码层与层之间隔离模型model上非常有用.

AutoMapper 使用

初始化

创建两个简单的类用于测试:

public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class UserDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
}

AutoMapper可以使用静态类和实例方法来创建映射.

  • 静态类方式

    Mapper.Initialize(cfg => cfg.CreateMap<UserEntity, UserDTO>());
    var userDTO = Mapper.Map<UserDTO>(user);
    
  • 实例方式

    var config = new MapperConfiguration(cfg => cfg.CreateMap<UserEntity, UserDTO>());
    var mapper = config.CreateMapper();
    
    var userDTO = mapper.Map<UserDTO>(user);
    
  • 依赖注入

    使用扩展 AutoMapper.Extensions.Microsoft.DependencyInjection 来实现AutoMapper的依赖注入. 本质是注册一个MapperConfiguration的单例和IMapper的scope实例, 通过程序集扫描添加AutoMapper的相关配置和映射.

    IServiceCollection services = new ServiceCollection();
    services.AddAutoMapper();
    
    var provider = services.BuildServiceProvider();
    using (var scope = provider.CreateScope())
    {
        var mapper = scope.ServiceProvider.GetService<IMapper>();
        var userDTO = mapper.Map<UserDTO>(user);
    }
    

Profile设置

可以使用Profie配置来实现映射关系, 然后通过AddProfile添加.

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>();
    }
}

var config = new MapperConfiguration(cfg => cfg.AddProfile<UserProfile>());

扁平化映射

AutoMapper支持扁平化映射, 它会根据Pascal命名方式分割目标字段为单个单词, 可自动映射属性名+内嵌属性名. 如下例AutoMapper自动映射UserEntity.Address.City -> UserDTO.AddressCity。

public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string Country { get; set; }
}

public class UserDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string AddressCity { get; set; }
    public string AddressCountry { get; set; }
}

集合映射

AutoMapper除了可以映射单个对象外,也可以映射集合对象。

CreateMap<UserEntity, UserDTO>();

var userList = new List<UserEntity> {
    new UserEntity { Id = 1, Name="Test1" },
    new UserEntity { Id = 2, Name="Test2" },
};
var dtoList = mapper.Map<List<UserDTO>>(userList);
public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<AddressEntity> AddressList { get; set; }
}

public class AddressEntity
{
    public string City { get; set; }
    public string Country { get; set; }
}

public class UserDTO
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<AddressDTO> AddressList { get; set; }
}

public class AddressDTO
{
    public string City { get; set; }
    public string Country { get; set; }
}

CreateMap<AddressEntity, AddressDTO>();
CreateMap<UserEntity, UserDTO>();

var user = new UserEntity
{
    Id = 1,
    Name = "Test",
    AddressList = new List<AddressEntity>
    {
        new AddressEntity { City = "ShangHai", Country = "China"},
        new AddressEntity { City = "BeiJing", Country = "China"}
    }
};

var userDTO = mapper.Map<UserDTO>(user);

投影

当把一个源值投影到一个不精准匹配源结构的目标值时,使用MapFrom指明成员映射定义。

public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
}

public class UserDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string BirthYear { get; set; }
    public string BirthMonth { get; set; }
}

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ForMember(d => d.BirthYear, o => o.MapFrom(s => s.BirthDate.Year))
            .ForMember(d => d.BirthMonth, o => o.MapFrom(s => s.BirthDate.Month));
    }
}

var user = new UserEntity
{
    Id = 1,
    Name = "Test",
    BirthDate = DateTime.Today,
};

var userDTO = mapper.Map<UserDTO>(user);

条件映射

有些情况下,我们将只满足映射条件的才添加到属性上.

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ForMember(d => d.Id, o => o.Condition(s => s.Id > 1));
    }
}

值转换

AutoMapper可以配置值转换和空值替换

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ForMember(d => d.Name, o => o.NullSubstitute("Default Name"))
            .ForMember(d => d.Name, o => o.AddTransform(val => string.Format("Name: {0}", val)));
    }
}

设置转换前后行为

有时候,在映射发生之前或之后,可能需要执行一些自定义的逻辑。

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .BeforeMap((s, d) => s.BirthDate = s.BirthDate.AddYears(-12))
            .AfterMap((s, d) => d.BirthMonth = "July");
    }
}

配置验证及设置

配置了映射,但是如何确定是否映射成功或者是否有字段没有映射呢?可以使用mapper.ConfigurationProvider.AssertConfigurationIsValid()来验证是否映射成功。但也可以指定单个字段不验证.

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ForMember(d => d.NickName, o => o.Ignore());
    }
}

反向映射

从6.1.0开始,AutoMapper通过ReverseMap可以实现反向映射。使用ReverseMap, 不用再创建DTO -> Entity的映射, 而且还能保留正向的映射规则。

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ReverseMap();
    }
}

自定义转换器

有些情况下目标字段类型和源字段类型不一致,可以通过类型转换器实现映射,类型转换器有三种实现方式:

void ConvertUsing(Func<TSource, TDestination> mappingFunction);
void ConvertUsing(ITypeConverter<TSource, TDestination> converter);
void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;

自定义解析器

某些情况下,解析规则会很复杂,使用自带的解析规则无法实现。这时可以自定义解析规则,可以通过以下三种方式使用自定义的解析器:

ResolveUsing<TValueResolver>
ResolveUsing(typeof(CustomValueResolver))
ResolveUsing(aValueResolverInstance)
public class UserEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class UserDTO
{
    public string Name { get; set; }
}

public class UserNameResolver : IValueResolver<UserEntity, UserDTO, string>
{
    public string Resolve(UserEntity source, UserDTO destination, string destMember, ResolutionContext context)
    {
        if (source != null && !string.IsNullOrEmpty(source.FirstName) && !string.IsNullOrEmpty(source.LastName))
        {
            return string.Format("{0} {1}", source.FirstName, source.LastName);
        }

        return string.Empty;
    }
}

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<UserEntity, UserDTO>()
            .ForMember(d => d.Name, o => o.ResolveUsing<UserNameResolver>());
    }
}

参考

以上是关于automapper在ORM中的作用,求教?的主要内容,如果未能解决你的问题,请参考以下文章

Automapper:不同类型之间的映射

求教,ORM是啥?能用从C#中说明么

.net webapi+autofac+autoMapper 单元测试

如何使用automapper映射与多个表的数据集

解决 AutoMapper ProjectTo 不起作用的问题

如何使用automapper映射与多个表的数据集