C#進階系列——DDD領域驅動設計初探(五):AutoMapper使用


前言:前篇搭建了下WCF的代碼,就提到了DTO的概念,對於為什么要有這么一個DTO的對象,上章可能對於這點不太詳盡,在此不厭其煩再來提提它的作用:

  • 從安全上面考慮,領域Model都帶有領域業務,讓Client端引用Domain Model就意味着Client端可以繞過應用層直接完成業務邏輯的調用,這樣是一種不安全的機制。
  • 從對象傳遞效率上面考慮,領域Model帶有業務,而這些業務一般對於UI層是沒有意義的,所以帶有業務的model傳遞起來會加重網絡負擔。
  • 網上還說了DTOmodel最大的意義在於跨平台,Domain Model都是與特定的語言的數據類型有關,而這些數據類型是不能跨平台的,比如Java的類型就不能被C#使用。但在分布式模式下,Client端與Server端的平台不同是很正常的,如果Service直接返回Domain Model,Client端根本無法解析,這就要求Service返回的結果必須是標准的格式字節流。讓Domain Model只使用簡單類型(字符和數值)?讓數據類型約束Domain Model顯然不是一個好想法,所以DTO似乎是必不可少的了。

既然我們要使用DTO,那么有一件事我們就非做不可了,我們從領域層得到的是領域Model,如何把領域Model轉換成只帶有數據屬性的DTO傳遞到前台呢?又或者我們從前台提交一個DTO對象,如何將DTO轉換成領域Model而提交到后台呢?這個時候就需要我們的對象映射工具,目前市面上對象映射工具較多,但博主最熟悉的還是Automapper,這章就來分享下Automapper的使用。

DDD領域驅動設計初探系列文章:

一、AutoMapper

Automapper是一個object-object mapping(對象映射)工具,一般主要用於兩個對象之間數據映射和交換。當然你也可以自己通過反射去寫對象的映射,對於簡單的兩個屬性間的數據轉換,肯定沒什么問題。但是如果遇到某些復雜的數據轉換,比如指定某一個對象的某個屬性映射到另一個對象的某一個屬性,這種情況如果我們自己手動映射,恐怕就有點麻煩了吧。既然我們有現成的工具,為什么不用呢?

二、AutoMapper引用到項目中

向項目中添加AutoMapper的引用有兩種方式:

1、Nuget方式

在需要使用AutoMapper的項目文件上面右鍵→管理Nuget程序包,打開Nuget界面,搜索Automapper,然后安裝第一個即可。如下圖:

2、程序包管理控制台方式

點擊Visual Studio的工具菜單→程序包管理控制台,然后選擇需要安裝Automapper的項目(下圖中的默認項目),最后在控制台里面輸入命令“Install-Package AutoMapper”命令即可按照Automapper包:

 三、AutoMapper使用代碼示例

1、最簡單的對象映射

AutoMapper使用起來還是比較簡單的,最簡單的用法你只需要兩句話:

var oMenu = new TB_MENU() { MENU_NAME="權限管理", MENU_LEVEL="1" };
Mapper.CreateMap
<TB_MENU, DTO_TB_MENU>(); var oDto = Mapper.Map<DTO_TB_MENU>(oMenu);

首先創建映射,然后傳入需要映射的對象執行映射。相信在項目中使用過AutoMapper的原因肯定也寫過類似這樣的AutoMapperHelper 

    /// <summary>
    /// AutoMapper幫助類
    /// </summary>
    public static class AutoMapperHelper
    {
        /// <summary>
        ///  單個對象映射
        /// </summary>
        public static T MapTo<T>(this object obj)
        {
            if (obj == null) return default(T);
            Mapper.CreateMap(obj.GetType(), typeof(T));
            return Mapper.Map<T>(obj);
        }

        /// <summary>
        /// 集合列表類型映射
        /// </summary>
        public static List<TDestination> MapToList<TSource, TDestination>(this IEnumerable<TSource> source)
        {
            Mapper.CreateMap<TSource, TDestination>();
            return Mapper.Map<List<TDestination>>(source);
        }
    }

當然,這是最簡單的用法,稍微復雜點的用法我們在后面慢慢介紹。

2、指定字段的對象映射

前面說了,對於指定某一個對象的某個屬性映射到另一個對象的某一個屬性,這種場景,我們先來看看下面代碼: 

   public partial class TB_USERS : BaseEntity
    {
        public string USER_ID { get; set; }
        public string USER_NAME { get; set; }
        public string USER_PASSWORD { get; set; }
        public string FULLNAME { get; set; }
        public string DEPARTMENT_ID { get; set; }

      public virtual TB_DEPARTMENT TB_DEPARTMENT { get; set; }
     //...后面肯定還有其他領域行為
    }

 

   public partial class TB_DEPARTMENT : BaseEntity
    {
        public string DEPARTMENT_ID { get; set; }
        public string NAME { get; set; }
    }

 領域層有這兩個實體model,然后我們需要得到下面的DTO_TB_USERS這一個對象

   public class DTO_TB_USERS
    {
        [DataMember]
        public string USER_ID { get; set; }
        
        [DataMember]
        public string USER_NAME { get; set; }
        
        [DataMember]
        public string USER_PASSWORD { get; set; }
        
        [DataMember]
        public string FULLNAME { get; set; }
        
        [DataMember]
        public string DEPARTMENT_ID { get; set; }

     [DataMember]
     public string DEPARTMENT_NAME { get; set; }
}

 這個時候DTO_TB_USERS這個對象的屬性分布在其他兩個領域實體里面,我們看看AutoMapper如何解決:

var oDomainUser = userRepository.Entities.FirstOrDefault();
var map = Mapper.CreateMap<TB_USERS, DTO_TB_USERS>();
map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.MapFrom(x => x.TB_DEPARTMENT.NAME));
var oDto = Mapper.Map<TB_USERS, DTO_TB_USERS>(oDomainUser);  

通過上面的代碼,ForMember()方法會指定哪個字段轉換為哪個字段,這樣就完美的將對象的層級結構由二級變成了一級(即將TB_USERS下面TB_DEPARTMENT對象的NAME值轉換成了DTO_TB_USERS的DEPARTMENT_NAME值)。除此之外,Automapper里面還可以通過ForMember幫我們做其他很多我們想不到的事情,比如可以設置某個屬性值保留初始值,只需要通過

map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.Ignore());

這一句就幫我們搞定。 

3、傳遞lamada的表達式映射

還記得我們在倉儲里面封裝了傳遞lamada表達式的查詢方法么?試想,如果我們在Web層里面也希望傳遞lamada表達式去后台查詢,那么這個時候就有點問題了,因為我們Web里面只能訪問DTO的Model,所以只能傳入DTO Model的lamada,而我們倉儲里面需要傳入的是領域Model的lamada,那么問題就來了,這兩個lamada表達式之間必須存在一個轉換關系,試想,這些東西如果讓我們手動去處理,還是有難度的吧!還好,我們神奇的Automapper替我們想到了。它能夠幫我們將DTO的lamada轉換成領域Model的lamada,來看看代碼吧:

     [Import]
     public IUserRepository userRepository { get; set; }

     public virtual IList<DTO> Find(Expression<Func<DTO, bool>> selector)
        {
            //得到從Web傳過來和DTOModel相關的lamaba表達式的委托
            Func<DTO, bool> match = selector.Compile();
            //創建映射Expression的委托
            Func<T, DTO> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<T, DTO>(Mapper.Engine).Compile();
            //得到領域Model相關的lamada
            Expression<Func<T, bool>> lamada = ef_t => match(mapper(ef_t));
            List<T> list = userRepository.Find(lamada).ToList();
            return Mapper.Map<List<T>, List<DTO>>(list);
        }

上面方法完美實現了兩種lamada之間的轉換,但根據博主的使用經歷,這種轉換對屬性的類型有很嚴格的要求,必須保證領域model和DTO的Model同一個屬性的類型完全相同,否則容易報異常。使用的時候需要注意。實際使用的方法:

public List<DtoModel> GetDtoByLamada<DtoModel,DomainModel>(IRepository<DomainModel> oRepository,  Expression<Func<DtoModel, bool>> selector = null)
            where DomainModel : AggregateRoot
            where DtoModel : DTO_BASEMODEL
        {
            if (selector == null)
            {
                var lstDomainModel = oRepository.Entities.ToList();
                return Mapper.Map<List<DomainModel>, List<DtoModel>>(lstDomainModel);
            }
            //得到從Web傳過來和DTOModel相關的lamaba表達式的委托
            Mapper.CreateMap<DtoModel, DomainModel>();
            Mapper.CreateMap<DomainModel, DtoModel>();
            Func<DtoModel, bool> match = selector.Compile();
            //創建映射Expression的委托
            Func<DomainModel, DtoModel> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<DomainModel, DtoModel>(Mapper.Engine).Compile();
            //得到領域Model相關的lamada
            Expression<Func<DomainModel, bool>> lamada = ef_t => match(mapper(ef_t));
            List<DomainModel> list = oRepository.Find(lamada).ToList();
            return Mapper.Map<List<DomainModel>, List<DtoModel>>(list);
        }

調用

public class PowerManageWCFService :BaseService, IPowerManageWCFService
    {
        #region Fields
        [Import]
        private IUserRepository userRepository { get; set; }

        [Import]
        private IDepartmentRepository departmentRepository { get; set; }

        [Import]
        private IRoleRepository roleRepository { get; set; }

        [Import]
        private IMenuRepository menuRepository { get; set; } 
        #endregion

        #region Constust
        public PowerManageWCFService()
        {
            //注冊MEF
            Regisgter.regisgter().ComposeParts(this);
        }
        #endregion

        #region WCF服務接口實現
        public List<DTO_TB_USERS> GetUsers(Expression<Func<DTO_TB_USERS, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_USERS, TB_USERS>(userRepository, selector);
        }

        public List<DTO_TB_DEPARTMENT> GetDepartments(Expression<Func<DTO_TB_DEPARTMENT, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_DEPARTMENT, TB_DEPARTMENT>(departmentRepository, selector);
        }

        public List<DTO_TB_ROLE> GetRoles(Expression<Func<DTO_TB_ROLE, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_ROLE, TB_ROLE>(roleRepository, selector);
        }

        public List<DTO_TB_MENU> GetMenus(Expression<Func<DTO_TB_MENU, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_MENU, TB_MENU>(menuRepository, selector);
        } 
        #endregion
}

 

4、Automapper的其他應用

除了上面介紹的Automapper的幾個簡單使用,其他還有其他的一些用法。

網上很多介紹DataReader對象和實體類之間的映射:

using (IDataReader reader = db.ExecuteReader(command))
{
    if (reader.Read())
    {
        return AutoMapper.Mapper.DynamicMap<Product>(reader);
    }
}

 至此,AutoMapper的常見用法基本分享完了,至於更高級的用法,有興趣可以看看蟋蟀兄的【AutoMapper官方文檔】DTO與Domin Model相互轉換(上)。雖然很多高級用法在實際項目中很難用上,但多了解一點似乎也並沒有壞處。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM