我的轻型ORM实现
在具体介绍我的ORM解决方案之前,先说一下没有该方案之前的状况:
还是我上篇post中提及的公司OA,上篇post中提到说当时用文件序列化的形式来存储数据,但用过文件序列化的人都知道,数据必须整存整取,为了存入新的数据,还必须先反序列化以前的数据,然后再重新序列化,因此如果要达到更新的目的,无论是整个实体对象,还是一个实体对象的某几个属性,都是很麻烦的事情。
经过一番思量,为了数据的安全性考虑,决定将该存储方案移植到数据库平台上,毕竟数据库更加高效,更加安全。在移植之前,也考虑过NHibernate,因为公司前几个项目都用NH作ORM方案,但仔细考量一番之后还是觉得,NH的实现方案太约束OO的思想,甚至可以说了屈就NH,原本可以很完美的OO实现也要委曲求全,本人就有在公司某一个项目中,另外写了很多包含原有Entity的Entitylogic类的经历,也就是说在只有属性,不具动作的NH生成Entity类另外加上方法。不知道园子里的兄弟对NH怎么个看法。
说了文件序列化和NH的实现,还是仔细来说一下我这两天的ORM实现。先简要的看一下架构图:
可以看出在这张图中,我特意把Entities层画的短了很多,因为在我的DBImply中,根本就不需要用到Entities,而是利用反射去动态获取实体的属性信息,进而进行对象持久化。
详细解释我的实现之前,先简单说一下我对序列化实现的看法。为什么对象序列化之后对其进行反序列化,仍然能得到其关联对象的信息?我觉得莫非就是反射在其中作祟,而且m$在实现序列化时已经定义了一系列协议。
于是,在实现我的ORM方案时,根据我对序列化实现的想法,我确定了下面的协议:
1.类对象中,需要进行持久化的属性,必须定义自定义属性PersistAttribute//正如需持久化的类须定义Serializable属性一样
2.有关联关系的两个类,在其中之一定义AggregateRelationAttribute,并同时指明与之有聚合或组合关系的另一个类
//上述的两个Attribute都是我自定义的属性
在DBImply中,主要包含以下几个层次:
1.DynamicSqlBuilder
2.SqlCommandInvoker
3.DBImply
他们之间的关系可以用伪代码简要表示为:
IDataAccess da = new DBImply(entityObject);
string sql = DynamicSqlBuilder.GetInstance().GetCRUDSqlString(entityObject);
da.SqlCommandInvoker.InvokeCommand(sql);其中关键部分就在DynamicSqlBuilder.GetInstance().GetCRUDSqlString(entityObject)中,因为ORM本身的实现瓶颈就在于如何对OO世界中的一个对象和二维表结构数据互相转换,只要有了数据库能读懂的Sql语句,一切都不再是问题。
于是,在这个GetCRUDSqlString方法中,我根据反射得到的对象信息,动态获取了数据库执行需要的CRUD的sql语句,下面略举Create为例:
public string GetCreateString(object obj)
{
Debug.Assert(null != obj);
List<PropertyPair> proTypeList = GetPersistObjectProperties(obj);
if (0 == proTypeList.Count)
return string.Empty;
StringBuilder columnBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
columnBuilder.Append("(");
valueBuilder.Append("(");
for (int i = 0; i < proTypeList.Count; i++)
{
PropertyPair proPair = proTypeList[i];
if (i != proTypeList.Count - 1)
{
columnBuilder.Append(string.Format("{0},", proPair.Name));
valueBuilder.Append(string.Format("'{0}',", proPair.Value));
}
else
{
columnBuilder.Append(string.Format("{0}", proPair.Name));
valueBuilder.Append(string.Format("'{0}'", proPair.Value));
}
}
columnBuilder.Append(")");
valueBuilder.Append(")");
return string.Format("insert into {0} {1} values {2}",
obj.GetType().Name ,
columnBuilder,
valueBuilder);
}
private List<PropertyPair> GetPersistObjectProperties(object obj)
{
List<PropertyPair> proPairList = new List<PropertyPair>();
PropertyInfo[] proInfos = obj.GetType().GetProperties();
foreach (PropertyInfo info in proInfos)
{
if (info.IsDefined(typeof(PersistableAttribute), false))
{
object value = info.GetValue(obj, null);
string name = info.Name;
proPairList.Add(new PropertyPair(name, value));
}
}
return proPairList;
}其中,PropertyPair是一个Name与Value相对应的类,用来对应表结构中的Column与Row的交集,定义如下
public class PropertyPair
{
public PropertyPair(string name, object value)
{
_name = name;
_value = value;
}
private string _name;
public string Name
{
get
{ return _name; }
set
{ _name = value; }
}
private object _value;
public object Value
{
get
{ return _value; }
set
{ _value = value; }
}
}通过调用上述的Insert构造器,便可动态获取与一个类对象有关的Sql语句了,后面的工作自然不用赘述。
当然,用人会说Sql注入怎么办?这个自然会在调用Sql构造器之前就有一个过滤,不用担心。
欢迎拍砖
