EntityFramework(EF) 控制并发和事务防止超卖
2025-01-15
4
在使用 Entity Framework (EF) 时,实现加减库存并避免超卖的关键是正确地控制并发和事务。以下是实现方法:
解决超卖的关键点
事务管理:使用事务确保加减库存和库存校验是一个原子操作。 悲观锁:通过数据库层面的锁定(如 FOR UPDATE)确保其他线程无法同时修改相同的记录。 乐观并发控制:使用版本号或时间戳检测是否有其他线程在同一时间修改数据。 避免库存为负值:每次更新库存前检查库存是否充足。使用悲观锁
通过 EF Core 执行原生 SQL 或存储过程实现悲观锁。
示例代码
using (var context = new YourDbContext())
{
using (var transaction = await context.Database.BeginTransactionAsync())
{
try
{
// 使用原生 SQL 加锁 (悲观锁)
var product = await context.Products
.FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}", productId)
.FirstOrDefaultAsync();
if (product == null)
{
throw new Exception("Product not found");
}
if (product.Stock < quantity)
{
throw new Exception("Insufficient stock");
}
// 减库存
product.Stock -= quantity;
// 保存更改
await context.SaveChangesAsync();
// 提交事务
await transaction.CommitAsync();
}
catch
{
// 回滚事务
await transaction.RollbackAsync();
throw;
}
}
}
WITH (UPDLOCK, ROWLOCK):确保在查询过程中对目标行加锁,避免其他事务修改。
使用事务 (BeginTransactionAsync) 确保库存校验和更新是一个原子操作。
使用乐观并发控制
通过在表中添加一个 RowVersion 字段,EF Core 自动检测并发冲突。
步骤
在模型中添加一个 RowVersion 字段:
public class Product
{
public int Id { get; set; }
public int Stock { get; set; }
1736903070
public byte[] RowVersion { get; set; }
}
启用并发检查:
try
{
var product = await context.Products.FindAsync(productId);
if (product.Stock < quantity)
{
throw new Exception("Insufficient stock");
}
product.Stock -= quantity;
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
// 处理并发冲突
throw new Exception("Concurrency conflict occurred");
}
Timestamp 属性自动跟踪数据库中的版本号变化。
如果两个线程同时更新同一行,EF 会抛出 DbUpdateConcurrencyException。
数据库层存储过程
另一种方式是在数据库中定义存储过程,确保操作在数据库层面是原子的。
示例存储过程
CREATE PROCEDURE UpdateStock
@ProductId INT,
@Quantity INT
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRANSACTION;
-- 锁定行并检查库存
SELECT Stock
FROM Products WITH (UPDLOCK, ROWLOCK)
WHERE Id = @ProductId;
IF @@ROWCOUNT = 0
BEGIN
ROLLBACK;
THROW 50000, 'Product not found', 1;
END
-- 检查库存是否足够
IF (SELECT Stock FROM Products WHERE Id = @ProductId) < @Quantity
BEGIN
ROLLBACK;
THROW 50001, 'Insufficient stock', 1;
END
-- 更新库存
UPDATE Products
SET Stock = Stock - @Quantity
WHERE Id = @ProductId;
COMMIT;
END
在 EF 中调用存储过程:
await context.Database.ExecuteSqlRawAsync("EXEC UpdateStock @ProductId = {0}, @Quantity = {1}", productId, quantity);
总结
悲观锁适用于高并发、需要严格保证库存一致性的场景。乐观并发控制适合读取多、写入少的业务。如果业务逻辑复杂,建议将加减库存逻辑放在存储过程中,避免应用层并发问题。
更新于:6小时前赞一波!
相关文章
- EntityFrame(EF) SQLite常见问题和解决方案
- 【说站】python如何实现事务机制
- EntityFramework SQLite 为时间字段设置默认值为当前时间
- ASP.NET 使用Entity Framework (EF) 创建迁移修改SQLite数据库表结构
- SQL Server EF使用Sequence全局自增ID
- EF Core在非MVC项目中需要手动释放吗?
- Entity Framework Core 连接PostgreSQL
- MySQL 事务介绍及使用方法
- MySQL 事务特性和事务隔离级别
- .NET C# EntityFrameworkCore(EF)连接PostgreSQL数据库
- .NET EF连接MySQL数据库
- MySQL 中的事务控制机制
- EF报错Win32Exception: 证书链是由不受信任的颁发机构颁发的。
- EF Core 8 (EF8) Contains报错:Microsoft.Data.SqlClient.SqlException (0x80131904): 关键字 'WITH' 附近有语法错误。
- Entity Framework (EF) 数据库迁移命令教程
- .NET C#如何处理和避免并发冲突?
- Entity Framework Core反向工程DB First基于数据库生成DbContext和实体类
- EF报错System.Exception: You need to call SQLitePCL.raw.SetProvider().
- .NET Core MVC配置注入使用Entity Framework Core(EF) 无需手动释放
- .NET Entity Framework(EF)高性能分页
文章评论
评论问答