日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

Util应用框架基础(二) - 对象到对象映射(AutoMapper)

發布時間:2023/11/3 C# 122 coder
生活随笔 收集整理的這篇文章主要介紹了 Util应用框架基础(二) - 对象到对象映射(AutoMapper) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節介紹Util應用框架相似對象之間的轉換方法.

文章分為多個小節,如果對設計原理不感興趣,只需閱讀基礎用法部分即可.

概述

現代化分層架構,普遍采用了構造塊DTO(數據傳輸對象).

DTO是一種參數對象,當Web API接收到請求,請求參數被裝載到DTO對象中.

我們需要把 DTO 對象轉換成實體,才能保存到數據庫.

當返回響應消息時,需要把實體轉換成DTO,再傳回客戶端.

對于簡單的系統,DTO和實體非常相似,它們可能包含大量相同的屬性.

除此之外,還有很多場景也需要轉換相似對象.

下面的例子定義了學生實體和學生參數DTO.

它們包含兩個相同的屬性.

StudentService 是一個應用服務.

CreateAsync 方法創建學生,把DTO對象手工賦值轉換為學生實體,并添加到數據庫.

GetByIdAsync 方法通過ID獲取學生實體,并手工賦值轉換為學生DTO.

/// <summary>
/// 學生
/// </summary>
public class Student : AggregateRoot<Student> {
    /// <summary>
    /// 初始化學生
    /// </summary>
    public Student() : this( Guid.Empty ) {
    }

    /// <summary>
    /// 初始化學生
    /// </summary>
    /// <param name="id">學生標識</param>
    public Student( Guid id ) : base( id ) {
    }

    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }

    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 學生參數
/// </summary>
public class StudentDto : DtoBase {
    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }
    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 學生服務
/// </summary>
public class StudentService {
    /// <summary>
    /// 工作單元
    /// </summary>
    private IDemoUnitOfWork _demoUnitOfWork;
    /// <summary>
    /// 學生倉儲
    /// </summary>
    private IStudentRepository _repository;

    /// <summary>
    /// 初始化學生服務
    /// </summary>
    /// <param name="unitOfWork">工作單元</param>
    /// <param name="repository">學生倉儲</param>
    public StudentService( IDemoUnitOfWork unitOfWork, IStudentRepository repository ) {
        _demoUnitOfWork = unitOfWork;
        _repository = repository;
    }

    /// <summary>
    /// 創建學生
    /// </summary>
    /// <param name="dto">學生參數</param>
    public async Task CreateAsync( StudentDto dto ) {
        var entity = new Student { Name = dto.Name, Birthday = dto.Birthday };
        await _repository.AddAsync( entity );
        await _demoUnitOfWork.CommitAsync();
    }

    /// <summary>
    /// 獲取學生
    /// </summary>
    /// <param name="id">學生標識</param>
    public async Task<StudentDto> GetByIdAsync( Guid id ) {
        var entity = await _repository.FindByIdAsync( id );
        return new StudentDto { Name = entity.Name, Birthday = entity.Birthday };
    }
}

學生范例只有兩個屬性,手工轉換工作量并不大.

但真實的應用每個對象可能包含數十個屬性,使用手工賦值的方式轉換,效率低下且容易出錯.

我們需要一種自動化的轉換手段.

對象到對象映射框架 AutoMapper

Util應用框架使用 AutoMapper ,它是 .Net 最流行的對象間映射框架.

AutoMapper 可以自動轉換相同名稱和類型的屬性,同時支持一些約定轉換方式.

基礎用法

引用Nuget包

Nuget包名: Util.ObjectMapping.AutoMapper.

通常不需要手工引用它.

MapTo 擴展方法

Util應用框架在根對象 object 擴展了 MapTo 方法,你可以在任何對象上調用 MapTo 進行對象轉換.

擴展方法需要引用命名空間, MapTo 擴展方法在 Util 命名空間.

using Util;

有兩種調用形式.

  • 調用形式1: 源對象實例.MapTo<目標類型>()

    • 范例: 這里的源對象實例是學生參數 dto,目標類型是 Student,返回 Student 對象實例.
      /// <summary>
      /// 創建學生
      /// </summary>
      /// <param name="dto">學生參數</param>
      public async Task CreateAsync( StudentDto dto ) {
          var entity = dto.MapTo<Student>();
          ...
      }
    
  • 調用形式2: 源對象實例.MapTo(目標類型實例)

    當目標類型實例已經存在時使用該重載.

    • 范例:
      /// <summary>
      /// 創建學生
      /// </summary>
      /// <param name="dto">學生參數</param>
      public async Task CreateAsync( StudentDto dto ) {
          var entity = new Student();
          dto.MapTo(entity);
          ...
      }
    

MapToList 擴展方法

Util應用框架在 IEnumerable 擴展了 MapToList 方法.

如果要轉換集合,使用該擴展.

范例

將 StudentDto 集合轉換為 Student 集合.

傳入泛型參數 Student ,而不是 List<Student> .

List<StudentDto> dtos = new List<StudentDto> { new() { Name = "a" }, new() { Name = "b" } };
List<Student> entities = dtos.MapToList<Student>();

配置 AutoMapper

對于簡單場景,比如轉換對象的屬性都相同, 不需要任何配置.

AutoMapper服務注冊器自動完成基礎配置.

不過很多業務場景轉換的對象具有差異,需要配置差異部分.

Util.ObjectMapping.IAutoMapperConfig

Util提供了 AutoMapper 配置接口 IAutoMapperConfig.

/// <summary>
/// AutoMapper配置
/// </summary>
public interface IAutoMapperConfig {
    /// <summary>
    /// 配置映射
    /// </summary>
    /// <param name="expression">配置映射表達式</param>
    void Config( IMapperConfigurationExpression expression );
}

Config 配置方法提供配置映射表達式 IMapperConfigurationExpression 實例,它是 AutoMapper 配置入口.

由 AutoMapper 服務注冊器掃描執行所有 IAutoMapperConfig 配置.

約定: 將 AutoMapper 配置類放置在 ObjectMapping 目錄中.

為每一對有差異的對象實現該接口.

修改學生示例,把 StudentDto 的 Name 屬性名改為 FullName.

由于學生實體和DTO的Name屬性名不同,所以不能自動轉換,需要配置.

需要配置兩個映射方向.

  • 從 Student 到 StudentDto.

  • 從 StudentDto 到 Student.

/// <summary>
/// 學生
/// </summary>
public class Student : AggregateRoot<Student> {
    /// <summary>
    /// 初始化學生
    /// </summary>
    public Student() : this( Guid.Empty ) {
    }

    /// <summary>
    /// 初始化學生
    /// </summary>
    /// <param name="id">學生標識</param>
    public Student( Guid id ) : base( id ) {
    }

    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }

    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 學生參數
/// </summary>
public class StudentDto : DtoBase {
    /// <summary>
    /// 姓名
    ///</summary>
    public string FullName { get; set; }
    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 學生映射配置
/// </summary>
public class StudentAutoMapperConfig : IAutoMapperConfig {
    /// <summary>
    /// 配置映射
    /// </summary>
    /// <param name="expression">配置映射表達式</param>
    public void Config( IMapperConfigurationExpression expression ) {
        expression.CreateMap<Student, StudentDto>()
            .ForMember( t => t.FullName, t => t.MapFrom( r => r.Name ) );
        expression.CreateMap<StudentDto,Student>()
            .ForMember( t => t.Name, t => t.MapFrom( r => r.FullName ) );
    }
}

對象間映射最佳實踐

應該盡量避免配置,保持代碼簡單.

  • 統一對象屬性

    如果有可能,盡量統一對象屬性名稱和屬性類型.

  • 使用 AutoMapper 映射約定

    AutoMapper 支持一些約定的映射方式.

    范例

    添加班級類型,學生實體添加班級關聯實體 Class, 學生DTO添加班級名稱屬性 ClassName.

      /// <summary>
      /// 學生
      /// </summary>
      public class Student : AggregateRoot<Student> {
          /// <summary>
          /// 初始化學生
          /// </summary>
          public Student() : this( Guid.Empty ) {
          }
    
          /// <summary>
          /// 初始化學生
          /// </summary>
          /// <param name="id">學生標識</param>
          public Student( Guid id ) : base( id ) {
              Class = new Class();
          }
    
          /// <summary>
          /// 姓名
          ///</summary>
          public string Name { get; set; }
    
          /// <summary>
          /// 出生日期
          ///</summary>
          public DateTime? Birthday { get; set; }
    
          /// <summary>
          /// 班級
          /// </summary>
          public Class Class { get; set; }
      }
    
      /// <summary>
      /// 班級
      /// </summary>
      public class Class : AggregateRoot<Class> {
          /// <summary>
          /// 初始化班級
          /// </summary>
          public Class() : this( Guid.Empty ) {
          }
    
          /// <summary>
          /// 初始化班級
          /// </summary>
          /// <param name="id">班級標識</param>
          public Class( Guid id ) : base( id ) {
          }
    
          /// <summary>
          /// 班級名稱
          ///</summary>
          public string Name { get; set; }
      }
    
      /// <summary>
      /// 學生參數
      /// </summary>
      public class StudentDto : DtoBase {
          /// <summary>
          /// 姓名
          ///</summary>
          public string Name { get; set; }
          /// <summary>
          /// 班級名稱
          ///</summary>
          public string ClassName { get; set; }
          /// <summary>
          /// 出生日期
          ///</summary>
          public DateTime? Birthday { get; set; }
      }
    

    將 Student 的 Class實體 Name 屬性映射到 StudentDto 的 ClassName 屬性 ,不需要配置.

    var entity = new Student { Class = new Class { Name = "a" } };
    var dto = entity.MapTo<StudentDto>();
    //dto.ClassName 值為 a
    

    但不支持從 StudentDto 的 ClassName 屬性映射到 Student 的 Class實體 Name 屬性.

    var dto = new StudentDto { ClassName = "a" };
    var entity = dto.MapTo<Student>();
    //entity.Class.Name 值為 null
    

源碼解析

對象映射器 IObjectMapper

你不需要調用 IObjectMapper 接口,始終通過 MapTo 擴展方法進行轉換.

ObjectMapper 實現了 IObjectMapper 接口.

ObjectMapper映射源類型和目標類型時,如果發現尚未配置映射關系,則自動配置.

除了自動配置映射關系外,還需要處理并發和異常情況.

/// <summary>
/// 對象映射器
/// </summary>
public interface IObjectMapper {
    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TSource">源類型</typeparam>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    TDestination Map<TSource, TDestination>( TSource source );
    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TSource">源類型</typeparam>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    /// <param name="destination">目標對象</param>
    TDestination Map<TSource, TDestination>( TSource source, TDestination destination );
}

/// <summary>
/// AutoMapper對象映射器
/// </summary>
public class ObjectMapper : IObjectMapper {
    /// <summary>
    /// 最大遞歸獲取結果次數
    /// </summary>
    private const int MaxGetResultCount = 16;
    /// <summary>
    /// 同步鎖
    /// </summary>
    private static readonly object Sync = new();
    /// <summary>
    /// 配置表達式
    /// </summary>
    private readonly MapperConfigurationExpression _configExpression;
    /// <summary>
    /// 配置提供器
    /// </summary>
    private IConfigurationProvider _config;
    /// <summary>
    /// 對象映射器
    /// </summary>
    private IMapper _mapper;

    /// <summary>
    /// 初始化AutoMapper對象映射器
    /// </summary>
    /// <param name="expression">配置表達式</param>
    public ObjectMapper( MapperConfigurationExpression expression ) {
        _configExpression = expression ?? throw new ArgumentNullException( nameof( expression ) );
        _config = new MapperConfiguration( expression ); 
        _mapper = _config.CreateMapper();
    }

    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TSource">源類型</typeparam>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    public TDestination Map<TSource, TDestination>( TSource source ) {
        return Map<TSource, TDestination>( source, default );
    }

    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TSource">源類型</typeparam>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    /// <param name="destination">目標對象</param>
    public TDestination Map<TSource, TDestination>( TSource source, TDestination destination ) {
        if ( source == null )
            return default;
        var sourceType = GetType( source );
        var destinationType = GetType( destination );
        return GetResult( sourceType, destinationType, source, destination,0 );
    }

    /// <summary>
    /// 獲取類型
    /// </summary>
    private Type GetType<T>( T obj ) {
        if( obj == null )
            return GetType( typeof( T ) );
        return GetType( obj.GetType() );
    }

    /// <summary>
    /// 獲取類型
    /// </summary>
    private Type GetType( Type type ) {
        return Reflection.GetElementType( type );
    }

    /// <summary>
    /// 獲取結果
    /// </summary>
    private TDestination GetResult<TDestination>( Type sourceType, Type destinationType, object source, TDestination destination,int i ) {
        try {
            if ( i >= MaxGetResultCount )
                return default;
            i += 1;
            if ( Exists( sourceType, destinationType ) )
                return GetResult( source, destination );
            lock ( Sync ) {
                if ( Exists( sourceType, destinationType ) )
                    return GetResult( source, destination );
                ConfigMap( sourceType, destinationType );
            }
            return GetResult( source, destination );
        }
        catch ( AutoMapperMappingException ex ) {
            if ( ex.InnerException != null && ex.InnerException.Message.StartsWith( "Missing type map configuration" ) )
                return GetResult( GetType( ex.MemberMap.SourceType ), GetType( ex.MemberMap.DestinationType ), source, destination,i );
            throw;
        }
    }

    /// <summary>
    /// 是否已存在映射配置
    /// </summary>
    private bool Exists( Type sourceType, Type destinationType ) {
        return _config.Internal().FindTypeMapFor( sourceType, destinationType ) != null;
    }

    /// <summary>
    /// 獲取映射結果
    /// </summary>
    private TDestination GetResult<TSource, TDestination>( TSource source, TDestination destination ) {
        return _mapper.Map( source, destination );
    }

    /// <summary>
    /// 動態配置映射
    /// </summary>
    private void ConfigMap( Type sourceType, Type destinationType ) {
        _configExpression.CreateMap( sourceType, destinationType );
        _config = new MapperConfiguration( _configExpression );
        _mapper = _config.CreateMapper();
    }
}

AutoMapper服務注冊器

AutoMapper服務注冊器掃描 IAutoMapperConfig 配置并執行.

同時為 MapTo 擴展類 ObjectMapperExtensions 設置 IObjectMapper 實例.

/// <summary>
/// AutoMapper服務注冊器
/// </summary>
public class AutoMapperServiceRegistrar : IServiceRegistrar {
    /// <summary>
    /// 獲取服務名
    /// </summary>
    public static string ServiceName => "Util.ObjectMapping.Infrastructure.AutoMapperServiceRegistrar";

    /// <summary>
    /// 排序號
    /// </summary>
    public int OrderId => 300;

    /// <summary>
    /// 是否啟用
    /// </summary>
    public bool Enabled => ServiceRegistrarConfig.IsEnabled( ServiceName );

    /// <summary>
    /// 注冊服務
    /// </summary>
    /// <param name="serviceContext">服務上下文</param>
    public Action Register( ServiceContext serviceContext ) {
        var types = serviceContext.TypeFinder.Find<IAutoMapperConfig>();
        var instances = types.Select( type => Reflection.CreateInstance<IAutoMapperConfig>( type ) ).ToList();
        var expression = new MapperConfigurationExpression();
        instances.ForEach( t => t.Config( expression ) );
        var mapper = new ObjectMapper( expression );
        ObjectMapperExtensions.SetMapper( mapper );
        serviceContext.HostBuilder.ConfigureServices( ( context, services ) => {
            services.AddSingleton<IObjectMapper>( mapper );
        } );
        return null;
    }
}

對象映射擴展 ObjectMapperExtensions

ObjectMapperExtensions 提供了 MapToMapToList 擴展方法.

MapTo 擴展方法依賴 IObjectMapper 實例,由于擴展方法是靜態方法,所以需要將 IObjectMapper 定義為靜態變量.

通過 SetMapper 靜態方法將對象映射器實例傳入.

對象映射器 ObjectMapper 實例作為靜態變量,必須處理并發相關的問題.

/// <summary>
/// 對象映射擴展
/// </summary>
public static class ObjectMapperExtensions {
    /// <summary>
    /// 對象映射器
    /// </summary>
    private static IObjectMapper _mapper;

    /// <summary>
    /// 設置對象映射器
    /// </summary>
    /// <param name="mapper">對象映射器</param>
    public static void SetMapper( IObjectMapper mapper ) {
        _mapper = mapper ?? throw new ArgumentNullException( nameof( mapper ) );
    }

    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    public static TDestination MapTo<TDestination>( this object source ) {
        if ( _mapper == null )
            throw new ArgumentNullException( nameof(_mapper) );
        return _mapper.Map<object, TDestination>( source );
    }
        
    /// <summary>
    /// 將源對象映射到目標對象
    /// </summary>
    /// <typeparam name="TSource">源類型</typeparam>
    /// <typeparam name="TDestination">目標類型</typeparam>
    /// <param name="source">源對象</param>
    /// <param name="destination">目標對象</param>
    public static TDestination MapTo<TSource, TDestination>( this TSource source, TDestination destination ) {
        if( _mapper == null )
            throw new ArgumentNullException( nameof( _mapper ) );
        return _mapper.Map( source, destination );
    }

    /// <summary>
    /// 將源集合映射到目標集合
    /// </summary>
    /// <typeparam name="TDestination">目標元素類型,范例:Sample,不要加List</typeparam>
    /// <param name="source">源集合</param>
    public static List<TDestination> MapToList<TDestination>( this System.Collections.IEnumerable source ) {
        return MapTo<List<TDestination>>( source );
    }
}

禁用 AutoMapper 服務注冊器

ServiceRegistrarConfig.Instance.DisableAutoMapperServiceRegistrar();

總結

以上是生活随笔為你收集整理的Util应用框架基础(二) - 对象到对象映射(AutoMapper)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 五月天婷婷丁香花 | 国产高清在线观看视频 | 国产精品jizz视频 | 蜜臀视频一区二区三区 | av激情在线观看 | 欧美精品二区 | 日本黄色aaa | 青青草原av | 欧美日韩一区二区三区电影 | 亚洲香蕉在线视频 | 久久泄欲网| 啪啪在线视频 | 国产激情无码一区二区三区 | 日韩综合第一页 | 久久九 | 亚洲成人激情av | 波多野结衣视频免费在线观看 | 男生裸体视频网站 | 国产毛片毛片毛片毛片毛片毛片 | 中文在线а√天堂 | 日产mv免费观看 | 亚洲色图20p| 外国黄色网 | www.午夜视频 | 国产成人在线观看网站 | 高清av一区二区 | 日本久久久久久久久 | 尤物91| 97公开视频 | 伊人网免费视频 | 波多野结衣mp4 | 91色视频| 亚洲精品国产精品国自产在线 | 色肉色伦交av色肉色伦 | www.69视频| 国产av不卡一区 | 致单身男女免费观看完整版 | 国产性av | 亚洲va国产天堂va久久 en | 激情五月色播五月 | 成年视频在线 | 精品电影在线观看 | 国产精品人人爽 | 日韩毛片大全 | 美女流白浆视频 | 激情综合网激情 | 美日韩丰满少妇在线观看 | 日本美女久久 | 亚洲网站免费 | 激情小视频在线观看 | 无码人妻精品一区二区三区9厂 | 在线观看免费成人 | 大胸奶汁乳流奶水出来h | 久久久久久久久久99 | 日韩不卡在线播放 | 久草五月天 | 亚洲高清av在线 | 天海翼av在线| 浪漫樱花在线观看高清动漫 | 天天天天干 | 在线天堂v| 国产永久免费 | 日韩久久综合 | 蜜桃成熟时李丽珍在线观看 | 久久艹综合 | 德国性猛交xxxxhd | 自拍毛片| 国产美女在线观看 | 欧美色综合天天久久综合精品 | 中国免费毛片 | 精品国产乱码久久久久久预案 | 干美女av | 国产交换配乱淫视频免费 | 国产精品久久91 | 欧美一级不卡视频 | 欧美亚洲精品一区二区 | 欧美一级免费观看 | 91网页在线观看 | www.毛片 | 日日夜夜天天干 | 欧美日韩在线免费 | 色婷婷综合久久久久中文 | 少妇高潮久久久久久潘金莲 | 懂色av一区二区三区 | 亚洲成人精品在线观看 | 国产综合视频在线 | 影音先锋亚洲精品 | 中文在线一区 | 久久久人人爽 | 欧美第一页草草影院 | 一本久道久久综合无码中文 | www.射.com| 91精品人妻一区二区三区 | 欧美亚洲国产另类 | 日本调教电影 | 免费黄色av网站 | 亚洲av无码电影在线播放 | 日本50路肥熟bbw | 强睡邻居人妻中文字幕 |