如何使用LINQ防止多个用户同时更新记录

h6my8fg2  于 2022-12-06  发布在  其他
关注(0)|答案(6)|浏览(174)

我有下面的LINQ更新记录,在某些情况下,它可能是IEnumerable太。

public async Task<IActionResult> MyMethod(Int ID, decimal confirmQty)
{               
    using (var tran = _context.Database.BeginTransaction())
    {
        try
        {
            // Need to Lock here whether single record or multiple records
            var invReduce = _context.Inventorys.Where(w=>w.id == ID).FirstOrDefault();
            invReduce.availQty -= confirmQty;

            Thread.Sleep(60000); // One Min
            await _context.SaveChangesAsync();
            tran.Commit();

            // Need to Un-Lock here whether single record or multiple records
        }
        catch (Exception ex)
        {
            tran.Rollback();
        }
    }

    return Ok();
}

在这里,第一个用户可以查询数据,并且应该锁定它以防止第二个用户查询相同的数据。在第一个用户的进程完成时,第二个用户的查询应该自动运行。

更新:例如id:1,数量为1000,第一个用户请求减少数量100,同时第二个用户在第一个用户的SaveChanges()生效之前发出减少数量100的请求,最终减少数量应为1000 - 100 - 100 = 800。

因此,在第一个用户的操作完成之前,第二个用户的查询应该在队列中。
我使用ASP.NET核心2.2代码优先,PostgreSQL,没有存储过程。
如何在此处锁定行?

ej83mcc0

ej83mcc01#

尝试在查询中使用必须是原子的事务(不能对受此操作影响的数据执行任何操作)。
例如,读取this
此外,请阅读有关不同锁定级别的信息,以防止同时更新等。

8hhllhi2

8hhllhi22#

这样的锁定机制被称为悲观锁定,并且比乐观锁定更容易出现问题。作为更新的一部分,ORM将所有先前的值传递回DB,并形成查询,以便将每个当前表值与ORM已知的每个先前值进行比较(从提取记录的时间开始)。如果其他用户更改了任何值,则更新失败并返回0个记录更新。此时,表单可以向您引发一个异常,指示其他人编辑了同一条记录。您可以使用此异常并通知用户,也许可以让他们选择要做什么:

  • 用我的覆盖他们的
  • 保留他们的而抛弃我的
  • 与他们的合并

我敢肯定,您在编写代码并将更改提交到源代码管理时已经看到了这一点:)
当然,你必须写一个接口来完成这个操作,但这通常是正确的做法,因为只有用户才能知道数据应该是什么。如果你不提供合并,那么它可以是一个简单的对话框“保留我的/保留他们的”
您建议将内容保持在队列中并只按顺序应用编辑,这对我来说没有多大意义,因为第二次编辑是编辑第一个用户已经编辑过的数据;如果以编程的方式解决并发问题,您怎么知道最终会得到一个有效的状态呢?如果您只是打算用person 2的更改覆盖person 1的更改,那么您根本不需要任何并发控制
我已经介绍了乐观并发系统的工作原理,但是假设您使用的是EF Core,那么这本优秀的手册还有很多要说的:https://learn.microsoft.com/en-us/ef/core/saving/concurrency

jogvjijk

jogvjijk3#

我建议您使用以下原始更新查询:

UPDATE MyInv
SET qty = qty - @confirmQty
WHERE ID = @ID AND qty >=  @ConfirmQty

它可以防止您在代码中遇到并发问题。
请注意qty >= @ConfirmQty,它可防止将数量设置为低于0。您可以检查受影响的行,如果数量为0,则可以说没有足够的项目来提取库存。

pieyvz9o

pieyvz9o4#

public async Task<IActionResult> MyMethod(Int ID, decimal confirmQty)
    {
        using(var tran = _context.Database.BeginTransaction())
        {
            try
            {

                var invReduce= _context.MyInv.Where(w => w.ID == ID).FirstOrDefault();
                inventoryReduce.qty -= confirmQty;
    // some long operation too goes here...
                await _context.SaveChangesAsync();

                tran.Commit();
            }
            catch
            {
                tran.Rollback();
                // log error if necessary
            }

            return Ok();
    }
lxkprmvk

lxkprmvk5#

我所做的是在每个基本实体中添加int Version,以检查其版本是否与每个数据的当前版本匹配。

class Inventory
{
      //properties
      public int Version {get; set;}
}

且每个SaveChanges()都将具有以下属性

inventory.Version = inventory.Version + 1;

那么如果我们两个都将具有Version = 1,并且我们中的一个在另一个之前更新了相同的字段,则将由于Version增量而导致错误。
样品检查如下

var inventory = context.Inventory.FirstOrDefault(x => x.Id == Model.InventoryId);

 //check if inventory is not null if not continue
 if (inventory.Version != Model.Version)
 {
      //throw exception, DbContextConcurrencyIssue, error handling codes
 }
 else
 {
     //continue with process
 }

任何更新都将增加版本,并且不会与具有先前版本的其他用户匹配

b5lpy0ml

b5lpy0ml6#

您可以在表的字段顶部设置ConcurrencyCheck属性,以确保不会发生冲突。
请参阅thisthis

相关问题