• .NET AutoMapper学习记录


    在两个不同的类型对象之间传输数据,通常我们会用DTOs(数据传输对象),AutoMapper就是将一个对象自动转换为另一个对象的技术

    背景

    一些orm框架,在用到Entity的时候有一些开源代码用到了automapper(如:nopcommence),将数据对象转成DTO。比如在ORM中,与数据库交互用的Model模型是具有很多属性变量方法神马的。而当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑),我们不希望直接将这个Model模型传递给它们,这时我们会创建一个贫血模型来保存数据并传递。什么是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性什么的,只能保存必须的数据,没有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个创建的过程中,如果我们手动来进行,就会看到这样的代码:

    A a=new A();
    a.X1=b.X1;
    a.X2=b.X2;
    ...
    ...
    ...
    return a; 太麻烦

    此时,AutoMapper可以发挥的作用就是根据A的模型和B的模型中的定义,自动将A模型映射为一个全新的B模型。(不用一个属性一个属性的赋值)

    好处:

    1、 db或者模型 增加字段时,只需在DTO内部增加映射,赋值代码无需修改

    2、隔离,前端收集各参数,不用管后端定义的模型。前后端才用AutoMapper来做转换。

    使用

    Nuget引用:AutoMapper    版本不一样,里面的很多方法有些不一样

    AutoMapper是基于约定的,因此在实用映射之前,我们需要先进行映射规则的配置。

    我们要做的只是将要映射的两个类型告诉AutoMapper(调用Mapper类的Static方法CreateMap并传入要映射的类型): 

    Mapper.Initialize(cfg => { cfg.CreateMap<StudentEntity, StudentOutput>(); });

     

    也可以将实体类 放在配置文件MapperProfile中

    Mapper.Initialize(cfg => {

    cfg.AddProfile<MapperProfile>();

    cfg.AddProfile<ProxyAdapterProfile>();  //可增加多个

    });

    注意:多次调用 Mapper.Initialize() 只有最后一次生效。所以只能用一个Mapper.Initialize

    【AutoMapper.7.0.1】

    class MapperProfile : Profile
    
      {
    
    public MapperProfile()
    
            {
    
                CreateMap<StudentEntity, StudentOutput>();
    
                var map = CreateMap<UploadResponseBase, UploadResult>();
    
                //字段名称不一致,一次直接定义好所有字段的映射规则
    
                map.ConvertUsing(s => new UploadResult
    
                {
    
                    IsSuccess = s.success,
    
                    FileUrl = s.clientUrl,
    
                    ErrorMessage = s.rawFileName
    
                    , datetimeStr = (s.datetime).ToString(),
    
                });
    
            }
    
    }
    View Code

    【AutoMapper 4.2.1.0】

    AutoMapper使用ForMember来指定每一个字段的映射规则:

    protected override void Configure()
    
            {
    
                var mapResponst = CreateMap<Response, CResponse>();
    
                mapResponst.ForMember(dest => dest.departure_date, opt => opt.MapFrom(src => src.DepartureDate.ToString("yyyy-MM-dd HH:mm:ss")))
    
                                      .ForMember(dest => dest.ticket_price, opt => opt.MapFrom(src => src.TicketPrice));
    
     
    
                var mapContacts = CreateMap<CContacts, PassengerInputEntity>();
    
                mapContacts.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.First_Name))
    
                .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Last_Name))
    
                .ForMember(dest => dest.AreaCode, opt => opt.MapFrom(src => src.Area_Code));
    
            }
    View Code

    然后就可以交给AutoMapper帮我们搞定一切了: 

    //实例化实体List

                List<StudentEntity> StudentList = new List<StudentEntity>();

                //模拟数据

                StudentList.Add(new StudentEntity

                {

                    Id = 1,

                    Age = 12,

                    Gander = "boy",

                    Name = "WangZeLing",

                    Say = "Only the paranoid survive",

                    Score = 99M

                });

           //AuotMapper具体使用方法 将List<StudentEntity>转换为List<StudentOutput>

                List<StudentOutput> Output = Mapper.Map<List<StudentOutput>>(StudentList);

                Output.ForEach(output => Console.WriteLine(string.Format("name:{0},say:{1},score:{2}", output.Name, output.Say, output.Score)));

    解释

    1、 AutoMapper给我们提供的Convention或Configuration方式并不是“异或的”,我们可以结合使用两种方式,为名称不同的字段配置映射规则,而对于名称相同的字段则忽略配置。

    2、 在映射具有相同字段名的类型时,会自动转换

    3、 不相同名称的属性则需要 指定映射字段,设置ConvertUsing或者ForMember..

    4、 值为空的属性,AutoMapper在映射的时候会把相应属性也置为空       

    5、 如果传入一个空的AddressDto,AutoMapper也会帮我们得到一个空的Address对象。 

    Address address = Mapper.Map<AddressDto,Address>(null); 

    6、不需要映射的属性可以用Ignore忽略。【if有验证 目标类中的所有属性是否都被映射 时】

    使用Ignore方法:

    Mapper.CreateMap<Entity.Source, Entity.Destination>()

        .ForMember(dest => dest.SomeValuefff, opt =>

        {

            opt.Ignore();

        });

    最佳实践

    这段内容将讨论AutoMapper的规则写在什么地方的问题。

    在上一段中,我们已经知道了如何使用AutoMapper进行简单的对象映射,但是,在实际的项目中,我们会有很多类进行映射(从Entity转换为Dto,或者从Entity转换为ViewModel等),这么多的映射如何组织将成为一个问题。

    首先我们需要定义一个Configuration.cs的类,该类提供AutoMapper规则配置的入口,它只提供一个静态的方法,在程序第一次运行的时候调用该方法完成配置。

    当有多个Profile的时候,我们可以这样添加:

    public class Configuration

    {

        public static void Configure()

        {

            Mapper.Initialize(cfg =>

            {

                cfg.AddProfile<Profiles.SourceProfile>();

                cfg.AddProfile<Profiles.OrderProfile>();

                cfg.AddProfile<Profiles.CalendarEventProfile>();

            });

        }

    }

    在程序运行的时候,只需要调用Configure方法即可。

    了解了这些实现以后,我们可以再项目中添加AutoMapper文件夹。

     Configuration为我们的静态配置入口类;Profiles文件夹为我们所有Profile类的文件夹。如果是MVC,我们需要在Global中调用:

    AutoMapper.Configuration.Configure();

    问题:Missing type map configuration or unsupported mapping

    重现:本地调试直接打开出错的页面,调试发现是ok的;然后先打开用到了mapper所在控制器对应的页面,再去打开出错的页面,是报错的。

    从 GitHub 上签出 AutoMapper 的源代码一看 Mapper.Initialize() 的实现,恍然大悟。

    public static void Initialize(Action<IMapperConfigurationExpression> config)

    {

        Configuration = new MapperConfiguration(config);

        Instance = new Mapper(Configuration);

    }

    原来每次调用 Mapper.Initialize() 都会创建新的 Mapper 实例,也就是多次调用 Mapper.Initialize() 只有最后一次生效。

    切记不要多处调用Mapper.Initialize()

     优化方法:【写一个工具类】

    需程序集:AutoMapper

    /// <summary>
    
        ///     优化AutoMap映射工具,解决AutoMap只能Initialize一次的问题
    
        /// </summary>
    
        public class AutoMapperManager
    
        {
    
            /// <summary>
    
            /// 存储所有的profile
    
            /// </summary>
    
            static ConcurrentBag<Profile> Profiles;
    
            static AutoMapperManager()
    
            {
    
                Profiles = new ConcurrentBag<Profile>();
    
            }
    
            /// <summary>
    
            /// 新增Profile,必須放在靜態構造函數里
    
            /// </summary>
    
            /// <param name="profile"></param>
    
            public static void AddProfile(Profile profile)
    
            {
    
                Profiles.Add(profile);
    
            }
    
            /// <summary>
    
            /// 初始化,可以多次调用,同时之前的Profile也会生效
    
            /// </summary>
    
            public static void Initialize()
    
            {
    
                Mapper.Initialize(config =>
    
                {
    
                    Profiles.ToList().ForEach(file =>
    
                    {
    
                        config.AddProfile(file);
    
                    });
    
                });
    
            }
    
        }
    View Code

    其他地方需要用mapper的地方 调用方式:

    AutoMapperManager.AddProfile(new Profile1());

    AutoMapperManager.AddProfile(new Profile2());

    AutoMapperManager.Initialize();

    参考:

    https://www.cnblogs.com/jobs2/p/3503990.html

    https://www.cnblogs.com/youring2/p/automapper.html

    http://www.cnblogs.com/dudu/p/5875579.html

  • 相关阅读:
    es6-compact-table 名词备忘
    JS 防抖和节流函数
    为什么 JS 对象内部属性遍历的顺序乱了
    JS 发送 HTTP 请求方法封装
    JS 一些位操作的妙用
    JS 格式化时间
    linux ssh连接
    c# checked 和 unchecked
    c# mvc action 跳转方式
    IIS 动态与静态压缩
  • 原文地址:https://www.cnblogs.com/peterYong/p/10097178.html
Copyright © 2020-2023  润新知