# 02. 核心领域模型与接口

本文档定义了 `DMS.Core` 项目的最终设计，它是整个系统的基石，包含所有业务实体和核心接口的定义。

## 1. 目录结构

```
DMS.Core/
├── Enums/
│   ├── PollLevelType.cs
│   ├── ProtocolType.cs
│   └── SignalType.cs
├── Models/
│   ├── Device.cs
│   ├── MqttServer.cs
│   ├── Variable.cs
│   ├── VariableMqttAlias.cs
│   └── VariableTable.cs
├── Interfaces/
│   ├── IRepositoryManager.cs
│   ├── IBaseRepository.cs
│   ├── IDeviceRepository.cs
│   ├── IMqttServerRepository.cs
│   ├── IVariableMqttAliasRepository.cs
│   ├── IVariableRepository.cs
│   └── IVariableTableRepository.cs
└── DMS.Core.csproj
```

## 2. 核心接口

### 2.1. `IRepositoryManager.cs` (工作单元模式)

*   **设计思路**：实现工作单元（Unit of Work, UoW）模式。它作为所有仓储的统一入口，并管理数据库事务。所有通过 `IRepositoryManager` 获取的仓储实例都共享同一个数据库上下文和事务。
*   **优势**：
    *   **原子性**：确保跨多个仓储的操作（如创建设备、变量表和菜单）要么全部成功，要么全部失败，维护数据一致性。
    *   **简化事务管理**：应用层无需直接与数据库事务API交互，只需调用 `BeginTransaction()`, `CommitAsync()`, `RollbackAsync()`。
    *   **解耦**：应用层不直接依赖具体的仓储实现，而是依赖于 `IRepositoryManager` 这一抽象。
    *   **资源优化**：在单个业务操作中，所有仓储共享同一个数据库连接，减少了连接开销。
*   **劣势/权衡**：
    *   **复杂性增加**：相比于直接使用仓储，引入UoW模式增加了额外的抽象层和概念。
    *   **仓储依赖**：`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>
    IVariableRepository Variables { get; }

    /// <summary>
    /// 获取MQTT服务器仓储的实例。
    /// </summary>
    IMqttServerRepository MqttServers { get; }

    /// <summary>
    /// 获取变量MQTT别名仓储的实例。
    /// </summary>
    IVariableMqttAliasRepository VariableMqttAliases { get; }

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

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

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

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

### 2.2. 仓储接口 (`IBaseRepository.cs`, `IDeviceRepository.cs` 等)

*   **设计思路**：采用仓储（Repository）模式，为每个聚合根（或主要实体）定义一个数据访问接口。这些接口定义了对领域对象集合的操作，隐藏了底层数据存储的细节。
*   **优势**：
    *   **解耦**：将业务逻辑与数据访问技术（如SqlSugar、EF Core）分离，业务层不关心数据如何存储和检索。
    *   **可测试性**：可以轻松地为仓储接口创建Mock或Stub实现，便于单元测试业务逻辑。
    *   **领域驱动**：接口方法命名应反映领域语言，而不是数据库操作（如 `GetActiveDevicesWithDetailsAsync` 而非 `SelectFromDevicesJoinVariableTables`）。
*   **劣势/权衡**：
    *   **抽象开销**：对于简单的CRUD操作，引入仓储模式可能增加一些代码量和抽象层级。
    *   **过度抽象**：如果每个查询都定义一个方法，接口会变得非常庞大。需要权衡通用查询和特定业务查询。

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

/// <summary>
/// 提供泛型数据访问操作的基础仓储接口。
/// </summary>
/// <typeparam name="T">领域模型的类型。</typeparam>
public interface IBaseRepository<T> where T : class
{
    /// <summary>
    /// 异步根据ID获取单个实体。
    /// </summary>
    /// <param name="id">实体的主键ID。</param>
    /// <returns>找到的实体，如果不存在则返回null。</returns>
    Task<T> GetByIdAsync(int id);

    /// <summary>
    /// 异步获取所有实体。
    /// </summary>
    /// <returns>所有实体的列表。</returns>
    Task<List<T>> GetAllAsync();

    /// <summary>
    /// 异步添加一个新实体。
    /// </summary>
    /// <param name="entity">要添加的实体。</param>
    Task AddAsync(T entity);

    /// <summary>
    /// 异步更新一个已存在的实体。
    /// </summary>
    /// <param name="entity">要更新的实体。</param>
    Task UpdateAsync(T entity);

    /// <summary>
    /// 异步根据ID删除一个实体。
    /// </summary>
    /// <param name="id">要删除的实体的主键ID。</param>
    Task DeleteAsync(int id);
}

// 文件: DMS.Core/Interfaces/IDeviceRepository.cs
using DMS.Core.Models;

namespace DMS.Core.Interfaces;

/// <summary>
/// 继承自IBaseRepository，提供设备相关的特定数据查询功能。
/// </summary>
public interface IDeviceRepository : IBaseRepository<Device>
{
    /// <summary>
    /// 异步获取所有激活的设备，并级联加载其下的变量表和变量。
    /// 这是后台轮询服务需要的主要数据。
    /// </summary>
    /// <returns>包含完整层级结构的激活设备列表。</returns>
    Task<List<Device>> GetActiveDevicesWithDetailsAsync(ProtocolType protocol);

    /// <summary>
    /// 异步根据设备ID获取设备及其所有详细信息（变量表、变量、MQTT别名等）。
    /// </summary>
    /// <param name="deviceId">设备ID。</param>
    /// <returns>包含详细信息的设备对象。</returns>
    Task<Device> GetDeviceWithDetailsAsync(int deviceId);
}
```

## 3. 核心领域模型

*   **设计思路**：领域模型是业务规则和数据的核心载体。它们是贫血模型（Anemic Domain Model），主要包含数据属性，行为逻辑则由应用服务和领域服务（如果需要）处理。模型之间通过导航属性（如 `List<VariableTable>`）建立关系，反映业务实体间的关联。
*   **优势**：
    *   **清晰反映业务**：模型结构直接对应业务概念，易于理解和沟通。
    *   **可持久化**：模型可以直接或通过映射转换为数据库实体进行持久化。
    *   **技术中立**：不包含任何与特定技术（如数据库、UI）相关的代码。
*   **劣势/权衡**：
    *   **贫血模型争议**：一些DDD（领域驱动设计）倡导者认为贫血模型将数据和行为分离，导致领域逻辑分散。但在CRUD为主的应用中，这种简单性通常是可接受的。
    *   **对象图管理**：当模型之间存在复杂关系时，加载完整的对象图可能需要复杂的查询和映射。

### `Device.cs`

```csharp
// 文件: DMS.Core/Models/Device.cs
/// <summary>
/// 代表一个可管理的物理或逻辑设备。
/// </summary>
public class Device
{
    /// <summary>
    /// 唯一标识符。
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 设备名称，用于UI显示和识别。
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 设备使用的通信协议。
    /// </summary>
    public ProtocolType Protocol { get; set; }

    /// <summary>
    /// 设备的IP地址。
    /// </summary>
    public string IpAddress { get; set; }

    /// <summary>
    /// 设备的通信端口号。
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// S7 PLC的机架号。
    /// </summary>
    public int Rack { get; set; }

    /// <summary>
    /// S7 PLC的槽号。
    /// </summary>
    public int Slot { get; set; }

    /// <summary>
    /// 指示此设备是否处于激活状态。只有激活的设备才会被轮询。
    /// </summary>
    public bool IsActive { get; set; }

    /// <summary>
    /// 此设备包含的变量表集合。
    /// </summary>
    public List<VariableTable> VariableTables { get; set; } = new();
}
```

### `Variable.cs`

```csharp
// 文件: DMS.Core/Models/Variable.cs
/// <summary>
/// 核心数据点，代表从设备读取的单个值。
/// </summary>
public class Variable
{
    /// <summary>
    /// 唯一标识符。
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 变量名。
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 在设备中的地址 (例如: DB1.DBD0, M100.0)。
    /// </summary>
    public string Address { get; set; }

    /// <summary>
    /// 变量的数据类型。
    /// </summary>
    public SignalType DataType { get; set; }

    /// <summary>
    /// 变量的轮询级别，决定了其读取频率。
    /// </summary>
    public PollLevelType PollLevel { get; set; }

    /// <summary>
    /// 指示此变量是否处于激活状态。
    /// </summary>
    public bool IsActive { get; set; }

    /// <summary>
    /// 所属变量表的ID。
    /// </summary>
    public int VariableTableId { get; set; }

    /// <summary>
    /// 所属变量表的导航属性。
    /// </summary>
    public VariableTable VariableTable { get; set; }

    /// <summary>
    /// 此变量的所有MQTT发布别名关联。一个变量可以关联多个MQTT服务器，每个关联可以有独立的别名。
    /// </summary>
    public List<VariableMqttAlias> MqttAliases { get; set; } = new();

    /// <summary>
    /// 存储从设备读取到的最新值。此属性不应持久化到数据库，仅用于运行时。
    /// </summary>
    [System.ComponentModel.DataAnnotations.Schema.NotMapped] // 标记此属性不映射到数据库
    public object DataValue { get; set; }
}
```

### `VariableMqttAlias.cs`

```csharp
// 文件: DMS.Core/Models/VariableMqttAlias.cs
/// <summary>
/// 领域模型：代表一个变量到一个MQTT服务器的特定关联，包含专属别名。
/// 这是一个关联实体，用于解决多对多关系中需要额外属性（别名）的问题。
/// </summary>
public class VariableMqttAlias
{
    /// <summary>
    /// 唯一标识符。
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 关联的变量ID。
    /// </summary>
    public int VariableId { get; set; }

    /// <summary>
    /// 关联的MQTT服务器ID。
    /// </summary>
    public int MqttServerId { get; set; }

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

    /// <summary>
    /// 关联的变量导航属性。
    /// </summary>
    public Variable Variable { get; set; }

    /// <summary>
    /// 关联的MQTT服务器导航属性。
    /// </summary>
    public MqttServer MqttServer { get; set; }
}
```

## 4. 核心枚举

*   **设计思路**：使用C#枚举来表示业务中固定的、有限的分类，如协议类型、信号类型、轮询级别。这提供了类型安全和代码可读性。
*   **优势**：
    *   **类型安全**：避免使用魔术字符串或整数，减少运行时错误。
    *   **代码可读性**：提高代码的自解释性。
    *   **易于维护**：所有可能的值集中管理，修改方便。
*   **劣势/权衡**：
    *   **扩展性**：如果枚举值需要频繁变动或由用户定义，则枚举可能不是最佳选择，可能需要考虑数据库驱动的查找表。
    *   **持久化**：枚举在数据库中通常存储为整数或字符串，需要注意映射和转换。

### `PollLevelType.cs`

```csharp
// 文件: DMS.Core/Enums/PollLevelType.cs
/// <summary>
/// 定义了变量的轮询级别，决定了其读取频率。
/// </summary>
public enum PollLevelType
{
    /// <summary>
    /// 不进行轮询。
    /// </summary>
    Off,

    /// <summary>
    /// 高频轮询（例如：200ms）。
    /// </summary>
    High,

    /// <summary>
    /// 中频轮询（例如：1000ms）。
    /// </summary>
    Medium,

    /// <summary>
    /// 低频轮询（例如：5000ms）。
    /// </summary>
    Low
}
```

### `ProtocolType.cs`

```csharp
// 文件: DMS.Core/Enums/ProtocolType.cs
/// <summary>
/// 定义了设备支持的通信协议类型。
/// </summary>
public enum ProtocolType
{
    /// <summary>
    /// Siemens S7 通信协议。
    /// </summary>
    S7,

    /// <summary>
    /// OPC UA (Unified Architecture) 协议。
    /// </summary>
    OpcUa,

    /// <summary>
    /// Modbus TCP 协议。
    /// </summary>
    ModbusTcp
}
```

### `SignalType.cs`

```csharp
// 文件: DMS.Core/Enums/SignalType.cs
/// <summary>
/// 定义了变量支持的数据类型。
/// </summary>
public enum SignalType
{
    /// <summary>
    /// 布尔值 (true/false)。
    /// </summary>
    Boolean,

    /// <summary>
    /// 字节 (8-bit 无符号整数)。
    /// </summary>
    Byte,

    /// <summary>
    /// 16位有符号整数。
    /// </summary>
    Int16,

    /// <summary>
    /// 32位有符号整数。
    /// </summary>
    Int32,

    /// <summary>
    /// 单精度浮点数。
    /// </summary>
    Float,

    /// <summary>
    /// 字符串。
    /// </summary>
    String
}
```