为什么在尝试避免将具有相同键的实体插入数据库时​​ RepeatableRead 隔离级别不适用于 EF Core?

Why RepeatableRead isolation level not working with EF Core when trying to avoid inserting entities with the same key into the database?

我试图避免将具有相同 Identifier 字段值的多行插入到数据库中。这是代码:

public async Task InsertTransaction(Transaction transaction)
    {
        await using var dbTransaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead);
                 
        var existing = await _dbContext.Set<Transaction>()
            .AsNoTracking()
            .FirstOrDefaultAsync(t => t.Identifier == transaction.Identifier);

        if (existing != null) {
            return;
        }

        _dbContext.Set<Transaction>().Add(transaction);
        await _dbContext.SaveChangesAsync();
        
        await dbTransaction.CommitAsync();
        return;
    }

我假设 RepeatableRead 隔离级别在这里应该足够了,因为它应该锁定读取搜索条件包含 Identifier 的查询,并且在第一个进入事务之后的所有请求都将等待它完成。 但是,当 运行 并发测试时,我插入了多个具有相同 Identifier 的行,并且只有在将事务隔离级别更改为 Serializable.

后才能正常工作

I'm assuming that RepeatableRead isolation level should be enough here

没有。 REPEATABLE READ 不会对不存在的数据使用 key range locks。 SERIALIZABLE 是唯一的隔离级别。尽管 SERIALIZABLE 将采用共享 (S) 范围锁,因此在多个会话持有相同的 S 锁之后,冲突的插入将造成死锁,而不是在 SELECT 查询处阻塞第二个会话。

I'm getting multiple rows inserted with the same Identifier

除了 insert-if-not-exists 事务之外,您还应该 Identifier 上有一个唯一索引到 prevent重复。

在 SQL 服务器中,要使用的锁提示是 updlock,holdlock,它强制限制性更新 (U) 锁和键范围锁。所以像:

public bool Exists<TEntity>(int Id) where TEntity: class
{
    var et = this.Model.FindEntityType(typeof(TEntity));
    return this.Set<TEntity>().FromSqlRaw($"select * from [{et.GetSchema()??"dbo"}].[{et.GetTableName()}] with(updlock,holdlock) where Id = @Id",new SqlParameter("@Id",Id)).Any();
}

你还需要一个事务,否则U锁会立即释放,但事务可以在默认的READ COMMITTED隔离级别。