首页 技术 正文
技术 2022年11月13日
0 收藏 600 点赞 3,878 浏览 8022 个字

一、什么是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对比起来,这个框架就显得过于简单了,还有很多需要提高和完善的地方:比如,只可以进行扁平类之间的映射、只实现了属性的映射、只实现了一对一的类对象映射。但是,优点也恰恰是来源于此,由于它功能的简单,所以它能在满足日常开发需求的前提下达到轻量级、性能优秀的要求。最重要的还是在这个过程中学到了很多!!!!!

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,487
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,903
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,736
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,487
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,127
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,289