.NET中利用反射来实现自动映射两个对象中的数据成员

  在以前的项目开发之中,经常会遇到这样一个问题:比如在外面项目的架构设计之中,我们采用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

时间: 2024-10-10 08:07:41

.NET中利用反射来实现自动映射两个对象中的数据成员的相关文章

java中利用反射机制绕开编译器对泛型的类型限制

首先看下面这个例子 public static void main(String[] args) { ArrayList<Integer> al1 = new ArrayList<Integer>(); al1.add(1); ArrayList<String> al2 = new ArrayList<String>(); al2.add("hello"); //int型链表和string型链表,结果为true System.out.pr

java中父类与子类, 不同的两个类中的因为构造函数由于递归调用导致栈溢出问题

1 /* 2 对于类中对成员变量的初始化和代码块中的代码全部都挪到了构造函数中, 3 并且是按照java源文件的初始化顺序依次对成员变量进行初始化的,而原构造函数中的代码则移到了构造函数的最后执行 4 */ 5 import static java.lang.System.out; 6 7 public class PersonDemo 8 { 9 public static void main(String[] args) 10 { 11 //*********测试父类与子类之间的循环调用的问

Java中对比两个对象中属性值[反射、注解]

在Java中通常要比较两个对象在修改前与修改后的值是否相同,一般我们采用的是反射技术获取对象的get方法[或其他的方法]获取值并做比较.如果系统将修改的属性名称也显示出来,这样就能更直观的显示类中的哪一个属性的值被修改了.然后Java中只能获取属性的名称,也就是英文标识的属性名,但是一般我们都会在属性后面添加属性的注释,但是Java不提供注释获取的方法.所以我们只能使用另外一种方式来将属性和属性注释关联起来,这就是Java中的@Annotation. public static  Map<Str

java 中利用反射机制获取和设置实体类的属性值

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. JAVA反射(放射)机制:"程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言".从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言.但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可

winform中利用反射实现泛型数据访问对象基类

考虑到软件使用在客户端,同时想简化代码的实现,就写了一个泛型的数据访问对象基类,并不是特别健全,按道理应该参数化的方式实现insert和update,暂未使用参数化,抽时间改进. /// <summary> /// DAO基类 实体名必须要与数据表字段名一致 /// </summary> /// <typeparam name="T"></typeparam> public class BaseDao<T> where T :

C# 利用反射根据类名创建类的实例对象

“反射”其实就是利用程序集的元数据信息. 反射可以有很多方法,编写程序时请先导入 System.Reflection 命名空间. 1.假设你要反射一个 DLL 中的类,并且没有引用它(即未知的类型): Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); // 加载程序集(EXE 或 DLL) object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)");

如何在Scope中利用keyword来使自己的Scope在聚合Scope中展示自己

在前面的文章"Scopes 关键词 (keyword)及departments探讨(英文视频)"中,我们已经对Scope中的keyword做了一些基本的介绍.但是我们没有相应的教程.在这篇文章中,我们将通过youku Scope来介绍如何使用keyword从而使得一个Scope在不同的聚合Scope中来得到不同的呈现. 如果大家对如何开发Scope还不是很熟的话,请参阅我的教程"在Ubuntu OS上创建一个dianping Scope (Qt JSON)".如果大

Qt中利用QTime类来控制时间,这里简单介绍一下QTime的成员函数的用法:

---------------------------------------------------------------------------------------------------------------------------------------- QTime::QTime() 默认构造函数,构造一个时,分,秒都为0的时间,如00:00:00.000(午夜) QTime::QTime(int h, int m, int s=0, int ms = 0) 构造一个用户指定时

反射+自定义注解---实现Excel数据列属性和JavaBean属性的自动映射

简单粗暴,直奔主题.   需求:通过自定义注解和反射技术,将Excel文件中的数据自动映射到pojo类中,最终返回一个List<pojo>集合? 今天我只是通过一位使用者的身份来给各位分享一套超级可以的POI"工具",这套工具我只是第一个使用者,创作者是我的朋友,他喜好钻研底层和算法,擅长计算机软硬件,在我心里他一直是神一样的存在,每天晚上10点后我才能看到他,因为他每天需要加班,需要有更多时间能够学习,唉,这种毅力和耐力,我是真的羡慕,因为我也一直在努力,能够得到更多的东