AutoMapper

一事冇诚發表於2024-03-20

AutoMapper 是一個在 .NET 應用程式中自動將一個物件的屬性對映到另一個物件的屬性的開源庫。它旨在減少程式碼的重複性和提高開發人員的生產力。

在實際應用中,我們常常需要將一個實體物件轉換為另一個實體物件,或者從資料訪問層獲取到的資料物件對映到業務邏輯層的物件。手動實現這種轉換往往需要大量的重複程式碼,而 AutoMapper 則可以大大簡化這個過程。

AutoMapper 的主要特點包括:

  • 支援物件間複雜對映關係的配置;
  • 支援對映關係的自動發現,從而減少手動配置的工作量;
  • 支援 Fluent 介面,使得對映關係的配置更加清晰明瞭;
  • 支援 LINQ 查詢中的投影對映;
  • 支援批次對映;
  • 可以與 DI 容器整合;
  • 具有良好的效能。

安裝和配置

AutoMapper 可以透過 NuGet 包管理器進行安裝,也可以手動下載安裝。

在 Visual Studio 中使用 NuGet 安裝 AutoMapper,可以透過以下命令:

Install-Package AutoMapper

安裝完成後,在程式碼中使用以下名稱空間:

using AutoMapper;

使用示例

以下是一個簡單的使用示例,我們將從一個實體物件 Person 轉換為另一個實體物件 PersonDto

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class PersonDto
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 配置對映關係
Mapper.Initialize(cfg => cfg.CreateMap<Person, PersonDto>());

// 建立源物件
var person = new Person { Name = "張三", Age = 18 };

// 執行對映
var personDto = Mapper.Map<PersonDto>(person);

在上面的示例中,我們使用 Mapper.Initialize 方法進行對映關係的配置。該方法接受一個 Action<IMapperConfigurationExpression> 引數,可以使用其中的 CreateMap<TSource, TDestination> 方法進行源型別和目標型別之間的對映關係配置。

在配置完成後,我們可以透過 Mapper.Map<TDestination>(TSource source) 方法執行對映操作。該方法會自動根據對映關係將源物件轉換為目標物件。

接下來,我們將介紹 AutoMapper 的更多功能和用法。

基本對映

除了上面的示例,我們還可以透過 Fluent 介面進行對映關係的配置。例如,我們可以將上面的對映關係改寫為:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Person, PersonDto>()
        .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
        .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.Age));
        
});


在上面的示例中,我們使用 `ForMember` 方法指定了源型別和目標型別之間的對映關係。該方法接受兩個引數,第一個引數是目標屬性的表示式,第二個引數是選項。

選項中包含多個配置項,可以用於控制對映的行為,例如:

- `MapFrom`:指定源屬性的表示式;
- `Ignore`:忽略該屬性;
- `Condition`:根據條件判斷是否對映該屬性;
- `NullSubstitute`:指定當源屬性為 null 時,目標屬性應該使用的預設值;
- `ConvertUsing`:指定轉換器,用於將源屬性轉換為目標屬性。

在實際使用中,我們還可以透過 `ReverseMap` 方法自動生成反向對映關係。例如:
Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Person, PersonDto>()
        .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
        .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.Age))
        .ReverseMap();
});

在上面的示例中,我們使用了 ReverseMap 方法,自動生成了從 PersonDtoPerson 的反向對映關係。

投影對映

除了基本對映,AutoMapper 還支援在 LINQ 查詢中使用投影對映。例如,我們可以透過以下程式碼將 Person 實體物件列表中的所有物件轉換為 PersonDto 物件:

var persons = new List<Person>
{
    new Person { Name = "張三", Age = 18 },
    new Person { Name = "李四", Age = 20 },
    new Person { Name = "王五", Age = 22 }
};

var personDtos = persons.Select(p => Mapper.Map<PersonDto>(p)).ToList();

在上面的示例中,我們使用 Select 方法對 persons 列表進行投影對映,自動將 Person 物件轉換為 PersonDto 物件。

忽略某個屬性不對映

CreateMap<DeClass , SoClass>()
//跳過成員AAA的對映 忽略AAA的對映
.ForMember( dest => dest.AAA , opt => opt.Ignore() )

null替換

// 當源name中是null時,給目標一個預設值
CreateMap<Sou , Des>().ForMember( destination => destination.name , opt => opt.NullSubstitute( "預設值來了(搞個預設名字)" ) );

批次對映

在實際應用中,我們常常需要將多個實體物件轉換為另一個實體物件。如果使用迴圈逐一轉換,程式碼會非常繁瑣。而 AutoMapper 可以透過批次對映實現快速轉換。

例如,我們可以透過以下程式碼將 Person 實體物件列表轉換為 PersonDto 實體物件列表:

var persons = new List<Person>
{
    new Person { Name = "張三", Age = 18 },
    new Person { Name = "李四", Age = 20 },
    new Person { Name = "王五", Age = 22 }
};

var personDtos = Mapper.Map<List<PersonDto>>(persons);

在上面的示例中,我們直接呼叫 Mapper.Map 方法,將源物件列表轉換為目標物件列表。AutoMapper 會自動遍歷源列表,依次將每個物件轉換為目標物件。

巢狀對映

在實際應用中,我們常常需要將一個複雜的物件轉換為另一個複雜的物件。例如,我們有一個包含多個 Person 物件的 Department 物件,我們需要將其轉換為一個包含多個 PersonDto 物件的 DepartmentDto 物件。

這時,我們可以使用巢狀對映來解決這個問題。例如,我們可以定義以下實體物件和 DTO 物件:

public class Department
{
    public string Name { get; set; }
    public List<Person> Persons { get; set; }
}

public class DepartmentDto
{
    public string Name { get; set; }
    public List<PersonDto> PersonDtos { get; set; }
}

然後,我們可以透過以下程式碼實現巢狀對映:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Person, PersonDto>();
    cfg.CreateMap<Department, DepartmentDto>()
        .ForMember(dest => dest.PersonDtos, opt => opt.MapFrom(src => src.Persons));
});

var department = new Department
{
    Name = "IT部門",
    Persons = new List<Person>
    {
        new Person { Name = "張三", Age = 18 },
        new Person { Name = "李四", Age = 20 },
        new Person { Name = "王五", Age = 22 }
    }
};

var departmentDto = Mapper.Map<DepartmentDto>(department);

在上面的示例中,我們先使用 CreateMap 方法定義了 PersonPersonDto 之間的對映關係,然後定義了 DepartmentDepartmentDto 之間的對映關係。其中,我們使用 ForMember 方法指定了 PersonDtos 屬性與 Persons 屬性之間的對映關係。

對映前後處理(BeforeMap AfterMap)

方式1:Profile中直接使用

CreateMap<SourceClass , DestinationClass>()
        .ForMember( dest => dest.year , opt => opt.MapFrom( src => src.age + 2000 ) )
        .AfterMap( ( src , dest ) =>
        {
            DateTime now = DateTime.Now;

            dest.name = "admin";
            dest.now = now;
        } );

方式2:實現介面類

實現介面IMappingAction

public class BookAction : IMappingAction<Book , BookDto>
{
        //private readonly IHttpContextAccessor _httpContextAccessor;

        //public BookAction ( IHttpContextAccessor httpContextAccessor )
        //{
        //    如果需要:可以在這裡搞依賴注入    
        //    _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException( nameof( httpContextAccessor ) );
        //}

        public void Process ( Book source , BookDto destination , ResolutionContext context )
        {
            DateTime now = DateTime.Now;

            destination.now = now;
        }
}
CreateMap<Book , BookDto>()
                .ForMember( dest => dest.url , opt => opt.MapFrom( src => src.InfoUrl ) )
                //.AfterMap( ( src , dest ) =>
                //{
                //    DateTime now = DateTime.Now;

                //    dest.now = now;
                //} );
                .AfterMap<BookAction>();

方式3:轉換時直接使用

BookDto _BookDto2 = this.Mapper.Map<Book , BookDto>( newbook , opt =>
            {
                //opt.BeforeMap( ( Book src , BookDto dest ) => src.Value = src.Value + i );
                opt.AfterMap( ( Book src , BookDto dest ) => dest.url = dest.url + "___" );
            } );
var _SPIBOARDBINDSList = this._Mapper.Map<List<BoardBindReturnDto> , List<SPIBOARDBINDS>>( _BoardBindReturnDtoList , ( opt ) =>
            {
                //把id傳遞過去
                opt.AfterMap( ( List<BoardBindReturnDto> src , List<SPIBOARDBINDS> dest ) =>
                {
                    dest.ForEach( it =>
                    {
                        it.PID = ids;
                    } );
                } );
            } );

自定義轉換器

在實際應用中,我們常常需要將一種型別的值轉換為另一種型別的值。例如,我們需要將一個日期字串轉換為 DateTime 型別的值,或者將一個數字字串轉換為 decimal 型別的值。

AutoMapper 提供了 ConvertUsing 方法,用於自定義轉換器。例如,我們可以定義以下轉換器,將一個日期字串轉換為 DateTime 型別的值:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<string, DateTime>().ConvertUsing(s => DateTime.ParseExact(s, "yyyyMMdd", CultureInfo.InvariantCulture));
});

var dateStr = "20230420";
var date = Mapper.Map<DateTime>(dateStr);

在上面的示例中,我們使用 ConvertUsing 方法自定義了一個轉換器,將日期字串轉換為 DateTime 型別的值。在 ConvertUsing 方法中,我們傳入一個轉換函式,該函式接受源型別的值作為引數,返回目標型別的值。

相關文章