了解 EF Core 行/表锁和隔离

问题描述 投票:0回答:2

我正在开发一个会议的展位预订系统。这与音乐会门票类似。基本上每个展位都可以由一个用户预订。我正在使用 Entity Framework Core,在我购买摊位的逻辑中,我想确保当我以最高效的方式进行此交易时,没有其他人可以读取或写入。

我一直在阅读有关隔离级别和表命中的信息,但我对在这种情况下需要做什么有点困惑。我想我想要

Serializable
隔离级别,但我不确定。
UPDLOCK
ROWLOCK
查询提示呢?

基本上,我希望下面的这段代码只有在没有其他人更新此方法内的记录时才能运行。在此方法中也应该阻止读取,但在我查询此表的其他地方则不应阻止读取。

   var tx = await context.Database.BeginTransactionAsync(System.Data.IsolationLevel.Serializable);
   var product = await context.SponsorshipProducts.FirstOrDefaultAsync(x => x.Id == productId);
   product.Purchase(); //check to see if booth has been purchased already. If it is not, mark purchased to block other purchases.
   await context.SaveChangesAsync();
   tx.Commit();

此表有一个

ReservedById
栏,指示展品是否已被预订。我想确保一次只能由一个用户针对给定记录进行更新。

entity-framework-core azure-sql-database locking
2个回答
0
投票

通常有四种类型的隔离级别:

  • 未提交的阅读(最低级别) 在此隔离级别中,我们可以在操作提交数据之前读取数据。

  • 读取已提交(默认隔离级别) 在这种隔离级别下,我们只有在操作提交数据后才能读取数据。

  • 可重复读取 在此隔离级别中,它会读取并锁定它,直到操作结束。

  • 可序列化(最高级别) 可串行化与可重复读隔离级别非常相似,但在此隔离级别中,它不允许在事务期间不添加新数据。

我希望下面的这段代码只有在没有其他人更新此方法内的记录时才能运行。在此方法中也应该阻止读取,但在我查询此表的其他地方则不应阻止读取。

对于这种情况,可串行化或可重复读隔离级别可以适用。

欲了解更多信息,您可以参考此文档


0
投票

我需要一个简单的解决方案来锁定表中的 1 行。

我用这个:

public class LockTable
{
    public LockTable(string lockKeyId)
    {
        LockKeyId = lockKeyId;
        CreateAt = DateTime.Now;
    }

    [Key]
    public string LockKeyId { get; set; }
    public DateTime CreateAt { get; set; }
}

    public async Task<bool> UpdateDataAsyncLock(TestTable input, string rowKey)
    {
        // Check if row is lock
        LockTable? temp = await context.LockTable.FirstOrDefaultAsync(x => x.LockKeyId == rowKey);
        bool res = false;

        if (temp == null)
        {
            // No lock -> do work
            temp = new LockTable(rowKey);

            try
            {
                // lock row
                await context.LockTable.AddAsync(temp);
                await context.SaveChangesAsync();

                // Row lock, Update data
                res = await UpdateTestDataAsync(input);

                // Remove row lock
                context.LockTable.Remove(temp);
                await context.SaveChangesAsync();                    
            }
            catch (Exception ex)
            {
                // Dubplicate Primary Key, no update possible.
                // some one have made an insert in while code is running. (Shut not happening...)
            }
        }

        // Remove all lock that are 10 min or old. Do we need this?
        await CleanUpLocks();
        return res;
    }

    private async Task<bool> UpdateTestDataAsync(TestTable input)
    {
        TestTable? temp = await context.TestTable.FirstOrDefaultAsync(x => x.PublicId == input.PublicId);
        bool res = false;

        if (temp != null)
        {
            temp.ChangesCounter++;
            temp.ValueToUpdate = input.ValueToUpdate;
            await context.SaveChangesAsync();
            res = true;
        }
        return res;
    }

    public async Task CleanUpLocks()
    {
        // Delete stuff that are 10 min old. There shut be nothing to delete happening...
        DateTime timeOut = DateTime.Now.AddMinutes(-10);
        List<LockTable> toDelete = await context.LockTable.Where(x => x.CreateAt <= timeOut).ToListAsync();

        context.LockTable.RemoveRange(toDelete);
        await context.SaveChangesAsync();
    }

为了使代码可重用,可以这样做:

public class DataBase
 {
     public async virtual Task<bool> UpdateDate()
     {
         return false;
     }
 }

public class PersonUpdate : DataBase
{
    public TestTable testTable;
    public PersonUpdate(TestTable input)
    {
        testTable = input;
    }

    public override async Task<bool> UpdateDate()
    {
        DatabaseContext context = new DatabaseContext();
        TestTable? temp = await context.TestTable.FirstOrDefaultAsync(x => x.PublicId == testTable.PublicId);
        bool res = false;

        if (temp != null)
        {
            temp.ChangesCounter++;
            temp.ValueToUpdate = testTable.ValueToUpdate;
            await context.SaveChangesAsync();
            res = true;
        }
        return res;
    }
}

 public async Task<bool> UpdateDataAsyncLock(DataBase input, string rowKey)
 {
     // Check if row is lock
     LockTable? temp = await context.LockTable.FirstOrDefaultAsync(x => x.LockKeyId == rowKey);
     bool res = false;

     if (temp == null)
     {
         // No lock -> do work
         temp = new LockTable(rowKey);

         try
         {
             // lock row
             await context.LockTable.AddAsync(temp);
             await context.SaveChangesAsync();

             // Row lock, Update data
             res = await input.UpdateDate();

             // Remove row lock
             context.LockTable.Remove(temp);
             await context.SaveChangesAsync();                    
         }
         catch (Exception ex)
         {
             // Dubplicate Primary Key, no update possible.
             // some one have made an insert in while code is running. (Shut not happening...)
         }
     }

     // Remove all lock that are 10 min or old. Do we need this?
     await CleanUpLocks();
     return res;
 }
© www.soinside.com 2019 - 2024. All rights reserved.