简体   繁体   English

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

[英]How to prevent multiple users to update a record at the same time using LINQ

I have below LINQ to update a record and in some cases it may be IEnumarable too.我在 LINQ 下更新记录,在某些情况下它也可能是IEnumarable

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

Here, 1st user can query the data and should lock it to prevent 2nd user to querying same data.在这里,第一个用户可以查询数据并且应该锁定它以防止第二个用户查询相同的数据。 On 1st user's process completes, 2nd user query should automatically run.在第 1 个用户的进程完成后,第 2 个用户查询应自动运行。

Update更新

For Example: for id:1, qty is 1000, first user requests to reduce qty by 100, also 2nd user sends requests for 100 to reduce at the same time and before 1st user's saveChanges() takes effect.例如:对于 id:1,数量为 1000,第一个用户请求将数量减少 100,第二个用户同时在第一个用户的 saveChanges() 生效之前发送减少 100 的请求。 Final reduce qty should be 1000 - 100 - 100 = 800.最终减少数量应为 1000 - 100 - 100 = 800。

So, Till the time 1st user's operation completes, 2nd user's query should be in Que.因此,直到第 1 个用户的操作完成时,第 2 个用户的查询应该在 Que 中。

I use Asp.Net Core 2.2 Code First, PostgreSQL, No Stored Procedures.我使用 Asp.Net Core 2.2 Code First,PostgreSQL,无存储过程。

How do I Lock rows here?如何在此处锁定行?

I suggest you to use the following raw update query: 我建议您使用以下原始更新查询:

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

It prevents the concurrency issues you are facing in your code. 它可以防止代码中遇到的并发问题。

Note qty >= @ConfirmQty it prevents setting the quantity below 0. You can check the affected rows, if it is 0 you can say there is not enough items to pull out the inventory. 注意qty >= @ConfirmQty它防止将数量设置为0以下。您可以检查受影响的行,如果为0,则可以说没有足够的项目来拉出库存。

Try using transactions in your queries that have to be atomic (no operation can be executed on data affected by this operation). 尝试在查询中使用必须是原子的事务(无法对受此操作影响的数据执行任何操作)。

Read this for example. 例如阅读

Additionally, read about different locking levels, to prevent simultaneous updates, etc. 此外,请阅读有关不同锁定级别的信息,以防止同时进行更新等。

Such locking schemes are known as pessimistic locks and are more prone to problems than the alternative of optimistic locking. 这种锁定方案被称为悲观锁定,比乐观锁定的替代方案更容易出现问题。 Optimistic isn't actually a locking mechanism at all; 乐观实际上根本不是一种锁定机制。 it lets either user who has the same record try to make their edit. 它使具有相同记录的任何一个用户都可以尝试进行编辑。 As part of the update the ORM passes all the previous values back to the dB and forms the query such that it compares every current table value with every previous value known by the orm (from the time the record was pulled). 作为更新的一部分,ORM将所有先前的值传递回dB并形成查询,以便它将当前的每个表值与orm已知的每个先前的值进行比较(从提取记录时开始)。 If any o he values have been changed by another user then the update fails and returns 0 records updates. 如果其他用户更改了任何值,则更新将失败并返回0条记录更新。 At this point the orm can raise an exception to you indicating that someone else has edited the same record. 此时,orm可能会向您提出异常,表明其他人已经编辑了相同的记录。 You consume this exception and inform trhe user, perhaps giving them a choice of what to do: 您可以使用此异常并通知用户,也许可以让他们选择要做什么:

  • overwrite theirs with mine 用我的覆盖他们的
  • keep theirs and abandon mine 保留自己的并放弃我的
  • merge with theirs 与他们的合并

You've seen it when coding and committing your changes to source control, I'm sure :) 您可以在对更改进行编码并将其提交到源代码管理时看到它,我相信:)

Of course, you have to write an interface to do this but it's usually the right thing to do because only the user can know what the data should be. 当然,您必须编写一个接口才能执行此操作,但这通常是正确的做法,因为只有用户才能知道数据应该是什么。 If you're not offering merge then it can be a simple dialog box of "keep mine/keep theirs" 如果您不提供合并,则可以是“保留我的/保留他们的”的简单对话框

Your proposal to keep things in a queue and only apply edits sequentially doesn't make so much sense to me because the second edit is editing data the first user edited already; 您提出的将事情排在队列中并仅顺序应用编辑的建议对我来说意义不大,因为第二个编辑正在编辑第一个用户已经编辑过的数据。 how do you know you'll end up with a valid state if you solve the concurrency problem programmatically? 如果您以编程方式解决并发问题,您怎么知道最终将处于有效状态? If you're just going to overwrite person1's changes with person2's then you don't need any concurrency control at all 如果您只是要用person2覆盖person1的更改,则根本不需要任何并发控制

I've touched on how an optimistic concurrency system works, but assuming you're using EF Core, the fine manual has a lot more to say: https://docs.microsoft.com/en-us/ef/core/saving/concurrency 我已经谈到了乐观并发系统的工作原理,但是假设您使用的是EF Core,那么精美的手册还有很多要说的: https : //docs.microsoft.com/zh-cn/ef/core/saving /并发

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

What I do is add int Version in each base entity to check if his version is matching with the current version of each data. 我要做的是在每个基本实体中添加int版本,以检查其版本是否与每个数据的当前版本匹配。

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

and every SaveChanges() will have these property 并且每个SaveChanges()都将具有这些属性

inventory.Version = inventory.Version + 1;

then if both of us will have a Version = 1, and one of us updated same field before the other, will cause error due to the Version increment. 那么如果我们两个人的Version = 1,并且我们一个人在另一个字段之前更新了相同字段,则会由于Version增量而导致错误。

Sample checking below 下面的样品检查

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
 }

Any update will increment the version and will not match with the other users who have the previous Version 任何更新都会增加版本,并且不会与具有先前版本的其他用户匹配

you can ConcurrencyCheck attribute at the top of field of table to ensure conflict not happen.您可以在表的字段顶部设置ConcurrencyCheck属性以确保不会发生冲突。

see this and this看到这个这个

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM