Обработка состояния гонки

Я разрабатываю финансовое приложение с ASP.NET MVC и Entity Framework (версия 5).

В моем приложении пользователи могут одобрить или отклонить транзакцию. Если пользователь одобрит, деньги будут переведены, и будет отправлено электронное письмо с уведомлением об одобрении, в противном случае будет отправлено электронное письмо с уведомлением об отклонении.

Меня беспокоит, если два пользователя приходят одновременно, чтобы подтвердить одну и ту же транзакцию. Пользователь получит статус транзакции «WaitForApprove» и сделает двойную передачу (в случае, если оба пользователя подтвердят).

Как я могу справиться с этой ситуацией, какое решение лучше всего (имеет смысл и потребляет мало ресурсов).

Я провел небольшое исследование и обнаружил, что Mutex может с этим справиться. Но я не уверен, что это лучшее решение.

Вот как выглядит мой исходный код (мьютекс не применяется).

public void TransactionApproval(int sysTransferInfoID, string username, string command)
{
    // Get the transaction.
    var info = _transInfoRepo.GetSingle(i => i.SYS_TRANSFER_INFO_ID == sysTransferInfoID);

    if (info.SYS_TRANSFER_STATE_ID != (int)eTransferState.WaitForApprove) // Check status.
    {
        if (command == "Approve")
        {
            // Call another service to make transfer here.

            // Sent approve fund transfer notification email to other users.

            info.SYS_TRANSFER_STATE_ID = (int)eTranferState.Approved; // Change status.
            info.APPROVE_BY = username;
        }
        else if (command == "Reject")
        {
            // Sent reject fund transfer notification email to other users.

            info.SYS_TRANSFER_STATE_ID = (int)eTranferState.Rejected; // Change status.
            info.APPROVE_BY = username;
        }

        _context.SaveChanges();
    }
    else
    {
        throw new Exception("Cannot approve transaction.");
    }
}

person TaeV    schedule 10.10.2014    source источник
comment
Вы смотрели тип данных SQL Server ROWVERSION?   -  person DavidG    schedule 10.10.2014
comment
Используйте транзакцию базы данных, возможно, с оптимистичным параллелизмом.   -  person Joe    schedule 10.10.2014
comment
Если вы следуете совету использовать состояние, хранящееся в базе данных, для вашей транзакции (и это хороший, общепринятый совет), возможно, вы не захотите использовать для него EF. Хотя у него, вероятно, есть обертки для уровней изоляции, выяснить обертки может быть сложнее, чем просто вызвать хранимую процедуру, которую вы пишете сами (полагаю, в зависимости от вашего знакомства с EF).   -  person welegan    schedule 10.10.2014


Ответы (1)


Вам необходимо полностью отделить ответственность за перевод денег от пользовательского интерфейса. Не позволяйте пользовательскому интерфейсу напрямую устанавливать состояние передачи.

Вместо этого предоставьте методы для объекта, такие как ApproveTransfer() и RejectTransfer(), которые проверяют текущее состояние объекта и выполняют запрошенное действие только в том случае, если объект находится в соответствующем состоянии.

Объекту также нужен механизм, гарантирующий, что соответствующее состояние известно любому экземпляру объекта, даже созданному после запуска запрошенного действия в другом экземпляре объекта, возможно, на другом сервере. Для этого необходимо обратиться к базе данных.

Лучшая стратегия хранения этого состояния в базе данных зависит от того, как вы фактически выполняете транзакцию.

Если вам нужно просто установить флаг для объекта (который, возможно, подхвачен другим процессом), вероятно, будет достаточно транзакции базы данных с оптимистичным параллелизмом.

Если ваш объект инициирует вызовы других систем (возможно, вызов веб-службы для инициирования передачи), вам потребуется заблокировать строку в базе данных (транзакция с повторяемое чтение на уровне изоляции).

person Eric J.    schedule 10.10.2014