簡介
最近忙着新項目的架構,已經有一段時間沒有更新博客了,一直考慮着要寫些什么,直到有一天跟朋友談起他們公司開發數據層遇到的一些問題時,我想應該分享一些項目中使用的數據訪問模式。
雖然最近一直都在使用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框架的工作單元,那么文章就到這里了,如果有問題和疑問歡迎留言,代碼僅供參考請勿應用到實際項目中,謝謝。
