NopCommerce,一直没有事务机制。作为一个商城,我觉得事务也还是很有必要的。以下事务代码以3.9版本作为参考:
首先,IDbContext接口继承IDisposable接口,以便手动释放相关资源,并添加一个新方法CurrentEntries,目的是得到跟踪实体的当前跟踪状态(主要作用是使用事务回滚后改变当前实体对应的状态):
/// <summary>
/// 得到跟踪实体的当前跟踪状态
/// </summary>
/// <returns></returns>
IEnumerable<DbEntityEntry> CurrentEntries();
自然相应的IDbContext接口实现类NopObjectContext也要实现该方 CurrentEntries() { return ChangeTracker.Entries(); }
注意:主项目代码添加这个方法之后,所有需要操作数据库的插件都要实现该方法,这个大家自行斟酌,如果插件也需要事务的话。
添加一个接口命名IUnitOfWork,如下:
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);
}
}
代码很好理解,我就不多做注释了,不清楚的自行网上了解。
下面附上我写的单元测试:
[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);
}
}