如果我们要做一个需要能够支持各种数据库的ORM。可以用到AbstractFactory,Singleton等设计模式。
我们先分析一下,要实现一个ORM,我们首先需要一个能够和各种不同数据库平台交互的一致的接口,我们把它叫做DBWR,意思就是“数据库读写”。它可以读取数据库中的数据,对数据库执行DDL操作。我们认为它有这些功能:
1、读取数据,以DataTable的形式返回,其原型是:
public DataTable GetData(string Sql);
2、执行Sql语句,并返回受影响的行数,其原型是:
public int ExecSql(string Sql);
在实践中,我们经常以参数的形式作为某种条件来读取数据或者把数据写入数据库,这样一是方便,二是安全。因此还应该加上如下函数:
3、public DataTable GetData(string Sql, IDataParameter[] Params);
4、public int ExecSql(string Sql, IDataParameter[] Params);
另外,我们经常只要从数据库中获取一个值,完全没有必要用DataTable的形式,就用一个object好了。所以我们再加上下面的函数:
5、public object GetSingleValue(string Sql);
6、public object GetSingleValue(string Sql, IDataParameter [] Params);
下面是DBWR的简单类图:
在上面的函数定义中,我们为什么用IDataParameter,而不用SqlParameter呢?请注意,我们的DBWR类是需要与各种支持.net的数据库平台进行交互的,如果用SqlParameter的话,就只能与Sql Server进行交互了。
在.net中,对数据库进行连接的通用接口是IDbConnection.,执行Sql语句的是IDbCommand,当然还有用来填充IDbCommand的Parameters属性IDataParameter,当然,如果我们需要更加方面的操作,那就需要一个IDbDataAdapter的实例。但是各数据平台对他们的实现是不同的,不要期望Sql Server能够与Oracle能够统一。我们的DBWR类需要获取这一系列操作数据库的、相关的接口的实例,但是又不知道这些实例的具体的类,怎么办呢?这个时候,就可以用AbstractFactory设计模式来解决这个问题。它的意图就是“提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类”。
我们把这个能够为我们提供这些能与不同数据库平台打交道的接口,但又不必指定具体类的工具叫做DbInterfaceGenerator(意思就是“数据库接口生成器”),它有这些功能:
1、获取能够连接数据库的IDbConnection的实例:
public abstract IDbConnection GetConnection();
2、获取能够执行Sql语句的IDbCommand的实例:
public abstract IDbCommand GetCommand();
3、获取IDataParameter的实例:
A、按参数名称,参数的值获取参数:
public abstract IDataParameter GetParameter(string ParameterName, object ParameterValue);
B、按参数名称,参数的长度,参数的精度,参数的数据库类型的名字,比如“varchar”获取参数:
public abstract IDataParameter GetParameter(string ParameterName, int? Length, byte? Scale, string DbTypeName);
C、按参数名称,参数的长度,参数的精度,参数的数据库类型的名字,比如“varchar”,参数的值获取参数:
public abstract IDataParameter GetParameter(string ParameterName, int? Length, byte? Scale, string DbTypeName, object ParameterValue);
为什么把参数的长度,参数的精度声明为int?呢?因为有时我们并不需要指定参数的长度和精度,这时,就用null代替好了。
4、获取IDbDataAdapter
public abstract IDbDataAdapter GetAdapter();
那么对应Sql Server数据库,那就是SqlDbInterface,它返回的就是分别是SqlConnection,SqlCommand,SqlParameter,SqlDataAdapter。对应Oracel数据库,那就是OracleDbInterface.它返回的就是OracleConnection,OracleCommand,OracleParameter,OracleDataAdapter。
对其他的数据库,就是XXXDbInterface,返回就是对应的Connneciton,Command,Paramter和DataAdapter。
不同数据库进行交互的问题解决了,我们又碰到了一个问题,我们的DBWR类每次需要用到DbInterfaceGenerator类某个子类的实例的时候,难道每次都new一个新的实例么?这样做,既不方便,也会影响性能。一般来说,只要我们决定了用哪种数据库,系统一旦启动,就不会再更改。所以我们用以与数据交互的DbInterface只要有一个实例就可以了,它在第一次需要的的时候就生成。可以用Singleton模式达到我们的要求。它的目的就是“保证一个类仅有一个实例,并提供一个访问它的全局访问点”。
我们给DbInterfaceGenerator添加一个能够让其他类可以访问它本身唯一实例的“全局访问点”,叫做DbInterface。
改进后的DbInterfaceGenerator 的类图如下:
对于DBWR类,我们也需要提供一个这样的访问点,叫做Instance。改进后的类图是:
我们非常幸运,因为我们使用C#,实现这种Singleton模式,非常容易。两条简单的语句就可以实现DBWR的Instance:
public static readonly DBWR Instance = new DBWR();
protected DBWR() { }
当然,我们也可以把我们的DBWR类的构造函数申明为private.
不过,对于DbInterfaceGenerator的DbInterface,就没有这么容易了。但也不要紧张,大家注意到public static readonly和 DBWR的构造函数没有?套用领导们常说的话:“只要抓住了public static readonly和protected/private 构造函数(){…} 这两个基本点,我们的属性就符合Singleton模式了”。其实现如下:
public static readonly DbInterfaceGenerator DbInterface = GetDbInterface();
private static DbInterfaceGenerator GetDbInterface()
{
string InterfaceType = System.Configuration.ConfigurationManager.AppSettings["DbInterfaceType"];
string InterfaceAssembly = System.Configuration.ConfigurationManager.AppSettings["DbInterfaceAssembly"];
if (!string.IsNullOrEmpty(InterfaceType) && !string.IsNullOrEmpty(InterfaceAssembly))
{
return (DbInterfaceGenerator)
System.AppDomain.CurrentDomain.CreateInstance(InterfaceAssembly, InterfaceType).Unwrap();
}
return new SqlDbInterface();
}
在GetDbInterface函数中,我们用到了“反射”,这是.net中一个非常重要的特性,他对于实现工厂方法,提供了“无与伦比的方便”。相关函数还有“Activator.CreateInstance”等。
如果我们的数据库平台不是Sql Server,那么需要在我们的App.config文件的AppSettings 节中,添加“DbInterfaceType”和“DbInterfaceAssembly”这两个元素。
具体代码因为较长,不便在这里贴出,请参考DongLiORM中的DBWR.cs, DbInterface.cs,和SqlDbInterface.cs。
参考文件:
TerryLee的《.NET设计模式(2):单件模式(Singleton Pattern)》
TerryLee的《.NET设计模式(3):抽象工厂模式(Abstract Factory)》
《设计模式中文版》