.NET中利用反射來實現自動對應兩個對象中的資料成員

來源:互聯網
上載者:User

標籤:employees   擴充   ext   img   情況   這一   包含   產生   efault   

  在以前的項目開發之中,經常會遇到這樣一個問題:比如在外面項目的架構設計之中,我們採用MVC和EntityFramework來構建一個Web應用程式。比如我們採用常用的多層架構,例如有Presentation層、BusinessLogic層、DataAccess層等,各層之間是相對獨立並且職責分明的。比如我們在Presentation層中會定義ViewModel,在DataAccess層中的DbContext部分又會由EntityFramework來自動產生StorageModel,或者叫做DataModel。然後我們從DataAccess層從資料庫抓取到資料之後需要將這些資料傳遞給viewModel,並最終呈現給前段使用者,當然兩種Model之間定義的欄位(屬性)可能會有所區別,這個我們將會在稍後討論。

  我們先來看看如何解決這一類問題。首先最樸素笨拙的辦法就是,逐個屬性的為對象賦值,例如這樣:

var viewModels = new List<EmployeeViewModel>();            List<EmployeeStorageModel> storageModels = new List<EmployeeStorageModel>();            if (storageModels.Count > 0)            {                EmployeeViewModel viewModel = null;                foreach (var storageModel in storageModels)                {                    viewModel.Number = storageModel.Name;                    viewModel.Name = storageModel.Name;                    viewModel.HireDate = storageModel.HireDate;                    viewModel.Job = storageModel.Job;                    viewModel.Department = storageModel.Department;                    viewModel.Salary = storageModel.Salary;                    //....                }            }

如果對象的屬性比較多,那麼我們就不得不寫一長串的指派陳述式,這顯示不是一個聰明的辦法。說了這麼多目的是為了拋出一個問題。那麼解決方案呢?對於這個問題有許多的解決方案,比如使用AutoMapper等,之前我記得我們一個同事寫了一個擴充方法是使用JSON的序列化和還原序列化來實現對象資料成員之間的映射,不過我今天要拿出來說的是使用映射來解決這個問題。

  我想要實現的功能如下,或者說我要解決的問題吧:

1. 實現兩個不同對象資料成員的映射,資料成員包括屬性和公用可寫欄位;

2. 將具有相同名稱和資料類型的資料成員映射到另外另外一個對象;

在這裡我先給出核心代碼,後面將會對商務邏輯和一些注意問題進行詳細的說明:

第一、定義一個Attribute,這個特性主要用來標示類的資料成員的別名,後面我將介紹為什麼要怎麼做

//資料成員的別名    [AttributeUsage(AttributeTargets.Property| AttributeTargets.Field,        AllowMultiple=false, Inherited=true)]    public class DataMemberAliasAttribute : System.Attribute    {        private readonly string _alias;        public DataMemberAliasAttribute(string alias)        {            _alias = alias;        }        public string Alias        {            get            {                return _alias;            }        }    }

 

 

第二、定義一個類,這個類包含一個公用的靜態方法Mapping,這個方法是一個擴充方法,後面將會詳細介紹為什麼要這麼定義

private static T Mapping<T>(this object source) where T : class        {            Type t = typeof(T);            if (source == null)            {                return default(T);            }            T target = (T)t.Assembly.CreateInstance(t.FullName);            #region Mapping Properties            PropertyInfo[] targetProps = t.GetProperties();            if (targetProps != null && targetProps.Length > 0)            {                string targetPropName;  //目標屬性名稱                PropertyInfo sourceProp;                object sourcePropValue;                foreach (PropertyInfo targetProp in targetProps)                {                    //優先使用資料成員的別名,如果沒有別名則使用屬性名稱                    object[] targetPropAliasAttrs = targetProp.GetCustomAttributes(typeof(DataMemberAliasAttribute), true);                    if (targetPropAliasAttrs != null && targetPropAliasAttrs.Length > 0)                        targetPropName = ((DataMemberAliasAttribute)targetPropAliasAttrs[0]).Alias;                    else                        targetPropName = targetProp.Name;                    //檢索源屬性                    sourceProp = source.GetType().GetProperty(targetPropName);                    if (sourceProp != null && sourceProp.CanRead && targetProp.CanRead)                    {                        sourcePropValue = sourceProp.GetValue(source, null);                        //屬性類型一致時,直接填充屬性值                        if (targetProp.PropertyType == sourceProp.PropertyType)                            targetProp.SetValue(target, sourcePropValue, null);                    }                }            }            #endregion            #region Mapping Fields            FieldInfo[] targetFields = t.GetFields();            if (targetFields!=null&&targetFields.Length>0)            {                string targetFieldName;                FieldInfo sourceField;                foreach (FieldInfo targetField in targetFields)                {                    if (!targetField.IsInitOnly && !targetField.IsLiteral)                    {//欄位可以被賦值                        object[] targetFieldAttrs = targetField.GetCustomAttributes(typeof(DataMemberAliasAttribute), true);                        if (targetFieldAttrs != null && targetFieldAttrs.Length > 0)                            targetFieldName = ((DataMemberAliasAttribute)targetFieldAttrs[0]).Alias;                        else                            targetFieldName = targetField.Name;                        sourceField = source.GetType().GetField(targetFieldName);                        if (sourceField!=null)                        {                            //資料類型相同時映射值                            if (targetField.FieldType == sourceField.FieldType)                                targetField.SetValue(target, sourceField.GetValue(source));                        }                    }                }            }            #endregion            return target;        }        public static TOut Mapping<TOut,TIn>(this TIn source)             where TIn :class            where TOut :class        {            return source.Mapping<TOut>();        }    }

 

第三、然後我就可以使用下面這種文法來進行兩個對象之間資料成員的映射:

EmployeeViewModel viewmodel = storageModel.Mapping<EmployeeViewModel, EmployeeStorageModel>();

 

好的,代碼已經全部貼出來了。那麼我先來介紹一下第一個問題:為什麼要定義DataMemberAliasAttribute這個屬性類別。人們在做一件事情的時候通常都是為瞭解決某一類問題的,同樣,之所以這麼做,是為了適應兩個對象(類)包含的資料成員的名字可能不相同。就拿上面的例子來說,在上面的EmployeeStorageModel中可能或包含一個Property,叫做hire_date,表示員工的僱用日期,但是在EmployeeViewModel類中可能會有一個叫做HireDate的屬性與之對應。如果我們僅僅使用屬性本身的名字來進行映射的話這是不夠的,而且這種情況經常會發生,因為我們的StorageModel通常可能是有EntityFramework自動產生的,他麼的名稱通常與資料庫表中的欄位的名稱相同,而這完全取決於資料庫表設計人員的設計習慣,但是ViewModel很有可能是某一個.NET程式員設計的,他很有可能會按照微軟建議的屬性命名方法來進行屬性的命名,例如每個單詞的首字母都為大寫。所以我在這裡定義了這個屬性類別,用來表示某個資料成員的“別名”,使用方法如下:

public class EmployeeViewModel    {        public const string phoneNumber = "";        public readonly string emailAddress;        [DataMemberAlias("id")]        public int Id { get; set; }        [DataMemberAlias("employee_number")]        public string EmployeeNumber { get; set; }        [DataMemberAlias("employee_name")]        public string EmployeeName { get; set; }        [DataMemberAlias("hire_date")]        public DateTime HireDate { get; set; }        [DataMemberAlias("salary")]        public double Salary { get; set; }        [DataMemberAlias("job")]        public string Job { get; set; }        [DataMemberAlias("department")]        public Department Department        {            get;            set;        }        public int Status { get; set; }    }

這樣就可以解決上面提出的問題。注意如果在對象類中為資料成員設定了別名,那麼在進行映射時,會優先匹配別名;如果沒有為資料成員按設定別名,那麼就會匹配資料成員本身的名稱。同時需要注意以下細節:

1、對象B的屬性需要可寫,對象A的屬性需要可讀,否則將會忽略此資料成員;

2、對象B的欄位需要可以被賦值,即不能帶readonly或者const修飾符,因為這樣的話,無法為欄位進行賦值,只能忽略它們;

3、只映射公用的屬性和欄位(通常屬性都為public,而帶有public修飾符的欄位也具有屬性的一些特徵)

4、對象A和對象B相同資料成員的資料類型必須相同;

5、如果對象A和對象B中的資料成員的資料類型相同且都為參考型別,那麼傳遞是是引用,在使用中需要注意這一點,請見Department屬性的範例程式碼

   

 

[DataMemberAlias("department")]        public Department Department        {            get;            set;        }
public class Department    {        public int DepartmentCode { get; set; }        public string DepartmentName { get; set; }        public string DepartmentLeader { get; set; }    }

 

現在我們來看一下Mapping這個擴充方法,首先這個方法是一個番型方法,它包含兩個型別參數TIn和TOut,TOut為方法傳回值的資料類型,及對象B的資料類型,TIn為參數的資料類型,即為對象A的資料類型。同時限定這兩個型別參數在執行個體化的時候必須指定為類類型,這一點是出於對應用情境的設計。因為這是一個擴充方法,所以可以使用下面的文法來進行方法的調用:

EmployeeViewModel viewmodel = storageModel.Mapping<EmployeeViewModel, EmployeeStorageModel>();

 本來想講一下Mapping方法的商務邏輯的,但是因為時間的關係,在這裡就不再細說了,代碼裡面都有注釋。大家如果有興趣可以加我:Happy_Chopper

 

------------------------------------------------------------------------------------------------------------------------------------End

 

.NET中利用反射來實現自動對應兩個對象中的資料成員

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.