“土法炮制”之 OOM框架
一、什么是OOM框架?
OOM 的全拼是 Object-Object-Map,意思是对象与对象之间的映射,OOM框架要解决的问题就是对象与对象之间数据的自动映射。
举一个具体的例子:用过MVC模式开发Web后台的小伙伴们都知道EO(Entity Object,实体对象)与DTO(Data Transfer Object,数据传输对象)之间需要进行一个转换。使用最原始的方法,我们会像这样去进行转换操作:
- /// <summary>
- /// EO类
- /// </summary>
- public class Student_EO
- {
- public string Id { get; set; }
- public string Name { get; set; }
- public int Age { get; set; }
- }
- /// <summary>
- /// DTO类
- /// </summary>
- public class Student_DTO
- {
- public string Id { get; set; }
- public string StudentName { get; set; }
- public int Age { get; set; }
- }
- /// <summary>
- /// EO对象转DTO对象
- /// </summary>
- /// <param name="eo"></param>
- /// <returns></returns>
- public Student_DTO _toDTO(Student eo)
- {
- Student_DTO dto = new Student_DTO();
- dto.Id = eo.Id;
- dto.StudentName = eo.Name;
- dto.Age = eo.Age;
- return dto;
- }
看起来也不难嘛,这个代码很容易写。但是要注意的是,你的项目中可能有几十上百个EO类与DTO类需要进行这样的映射转换,作为一个懒惰的程序员这是最不能容忍的事情。那么,想偷懒,那就必须先动动脑子......
于是,我们希望有一个框架能帮我们避免这枯燥乏味重复的代码,有一点想法的程序员很快就会想到:我们可以使用反射去获取类的字段将它们对应的去赋值。当然,我也是这么想,并且也是这么做的。于是废话不多说,开始动手,创造属于你自己的OOM框架吧,奥利给!!!!!
二、现成的OOM框架
在即将自己动手实现OOM框架之前,当然是要先了解了解目前有哪些OOM框架在流行,毕竟你能想到的问题大部分人已经想到了,并且可能已经有了很好的解决方案,比如AutoMapper,EmitMapper...这些框架,不得不承认,这些轮子已经很好用了,它们已经提供了丰富而全面的功能,但是,这并不妨碍我探索的激情。最后我也会总结一下自己“土法炮制”的OOM框架与AutoMapper框架对比的情况。废话不多说,往下看。
三、自制OOM框架的需求分析及技术选择
决定要做一件事情之前,你最好要清楚地知道你将完成的东西最后具体是什么样的,这样的一个好处是能理清你需要的功能点,再一个就是你要确定你想要的是不是这样一个东西。
Ok,我现在想要做一个OOM框架,就把它叫做CoffeeMapper框架吧,那么我脑海里CoffeeMapper的模样是这样的:
1、实现任意两个类的对象之间的属性的映射,默认相同属性名的属性之间进行映射,也可指定要映射的属性名
2、可以自由设定映射的逻辑
3、映射转换的速度尽可能的快
其中第一条最好理解,那就是实现OOM框架最基本的功能,值的映射关系绑定,我们可以通过添加Attribute标签的方式进行绑定标注。
第二条也很好理解,默认的映射逻辑是等值映射,也就是映射类与被映射类之间对应的属性值是相等的;自由设定映射逻辑也就是说我可以根据需要设置我自己的映射逻辑,比如:原来的值加个前缀或后缀再映射...
最后一条是最模糊的,什么叫做映射转换的速度尽可能的快?那我们就要清楚,如果使用的是反射的方法先创建一个目标的对象,然后再逐个对对象的属性值进行赋值操作,那么你必须要知道的一点是利用反射去创建一个对象的性能是不理想的,就像这样子:
- Form1 form1 = Activator.CreateInstance(typeof(Form1)) as Form1;
看大神关于反射性能对比的分析:https://www.cnblogs.com/7tiny/p/9861166.html(再看ExpressionTree,Emit,反射创建对象性能对比)
那么如何避免这个性能的瓶颈呢?在这里我选择运用表达式树的技术来提升框架整体的性能。
四、源代码展示与思路分析
代码也不多,就一起放在这了,方便小伙伴们查看
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class MapperAttribute:Attribute
- {
- /// <summary>
- /// the Property Name that map to
- /// </summary>
- public string MapTo { get; set; }
- public MapperAttribute() { }
- public MapperAttribute(string mapTo)
- {
- this.MapTo = mapTo;
- }
- public static string GetMapToPropertyName(PropertyInfo propertyInfo)
- {
- object[] mapperAttrs = propertyInfo.GetCustomAttributes(typeof(MapperAttribute), false);
- MapperAttribute mapperAttr;
- if (mapperAttrs != null && mapperAttrs.Count() >= )
- {
- mapperAttr = mapperAttrs[] as MapperAttribute;
- return mapperAttr.MapTo;
- }
- else
- {
- return propertyInfo.Name;
- }
- }
- }
MapperAttribute
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class NoMapperAttribute:Attribute
- {
- }
NoMapperAttribute
- public static class TypeExtension
- {
- /// <summary>
- /// Get All level inherit class types of current type
- /// </summary>
- /// <param name="currentType"></param>
- /// <param name="outInheritClassList">storage result of type</param>
- public static void GetInheritClassTypes(Type currentType, ref List<Type> outInheritClassList)
- {
- outInheritClassList.Add(currentType);
- if (currentType.BaseType.Name != "Object")
- {
- GetInheritClassTypes(currentType.BaseType, ref outInheritClassList);
- }
- else
- {
- return;
- }
- }
- }
TypeExtension
- public sealed class CoffeeMapper<TIn, TOut> where TIn:class where TOut:class
- {
- private static readonly Func<TIn, TOut> funcCache = FuncFactory();
- public static TOut AutoMap(TIn InData, Action<TOut, TIn> action = null)
- {
- TOut _out = funcCache(InData);
- if (null != action) action(_out, InData);
- return _out;
- }
- private static Func<TIn, TOut> FuncFactory()
- {
- #region get Info through Reflection
- var _outType = typeof(TOut);
- var _inType = typeof(TIn);
- var _outTypeProperties = _outType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
- var _outTypePropertyNames = _outTypeProperties.Select(p => p.Name);
- #endregion
- #region some Expression class that can be repeat used
- //Student in
- var _inDeclare = Expression.Parameter(_inType, "_in");
- //StudentDTO _out
- var _outDeclare = Expression.Parameter(_outType, "_out");
- //new StudentDTO()
- var new_outEntityExpression = Expression.New(_outType);
- //default(StudentDTO)
- var default_outEntityValue = Expression.Default(_outType);
- //_in == null
- var _inEqualnullExpression = Expression.Equal(_inDeclare, Expression.Constant(null));
- #endregion
- var set_inEntityNotNullBlockExpressions = new List<Expression>();
- #region _out = new StudentDTO();
- set_inEntityNotNullBlockExpressions.Add(Expression.Assign(_outDeclare, new_outEntityExpression));
- #endregion
- PropertyInfo[] needMapPropertys = ScanAllPropertyNeedMap();
- foreach (var propertyInfo in needMapPropertys)
- {
- string mapToName = MapperAttribute.GetMapToPropertyName(propertyInfo);
- //no contain, no map
- if (!_outTypePropertyNames.Contains(mapToName))
- continue;
- //no type equal, no map and expection
- if (_outTypeProperties.First(p => p.Name == mapToName).PropertyType.FullName != propertyInfo.PropertyType.FullName)
- continue;
- if (propertyInfo.CanWrite)
- {
- //_out.Id
- var _outPropertyExpression = Expression.Property(_outDeclare, _outTypeProperties.First(p => p.Name == mapToName));
- //_in.Id
- var _inPropertyExpression = Expression.Property(_inDeclare, propertyInfo);
- //_out.Id = _in.Id;
- set_inEntityNotNullBlockExpressions.Add(
- Expression.Assign(_outPropertyExpression, _inPropertyExpression)
- );
- }
- }
- var checkIf_inIsNull = Expression.IfThenElse(
- _inEqualnullExpression,
- Expression.Assign(_outDeclare, default_outEntityValue),
- Expression.Block(set_inEntityNotNullBlockExpressions)
- );
- var body = Expression.Block(
- new[] { _outDeclare },
- checkIf_inIsNull,
- _outDeclare //return _out;
- );
- return Expression.Lambda<Func<TIn, TOut>>(body, _inDeclare).Compile();
- }
- /// <summary>
- /// Get All Property Info that need be mapped
- /// </summary>
- /// <typeparam name="TIn"></typeparam>
- /// <param name="InData"></param>
- /// <returns></returns>
- private static PropertyInfo[] ScanAllPropertyNeedMap()
- {
- List<PropertyInfo> propertyInfoList = new List<PropertyInfo>();
- //取得包括當前層級類在內的所有繼承的每一層祖先類型
- List<Type> inheritClassList = new List<Type>();
- TypeExtension.GetInheritClassTypes(typeof(TIn), ref inheritClassList);
- foreach (Type classType in inheritClassList)
- {
- var attrs = classType.GetCustomAttributes(typeof(MapperAttribute), false);
- if (null == attrs || attrs.Count() <= ) continue;
- PropertyInfo[] currentClassPropertyInfos = classType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
- .Where(proInfo => proInfo.GetCustomAttributes(typeof(NoMapperAttribute)).Count() <= )
- .ToArray();
- propertyInfoList.AddRange(currentClassPropertyInfos);
- }
- return propertyInfoList.ToArray();
- }
- #region
- //#region 實驗類型
- //public class EntityBase
- //{
- // /// <summary>
- // /// storage QueryField's Result
- // /// </summary>
- // private Dictionary<string, object> queryFieldsDictionary = new Dictionary<string, object>();
- //}
- //[Mapper]
- //public class BaseEO : EntityBase
- //{
- // public string id { get; set; }
- //}
- //[Mapper]
- //public class Student : BaseEO
- //{
- // [Mapper("studentName")]
- // public string name { get; set; }
- // public int age { get; set; }
- // [NoMapper]
- // public string nomapField { get; set; }
- //}
- //public class BaseDTO
- //{
- // public string id { get; set; }
- //}
- //public class StudentDTO : BaseDTO
- //{
- // public string studentName { get; set; }
- // public int age { get; set; }
- //}
- //#endregion
- //public StudentDTO Student_AutoMapTo_StudentDTO(Student _in)
- //{
- // StudentDTO _out;
- // if (_in == null)
- // _out = default(StudentDTO);
- // else
- // {
- // _out = new StudentDTO();
- // _out.id = _in.id;
- // _out.age = _in.age;
- // _out.studentName = _in.name;
- // }
- // return _out;
- //}
- #endregion
- }
CoffeeMapper
重要优化点思路:
每一个映射类都会生成一个进行值映射的方法委托,并将其以类静态字段的方式缓存下来,除第一次需要执行表达式树生成映射方法之外,以后的每一次调用都是直接调用缓存下来的委托,其执行效率和直接执行一个方法的效率几乎是一样的,这就达到了映射转换的速度尽可能的快的要求。
现在,我们可以看一下如何使用“土炮”—CoffeeMapper进行对象之间的属性值映射:
- public class EntityBase
- {
- }
- [Mapper]
- public class BaseEO:EntityBase
- {
- public string id { get; set; }
- }
- [Mapper]
- public class Student:BaseEO
- {
- [Mapper("studentName")]
- public string name { get; set; }
- public int age { get; set; }
- }
- public class BaseDTO
- {
- public string id { get; set; }
- }
- public class StudentDTO:BaseDTO
- {
- public string studentName { get; set; }
- public int age { get; set; }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Student s = new Student { id = "", name = "wuqiansen", age = };
- //一行代码实现映射
- StudentDTO t = CoffeeMapper<Student, StudentDTO>.AutoMap(s, (t1, t2)=> { t1.studentName = t2.name+"default"; });
- }
- }
我们看到程序中使用了一行代码就完成了对象之间的属性值映射,程序员的头发又可以少掉几根了!!!!
五、总结收获
通过这次OOM框架的造轮子,更加熟练了表达式树技术的使用。这是一个非常简单的轮子,其实与AutoMapper对比起来,这个框架就显得过于简单了,还有很多需要提高和完善的地方:比如,只可以进行扁平类之间的映射、只实现了属性的映射、只实现了一对一的类对象映射。但是,优点也恰恰是来源于此,由于它功能的简单,所以它能在满足日常开发需求的前提下达到轻量级、性能优秀的要求。最重要的还是在这个过程中学到了很多!!!!!
“土法炮制”之 OOM框架的更多相关文章
- 自己造轮子系列之OOM框架AutoMapper
[前言] OOM框架想必大家在Web开发中是使用频率非常之高的,如果还不甚了解OOM框架,那么我们对OOM框架稍作讲解. OOM顾名思义,Object-Object-Mapping实体间相互转换.常见 ...
- 五步掌握OOM框架AutoMapper基本使用
本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文地址 www.cnblogs.com/tdws 写在前面 OOM顾名思义,Object-Object-Mapping实体间相互转换,Aut ...
- OOM框架AutoMapper基本使用(2)
出于安全考虑,在后台与前台进行数据传输时,往往不会直接传输实体模型,而是使用Dto(Data transfer object 数据传输对象),这样在后台往前台传递数据时可以省略不必要的信息,只保留必要 ...
- OOM框架AutoMapper基本使用(1)
OOM顾名思义,Object-Object-Mapping实体间相互转换,AutoMapper也是个老生常谈了,其意义在于帮助你无需手动的转换简单而又麻烦的实体间关系,比如ViewModel和enti ...
- “造轮运动”之 ORM框架系列(三)~ 干货呈上
这一趴里面,我就来正式介绍一下CoffeeSQL的干货. 首先要给CoffeeSQL来个定位:最开始就是由于本人想要了解ORM框架内部的原理,所以就四处搜寻有关的博客与学习资料,就是在那个夏天 ...
- [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表
[源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 ...
- [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表
[源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- ...
- [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播
[源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...
- [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播
[源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...
随机推荐
- git的安装与命令行基本的使用
1.https://git-scm.com/ 点击这个网址进入git的官方网站 2,.进去里面会有提示,64位于32位的,根据自己的电脑安装 3 下载完了过后就直接安装,一般会安装在c盘里面 ,进入安 ...
- Codeforces Round #187 (Div. 1 + Div. 2)
A. Sereja and Bottles 模拟. B. Sereja and Array 维护全局增量\(Y\),对于操作1(即\(a_{v_i}=x\))操作,改为\(a_{v_i}=x-Y\). ...
- java 标准流
标准输入流: System.in 默认表示的是键盘录入 标准输出流: System.out 默认表示的是屏幕输出 Eg: package june6D; import java.io. ...
- 2018-9-2-WPF-开发自动删除软件
title author date CreateTime categories WPF 开发自动删除软件 lindexi 2018-09-02 14:51:48 +0800 2018-08-09 09 ...
- POJ 2387 Til the Cows Come Home(最短路模板)
题目链接:http://poj.org/problem?id=2387 题意:有n个城市点,m条边,求n到1的最短路径.n<=1000; m<=2000 就是一个标准的最短路模板. #in ...
- 开包即食的教程带你浅尝最新开源的C# Web引擎 Blazor
在今年年初, 恰逢新春佳节临近的时候. 微软给全球的C#开发者们, 着实的送上了一分惊喜. 微软正式开源Blazor ,将.NET带回到浏览器. 这个小惊喜, 迅速的在dotnet开发者中间传开了. ...
- markdown color
深青色 深蓝色 seagreen darkgreen indianred cornflowerblue lightskyblue coral lightcoral darkorange
- 拒绝FileNotFoundException!总结了这几个读取jar包外配置文件的知识点
前言 相信很多人遇到过这个问题:本地运行的好好的程序,怎么部署到线上就报找不到配置呢? 初识getResource 案例一 FieldMapConfig.class.getResource(" ...
- xcode无线调试
前言: xcode9 以上才会有无线调试这个功能,换了一个type-c口的mac,公司的新电脑,但是公司不给配转接口,到某东看了一下,type-c口同时可以转化usb和VGA的要198,官网差不多50 ...
- python写冒泡排序
冒泡就是重复地遍历要排序的数列,一次比较两个元素(泡泡),如果他们的顺序错误就把他们交换过来,像泡泡一样,依次按照顺序上升排列. 冒泡排序算法的运作如下: 比较相邻的元素.如果第一个比第二个大(升序) ...