.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 > )
{
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 > )
{
string targetPropName; //目标属性名称
PropertyInfo sourceProp;
object sourcePropValue;
foreach (PropertyInfo targetProp in targetProps)
{
//优先使用数据成员的别名,如果没有别名则使用属性名
object[] targetPropAliasAttrs = targetProp.GetCustomAttributes(typeof(DataMemberAliasAttribute), true);
if (targetPropAliasAttrs != null && targetPropAliasAttrs.Length > )
targetPropName = ((DataMemberAliasAttribute)targetPropAliasAttrs[]).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>)
{
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 > )
targetFieldName = ((DataMemberAliasAttribute)targetFieldAttrs[]).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中利用反射来实现自动映射两个对象中的数据成员的更多相关文章
- [.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦
[.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦 本节导读:上篇文章简单介绍了.NET面向对象中一个重要的技术反射的基本应用,它可以让我们动态的调 ...
- C# 中利用反射机制拷贝类的字段和属性(拷贝一个类对象的所有东西付给另一个类对象,而不是付给引用地址)
from:https://blog.csdn.net/poxiaohai2011/article/details/27555951 //C# 中利用反射机制拷贝类的字段和属性(拷贝一个类对象的所有东西 ...
- java 中利用反射机制获取和设置实体类的属性值
摘要: 在java编程中,我们经常不知道传入自己方法中的实体类中到底有哪些方法,或者,我们需要根据用户传入的不同的属性来给对象设置不同的属性值,那么,java自带的反射机制可以很方便的达到这种目的,同 ...
- Qt中利用QTime类来控制时间,这里简单介绍一下QTime的成员函数的用法:
Qt中利用QTime类来控制时间,这里简单介绍一下QTime的成员函数的用法: ------------------------------------------------------------ ...
- C#比较两个对象中的指定字段值是否相等
一.创建CompareFieldAttribute标识要比较的字段 using System; namespace CompareObjField { /// <summary> /// ...
- JAVA中利用反射机制进行对象和Map相互转换的方法
JAVA的反射机制主要作用是用来访问对象的属性.方法等等.所以,JAVA中对象和Map相互转换可以利用JAVA的反射机制来实现.例子如下: 一.对象转Map的方法 public static Map& ...
- 利用反射把DataTable自动赋值到Model实体(自动识别数据类型)
转:http://www.cnblogs.com/the7stroke/archive/2012/04/22/2465591.html using System.Collections.Generic ...
- JAVA反射机制示例,读取excel数据映射到JAVA对象中
import java.beans.PropertyDescriptor; import java.io.File; import java.io.FileInputStream; import ja ...
- java利用反射交换两个对象中的字段相同的字段值
有时候我们的两个对象字段都是一样的,只有极少的区别,想要把一个对象字段的值,赋值给另外一个对象值 然后传给另外一个方法使用,但是这个字段太多,一个一个的复制太过繁琐. 这时候利用反射解决这个问题. c ...
随机推荐
- Cassandra 单机入门例子——有索引
入门例子: http://wiki.apache.org/cassandra/GettingStarted 添加环境变量并source生效,使得可以在任意位置执行cassandra/bin安装目录下的 ...
- 读取SQLServer数据库存储过程列表及参数信息
得到数据库存储过程列表: select * from dbo.sysobjects where OBJECTPROPERTY(id, N'IsProcedure') = 1 order by name ...
- sql server停止和重启命令
http://www.ynpxrz.com/n822732c2024.aspx 我们知道:sql server重启分分两步走 1.停止 net stop mssqlserver 2.重启 net st ...
- JAVA中Singleton的用法
Java Singleton模式属于管理实例化过程的设计模式家族.Singleton是一个无法实例化的对象.这种设计模式暗示,在任何时候,只能由JVM创建一个Singleton(对象)实例. JAVA ...
- 用《VisualStudio命令提示》生成WSDL客户端文件
1.找到vs命令提示符并且以管理员方式打开. 2.输入:wsdl + wsdl文件路径(可以为url).如:“wsdl http://localhost:30373/PDAWebService/SH3 ...
- 【Zookeeper】源码分析之Watcher机制(三)之Zookeeper
一.前言 前面已经分析了Watcher机制中的大多数类,本篇对于ZKWatchManager的外部类Zookeeper进行分析. 二.Zookeeper源码分析 2.1 类的内部类 Zookeeper ...
- Spark工作机制简述
Spark工作机制 主要模块 调度与任务分配 I/O模块 通信控制模块 容错模块 Shuffle模块 调度层次 应用 作业 Stage Task 调度算法 FIFO FAIR(公平调度) Spark应 ...
- 一个可以提高开发效率的Git命令-- Cherry-Pick
在实际的项目开发中(使用Git版本控制),在所难免会遇到没有切换分支开发.需要在另一个分支修改bug然后合并到当前分支的情况.之前遇到这种第一反应就是将分支合并过去来解决问题.如果你那些提交当中也穿插 ...
- quartz配置时间
我们需要把log4j的配置文件放入src目录下,启动main类就可以了. Cron Expressions cron的表达式被用来配置CronTrigger实例. cron的表达式是字符串,实际上是由 ...
- 5个步骤创建你的第一个RESTFul 服务
1.啥是RESTFul 服务 在我们创建简单小程序前,先来学习下RESTFul 服务.RESTFul服务就是遵循了 Representational State Transfer(可以参考http:/ ...