昨天收到帖子一位朋友的回复,建议我把框架的ORM改成 DotNet 3.5的Linq,其实这曾经也是我心中的迷惑。有N个用Linq的理由,也有N个不用Linq的理由。
在完善框架的时候,电信的一位技术总监跟我说:我不建议你做框架,那是微软等大公司的事,我们主要是用框架,你现在写ORM,现在的DotNet3.5 已经处于Alpha测试阶段了,你的ORM还有多大的用处。我当时一下子迷惘了,赶快找资料,从Lamda表达式开始学起,对于我来说难度还是不少的(是在考验我的数学呀)。最终还是放弃了。
1、 没有Linq的源代码,不知道清楚中间的数据是如何处理的,特别是缓存是如何处理的。
2、 用Linq翻译到存储过程/Sql语句效率很低的,对于复杂的对效率有一定的要求的,还是要写存储过程。而且翻译出来的存储过程/Sql语句的怎么缓存,第二次执行的时候还要重新翻译,不能很好的利用缓存。
3、 工具的支持。这一点是非常重要。如果没有有力工具的支持,用ORM简直就是恶梦。Linq的工具支持总的来说,现在在VS2008里的支持我感觉还不是很友好。至少有一点,我们通常在表的备注里面写下这个字段的注释,很多情况下对应到界面上就是这个字段的抬头,这个工具不能直接生成到项目里去,因为我们不能控制,再者,现在的项目都不是由一个人完成,如果一个类有几十个字段,很难记住解释,自己的工具可以加入注释,方便开发。
4、 与业务层的综合。通常ORM映射就是把对象与关系表之间形成映射关系,映射的目标是为了方便业务层的逻辑处理,如果不能与业务层无缝的结合起来,那么对于项目的开发,也未必有多少好处。至少在的ORM里面,与的我业务层可以非常友好的集成在一起。
5、 多数据源的支持。对于我来说,这点也是非常关键的。我说的多数据源是指一个项目里面,有多个数据库,比如:基础数据、客户管理、业务运作、权限管理等,这些都是相对独立的模块,开发的时候,可以把这几个作为单独的项目开发,各自有自己独立的数据库,而且在多个项目里面,这些模块都是非常相似,像权限模块基本上是通用的,没有必要再重复开发。如果能够做到数据库层分离,则新项目开发的时候,可以达到更快的速度。当然也可以做到很容易的分合,把两个数据库合并成一个,或者把一个数据库分开成两个,只涉及到配置文件的更改,在开发的时候可以不关心的。
6、 对于可变查询的处理。也就是相当于拼接字符串了。可能会说,这个是Linq的强项,但是,如果对于一个分布式的项目呢?又应该如何处理。知道ORM的朋友应该都知道IBatis.net 和NHibnate ,I家的参数处理是非常的方便的,N家呢就没有I的直观,但是对于面向对象的处理I比N家好像就有太多的不足了。对于远程处理要能够通过参数传递这些只对映射层公开的参数,这点对于程序的某些地方可能是至关重要的影响。我的ORM里面,随便起了一个名字,叫通用查询,业务逻辑里用的查询通常会写在存储过程里,可以通过工具直接生成业务层代码里的函数。
7、 对于泛形、继承的支持与处理。泛形,可以减少很多代码,同时,还可以减少很多人为的错误。
因为人的精力总是有限的,你把精力花在一个地方,那么其他方面肯定关注的少了,我也一样。可能是我把精力过多的放在我的ORM里面,忽略了其他的方面,请大家能够给出批评指正。
接下来,把我的ORM映射部分做一详细的介绍
ORM映射主要分为以下几个方面:
1、 对象、参数翻译
a) 对象的表达。对象的映射,我采用的是自定义属性方法。

Code
namespace LG.Common.Enities



{

using System;

using System.Collections.Generic;

using System.Text;

using System.Data;

using System.Data.SqlClient;

using Unie2e.Common;

using Unie2e.ORM;

using Unie2e.ORM.Mapping;

[System.SerializableAttribute()]

[Unie2e.ORM.Mapping.TableAttribute("City", MapFileName="CityCustParam.Xml")]

public sealed class CityEntity : Unie2e.ORM.Mapping.Entity


{


/**//// <summary>

/// 映射字段的总长度

/// </summary>

private const int length = 5;


映射字段#region 映射字段


/**//// <summary>

/// 城市Id

/// </summary>

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityId", true, SqlDbType.UniqueIdentifier, 0, 0, 0, "城市Id")]

public Guid CityId = System.Guid.NewGuid();


/**//// <summary>

/// 省份Id

/// </summary>

[Unie2e.ORM.Mapping.FieldAttribute("City", "ProvinceId", false, SqlDbType.UniqueIdentifier, 0, 0, 0, "省份Id")]

public Guid ProvinceId = System.Guid.Empty;


/**//// <summary>

/// 城市代码

/// </summary>

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityCode", false, SqlDbType.VarChar, 50, 0, 0, "城市代码")]

public String CityCode;


/**//// <summary>

/// 城市名称

/// </summary>

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityName", false, SqlDbType.VarChar, 50, 0, 0, "城市名称")]

public String CityName;


/**//// <summary>

/// 备注

/// </summary>

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityComment", false, SqlDbType.VarChar, 200, 0, 0, "备注")]

public String CityComment;

#endregion


重载基类函数#region 重载基类函数

public override void Initialize(object[] parameters)


{

if ((length != parameters.Length))


{

throw new E2EException("参数个数与字段数不相等");

}

CityId = ((System.Guid)(parameters[0]));

ProvinceId = ((System.Guid)(parameters[1]));

CityCode = ((string)(parameters[2]));

CityName = ((string)(parameters[3]));

CityComment = ((string)(parameters[4]));

}

public override object[] ToArray()


{

object[] objs = new object[5];

objs[0] = CityId;

objs[1] = ProvinceId;

objs[2] = CityCode;

objs[3] = CityName;

objs[4] = CityComment;

return objs;

}

#endregion

}

[System.SerializableAttribute()]

[Unie2e.ORM.Mapping.TableAttribute("City")]

public abstract class CityFindParam<T> : EntityParam<T> where T: IEntity


{


/**//// <summary>

/// 映射字段的总长度

/// </summary>

private const int length = 6;


映射字段#region 映射字段

[Unie2e.ORM.Mapping.FieldAttribute("City", "Method", SqlDbType.VarChar, 50, 0, 0, "")]

public String Method;

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityId", SqlDbType.UniqueIdentifier, 0, 0, 0, "")]

public Guid CityId = System.Guid.Empty;

[Unie2e.ORM.Mapping.FieldAttribute("City", "ProvinceId", SqlDbType.UniqueIdentifier, 0, 0, 0, "")]

public Guid ProvinceId = System.Guid.Empty;

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityCode", SqlDbType.VarChar, 50, 0, 0, "")]

public String CityCode;

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityName", SqlDbType.VarChar, 50, 0, 0, "")]

public String CityName;

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityComment", SqlDbType.VarChar, 200, 0, 0, "")]

public String CityComment;

#endregion


重载基类函数#region 重载基类函数

public override object[] ToArray()


{

object[] objs = new object[6];

objs[0] = Method;

objs[1] = CityId;

objs[2] = ProvinceId;

objs[3] = CityCode;

objs[4] = CityName;

objs[5] = CityComment;

return objs;

}

public override void Initialize(object[] parameters)


{

if ((length != parameters.Length))


{

throw new E2EException("参数个数与字段数不相等");

}

Method = ((String)(parameters[0]));

CityId = ((Guid)(parameters[1]));

ProvinceId = ((Guid)(parameters[2]));

CityCode = ((String)(parameters[3]));

CityName = ((String)(parameters[4]));

CityComment = ((String)(parameters[5]));

}

#endregion

}

[System.SerializableAttribute()]

[Unie2e.ORM.Mapping.TableAttribute("City")]

public sealed class CityFindParam : CityFindParam<CityEntity>


{

}

[System.SerializableAttribute()]

[Unie2e.ORM.Mapping.TableAttribute("City")]

public abstract class CityActionParam<T> : EntityParam<T> where T: IEntity


{


/**//// <summary>

/// 映射字段的总长度

/// </summary>

private const int length = 2;


映射字段#region 映射字段

[Unie2e.ORM.Mapping.FieldAttribute("City", "Method", SqlDbType.VarChar, 50, 0, 0, "")]

public String Method;

[Unie2e.ORM.Mapping.FieldAttribute("City", "CityId", SqlDbType.UniqueIdentifier, 0, 0, 0, "")]

public Guid CityId = System.Guid.Empty;

#endregion


重载基类函数#region 重载基类函数

public override object[] ToArray()


{

object[] objs = new object[2];

objs[0] = Method;

objs[1] = CityId;

return objs;

}

public override void Initialize(object[] parameters)


{

if ((length != parameters.Length))


{

throw new E2EException("参数个数与字段数不相等");

}

Method = ((String)(parameters[0]));

CityId = ((Guid)(parameters[1]));

}

#endregion

}

[System.SerializableAttribute()]

[Unie2e.ORM.Mapping.TableAttribute("City")]

public sealed class CityActionParam : CityActionParam<CityEntity>


{

}

}


[Unie2e.ORM.Mapping.TableAttribute("City", MapFileName="CityCustParam.Xml")]
给出了映射对象与数据库表或者视图的关系,MapFileName给出了所对应的通用查询的配置文件,当然也可以把Sql语句写在配置文件里面,实现查询之外的其他动作处理。因为工具没有对其他生成代码函数的支持,所以通常还是通过工具来生成存储过程来处理。
在Entity里面,有两上重载的函数:public override void Initialize(object[] parameters)和public override object[] ToArray(),这两个函数是为了加速数据加载的。底层用的是DataReader读取数据,尽可能少的通过反射加载数据,实现数据的快速从数据库加载到对象里面,因为这些代码都是通过工具生成的,不会出现错误,比较要注意的是用工具写存储过程的时候要注意一点就可以了。
[Unie2e.ORM.Mapping.FieldAttribute("City", "CityId", true, SqlDbType.UniqueIdentifier, 0, 0, 0, "城市Id")]
给出了对象Field与表或视图的列的映射关系。最后一个是对于注释,对于BO的业务数据会生成Summary。最终会生成到界面的表述,以及列表的配置文件的头。
除了Entity 外,还有CityFindParam<T> ,CityFindParam,CityActionParam<T>,CityActionParam这几个类,CityFindParam<T> 主要是为了实现可变返回数据,默认情况下返回的是完整表的数据,有时候,数据库表或视图的字段很多,比较重要的地方,可能只会要求返回其中的部分字段,用在这方面处理。CityActionParam,主要是针对批量更新等特殊处理,对个别地方进行性能优化处理。
b) 对象翻译
对象翻译就是把对象属性翻译到成相对应的存储过程的调用。我的ORM映射主要是对象参数处理。添加、删除、修改都是对完整的对象的映射,对于特别的处理是通过Action处理的。

Code
SqlParameter[] BuilderSqlParameters(Type type,SQLActionType actionType)


{

FieldInfo[] infos = type.GetFields();

SqlParameter[] parameters = new SqlParameter[infos.Length];

for (int i = 0; i < infos.Length; i++)


{

object[] objs = infos[i].GetCustomAttributes(typeof(FieldAttribute), false);

FieldAttribute attr = objs[0] as FieldAttribute;

SqlParameter sp;

if (attr.Length != 0)

sp = new SqlParameter("@" + attr.ColumnName, attr.SqlDbType, attr.Length);

else


{

sp = new SqlParameter("@" + attr.ColumnName, attr.SqlDbType);

}

if (attr.IsKey&&actionType== SQLActionType .Insert)

sp.Direction