Правильное использование транзакции Entity Framework для изоляции - PullRequest
0 голосов
/ 21 октября 2018

Я использую Entity Framework 6.0 и SQL Server 2016 для своего веб-сайта ASP.Net.Недавно я обнаружил проблему с параллелизмом в одной из моих функций.Функция используется для обработки неоплаченного заказа, и иногда эта функция выполняется несколько раз для одного и того же ключа и в одно и то же время (потому что несколько пользователей обращаются к нему вместе).

Вот как это выглядит.

public void PaidOrder(string paymentCode)
{
    using (MyEntities db = new MyEntities())
    {
        using (DbContextTransaction trans = db.Database.BeginTransaction())
        {
            try
            {
                Order_Payment_Code payment = db.Order_Payment_Code.Where(item => item.PaymentCode == paymentCode).FirstOrDefault();
                if(payment.Status == PaymentStatus.NotPaid)
                {
                    //This Scope can be executed multiple times
                    payment.Status = PaymentStatus.Paid;
                    db.Entry(payment).State = EntityState.Modified;
                    db.SaveChanges();

                    //Continue processing Order

                    trans.Commit();
                }
            }
            catch (Exception ex)
            {
                trans.Rollback();
            }
        }
    }
}

Что я не понимаю, так это то, почему область внутри моего оператора if может выполняться несколько раз, даже если она находится внутри транзакции?Разве транзакция не предполагает изоляции данных?Или мое понимание транзакции неверно?Если это так, то как правильно сделать область действия внутри моего оператора if только один раз?

1 Ответ

0 голосов
/ 21 октября 2018

Простой и надежный способ сериализации транзакции EF SQL Server заключается в использовании Application Lock .

Добавьте этот метод в свой DbContext:

public void GetAppLock(string lockName)
{
    var sql = "exec sp_getapplock @lockName, 'exclusive';";
    var pLockName = new SqlParameter("@lockName", SqlDbType.NVarChar, 255);
    pLockName.Value = lockName;
    this.Database.ExecuteSqlCommand(sql, pLockName);
}

И позвоните сразу после начала транзакции.

public void PaidOrder(string paymentCode)
{
    using (MyEntities db = new MyEntities())
    {
        using (DbContextTransaction trans = db.Database.BeginTransaction())
        {
            db.GetAppLock("PaidOrder");
            Order_Payment_Code payment = db.Order_Payment_Code.Where(item => item.PaymentCode == paymentCode).FirstOrDefault();
            if(payment.Status == PaymentStatus.NotPaid)
            {
                //This Scope can be executed multiple times
                payment.Status = PaymentStatus.Paid;
                db.Entry(payment).State = EntityState.Modified;
                db.SaveChanges();

                //Continue processing Order

            }
            trans.Commit();
        }
    }
}

Тогда одновременно может выполняться только один экземпляр этой транзакции, даже если у вас несколько интерфейсных серверов.Так что это похоже на Mutex, который работает на всех клиентах, которые обращаются к одной и той же базе данных.

...