我的框架(4)——与数据库交互
与数据库交互其实并不复杂,只是有些想法在这里和大家交流一下。
1、ActiveRecord
ActiveRecord相信大家都不陌生(我不是说Castle的那个,是《企业应用架构模式》里的那个)。ActiveRecord比较明显的一个标志就是持久化是实体自己的一个行为(自己持久化自己)。一般情况下,我们的一个应用程序只对应一个数据库连接,所以用ActiveRecord还是比较惬意的。举个比较常见的例子,比如:
{
public string Name{get;set;}
public List<Department> Departments{get; set;}
public override Save()
{
base.Save();
}
}
如果要保存User和User所属的部门,比较正常的写法是:
{
public static void SaveUser(User user)
{
user.Save();
user.Departments.Save();
}
}
还有一种做法是:
{


public override Save()
{
base.Save();
this.Departments.Save();
}
}
class UserManage
{
public static void SaveUser(User user)
{
user.Save();
}
}
当我们的应用程序需要和2个数据库打交道的时候该如何呢?由于ActiveRecord模式是自己持久化自己,所以持久化时必须指定数据库的环境。代码就变成了:
{
public static void SaveUser(User user)
{
DbContext1 context1 = new DbContext1();
user. DbContext = context1;
user.Save();
user.Departments. DbContext = context1;
user.Departments.Save();
DbContext2 context2 = new DbContext2();
user. DbContext = context2;
user.Save();
user.Departments. DbContext = context2;
user.Departments.Save();
}
}
似乎麻烦了一点。不如反过来,写成这样:
{
public static void SaveUser(User user)
{
using(DbSession1 session1 = new DbSession1())
{
session1.Persist(user);
session1.Persist(user.Departments);
}
using(DbSession2 session2 = new DbSession2())
{
session2.Persist(user);
session2.Persist(user.Departments);
}
}
}
是不是看起来也顺眼一些?
关于ActiveRecord最简单的想法是,由实体的基类负责根据实体的元数据在Save的时候创建所需的DbCommand,然后执行ExecuteNonQuery。这点必然决定了每个实体持久化时必须都有其生存的数据环境DbContext。换一种方式,仍然由实体创建持久化所需的DbCommand,但并不由实体自身去执行,而是由负责持久化实体的数据会话DbSession在执行持久化操作时根据实体的状态向实体请求所需的DbCommand,然后再执行ExecuteNonQuery。这样就避免了频繁设置DbContext的麻烦,也使代码变得更加清晰。当然,如果应用程序仅仅使用一个数据库,这么做的区别就不是那么明显了。
2、事务处理
曾经有人和我建议,事务控制也该自动。因为有时候在处理主从表这类的业务或多不提交时时,由于忘了启动事务,导致出错数据没有回滚。在DbSession实例化的时候就启动一个事务,在最后自动提交,可以省掉好多问题。但我觉得,自动开始事务并不是什么好主意。首先,DbSession不仅仅负责存储,还负责读取。如果在一开始就启动事务,并且设置了错误的隔离级别,将导致记录的锁定,显然这并不是个好主意。其次,我无法替用户做主,决定是否应该启动事务,该什么时候启动事务;也无法保证每个数据提供程序都像SQLClient那样会有可提升事务的支持。最后,我认为启动事务,提交/回滚事务是一个程序员应该牢记,并且形成一种习惯的东西。所以,DbSession仅提供BeginTransaction()启动数据库事务,通过Commit提交、Rollback回滚;要实现分布式事务,可以自己用TransactionScope或其他方式来实现;而且不提供自动化事务的支持。
3、缓存
为了优化应用程序的性能,我们会把比较常用,但是更新较少的数据一次性加载,然后存储在内存中或文件中。当需要访问的时候就直接从缓存加载数据即可。
管理缓存是个麻烦事,如果直接放在内存中,不要多久就会将内存全部耗费光了。上次说了,用Db4O来当缓存其实也是个不错的主意,但前提是Db4O的存取速度够快。还有些情况可能是更加高级的那种一级、二级缓存等。而且可能会有更多的解决方案或开源项目来处理缓存的管理问题。因此,和事务一样,不准备控制缓存。
但是上面说的那个情况该如何处理呢?说实话,这个问题最困扰人。我目前仅有的一个想法是:DbSession进行持久化或加载操作的时候,触发会话动作SessionAction事件。事件的参数包括动作类型ActionType,实体类型,查询条件,查询结果以及是否跳过系统正常流程。
理想化的流程如下:当加载实体数据时,如果实体存在缓存中。使用者根据SessionAction的事件参数,在缓存中进行查询。如果要方便可以考虑先从SQL语句生成Linq表达式,再对缓存进行查询。查询后将结果存在事件参数的结果属性中。如果实体从缓存中得到结果后不需进行其他处理,则设置Handled属性为true,跳过正常的加载流程。如果是持久化也同样,可以先根据事件参数更新缓存中的实体数据,然后再设置Handled属性决定是否需要执行持久化。
流程图如下:

