正确使用实体框架事务进行隔离

时间:2018-10-21 08:52:31

标签: c# sql-server entity-framework transactions

我将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 :(得分:1)

序列化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();
        }
    }
}

那么即使您有多个前端服务器,一次也只能运行该事务的一个实例。因此,这就像互斥量,可在访问同一数据库的所有客户端中使用。