简体   繁体   中英

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.

    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.

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. Final reduce qty should be 1000 - 100 - 100 = 800.

So, Till the time 1st user's operation completes, 2nd user's query should be in Que.

I use Asp.Net Core 2.2 Code First, PostgreSQL, No Stored Procedures.

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.

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). If any o he values have been changed by another user then the update fails and returns 0 records updates. At this point the orm can raise an exception to you indicating that someone else has edited the same record. 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

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

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.

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

and every SaveChanges() will have these property

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.

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.

see this and this

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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