Asp.net core 学习笔记 ( ef core transaction scope & change level )

更新: 2020-02-21 

补上一些忧虑

public async Task Inside()
{
    using var scope = SqlCommonMethod.CreateTransactionScope();
    await SqlCommonMethod.SetIsolationLevel(Db, IsolationLevel.Serializable); // 开启高级别锁
    var countries = await Db.Countries.ToListAsync(); // counties 表被高级锁了
    await SqlCommonMethod.SetIsolationLevel(Db, IsolationLevel.ReadCommitted); // 设置回普通锁, 不然接下来都会一直是高级锁
    await Inside2();
    scope.Complete(); // 这里的 complete 结束并不会解锁, 会一直等到最外面的 scope 被释放, 符合逻辑
}

public async Task Inside2()
{
    using var scope = SqlCommonMethod.CreateTransactionScope();
    var countries = await Db.States.ToListAsync(); // // states 表被普通锁了
    scope.Complete();
}

#region Simple test 
[HttpPost("simple-test")]
public async Task<IActionResult> SimpleTest()
{
    using (var scope = SqlCommonMethod.CreateTransactionScope())
    {
        await Inside();
        scope.Complete(); // 这里依然不会解锁, 要等到 scope 释放
    } 
    // 直到 using 结束才会解锁 table 哦
    return Ok("ok");
}

总结 : 每次设置级别后最好是设置回去. 不然全场都会使用高级锁. 

ef core 有 unit of work 的概念,当我们 save change 时会自动使用 transaction 确保更新的一致性. 隔离级别是默认的 read committed 不允许脏读. 

但是呢, 有时候我们希望拥有更好的隔离级别, 比如 repeatable read, serializable 

那么就需要调用 database.beginTransaction 了. 

一旦需要自己控制 trans 麻烦就跟着来了。

比如在多个服务嵌套调用时, 如何共享 trans 呢 ? 

每个服务的 trans 级别也有可能是不同的.

如果我们单纯使用 beginTransaction 那么需要在每个服务写判断,是否有 current transaction 做出不同的处理.   

在早期, 我们会用 transaction scope 作为业务级别的事务. 

transaction scope 非常强大, 可以跨库, 分布式, 甚至可以链接 file system 

比如一个事务内做了数据库修改,也创建了 file, 如果事务最终失败,连 file 也可以 rollback 删除掉. 

但自从 ef 出现后, 这个就变得大材小用了些. ef 也不推荐我们使用了 refer https://docs.microsoft.com/zh-cn/ef/ef6/saving/transactions?redirectedfrom=MSDN

ef core 在 2.1 的时候支持了 transactionscope 但是不支持分布式, 好像是不兼容 linux 所以去掉了.

但是在我说的这种情况下,使用它依然会让代码变得更好.

调用起来是这样的

            using (var scope = new TransactionScope(
                scopeOption: TransactionScopeOption.Required,
                transactionOptions: new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead },
                asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled
            ))
            {
                try
                {
                 
                }
                catch (Exception ex)
                {
                    return BadRequest(ex.Message);
                }
                scope.Complete();
            }

默认隔离级别是 serializable. 

如果想在 service 里面嵌套, 那么重要设定 scopeOption 是 required 就可以了. 

它还有另外 2 个选择, 1 个是 new 意思是另外开启一个独立的 trans, 再一个是 suppend 就是完全没有 trans 无关.

有了这些就很灵活了,在不同 service 中我们可以去实现独立或无关的事务处理. 

使用过程中需要注意几件事情

嵌套 scope 需要使用同一种级别

这个挺麻烦的,通常不可能全部一个级别吧... 

目前没有看到方法可以修改的,一个可能的办法是直接调用 sql 语句 

set transaction isolation level read committed
set transaction isolation level repeatable read;
set transaction isolation level serializable

去设定它. 

这里顺便说说 sql server 对于这一块的处理. 

https://www.cnblogs.com/keatkeat/p/11830113.html

另一个要注意的是, 一定要设置 async enabled 如果 scope 内需要 async 的话

asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled

refer : 

https://www.cnblogs.com/csdbfans/p/transactionscope.html

https://blog.csdn.net/qin_yu_2010/article/details/86150247

https://docs.microsoft.com/en-us/ef/core/saving/transactions

https://weblogs.thinktecture.com/pawel/2018/06/entity-framework-core-use-transactionscope-with-caution.html

https://www.cnblogs.com/taiyonghai/p/6047849.html

https://www.21cto.com/article/1075

https://www.codeproject.com/Articles/690136/All-About-TransactionScope#hBusinessTrans

https://codewala.net/2018/05/06/transactionscope-a-simple-way-to-handle-transactions-in-net/

https://docs.microsoft.com/zh-cn/dotnet/framework/data/transactions/implementing-an-implicit-transaction-using-transaction-scope

原文地址:https://www.cnblogs.com/keatkeat/p/11830651.html