Unit Of Work--工作单元(一)
简介
最近忙着新项目的架构,已经有一段时间没有更新博客了,一直考虑着要写些什么,直到有一天跟朋友谈起他们公司开发数据层遇到的一些问题时,我想应该分享一些项目中使用的数据访问模式。
虽然最近一直都在使用Go语言开发数据服务器,但是本篇文章用到的语言仍然是C#,文章内提供的代码仅仅是分享如何使用工作单元,至于如何将这个模式引入到项目中去,就需要各位自己去实现了,毕竟每个项目都是不一样的,需要根据项目具体的环境来进行组合。
本篇文章包括以下内容:
- 什么是工作单元
- 基于ADO.NET的实现
- 总结
什么是工作单元
该模式用来维护一个由已经被业务事务修改(CRUD除了R)的业务对象组成的列表并负责协调这些修改的持久化工作以及所有标记的并发问题。
在web应用中,由于每个用户的请求都是属于不同线程的,需要保持每次请求的所有数据操作都成功的情况下提交数据,只要有一个失败的操作,则会对用户的此次请求的所有操作进行回滚,以确保用户操作的数据始终处于有效的状态。
需要更详细的了解,可以查看此篇文章。
基于ADO.NET的实现
在不使用任何数据层框架,仅仅使用ADO.NET的情况下,一般流程的方式如下:
- 实例化IDbConnection
- 然后实例化IDbCommand
- 设置IDbCommand的Text和Parameters
- 执行IDbCommand.ExecuteNoQuery
- 释放IDbCommand、IDbConnection
一般情况下,我们会给每个数据库表创建对应的数据库交互类并提供CRUD方法,除了R以外,其他的方法都会实现以上的流程。
然而如果用户一次请求会进行多次CUD操作的情况下,只能在用户开始进行数据操作之前使用TransactionScope将多次操作包裹在内,这样实现相对麻烦的。
其实我们可以将用户进行的CUD的SQL语句与参数现在保存起来,到最后再一并进行提交,有点类似存储过程的样子,这其实就是工作单元模式了,首先创建一个用来存储SQL语句和参数的类,代码如下:
class SQLEntity { private string m_SQL = null; public string SQL { get { return m_SQL; } } private IDataParameter[] m_Parameters = null; public IDataParameter[] Parameters { get { return m_Parameters; } } public Entity(string sql, params IDataParameter[] parameters) { this.m_SQL = sql; this.m_Parameters = parameters; } }
如果将所有CUD的操作都放在一个List<SQLEntity>内,在需要提交的时候只需要遍历List<SQLEntity>内的元素,并通过重复开始的流程就行(这里需要稍微重构一下),代码如下:
class SQLUnitOfWork { private IDbConnection m_connection = null; private List m_operations = new List(); public SQLUnitOfWork(IDbConnection connection) { this.m_connection = connection; } public void RegisterAdd(string sql, params IDataParameter[] parameters) { this.m_operations.Add(new SQLEntity(sql, parameters)); } public void RegisterSave(string sql, params IDataParameter[] parameters) { this.m_operations.Add(new SQLEntity(sql, parameters)); } public void RegisterRemove(string sql, params IDataParameter[] parameters) { this.m_operations.Add(new SQLEntity(sql, parameters)); } public void Commit() { using (IDbTransaction trans = this.m_connection.BeginTransaction()) { try { using (IDbCommand cmd = this.m_connection.CreateCommand()) { cmd.Transaction = trans; cmd.CommandType = CommandType.Text; this.ExecuteQueryBy(cmd); } trans.Commit(); } catch (Exception ex) { trans.Rollback(); } } } private void ExecuteQueryBy(IDbCommand cmd) { foreach (var entity in this.m_operations) { cmd.CommandText = entity.SQL; cmd.Parameters.Clear(); foreach (var parameter in entity.Parameters) { cmd.Parameters.Add(parameter); } cmd.ExecuteNonQuery(); } this.m_operations.Clear(); } }
有了以上的SQLUnitOfWork,我们的数据库类在调用CUD方法的时候,就只要调用相应RegisterXXX方法了,数据层实现代码如下:
class SchoolRepository { private SQLUnitOfWork m_uow = null; public SchoolRepository(SQLUnitOfWork uow) { this.m_uow = uow; } public void Add(School school) { this.m_uow.RegisterAdd("insert school values(@id, @name)", new IDbDataParameter[]{ new SqlParameter("@id", school.Id), new SqlParameter("@name", school.Name) }); } public void Save(School school) { this.m_uow.RegisterSave("update school set name = @name where id = @id", new IDbDataParameter[]{ new SqlParameter("@id", school.Id), new SqlParameter("@name", school.Name) }); } public void Remove(School school) { this.m_uow.RegisterRemove("delete from school where id = @id", new IDbDataParameter[]{ new SqlParameter("id", school.Id) }); } } class StudentRepository { //代码类似,因此省略 }
有了以上的准备,再使用以下的代码来看看工作单元的效果,代码如下:
SQLUnitOfWork uow = new SQLUnitOfWork(connection); SchoolRepository schoolRepositry = new SchoolRepository(uow); StudentRepository studentRepository = new StudentRepository(uow); School school = new School { Id = Guid.NewGuid().ToString(), Name = "一中", }; schoolRepositry.Add(school); for (int i = 0; i < 7; i++) { Student student = new Student { Id = Guid.NewGuid().ToString(), Name = string.Format("学生{0}号", i), Age = 7 + i, SchoolId = school.Id }; studentRepository.Add(student); } school.Name = "二中"; schoolRepositry.Save(school); uow.Commit();
接着会看到数据库中会看到图中的数据,如图:
总结
这样就完成了基于ADO.NET的简单工作单元实现了,可能有些人会有疑问,跟其他人实现的工作单元有很多不同的地方,这是因为该版本只是一个大致思路的实现,并没有跟其他框架、库的结合,为了能使其他人理解工作单元的原理,因此实现相对比较简单。
由于模式是一种解决方案,一种思路,每个项目的环境、功能、结构都不一样,因此实现的方式会有不同。对于模式不能死记硬背,要理解其中的原理,可以通过自我实践或者参考他人的代码来实现,如果每个项目都是照抄模式的话,那么就失去了它该有的作用了。
在下一篇文章中,我会重构以上的代码,实现以兼容ADO.NET和ORM框架的工作单元,那么文章就到这里了,如果有问题和疑问欢迎留言,代码仅供参考请勿应用到实际项目中,谢谢。