NHibernate 和新创建的记录锁定

NHibernate and newly created records locking

我有两个 table。 钱包营业额和钱包余额。

钱包周转是针对特定客户以特定货币对钱包进行的贷记和借记操作。

每个客户和货币的钱包余额最多包含一条记录,其中包含客户和货币的余额。余额是钱包周转的贷方和借方总和的结果。 我想要应用程序的行为方式,当我将营业额添加到营业额 table 时,我需要通过添加当前营业额来重新计算余额 table。如果当前营业额的货币和客户的记录还没有,我想创建一个新的。

问题是,我应该如何防止应用程序为同一客户和余额中的货币创建多个余额记录 table。

想象一下,我正在为 ID=1 且货币=USD 的客户添加营业额。 该记录尚不存在,因此应创建它。 但是,当应用程序的两个并发用户 A 和 B 同时为客户 1 和货币美元添加营业额时会发生什么?

我应该怎么做才能实现这样的行为,当我第一次创建用户 A 的钱包余额记录并且它现在还没有保存到数据库中时,应用程序的第二个用户 B 必须等到第一个用户提交它的记录,只是读取当前余额(其中包含来自用户 A 的更新余额)并继续使用正确的余额值?

这样的事情还能实现吗?我应该如何让 nhibernate 知道,客户 ID = 1 和货币 = USD 的余额记录已经有一些用户创建,因此,当其他用户在 table 中查找此组合时,此记录已经创建,用户应该等到用户 A 提交它?

GetOrCreateBalance 代码:

    public ClientWalletBalance GetOrCreateWalletBalance(int clientId, int currency)
    {
        var clientObj = ClientService.GetClient(clientId);

        var res = clientObj.WalletBalances.FirstOrDefault(x => x.Currency.Id == currency);

        if (res == null)
        {
            res = new ClientWalletBalance()
            {
                Client = clientObj,
                Currency = ClientService.GetCurrency(currency),
            };
        };
        clientObj.WalletBalances.Add(res);

        return res;
    }

我已经知道,这可以通过

实现

session.Lock(对象, LockMode.Upgrade);

我只是不太清楚,如果我锁定新创建的对象,它还没有保存到数据库 - 所以它没有关联的 id,nhibernate 怎么知道客户端 1 和货币 USD 的组合已经创建了一些其他用户 "memory?"

请问这种情况的标准配方是什么?

非常感谢,

彼得

编辑 1: 到目前为止,我最终得到了这样的结果: 调用代码:

using (ISession session = NHibernateConfiguration.ReturnOpenedSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    var cwb = Service.OrderAndPaymentService.GetOrCreateWalletBalance(clientId, currency, delay);

                    response.Error = cwb.Balance.ToString();

                    transaction.Commit();
                }
            }

`

调用代码:`

public ClientWalletBalance GetOrCreateWalletBalance(int clientId, int currency, int delay = 0)
        {
            var clientObj = ClientService.GetClient(clientId);

            var res = clientObj.WalletBalances.FirstOrDefault(x => x.Currency.Id == currency);

            if (res != null)
            {
                NHibernateConfiguration.GetCurrentSession().Lock(res, LockMode.Upgrade);
            }
            else
            {
                res = new ClientWalletBalance()
                {
                    Client = clientObj,
                    Currency = ClientService.GetCurrency(currency),
                    Balance = delay,
                };

                clientObj.WalletBalances.Add(SaveClientWalletBalance(res));

                NHibernateConfiguration.GetCurrentSession().Lock(res, LockMode.Upgrade);
            }

            System.Threading.Thread.Sleep(delay * 1000);

            return res;
        }

`

但是两个不同的网络用户第一次调用相同的客户 ID 和货币组合的可能性仍然存在问题,在这种情况下,将创建相同客户和货币的两条记录。而且我不能使用 lock(object) 语法,因为我们使用的网络场有多个不共享静态变量的应用程序服务器。

如果您希望对象在数据库中的某些属性上是唯一的,您应该通过在数据库中定义适当的 UNIQUE 约束来确保这一点。然后它将确保不会有重复。

在下一步中,您想考虑如果两个进程确实尝试添加重复记录应该发生什么,因为它们是 运行 并发的:

  • 您可以捕获 UNIQUE 违规并重试该进程。在第二 去它会找到另一个事务提交的记录。

  • 您可以使用 SERIALIZABLE 事务隔离级别。这是最安全的隔离 等级。如果我的想法是正确的,它应该确保当任何进程试图 读取现有记录,它将没有记录,现有记录,或者如果 任何其他打开的交易已经在寻找相同的记录,它将被阻止 并等待另一笔交易完成。