# 9.10、工作单元和事务

## 什么是事务？

视图指作为单个逻辑工作单元执行的一系列操作，**要么完全地执行，要么完全地不执行**。

简单的说，事务就是并发控制的单位，是用户定义的一个操作序列。 而一个逻辑工作单元要成为事务，就必须满足ACID属性。

&#x20;A：`原子性（Atomicity）`：事务中的操作要么都不做，要么就全做。&#x20;

C：`一致性（Consistency）`：事务执行的结果必须是从数据库从一个一致性状态转换到另一个一致性状态。

&#x20;I：`隔离性（Isolation）`：一个事务的执行不能被其他事务干扰&#x20;

D：`持久性（Durability）`：一个事务一旦提交，它对数据库中数据的改变就应该是永久性的

### 事务使用

默认情况下，Hoa Framework 每一次请求都开启了分布式事务，保证了每一次请求都是一个完整的 `工作单元`。

当然，我们也可以手动开启事务，在Hoa Framework 框架中，框架默认的数据库操作ORM： [EF Core](https://docs.microsoft.com/zh-cn/ef/core/get-started/?tabs=netcore-cli) 中的`SaveChanges()` 已经自动开启了事务。所以很多时候，我们无需手动开启事务。

下面，也给读者展示几个 [EF Core](https://docs.microsoft.com/zh-cn/ef/core/get-started/?tabs=netcore-cli) 集中事务示例：

### 示例一

```csharp
// 开启事务
using (var transaction = _testRepository.Database.BeginTransaction())
{
    try
    {
        _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        _testRepository.SaveChanges();

        _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
        _testRepository.SaveChanges();

        var blogs = _testRepository.Entity
                .OrderBy(b => b.Url)
                .ToList();

        // 提交事务
        transaction.Commit();
     }
     catch (Exception)
     {
        // 回滚事务
        transaction.RollBack();
     }
}
```

### 示例二

```csharp
var options = new DbContextOptionsBuilder<HoaDbContext>()
    .UseSqlServer(new SqlConnection(connectionString))
    .Options;

// 创建连接字符串
using (var context1 = new HoaDbContext(options))
{
    // 开启事务
    using (var transaction = context1.Database.BeginTransaction())
    {
        try
        {
            _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            _testRepository.SaveChanges();
            
            context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context1.SaveChanges();

            // 创建新的连接对象
            using (var context2 = new HoaDbContext(options))
            {
                // 共享连接事务
                context2.Database.UseTransaction(transaction.GetDbTransaction());

                var blogs = context2.Blogs
                    .OrderBy(b => b.Url)
                    .ToList();
            }

            // 提交事务
            transaction.Commit();
        }
        catch (Exception)
        {
            // 回滚事务
            transaction.RollBack();
        }
    }
}
```

### 示例三

采用分布式事务

```csharp
// 开启分布式事务
using (var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();

        try
        {
            // 这里是 Ado.NET 操作
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            command.ExecuteNonQuery();

            // 创建EF Core 数据库上下文
            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
            using (var context = new BloggingContext(options))
            {
                context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
                context.SaveChanges();
            }
            
            // 框架封装的仓储
            _testRepository.Insert(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            _testRepository.SaveChanges();

           // 提交事务
            scope.Complete();
        }
        catch (System.Exception)
        {
            // 自动回滚
        }
    }
}
```

更多事务操作[可查官方文档](https://docs.microsoft.com/zh-cn/ef/core/saving/transactions)。

## 什么是工作单元

简单来说，就是为了保证一次完整的功能操作所产生的一些列提交数据的完整性，起着事务的作用。在计算机领域中，工作单元通常用 `UnitOfWork` 名称表示。

通常我们保证用户的**每一次请求都是处于在一个功能单元中**，也就是工作单元。

### 工作单元的使用

默认情况下，在 Hoa Framework 中，工作单元无需我们手动维护，框架会自动保证了每一次请求都是一个 工作单元，要么同时成功，要么同时失败。也就是保证了数据的完整性。

但有些时候，**我们出于某种特殊原因，我们不需要保证数据的完整性，也就是无需开启工作单元模式**。😢🤢，这时候，我们可以手动关闭工作单元模式，只需要在当前的方法上面贴 `[UnitOfWork(false)]`即可，如：

```csharp
[UnitOfWork(false)]
public void IKnowWillThrowExceptionAndInsistHandle(){
    // 执行数据库操作
    // 执行其他操作
    // Oops! 出错了，此时上面的 数据库操作 不会回滚！！！
}
```

通过上面的例子，我们可得知，如果**关闭了工作单元模式，一旦内部出错，所有数据库操作并不会回滚！所以，要慎重使用此特性！**��‍👤

### 工作单元特性参数说明

`Disabled`：禁用工作单元模式，默认 `false`

`IsolationLevel`：设置事务隔离级别，默认 `IsolationLevel.ReadCommitted;`

`AsyncFlowOption`: 包含数据库异步操作配置选项，默认 `TransactionScopeAsyncFlowOption.Suppress`，**也就是默认不支持异步多线程分布式事务**。**设置为 `TransactionScopeAsyncFlowOption.Enabled` 即可启用分布式异步支持。**

`TransactionScopeOption`：定义事务范围行为，默认 `TransactionScopeOption.Required`

## 常见错误

### 错误一：事务必须在创建的线程上释放

```
A TransactionScope must be disposed on the same thread that it was created.
```

这时候只需要在方法上贴即可。

```csharp
[UnitOfWork(AsyncFlowOption = TransactionScopeAsyncFlowOption.Enabled)]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://monksoul.gitbook.io/hoa/shujukucaozuoshinan/gongzuodanyanheshiwu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
