NopCommerce,一直沒有事務機制。做爲一個商城,我以爲事務也仍是頗有必要的。如下事務代碼以3.9版本做爲參考:數據庫
首先,IDbContext接口繼承IDisposable接口,以便手動釋放相關資源,並添加一個新方法CurrentEntries,目的是獲得跟蹤實體的當前跟蹤狀態(主要做用是使用事務回滾後改變當前實體對應的狀態):單元測試
/// <summary> /// 獲得跟蹤實體的當前跟蹤狀態 /// </summary> /// <returns></returns> IEnumerable<DbEntityEntry> CurrentEntries();
天然相應的IDbContext接口實現類NopObjectContext也要實現該方 CurrentEntries() { return ChangeTracker.Entries(); } 測試
注意:主項目代碼添加這個方法以後,全部須要操做數據庫的插件都要實現該方法,這個你們自行斟酌,若是插件也須要事務的話。this
添加一個接口命名IUnitOfWork,以下:spa
public interface IUnitOfWork : IDisposable { /// <summary> /// 開啓事務 /// </summary> /// <param name="isolationLevel"></param> void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified); /// <summary> /// 提交 /// </summary> void Commit(); /// <summary> /// 回滾 /// </summary> void Rollback(); /// <summary> /// 釋放資源 /// </summary> /// <param name="disposing">是否釋放</param> void Dispose(bool disposing); }
並實現該接口,添加實現類命名UnitOfWork,以下:插件
public class UnitOfWork : IUnitOfWork { private IDbContext _context; private ObjectContext _objectContext; private IDbTransaction _transaction; private bool _disposed; public UnitOfWork(IDbContext context) { _context = context; } public void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) { _objectContext = ((IObjectContextAdapter)_context).ObjectContext; if (_objectContext.Connection.State != ConnectionState.Open) _objectContext.Connection.Open(); _transaction = _objectContext.Connection.BeginTransaction(isolationLevel); } public void Commit() { _transaction.Commit(); } public void Rollback() { _transaction.Rollback(); foreach (var entry in _context.CurrentEntries()) { switch (entry.State) { case EntityState.Modified: entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; case EntityState.Deleted: entry.State = EntityState.Unchanged; break; } } } public void Dispose(bool disposing) { if (_disposed) return; if (disposing) { try { if (_objectContext != null && _objectContext.Connection.State == ConnectionState.Open) _objectContext.Connection.Close(); } catch (ObjectDisposedException) { } if (_context != null) { _context.Dispose(); _context = null; } } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
代碼很好理解,我就很少作註釋了,不清楚的自行網上了解。code
下面附上我寫的單元測試:blog
[TestClass]
public class UnitTest1 { protected static NopObjectContext Context = new NopObjectContext(ConfigurationManager.ConnectionStrings["ConnectionStr"].ToString()); private readonly IUnitOfWork _unitOfWork = new UnitOfWork(Context); protected readonly IRepository<ForumGroup> ForumGroupRepository = new EfRepository<ForumGroup>(Context); protected readonly IRepository<Setting> SettingRepository = new EfRepository<Setting>(Context); [TestMethod] public void Can_Commit_Test() { try { _unitOfWork.BeginTransaction(); // 開啓事務 var forumGroup = new ForumGroup { Name = "ForumGroup1", // 自行創建Name的惟一約束測試,測試兩次第二次會自行回滾 DisplayOrder = 1, CreatedOnUtc = DateTime.Now, UpdatedOnUtc = DateTime.Now.AddDays(1) }; ForumGroupRepository.Insert(forumGroup); // 第一次插入數據 var setting = new Setting { Name = "test_transaction_name", Value = "test_transaction_value", StoreId = 1 }; SettingRepository.Insert(setting); _unitOfWork.Commit(); // 提交 } catch (Exception) { _unitOfWork.Rollback(); // 回滾 } Assert.AreEqual(ForumGroupRepository.TableNoTracking.Count(), 1);
Assert.AreEqual(SettingRepository.TableNoTracking.Count(x => x.Name == "test_transaction_name"), 1); } }