【问题标题】:"Buy Items" with SQL Transaction in EF Core, if user send many requests, items are bought even if they can't afford it在 EF Core 中使用 SQL 事务“购买商品”,如果用户发送许多请求,即使买不起商品也会购买
【发布时间】: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


【解决方案1】:

_context.Database.BeginTransaction(IsolationLevel.Serializable)

指定以下内容: 语句不能读取已被其他事务修改但尚未提交的数据。 在当前事务完成之前,任何其他事务都不能修改当前事务已读取的数据。 在当前事务完成之前,其他事务不能插入键值在当前事务中任何语句读取的键范围内的新行。

文档: https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?redirectedfrom=MSDN&view=sql-server-ver15

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-08
    • 2012-06-09
    • 2014-11-05
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 2012-07-25
    相关资源
    最近更新 更多