C# ASP.NET UnitOfWork 模式:搞定多表事务一致性,避开 90% 的坑
小节:用 “超市购物” 理解 UnitOfWork先抛掉技术术语,用生活场景类比:你去超市购物时,不会拿一件商品就去结账(对应:单表操作),而是把牛奶、面包、水果都放进购物车(对应:多表操作),确认所有商品都选好后,再一次性去收银台结账(对应:事务提交)。把一组相关操作封装成 “一个工作单元”,要么全部成功,要么全部失败。核心定义UnitOfWork(工作单元)是一种设计模式,核心作用是:1.跟踪
目录
在日常开发中,你一定遇到过这样的场景:下单时需要同时扣减库存、生成订单、记录支付流水,只要其中一步出错,所有操作都得回滚。如果用传统的 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:
/// <summary>
/// 通用仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
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)
/// <summary>
/// 通用仓储实现
/// </summary>
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; // 影响行数>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 接口和实现
/// <summary>
/// 工作单元接口
/// </summary>
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();
}
/// <summary>
/// 工作单元实现
/// </summary>
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);
// 1. 注册DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
// 2. 注册UnitOfWork(生命周期:Scoped,每次请求创建一个实例)
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
// 3. 注册仓储(可选,因为UnitOfWork内部已创建)
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderItemRepository, OrderItemRepository>();
步骤 6:业务层使用 UnitOfWork(下单场景)
/// <summary>
/// 订单业务服务
/// </summary>
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
/// <summary>
/// 创建订单(多表事务示例)
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="productItems">商品列表(商品ID+数量)</param>
/// <returns>订单ID</returns>
public async Task<int> CreateOrderAsync(int userId, List<(int ProductId, int Quantity)> productItems)
{
if (productItems == null || productItems.Count == 0)
throw new ArgumentException("商品列表不能为空");
try
{
// 1. 开启事务
await _unitOfWork.BeginTransactionAsync();
// 2. 初始化订单
var order = new Order
{
UserId = userId,
TotalAmount = 0,
CreateTime = DateTime.Now
};
_unitOfWork.OrderRepository.Add(order);
// 3. 处理每个商品
var orderItems = new List<OrderItem>();
foreach (var item in productItems)
{
// 3.1 查询商品信息
var product = await _unitOfWork.ProductRepository.GetByIdAsync(item.ProductId);
if (product == null)
throw new Exception($"商品ID{item.ProductId}不存在");
// 3.2 扣减库存(乐观锁保证并发安全)
var reduceSuccess = await _unitOfWork.ProductRepository.ReduceStockAsync(item.ProductId, item.Quantity);
if (!reduceSuccess)
throw new Exception($"商品{product.Name}库存不足");
// 3.3 创建订单明细
var orderItem = new OrderItem
{
OrderId = order.Id, // 先添加订单,EF会自动生成ID
ProductId = item.ProductId,
Quantity = item.Quantity,
UnitPrice = product.Price
};
orderItems.Add(orderItem);
// 3.4 累加订单总金额
order.TotalAmount += product.Price * item.Quantity;
}
// 4. 批量添加订单明细
_unitOfWork.OrderItemRepository.AddRange(orderItems);
// 5. 提交事务(所有操作成功才提交)
await _unitOfWork.CommitTransactionAsync();
return order.Id;
}
catch (Exception ex)
{
// 6. 发生异常,回滚所有操作
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。
如果这篇文章帮你搞懂了 UnitOfWork 模式,欢迎点赞 + 收藏 + 关注!后续会更新更多ASP.NET设计模式实战内容~
更多推荐


所有评论(0)