# 04. 基础设施层 - 仓储与事务

本文档详细设计了 `DMS.Infrastructure` 层中负责数据持久化和事务管理的部分。

## 1. 目录结构

```
DMS.Infrastructure/
├── Data/
│   └── RepositoryManager.cs
├── Entities/
│   ├── DbDevice.cs
│   ├── DbVariable.cs
│   ├── DbVariableMqttAlias.cs
│   └── ... (所有数据库表对应的实体)
├── Repositories/
│   ├── BaseRepository.cs
│   ├── DeviceRepository.cs
│   └── ... (所有仓储接口的实现)
└── ...
```

## 2. 数据库实体

### 2.1. 设计思路与考量

*   **职责**：数据库实体（Entities）是与数据库表结构直接对应的C#类。它们主要用于ORM（对象关系映射）框架（如SqlSugar）进行数据映射。
*   **与领域模型的区别**：数据库实体可能包含一些与持久化相关的特性（如 `[SugarColumn]`），或者为了数据库优化而进行的反范式设计。它们与 `DMS.Core` 中的领域模型通过AutoMapper进行映射转换。

### 2.2. 设计优势

*   **ORM友好**：直接映射数据库表结构，简化了数据持久化操作。
*   **隔离持久化细节**：将数据库特有的细节（如列名、主键、外键定义）封装在这一层，不暴露给上层。
*   **性能优化潜力**：可以根据数据库的特点进行优化（如索引、视图），而不会影响领域模型。

### 2.3. 设计劣势/权衡

*   **映射开销**：需要在数据库实体和领域模型之间进行映射，增加了代码量和运行时开销。
*   **可能与领域模型重复**：对于简单的CRUD操作，数据库实体和领域模型可能看起来非常相似，容易混淆。

### 2.4. 示例：`DbVariableMqttAlias.cs`

```csharp
// 文件: DMS.Infrastructure/Entities/DbVariableMqttAlias.cs
using SqlSugar;

namespace DMS.Infrastructure.Entities;

/// <summary>
/// 数据库实体：对应数据库中的 VariableMqttAliases 表。
/// </summary>
[SugarTable("VariableMqttAliases")]
public class DbVariableMqttAlias
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }

    /// <summary>
    /// 外键，指向 Variables 表的 Id。
    /// </summary>
    public int VariableId { get; set; }

    /// <summary>
    /// 外键，指向 MqttServers 表的 Id。
    /// </summary>
    public int MqttServerId { get; set; }

    /// <summary>
    /// 针对此特定[变量-服务器]连接的发布别名。
    /// </summary>
    public string Alias { get; set; }
}
```

## 3. 事务管理 (`RepositoryManager`)

### 3.1. 设计思路与考量

*   **模式**：`RepositoryManager` 是工作单元（Unit of Work, UoW）模式的具体实现。它作为所有仓储的统一入口，并管理数据库事务。
*   **共享上下文**：所有通过 `RepositoryManager` 获取的仓储实例都共享同一个 `ISqlSugarClient` 实例，从而确保它们在同一个数据库会话和事务中操作。
*   **生命周期**：`RepositoryManager` 通常被注册为 `Scoped` 或 `Transient` 生命周期，确保每个业务操作都有一个独立的工作单元。

### 3.2. 设计优势

*   **原子性**：确保跨多个仓储的操作（如创建设备、变量表和菜单）要么全部成功，要么全部失败，维护数据一致性。
*   **简化事务管理**：应用层无需直接与数据库事务API交互，只需调用 `BeginTransaction()`, `CommitAsync()`, `RollbackAsync()`。
*   **解耦**：应用层不直接依赖具体的仓储实现，而是依赖于 `IRepositoryManager` 这一抽象。
*   **资源优化**：在单个业务操作中，所有仓储共享同一个数据库连接，减少了连接开销。

### 3.3. 设计劣势/权衡

*   **复杂性增加**：相比于直接使用仓储，引入UoW模式增加了额外的抽象层和概念。
*   **仓储依赖**：`IRepositoryManager` 接口需要列出所有它管理的仓储，当新增仓储时，需要修改此接口。
*   **懒加载**：为了避免在 `RepositoryManager` 构造时就创建所有仓储实例的开销，通常会使用懒加载（`Lazy<T>`），这增加了少量复杂性。

### 3.4. 示例：`RepositoryManager.cs`

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

namespace DMS.Infrastructure.Data;

/// <summary>
/// IRepositoryManager 的 SqlSugar 实现，管理所有仓储实例和数据库事务。
/// </summary>
public class RepositoryManager : IRepositoryManager
{
    private readonly ISqlSugarClient _db;
    private readonly Lazy<IDeviceRepository> _lazyDevices;
    private readonly Lazy<IVariableTableRepository> _lazyVariableTables;
    private readonly Lazy<IVariableRepository> _lazyVariables;
    private readonly Lazy<IMqttServerRepository> _lazyMqttServers;
    private readonly Lazy<IVariableMqttAliasRepository> _lazyVariableMqttAliases;
    private readonly Lazy<IMenuRepository> _lazyMenus;

    /// <summary>
    /// 构造函数，通过依赖注入获取 SqlSugar 客户端实例。
    /// </summary>
    public RepositoryManager(ISqlSugarClient db)
    {
        _db = db;
        // 使用 Lazy<T> 实现仓储的懒加载，确保它们在第一次被访问时才创建。
        // 所有仓储都共享同一个 _db 实例，以保证事务的一致性。
        _lazyDevices = new Lazy<IDeviceRepository>(() => new DeviceRepository(_db));
        _lazyVariableTables = new Lazy<IVariableTableRepository>(() => new VariableTableRepository(_db));
        _lazyVariables = new Lazy<IVariableRepository>(() => new VariableRepository(_db));
        _lazyMqttServers = new Lazy<IMqttServerRepository>(() => new MqttServerRepository(_db));
        _lazyVariableMqttAliases = new Lazy<IVariableMqttAliasRepository>(() => new VariableMqttAliasRepository(_db));
        _lazyMenus = new Lazy<IMenuRepository>(() => new MenuRepository(_db));
    }

    public IDeviceRepository Devices => _lazyDevices.Value;
    public IVariableTableRepository VariableTables => _lazyVariableTables.Value;
    public IVariableRepository Variables => _lazyVariables.Value;
    public IMqttServerRepository MqttServers => _lazyMqttServers.Value;
    public IVariableMqttAliasRepository VariableMqttAliases => _lazyVariableMqttAliases.Value;
    public IMenuRepository Menus => _lazyMenus.Value;

    /// <summary>
    /// 开始一个新的数据库事务。
    /// </summary>
    public void BeginTransaction()
    {
        _db.BeginTran();
    }

    /// <summary>
    /// 异步提交当前事务中的所有变更。
    /// </summary>
    public async Task CommitAsync()
    {
        await _db.CommitTranAsync();
    }

    /// <summary>
    /// 异步回滚当前事务中的所有变更。
    /// </summary>
    public async Task RollbackAsync()
    {
        await _db.RollbackTranAsync();
    }

    /// <summary>
    /// 释放 SqlSugar 客户端资源。通常由 DI 容器管理。
    /// </summary>
    public void Dispose()
    {
        _db.Dispose();
    }
}
```

## 4. 仓储实现

### 4.1. 设计思路与考量

*   **职责**：仓储（Repository）是数据访问层的一部分，负责实现 `DMS.Core` 中定义的仓储接口。它们使用ORM框架（如SqlSugar）与数据库进行实际交互，将数据库实体转换为领域模型，反之亦然。
*   **通用性**：可以有一个 `BaseRepository` 来实现通用的CRUD操作，减少重复代码。
*   **特定查询**：为每个仓储接口实现其特有的查询方法（如 `GetActiveDevicesWithDetailsAsync`）。

### 4.2. 设计优势

*   **实现细节封装**：将ORM框架和数据库查询的细节封装在仓储内部，上层无需关心。
*   **可替换性**：如果需要更换ORM框架或数据库类型，只需修改仓储实现，而无需修改应用层或核心层。
*   **性能优化点**：可以在仓储层进行查询优化（如使用 `Include` 或 `Join` 预加载关联数据）。

### 4.3. 设计劣势/权衡

*   **代码量**：即使有 `BaseRepository`，每个仓储仍然需要一些样板代码。
*   **复杂查询**：对于非常复杂的、跨多个聚合根的查询，有时仓储模式会显得笨拙，可能需要引入查询对象（Query Object）模式。

### 4.4. 示例：`BaseRepository.cs`

```csharp
// 文件: DMS.Infrastructure/Repositories/BaseRepository.cs
using AutoMapper;
using DMS.Core.Interfaces;
using SqlSugar;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace DMS.Infrastructure.Repositories;

/// <summary>
/// 仓储基类，实现了 IBaseRepository 接口的通用 CRUD 操作。
/// </summary>
/// <typeparam name="TDomain">领域模型类型。</typeparam>
/// <typeparam name="TDbEntity">数据库实体类型。</typeparam>
public abstract class BaseRepository<TDomain, TDbEntity> : IBaseRepository<TDomain>
    where TDomain : class
    where TDbEntity : class, new()
{
    protected readonly ISqlSugarClient _db;
    protected readonly IMapper _mapper;

    public BaseRepository(ISqlSugarClient db, IMapper mapper)
    {
        _db = db;
        _mapper = mapper;
    }

    public virtual async Task AddAsync(TDomain entity)
    {
        var dbEntity = _mapper.Map<TDbEntity>(entity);
        await _db.Insertable(dbEntity).ExecuteCommandAsync();
        // 映射回ID，如果实体是自增ID
        _mapper.Map(dbEntity, entity);
    }

    public virtual async Task DeleteAsync(int id)
    {
        await _db.Deleteable<TDbEntity>(id).ExecuteCommandAsync();
    }

    public virtual async Task<List<TDomain>> GetAllAsync()
    {
        var dbEntities = await _db.Queryable<TDbEntity>().ToListAsync();
        return _mapper.Map<List<TDomain>>(dbEntities);
    }

    public virtual async Task<TDomain> GetByIdAsync(int id)
    {
        var dbEntity = await _db.Queryable<TDbEntity>().InSingleAsync(id);
        return _mapper.Map<TDomain>(dbEntity);
    }

    public virtual async Task UpdateAsync(TDomain entity)
    {
        var dbEntity = _mapper.Map<TDbEntity>(entity);
        await _db.Updateable(dbEntity).ExecuteCommandAsync();
    }
}
```

### 4.5. 示例：`DeviceRepository.cs`

```csharp
// 文件: DMS.Infrastructure/Repositories/DeviceRepository.cs
using AutoMapper;
using DMS.Core.Interfaces;
using DMS.Core.Models;
using DMS.Infrastructure.Entities;
using SqlSugar;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DMS.Infrastructure.Repositories;

/// <summary>
/// 设备仓储的具体实现。
/// </summary>
public class DeviceRepository : BaseRepository<Device, DbDevice>, IDeviceRepository
{
    public DeviceRepository(ISqlSugarClient db, IMapper mapper) : base(db, mapper) { }

    /// <summary>
    /// 异步获取所有激活的S7设备，并级联加载其下的变量表和变量。
    /// </summary>
    public async Task<List<Device>> GetActiveDevicesWithDetailsAsync(ProtocolType protocol)
    {
        var dbDevices = await _db.Queryable<DbDevice>()
                                 .Where(d => d.IsActive && d.Protocol == (int)protocol)
                                 .Mapper(d => d.VariableTables, d => d.Id, d => d.VariableTables.Select(vt => vt.DeviceId).ToList())
                                 .Mapper(d => d.VariableTables.Select(vt => vt.Variables), vt => vt.Id, vt => vt.Variables.Select(v => v.VariableTableId).ToList())
                                 .ToListAsync();
        return _mapper.Map<List<Device>>(dbDevices);
    }

    /// <summary>
    /// 异步根据设备ID获取设备及其所有详细信息（变量表、变量、MQTT别名等）。
    /// </summary>
    public async Task<Device> GetDeviceWithDetailsAsync(int deviceId)
    {
        var dbDevice = await _db.Queryable<DbDevice>()
                                .Where(d => d.Id == deviceId)
                                .Mapper(d => d.VariableTables, d => d.Id, d => d.VariableTables.Select(vt => vt.DeviceId).ToList())
                                .Mapper(d => d.VariableTables.Select(vt => vt.Variables), vt => vt.Id, vt => vt.Variables.Select(v => v.VariableTableId).ToList())
                                .FirstAsync();

        if (dbDevice == null) return null;

        // 手动加载 VariableMqttAlias，因为 SqlSugar 的 Mapper 可能无法直接处理多层嵌套的关联实体
        foreach (var variableTable in dbDevice.VariableTables)
        {
            foreach (var variable in variableTable.Variables)
            {
                var dbAliases = await _db.Queryable<DbVariableMqttAlias>()
                                         .Where(a => a.VariableId == variable.Id)
                                         .Mapper(a => a.MqttServer, a => a.MqttServerId)
                                         .ToListAsync();
                variable.MqttAliases = _mapper.Map<List<VariableMqttAlias>>(dbAliases);
            }
        }

        return _mapper.Map<Device>(dbDevice);
    }
}
```