一个糟糕的设计
今天要总结的是其中一个糟糕的设计,这个设计存在于一个数据访问类中,至今没有被解决掉—因为修改设计需要修改大量的代码,而且不修改设计的话,我们的软件确实可以工作—至少目前是这样的。
这个类的类图大概是这样的
为了简单,我们省略了其中存在的大量的public方法(包括执行存储过程、获取DataSet、DataReader的大量方法和重载方法)和一些异常处理代码。图中的Instance属性实际上实现了Singleton模式。
其中,我们已ExecuteNoneQuery的执行过程为例
从图中我们可以看出,方法首先调用this.Open()来打开数据库连接,然后执行任务,最后调用this.Close()来关闭连接。
这看起来没有什么问题,调用起来也很方便,直接CommonDataAccess.Instance.ExecuteNoneQuery就可以了,而且,由于采用了单体模式,也带来了一些其他一些好处,比如内存使用上的。事实证明,很久以来,我们都对这种模式相当满意,但是随着项目的推进,问题慢慢浮现了,由于我们的系统经常要同远端的WebServive服务连接,为了解决用户响应上的问题,我们开始使用多线程—我们把长时间之行的任务放到新的线程里面执行,这些任务里面当然也包括数据访问。那么问题就是,我们的数据访问类是为单线程应用设计的,在引入其他线程以后,它开始出现不同线程访问同一数据连接—在多数情况下—这将导致并发错误!也就是说,当我的连接在执行一项查询的时候,如果另外一个线程使用它执行另外一个查询或者是更新,将出现错误。
我们试着修改这个类,让它可以工作在多线程环境中。但是我们发现要修改的代码非常惊人,几乎每一个使用this.Open()和使用数据连接的地方都需要更改。我们比较懒,所以我们决定只修改了一处—那就是Instance属性的get访问器。
原先的代码是这样的
public CommonDataAccess Instance
{
get
{
if( instance == null )
instance = new CommonDataAccess();
return instance ;
}
}
修改以后成了这样的
public CommonDataAccess Instance
{
get
{
/*
if( instance == null )
instance = new CommonDataAccess();
*/
return new CommonDataAccess();
}
}
不许笑,这确实解决了多线程的问题,但再也不是Singleton了,倒是成了不断生产新产品的Factory了,而且即使在一个线程内部,我们使用Instance属性的时候,都创建了新的CommonDataAccess实例,而在我们写那些代码的时候,完全没有预料到。
这真是一个糟糕的设计,那么应该有改良的办法吧。
类图:
ExecuteNoneQuery的序列图
如果你还没有明白,那还是看代码吧:
{
using ( IDbConnection conn = this.Open() )
{
IDbCommand cmd = new SqlCommand(cmdText);
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
}
IDbConnection Open()
{
IDbConnection conn = new SqlConnection(connStr);
conn.Open();
return conn ;
}
也就是说,每次Open的时候,产生新的连接,我们不必担心new SqlConnection的效率,事实上,使用Ado.net,连接池是DotNetFrameWork自动维护的,从连接池中获取连接的速度也非常快。如果你愿意的话,你甚至可以在Open()方法中使用自己的连接池逻辑。