1. 程式人生 > >【.NET】AutoMapper學習記錄

【.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