【发布时间】:2023-03-25 16:41:01
【问题描述】:
我正在尝试制作一款用户可以使用游戏币购买物品的游戏。硬币和物品存储在 SQL 数据库中,用户应该(显然)只能购买他们买得起的物品。
此代码的问题在于,如果用户同时发送大量请求,则由于下一个请求是在前一个请求完成更新数据库之前发送的,因此他获得的项目超出了他的承受能力。
如何防止这种“垃圾邮件发布错误”?
public async Task<ActionResult<UserProfile>> BuyItem(ShopItem item)
{
try
{
//Begin Transaction
using var transaction = _context.Database.BeginTransaction();
var user = await _context.UserProfiles.FirstOrDefaultAsync(u => u.Name == User.Identity.Name);
await _context.Database.OpenConnectionAsync();
//If user can afford the item
if (user.Coins >= item.Price)
{
//Decrease money from user
user.Coins -= item.Price;
_context.Entry(user).State = EntityState.Modified;
await _context.SaveChangesAsync();
//Create new item in database that belongs to the user who bought it
var userItem = new UserItem()
{
ShopItemId = item.Id,
UserProfileId = user.Id
};
_context.UserItems.Add(userItem);
await _context.SaveChangesAsync();
}
else
{
//Throw exception, this should rollback the transaction?
throw new Exception();
}
//End Transaction
_context.Database.CloseConnection();
transaction.Commit();
return new JsonResult(user);
}
catch (Exception ex)
{
throw;
}
}
【问题讨论】:
-
删除此行
_context.Database.CloseConnection();。上下文应该在Commit之前打开 -
您可能需要像
_context.Database.BeginTransaction(IsolationLevel.Serializable)这样的事务的SERIALIZABLE隔离级别,因为您正在将数据读入客户端进行修改并将其保存回来。最好编写一个存储过程,在事务中完成所有这些,然后从这里调用它。 -
@SvyatoslavDanyliv 我得到了同样的结果
-
@Charlieface 非常感谢! (IsolationLevel.Serializable) 是答案
-
我强烈推荐一个存储过程,因为
SERIALIZABLE在大量使用下会导致阻塞和死锁问题,而这不能用 EF 解决,你需要一个自定义的过程跨度>
标签: c# sql .net database entity-framework-core