# 软件开发文档 - 05. 事务与工作单元设计

本文档详细阐述了为确保数据一致性而引入的**工作单元模式 (Unit of Work)** 的设计与实现。我们将此模式的接口命名为 `IRepositoryManager`，以更直观地反映其职责。

## 1. 设计目标

在复杂的业务操作中（例如，创建一个设备，同时需要创建其关联的变量表和菜单项），必须保证所有数据库操作要么全部成功，要么全部失败。这要求一个能够跨越多个仓储（Repository）的事务管理机制。

`IRepositoryManager` 的核心职责就是：

*   **组合操作**：将一系列独立的数据库操作组合成一个单一的、原子的工作单元。
*   **事务控制**：提供统一的 `Commit` (提交) 和 `Rollback` (回滚) 方法。
*   **共享上下文**：确保所有通过它访问的仓储共享同一个数据库连接和事务，这是实现原子性的前提。

## 2. `DMS.Core` - 接口定义

我们在核心层定义 `IRepositoryManager` 接口，它作为所有仓储的容器和事务的控制器。

```csharp
// 文件: DMS.Core/Interfaces/IRepositoryManager.cs
namespace DMS.Core.Interfaces;

/// <summary>
/// 定义了一个仓储管理器，它使用工作单元模式来组合多个仓储操作，以确保事务的原子性。
/// 实现了IDisposable，以确保数据库连接等资源能被正确释放。
/// </summary>
public interface IRepositoryManager : IDisposable
{
    /// <summary>
    /// 获取设备仓储的实例。
    /// 所有通过此管理器获取的仓储都共享同一个数据库上下文和事务。
    /// </summary>
    IDeviceRepository Devices { get; }

    /// <summary>
    /// 获取变量表仓储的实例。
    /// </summary>
    IVariableTableRepository VariableTables { get; }

    /// <summary>
    /// 获取菜单仓储的实例。
    /// </summary>
    IMenuRepository Menus { get; }

    // ... 此处可以添加项目中所有其他的仓储

    /// <summary>
    /// 开始一个新的数据库事务。
    /// </summary>
    void BeginTransaction();

    /// <summary>
    /// 异步提交当前事务中的所有变更。
    /// </summary>
    /// <returns>一个表示异步操作的任务。</returns>
    Task CommitAsync();

    /// <summary>
    /// 异步回滚当前事务中的所有变更。
    /// </summary>
    /// <returns>一个表示异步操作的任务。</returns>
    Task RollbackAsync();
}
```

## 3. `DMS.Infrastructure` - 具体实现

在基础设施层，我们提供 `IRepositoryManager` 的 `SqlSugar` 实现。

```csharp
// 文件: DMS.Infrastructure/Data/RepositoryManager.cs
using DMS.Core.Interfaces;
using SqlSugar;

namespace DMS.Infrastructure.Data;

/// <summary>
/// 使用 SqlSugar 实现的仓储管理器。
/// </summary>
public class RepositoryManager : IRepositoryManager
{
    private readonly ISqlSugarClient _db;
    private readonly Lazy<IDeviceRepository> _lazyDevices;
    private readonly Lazy<IVariableTableRepository> _lazyVariableTables;
    private readonly Lazy<IMenuRepository> _lazyMenus;

    public RepositoryManager(ISqlSugarClient db)
    {
        _db = db;
        // 使用 Lazy<T> 实现懒加载，只有在第一次访问时才创建仓储实例
        // 关键点：将当前的数据库客户端 (_db) 传递给仓储，确保它们共享事务
        _lazyDevices = new Lazy<IDeviceRepository>(() => new DeviceRepository(_db));
        _lazyVariableTables = new Lazy<IVariableTableRepository>(() => new VariableTableRepository(_db));
        _lazyMenus = new Lazy<IMenuRepository>(() => new MenuRepository(_db));
    }

    public IDeviceRepository Devices => _lazyDevices.Value;
    public IVariableTableRepository VariableTables => _lazyVariableTables.Value;
    public IMenuRepository Menus => _lazyMenus.Value;

    public void BeginTransaction()
    {
        _db.BeginTran();
    }

    public async Task CommitAsync()
    {
        await _db.CommitTranAsync();
    }

    public async Task RollbackAsync()
    {
        await _db.RollbackTranAsync();
    }

    public void Dispose()
    {
        // 数据库客户端的生命周期由DI容器管理，此处无需手动释放
        // 但如果事务未提交/回滚，SqlSugar的Dispose会处理它
        _db.Dispose();
    }
}
```

## 4. `DMS.Application` - 应用服务重构

应用服务现在注入 `IRepositoryManager` 来执行事务性操作。

```csharp
// 文件: DMS.Application/Services/DeviceAppService.cs
using DMS.Core.Interfaces;

public class DeviceAppService : IDeviceAppService
{
    private readonly IRepositoryManager _repoManager;
    private readonly IMapper _mapper;

    public DeviceAppService(IRepositoryManager repoManager, IMapper mapper)
    {
        _repoManager = repoManager;
        _mapper = mapper;
    }

    public async Task<int> CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto)
    {
        // 不再需要 using 语句，因为 DI 容器会管理 RepositoryManager 的生命周期
        try
        {
            _repoManager.BeginTransaction();

            var device = _mapper.Map<Device>(dto.Device);
            await _repoManager.Devices.AddAsync(device);

            var variableTable = _mapper.Map<VariableTable>(dto.VariableTable);
            variableTable.DeviceId = device.Id;
            await _repoManager.VariableTables.AddAsync(variableTable);

            var menu = _mapper.Map<Menu>(dto.Menu);
            menu.TargetId = device.Id;
            await _repoManager.Menus.AddAsync(menu);

            await _repoManager.CommitAsync();

            return device.Id;
        }
        catch (Exception ex)
        {
            await _repoManager.RollbackAsync();
            // 可以在此记录日志
            throw new ApplicationException("创建设备时发生错误，操作已回滚。", ex);
        }
    }
}
```

## 5. 依赖注入配置 (`App.xaml.cs`)

在WPF项目中注册 `IRepositoryManager`。

```csharp
// 文件: DMS.WPF/App.xaml.cs
private void ConfigureServices(IServiceCollection services)
{
    // ...

    // 注册SqlSugar客户端 (Singleton)
    services.AddSingleton<ISqlSugarClient>(provider => {
        // ...数据库连接配置...
        return new SqlSugarClient(new ConnectionConfig() { ... });
    });

    // 注册仓储管理器 (Scoped 或 Transient)
    // 对于WPF，每个业务流程使用一个新的实例是安全的，因此用Transient
    services.AddTransient<IRepositoryManager, RepositoryManager>();

    // 注册应用服务
    services.AddTransient<IDeviceAppService, DeviceAppService>();

    // 注意：不再需要单独注册每个仓储
}
```
