当然EF Core提供的SaveChanges方法本身是支持事务的,如果多个操作,其中任何一个操作失败了,都会触发事务回滚,保证所有操作都不会生效。
但在我们实际业务中,业务往往都是比较复杂的,默认事务无法满足我们的需求,这时候我们就需要手动控制事务,在EF提供的事务API有:开始、提交和回滚事务。
具体实现方式如下:
public interface IUnitOfWork
{
/// <summary>
/// 开始事务
/// </summary>
void BeginTransaction();
/// <summary>
/// 提交事务
/// </summary>
void CommitTransaction();
}
public class UnitOfWork : IUnitOfWork, IDisposable
{
private readonly ApplicationDbContext _dbContext;
/// <summary>
/// 事务
/// </summary>
private IDbContextTransaction? _transaction;
/// <summary>
/// 是否提交
/// </summary>
private bool _isCommit = false;
public UnitOfWork(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
/// <summary>
/// 开启事务
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void BeginTransaction()
{
_transaction = _dbContext.Database.BeginTransaction();
}
/// <summary>
/// 提交事务
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void CommitTransaction()
{
if (_transaction != null)
{
_transaction.Commit();
}
_isCommit = true;
}
/// <summary>
/// 回滚事务
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void Dispose()
{
// 未提交事务,回滚
if (_transaction != null && !_isCommit)
{
_transaction.Rollback();
}
// 提交过的事务,释放资源
if(_transaction != null)
{
_transaction.Dispose();
}
}
}
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
try
{
// 开启事务
_unitOfWork.BeginTransaction();
/**
业务逻辑,设计数据库的一些操作
*/
//提交事务
_unitOfWork.CommitTransaction();
}
catch
{
responseResult.SetError("服务器异常");
return BadRequest(responseResult);
}
按照前面的步骤我们在需要使用事务的地方都需要手动添加事务的开启以及提交.如果项目很庞大的话工作量岂不是很大,而且也会造成代码大量重复.
所以我们可以通过自定义WebApi的行为过滤器,来实现在Action执行前与执行后,控制事务开启与提交。
具体实现如下:
public class UnitOfWorkFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// 在Action执行前判断是否开启事务
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
var _unitOfWork = context.HttpContext.RequestServices.GetService<IUnitOfWork>();
// 开启事务
_unitOfWork.BeginTransaction();
}
/// <summary>
/// 在Action执行后判断是否需要提交事务
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuted(ActionExecutedContext context)
{
var _unitOfWork = context.HttpContext.RequestServices.GetService<IUnitOfWork>();
// 提交事务
_unitOfWork.CommitTransaction();
}
}
实现了过滤器的功能,我们可以通过给api添加表示的方式为api添加过滤器
为了方便我们选择给所有控制器添加都添加该标识
builder.Services.AddControllers(
configure =>
{
configure.Filters.Add<UnitOfWorkFilterAttribute>();
})
这样所有的api都添加上了UnitOfWorkFilterAttribute,但是这样做有着很明显的缺陷就是会增加事务的开销,即使是简单的一个Select查询语句都会被加上事务,所以我们可以创建一个事务的开关.
public class UnitOfWorkAttribute : Attribute
{
/// <summary>
/// 是否开启事务
/// </summary>
public bool IsTransactional { get; set; } = true;
}
此时我们修改之前的过滤器配置,添加开关判断语句
我们可以在不需要事务的地方添加上图注释.我们从操作方法的元数据中获取UnitOfWorkAttribute
类型的属性,判断IsTransactional的值是否为false,为false时就不开启事务
自此,事务的基本使用就告捷啦