1

I am using EF Core 2.2.4. All SQL tables have timestamp column. I have method below that tries to get next WorkOrder for given user. If there are multiple users competing at the same time we may get DbUpdateConcurrencyException which i handle using re-try logic.

Note that DBCntext is injected into service's constructor as Scoped instance

    public async Task<WorkOrder> GetNextOrder(int userID)
    {
        var maxTry = 3;            

        WorkOrder order = null;

            for (int i = 0; i < maxTry; i++)
            {
                order = await _dbContext.WorkOrder
                    .Where(o => o.UserID == null)
                    .OrderBy(o => o.CreatedDateTime)
                    .FirstOrDefaultAsync();

                if (order == null)
                {
                    break; //exit out of the loop
                }

                order.StatusID = (int)Statuses.InProgress;
                order.UserID = userID;
                order.AssignedDateTime = DateTime.UtcNow;
                try
                {
                    await _dbContext.SaveChangesAsync();
                    i = maxTry;
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    // since mutiple users are compting for orders. We may get concurrency issue
                    // in such case retry to get next order                    
                    await _dbContext.Entry(order).ReloadAsync();
                    order = null;                        
                }
            }    

        return order;

    }

This is been working, but now number of users are increased and when they competing for WorkOrders most of the time method above exhaust retry and returns null for WorkOrder. Of-course i can increase the counter to 10 or 20 and that might work but i cannot keep increasing the counter as users are increasing.

I wanted to know if there is a way to lock the selected record so other execution does not read the same record or waits till the first one complete? I would defiantly do not want to lock the whole table

I tried transaction but it did not work

        public async Task<WorkOrder> GetNextOrder(int userID)
        {
            var maxTry = 3;            

            WorkOrder order = null;
            using (var transaction = await _dbContext.Database.BeginTransactionAsync())
            {
                for (int i = 0; i < maxTry; i++)
                {
                    order = await _dbContext.WorkOrder
                    .Where(o => o.UserID == null)
                        .OrderBy(o => o.CreatedDateTime)
                        .FirstOrDefaultAsync();

                    if (order == null)
                    {
                        break; //exit out of the loop
                    }

                    order.StatusID = (int)Statuses.InProgress;
                    order.UserID = userID;
                    order.AssignedDateTime = DateTime.UtcNow;
                    try
                    {
                        await _dbContext.SaveChangesAsync();
                        i = maxTry;
                    }
                    catch (DbUpdateConcurrencyException ex)
                    {
                        // since mutiple users are compting for orders. We may get concurrency issue
                        // in such case retry to get next order                    
                        await _dbContext.Entry(order).ReloadAsync();
                        order = null;                        
                    }
                }

                transaction.Commit();
            }
            return order;

        }

Update 1
So EF Core does not support Pessimistic Concurrency (Locking) out of the box, It supports only Optimistic Concurrency. That means you allow concurrency conflict to happen and then react accordingly.

So next solution i was thinking instead of taking FirstOrDefault() is it possible to take random record?

 order = await _dbContext.WorkOrder
                    .Where(o => o.UserID == null)
                    .OrderBy(o => o.CreatedDateTime)
                    .TakeRandonRecord() ??

Is there any way to select random record without loading entire recordset into memory?

pfx
  • 20,323
  • 43
  • 37
  • 57
LP13
  • 30,567
  • 53
  • 217
  • 400

0 Answers0