【问题标题】:Using Entity Framework Transaction Correctly for Isolation正确使用实体框架事务进行隔离
【发布时间】:2018-10-21 08:52:30
【问题描述】:

我正在为我的 ASP.Net 网站使用 Entity Framework 6.0 和 SQL Server 2016。我最近在我的一个函数中发现了并发问题。该函数用于处理未付款的订单,有时该函数会针对同一个key、同一时间执行多次(因为多个用户同时访问)。

这是它的样子。

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 语句中的范围只执行一次的正确方法是什么?

【问题讨论】:

  • 取决于几件事:事务的隔离级别(默认为 ReadCommitted),以及您在该事务中执行的操作。
  • @MitchWheat 我没有设置任何隔离级别选项,所以我认为这是默认设置。除了更新上面的PaymentStatus 之外,我所做的基本上是插入和更新其他一些表。
  • 你真正的问题在这里“有时这个函数会在同一个键和同一时间执行多次(因为多个用户一起访问它)。”与其锁定表并阻止所有其他访问,不如解决原因而不是症状。
  • @MitchWheat,我认为这不可能,因为连接到此功能的页面显示未付款订单列表和一个将付款状态设置为已付款的按钮。我无法阻止该按钮仅单击一次,因为该页面由多个用户访问。所以我试图阻止使用事务的表(至少即使多次单击该按钮,它也只在第一次单击时处理一次)。但不幸的是,它没有按预期运行。
  • 至少你应该实现乐观并发。

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


【解决方案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();
        }
    }
}

然后,即使您有多个前端服务器,一次也只能运行该事务的一个实例。所以这就像一个互斥体,可以在所有访问同一数据库的客户端上工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-03
    • 1970-01-01
    • 2012-08-01
    • 2013-02-03
    • 2015-10-03
    • 1970-01-01
    • 2016-09-02
    相关资源
    最近更新 更多