2

我正在一個Web API項目(ninject,原始sql查詢,存儲庫模式,UoW模式)我已經檢查幾乎無處不在的一篇文章,將描述UoW的實現簡單的數據庫事務W/O使用實體框架(普通SQL請求和SqlConnection等等),但找不到任何東西。C#UnitOWork實現沒有EF與數據庫事務或TransactionScope

我遇到的問題是以下幾點。我有一個Web API,它有一些控制器與存儲庫一起工作,而這些控制器又通過DBManager類通過UoW注入到DB中。

讓我們想象一下我有2個方法,在一個倉庫,數據庫中的每個人的更新數據:

方法1 - 更新票(從客戶添加後)。方法2 - 更新故障單的狀態(僅在發佈成功後)。

這些方法可以被一個接一個地調用,或者單獨調用,也就是說,例如,在售票關閉之後,可以從某種其他方法調用Method2。

方法1在更新數據庫之前通過DBManager創建事務。然後它更新DB並調用Method2來完成他的工作。 Method2,因爲它也可以作爲一個獨立的方法調用,所以在更新數據庫之前也會啓動事務。當查詢被執行時,它提交事務並返回到Method1。此階段的Method1也提交事務,因爲沒有任何異常,它想要保留對數據庫所做的更改。但是,它不能因爲方法2已經做出了改變。

這樣的動作圖類似於一個下面的東西:

Method1() 
    DBManager.BeginTransaction() - begins new transaction 

    update DB - adds post to the ticket 

    Method2() - calls method 2 to update ticket status 
    DBManager.BeginTransaction() - returns transaction started by Method1() 

    update DB - updates ticket status 

    DBManager.CommitTransaction() - commits transaction 

    return 

    DBManager.CommitTransaction() - commits transaction to save ALL changes but can't since transaction was already committed. 

如果我需要調用其他方法票狀態更新後,再方法將用全新的一組數據,因爲變化是工作由Method2()提交到DB中。

我開始思考如何解決這個問題,但找不到任何東西。我已閱讀有關的TransactionScope,我想我可以做這樣的事情:

public class UnitOfWork : IUnitOfWork, IDisposable 
{ 
    /// <summary> 
    /// DB context. 
    /// </summary> 
    private IDBManager _dbManager; 

    /// <summary> 
    /// Repository provider class which can create repositories on demand. 
    /// </summary> 
    private IRepositoryProvider _repositoryProvider; 

    private TransactionScope _transaction; 

    public UnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) 
    { 
     _dbManager = dbManager; 
     _repositoryProvider = repoProvider; 
    } 

    public T GetRepository<T>() 
    { 
     if (_transaction == null) 
      _transaction = new TransactionScope(); 

     return _repositoryProvider.Create<T>(_dbManager); 
    } 

    public void Save() 
    { 
     _transaction.Complete(); 
    } 

    public void Dispose() 
    { 
     _transaction.Dispose(); 
    } 
} 

在這種情況下,當我創建我的第一個存儲庫中的TransactionScope會開始,然後我可以調用保存在我的控制器,就像這樣:

public TicketPost AddTicketPost(int tid, TicketPost update) 
    { 
     TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update); 
     Uow.Save(); 

     return post; 
    } 

但是,這意味着TransactionScope會爲任何操作創建 - 選擇/更新/刪除,它會持續從創建第一個存儲庫的時刻開始(即使我可能不需要它),直到交易處置或完成。

另一種解決方案是使用DBManager的事務,並在控制器中調用BeginTransaction並在需要時調用Commit或Rollback。像這樣:

Uow.BeginTransaction(); 
try 
{ 
    TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update); 
} 
catch (Exception e) 
{ 
    Uow.RollbacTransaction(); 
} 
Uow.CommitTransaction(); 

但我不太喜歡這種方法。在第一種情況下,我需要捕獲異常,這些異常會冒泡到我的ExceptionsHandler中,它會向客戶端創建響應消息。另外,我認爲一個控制器是一箇中間人,他會得到請求並說:「嘿,存儲庫,這裏是數據,我已經檢查過,做你的事情,然後給我回電話。」當它從存儲庫中獲得「調用」時,它可能會做一些與數據無關的事情,比如發送電子郵件。我喜歡當控制器不需要在同一個庫調用方法逐一認爲它需要做的,像完成任務的事情:

  1. 更新售票
  2. 將狀態設置
  3. 巨蛋別的東西與票
  4. 發送電子郵件

,而不是這個,控制器問庫照顧票更新和等待的同時,也可以發送電子郵件:

  1. 告訴控制器做他需要做的事情來更新票證。
  2. 等待併發送電子郵件。

我在踩踏控制器和存儲庫時可能會出錯。如果我錯了,請糾正我。

我希望有人能指點我一個資源,或者可能有人有類似的設置,並已經找到了解決方案(交易問題)。

任何幫助,將不勝感激。非常感謝你提前。

回答

0

如果我理解你是正確的,你想要一種方法來避免創建不需要的事務?

爲此,我建議您創建一個BaseUnitOfWork類型和兩個其他類型:ReadOnlyUnitOfWork和ReadWriteUnitOfWork。

然後,當您需要同時選擇東西和ReadWrite時,您將只使用ReadOnly。

在C#中的骨架將是類似的東西。

public class BaseUnitOfWork // YOUR INTERFACES HERE 
{ 
    /// <summary> 
    /// DB context. 
    /// </summary> 
    private IDBManager _dbManager; 

    /// <summary> 
    /// Repository provider class which can create repositories on demand. 
    /// </summary> 
    private IRepositoryProvider _repositoryProvider; 

    public BaseUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) 
    { 
     _dbManager = dbManager; 
     _repositoryProvider = repoProvider; 
    } 

    public T GetRepository<T>() 
    { 
     return _repositoryProvider.Create<T>(_dbManager); 
    } 
} 

public class ReadOnlyUnitOfWork : BaseUnitOfWork 
{ 
    public ReadOnlyUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider) 
    { 
     _dbManager = dbManager; 
     _repositoryProvider = repoProvider; 
    } 
} 

public class ReadWriteUnitOfWork : BaseUnitOfWork// YOUR INTERFACES HERE 
{ 
    private TransactionScope _transaction; 

    public ReadWriteUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider) 
    { 
     if (_transaction == null) 
      _transaction = new TransactionScope(); 
    } 

    public void Save() 
    { 
     _transaction.Complete(); 
    } 

    public void Dispose() 
    { 
     _transaction.Dispose(); 
    } 
} 

我在幾個項目中已經成功地使用了這個策略。

這個策略的很好的部分在於你遵守SOLID(http://en.wikipedia.org/wiki/SOLID_(object-oriented_design))設計中的S:單一職責。

一個類有責任用事務處理數據庫操作,而另一個類只負責處理無事務操作。另外,你必須明白,工作單元塊應該是快速的(在執行中)和(如果可能的話)代碼小(作爲最佳實踐)。

因此,你可以使用它是這樣的:

using(IReadWriteUnitOfWork uow = InjectionFramework.ResolveDependencyOfType<IReadWriteUnitOfWork>()) 
{ 
    //do your database stuff here, try to keep it simple. 
    //after doing everything, you **commit** the transaction (in your case, you save) 
    uow.Save(); 
} 

使用命令的好的部分是,你已經完成了這段代碼會自動調用Dispose方法之後。

+0

哇,太好了。非常感謝你的想法。這個想法是以某種方式強制所有更新/刪除操作在我認爲您的方法允許的同一個事務保護下工作。我也想問一下,在創建TransactionScope的過程中創建TransactionScope是否可行,在UoW中,然後讓它在那裏,打開,在我使用數據庫並執行其他活動的整個過程中都可以。或者有什麼我可能在這裏失蹤?當有一個TransactionScope打開時,可能某些操作可能會以不同的方式工作,而不僅僅是數據庫請求? –

+0

我試圖想出一種使用你建議的方法。正如我所提到的,我使用Ninject。它將UoW注入到我的控制器中,然後使用它創建存儲庫。現在,而不是UoW我需要注入某種UoW工廠,通過它我將創建所需的UoW類型對象。我想 –

+0

那麼,關於你的第一個評論,你必須明白,工作單元塊應儘可能快(小)。否則,你會開始有數據庫性能問題。 所以,當使用uow時,你應該儘量保持它很小。我將編輯答案,以作爲例證。 –

相關問題