一、什么是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框架的更多相关文章

  1. 自己造轮子系列之OOM框架AutoMapper

    [前言] OOM框架想必大家在Web开发中是使用频率非常之高的,如果还不甚了解OOM框架,那么我们对OOM框架稍作讲解. OOM顾名思义,Object-Object-Mapping实体间相互转换.常见 ...

  2. 五步掌握OOM框架AutoMapper基本使用

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文地址 www.cnblogs.com/tdws  写在前面 OOM顾名思义,Object-Object-Mapping实体间相互转换,Aut ...

  3. OOM框架AutoMapper基本使用(2)

    出于安全考虑,在后台与前台进行数据传输时,往往不会直接传输实体模型,而是使用Dto(Data transfer object 数据传输对象),这样在后台往前台传递数据时可以省略不必要的信息,只保留必要 ...

  4. OOM框架AutoMapper基本使用(1)

    OOM顾名思义,Object-Object-Mapping实体间相互转换,AutoMapper也是个老生常谈了,其意义在于帮助你无需手动的转换简单而又麻烦的实体间关系,比如ViewModel和enti ...

  5. “造轮运动”之 ORM框架系列(三)~ 干货呈上

     这一趴里面,我就来正式介绍一下CoffeeSQL的干货.    首先要给CoffeeSQL来个定位:最开始就是由于本人想要了解ORM框架内部的原理,所以就四处搜寻有关的博客与学习资料,就是在那个夏天 ...

  6. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 ...

  7. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- ...

  8. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播

    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...

  9. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播

    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...

随机推荐

  1. jq实现二级菜单/下拉菜单

    https://www.cnblogs.com/sandraryan/ 不是很难,直接上代码~ 有写注释 <!DOCTYPE html> <html lang="en&qu ...

  2. <climits>头文件

    <climits>头文件定义的符号常量 CHAR_MIN char的最小值SCHAR_MAX signed char 最大值SCHAR_MIN  signed char 最小值UCHAR_ ...

  3. MySQL高级配置

    参考文章:http://www.jb51.net/article/47419.htm https://blog.csdn.net/waneto2008/article/details/52502208 ...

  4. H5 多媒体标签

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. windows常用命令行命令

    https://blog.csdn.net/qq_32451373/article/details/77743869 打开"运行"对话框(Win+R),输入cmd,打开控制台命令窗 ...

  6. HDU 3746 Cyclic Nacklace(kmp next数组运用)

    Cyclic Nacklace Problem Description CC always becomes very depressed at the end of this month, he ha ...

  7. Codevs 四子连棋 (迭代加深搜索)

    题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向上下左右四个方向移动到相邻的空格,这叫行棋一步,黑白双 ...

  8. 手机网页H5 自适应不同分辨率的屏幕 必学标签meta之viewport

    viewport 语法介绍 <meta name="viewport"content=" height = [pixel_value | device-height ...

  9. 递归&时间模块&os模块

    递归 递归调用 一个函数,调用了自身,称为递归调用 递归函数:一个会调用自身的函数称为递归函数 凡是循环能干的事,递归都能干 方式: 写出临界条件 找这一次和上一次的关系 假设当前函数已经能用,调用自 ...

  10. [板子]Kruskal

    众所周知求最小生成树的两种方法: 1.Kruskal 2.Prim 这里只挂第一种,因为noip掌握第一种就够了. 两种做法的区别可以参考这个博客:http://blog.csdn.net/molln ...