在日常开发中,你一定遇到过这样的场景:下单时需要同时扣减库存、生成订单、记录支付流水,只要其中一步出错,所有操作都得回滚。如果用传统的 DbContext 直接操作,很容易出现 “部分成功” 的烂摊子 —— 库存扣了但订单没生成,数据一致性直接崩盘。
今天就手把手教你用 UnitOfWork(工作单元)模式 解决这个问题,从代码实现到避坑指南,再到生活类比,让你彻底搞懂 “统一事务管理” 的核心逻辑。
一、先搞懂:UnitOfWork 到底是什么?(生活类比 + 核心定义)
小节:用 “超市购物” 理解 UnitOfWork
先抛掉技术术语,用生活场景类比:
你去超市购物时,不会拿一件商品就去结账(对应:单表操作),而是把牛奶、面包、水果都放进购物车(对应:多表操作),确认所有商品都选好后,再一次性去收银台结账(对应:事务提交)。如果中途发现牛奶过期了,你会把所有商品都放回货架(对应:事务回滚)—— 这就是 UnitOfWork 的核心思想:把一组相关操作封装成 “一个工作单元”,要么全部成功,要么全部失败。
核心定义
UnitOfWork(工作单元)是一种设计模式,核心作用是:
1.跟踪多个数据操作(新增 / 修改 / 删除)的状态;
2.统一管理事务,确保多表操作的原子性(要么全成,要么全败);
3.减少数据库连接次数,提升性能。
二、实战代码:ASP.NET中实现 UnitOfWork 模式
前置条件
- 框架:ASP.NET Core 6.0+
- ORM:Entity Framework Core
- 数据库:SQL Server(其他数据库同理)
- 场景:模拟 “用户下单”(涉及 3 张表:订单表、商品表、订单明细表)
步骤 1:定义实体类(Model)
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
}
public class Order
{
public int Id { get; set; }
public int UserId { get; set; }
public decimal TotalAmount { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
}
步骤 2:定义仓储接口(IRepository)
仓储模式是 UnitOfWork 的基础,负责单表的 CRUD:
public interface IRepository<TEntity, TKey> where TEntity : class
{
Task<TEntity> GetByIdAsync(TKey id);
void Add(TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
}
public interface IProductRepository : IRepository<Product, int>
{
Task<bool> ReduceStockAsync(int productId, int quantity);
}
public interface IOrderRepository : IRepository<Order, int> { }
public interface IOrderItemRepository : IRepository<OrderItem, int> { }
步骤 3:实现仓储类(Repository)
public class Repository<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : class
{
protected readonly DbContext _dbContext;
protected readonly DbSet<TEntity> _dbSet;
public Repository(DbContext dbContext)
{
_dbContext = dbContext;
_dbSet = dbContext.Set<TEntity>();
}
public async Task<TEntity> GetByIdAsync(TKey id)
{
return await _dbSet.FindAsync(id);
}
public void Add(TEntity entity)
{
_dbSet.Add(entity);
}
public void Update(TEntity entity)
{
_dbSet.Update(entity);
}
public void Delete(TEntity entity)
{
_dbSet.Remove(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
_dbSet.AddRange(entities);
}
}
public class ProductRepository : Repository<Product, int>, IProductRepository
{
public ProductRepository(DbContext dbContext) : base(dbContext) { }
public async Task<bool> ReduceStockAsync(int productId, int quantity)
{
var sql = "UPDATE Products SET Stock = Stock - @Quantity WHERE Id = @ProductId AND Stock >= @Quantity";
var rows = await _dbContext.Database.ExecuteSqlRawAsync(sql,
new SqlParameter("@ProductId", productId),
new SqlParameter("@Quantity", quantity));
return rows > 0;
}
}
public class OrderRepository : Repository<Order, int>, IOrderRepository
{
public OrderRepository(DbContext dbContext) : base(dbContext) { }
}
public class OrderItemRepository : Repository<OrderItem, int>, IOrderItemRepository
{
public OrderItemRepository(DbContext dbContext) : base(dbContext) { }
}
步骤 4:定义 UnitOfWork 接口和实现
public interface IUnitOfWork : IDisposable
{
IRepository<TEntity, TKey> GetRepository<TEntity, TKey>() where TEntity : class;
IProductRepository ProductRepository { get; }
IOrderRepository OrderRepository { get; }
IOrderItemRepository OrderItemRepository { get; }
Task<int> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _dbContext;
private IDbContextTransaction _transaction;
private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>();
public IProductRepository ProductRepository { get; }
public IOrderRepository OrderRepository { get; }
public IOrderItemRepository OrderItemRepository { get; }
public UnitOfWork(DbContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
ProductRepository = new ProductRepository(_dbContext);
OrderRepository = new OrderRepository(_dbContext);
OrderItemRepository = new OrderItemRepository(_dbContext);
}
public IRepository<TEntity, TKey> GetRepository<TEntity, TKey>() where TEntity : class
{
var type = typeof(TEntity);
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(Repository<,>).MakeGenericType(type, typeof(TKey));
_repositories[type] = Activator.CreateInstance(repositoryType, _dbContext);
}
return (IRepository<TEntity, TKey>)_repositories[type];
}
public async Task<int> SaveChangesAsync()
{
return await _dbContext.SaveChangesAsync();
}
public async Task BeginTransactionAsync()
{
if (_transaction != null) return;
_transaction = await _dbContext.Database.BeginTransactionAsync();
}
public async Task CommitTransactionAsync()
{
if (_transaction == null) throw new InvalidOperationException("未开启事务");
try
{
await _dbContext.SaveChangesAsync();
await _transaction.CommitAsync();
}
catch
{
await RollbackTransactionAsync();
throw;
}
finally
{
await _transaction.DisposeAsync();
_transaction = null;
}
}
public async Task RollbackTransactionAsync()
{
if (_transaction == null) return;
await _transaction.RollbackAsync();
await _transaction.DisposeAsync();
_transaction = null;
}
public void Dispose()
{
_dbContext.Dispose();
}
}
步骤 5:注册服务(Program.cs)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderItemRepository, OrderItemRepository>();
步骤 6:业务层使用 UnitOfWork(下单场景)
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<int> CreateOrderAsync(int userId, List<(int ProductId, int Quantity)> productItems)
{
if (productItems == null || productItems.Count == 0)
throw new ArgumentException("商品列表不能为空");
try
{
await _unitOfWork.BeginTransactionAsync();
var order = new Order
{
UserId = userId,
TotalAmount = 0,
CreateTime = DateTime.Now
};
_unitOfWork.OrderRepository.Add(order);
var orderItems = new List<OrderItem>();
foreach (var item in productItems)
{
var product = await _unitOfWork.ProductRepository.GetByIdAsync(item.ProductId);
if (product == null)
throw new Exception($"商品ID{item.ProductId}不存在");
var reduceSuccess = await _unitOfWork.ProductRepository.ReduceStockAsync(item.ProductId, item.Quantity);
if (!reduceSuccess)
throw new Exception($"商品{product.Name}库存不足");
var orderItem = new OrderItem
{
OrderId = order.Id,
ProductId = item.ProductId,
Quantity = item.Quantity,
UnitPrice = product.Price
};
orderItems.Add(orderItem);
order.TotalAmount += product.Price * item.Quantity;
}
_unitOfWork.OrderItemRepository.AddRange(orderItems);
await _unitOfWork.CommitTransactionAsync();
return order.Id;
}
catch (Exception ex)
{
await _unitOfWork.RollbackTransactionAsync();
throw new Exception($"创建订单失败:{ex.Message}", ex);
}
}
}
步骤 7:控制器调用
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly OrderService _orderService;
public OrderController(OrderService orderService)
{
_orderService = orderService;
}
[HttpPost("create")]
public async Task<IActionResult> CreateOrder(int userId, [FromBody] List<(int ProductId, int Quantity)> productItems)
{
try
{
var orderId = await _orderService.CreateOrderAsync(userId, productItems);
return Ok(new { Code = 200, Message = "创建成功", OrderId = orderId });
}
catch (Exception ex)
{
return BadRequest(new { Code = 500, Message = ex.Message });
}
}
}
三、UnitOfWork 执行流程(流程图)
四、90% 的开发者会踩的坑(避坑指南)
小节:踩坑不可怕,关键是知道 “为什么错”
坑 1:UnitOfWork 生命周期错误(最常见)
- 错误做法:将 UnitOfWork 注册为Singleton(单例)。
- 问题:单例的 DbContext 会被多个请求共享,导致事务混乱、数据脏读 / 幻读。
- 正确做法:注册为Scoped(每次请求一个实例),符合ASP.NET Core 的请求生命周期。
坑 2:嵌套事务未处理
- 场景:在一个 UnitOfWork 事务中,调用另一个也用了 UnitOfWork 的方法。
- 问题:EF Core 默认不支持嵌套事务,会抛出 “已存在活跃事务” 异常。
- 解决方案:
1.统一由顶层方法管理事务;
2.使用TransactionScope替代(但性能略差)。
坑 3:忽略并发问题(扣库存超卖)
- 错误做法:先查询库存,再更新(if (product.Stock >= quantity) { product.Stock -= quantity; })。
- 问题:高并发下,多个请求同时查询到 “库存充足”,导致超卖。
- 解决方案:使用乐观锁(如代码中UPDATE … WHERE Stock >= @Quantity)或悲观锁。
坑 4:忘记释放资源
- 错误做法:未实现IDisposable,导致 DbContext 和事务未释放,数据库连接泄漏。
- 解决方案:UnitOfWork 实现IDisposable,释放 DbContext 和事务资源(如代码所示)。
坑 5:滥用 UnitOfWork(单表操作也用)
- 错误场景:仅查询单表数据,也走 UnitOfWork + 事务。
- 问题:增加不必要的性能开销。
- 解决方案:只有多表操作需要事务时才用 UnitOfWork,单表 CRUD 直接用仓储。
五、与读者互动:
💬 互动问答
1.你在项目中是如何管理多表事务的?是用 UnitOfWork 还是其他方式?
2.除了扣库存,你还遇到过哪些需要保证数据一致性的场景?
3.如果你有更好的 UnitOfWork 实现方式,欢迎在评论区分享!
总结
1.UnitOfWork 模式的核心是统一事务管理,解决多表操作的原子性问题,可类比 “超市购物车结账” 理解;
2.实现 UnitOfWork 的关键:仓储模式基础 + 事务管理(Begin/Commit/Rollback) + 正确的生命周期(Scoped);
3.避坑重点:生命周期不能错、并发要加锁、避免嵌套事务、不滥用 UnitOfWork。
转自https://blog.csdn.net/William_cl/article/details/158812073