# 软件开发文档 - 05. 事务与工作单元设计 本文档详细阐述了为确保数据一致性而引入的**工作单元模式 (Unit of Work)** 的设计与实现。我们将此模式的接口命名为 `IRepositoryManager`,以更直观地反映其职责。 ## 1. 设计目标 在复杂的业务操作中(例如,创建一个设备,同时需要创建其关联的变量表和菜单项),必须保证所有数据库操作要么全部成功,要么全部失败。这要求一个能够跨越多个仓储(Repository)的事务管理机制。 `IRepositoryManager` 的核心职责就是: * **组合操作**:将一系列独立的数据库操作组合成一个单一的、原子的工作单元。 * **事务控制**:提供统一的 `Commit` (提交) 和 `Rollback` (回滚) 方法。 * **共享上下文**:确保所有通过它访问的仓储共享同一个数据库连接和事务,这是实现原子性的前提。 ## 2. `DMS.Core` - 接口定义 我们在核心层定义 `IRepositoryManager` 接口,它作为所有仓储的容器和事务的控制器。 ```csharp // 文件: DMS.Core/Interfaces/IRepositoryManager.cs namespace DMS.Core.Interfaces; /// /// 定义了一个仓储管理器,它使用工作单元模式来组合多个仓储操作,以确保事务的原子性。 /// 实现了IDisposable,以确保数据库连接等资源能被正确释放。 /// public interface IRepositoryManager : IDisposable { /// /// 获取设备仓储的实例。 /// 所有通过此管理器获取的仓储都共享同一个数据库上下文和事务。 /// IDeviceRepository Devices { get; } /// /// 获取变量表仓储的实例。 /// IVariableTableRepository VariableTables { get; } /// /// 获取菜单仓储的实例。 /// IMenuRepository Menus { get; } // ... 此处可以添加项目中所有其他的仓储 /// /// 开始一个新的数据库事务。 /// void BeginTransaction(); /// /// 异步提交当前事务中的所有变更。 /// /// 一个表示异步操作的任务。 Task CommitAsync(); /// /// 异步回滚当前事务中的所有变更。 /// /// 一个表示异步操作的任务。 Task RollbackAsync(); } ``` ## 3. `DMS.Infrastructure` - 具体实现 在基础设施层,我们提供 `IRepositoryManager` 的 `SqlSugar` 实现。 ```csharp // 文件: DMS.Infrastructure/Data/RepositoryManager.cs using DMS.Core.Interfaces; using SqlSugar; namespace DMS.Infrastructure.Data; /// /// 使用 SqlSugar 实现的仓储管理器。 /// public class RepositoryManager : IRepositoryManager { private readonly ISqlSugarClient _db; private readonly Lazy _lazyDevices; private readonly Lazy _lazyVariableTables; private readonly Lazy _lazyMenus; public RepositoryManager(ISqlSugarClient db) { _db = db; // 使用 Lazy 实现懒加载,只有在第一次访问时才创建仓储实例 // 关键点:将当前的数据库客户端 (_db) 传递给仓储,确保它们共享事务 _lazyDevices = new Lazy(() => new DeviceRepository(_db)); _lazyVariableTables = new Lazy(() => new VariableTableRepository(_db)); _lazyMenus = new Lazy(() => 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 CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto) { // 不再需要 using 语句,因为 DI 容器会管理 RepositoryManager 的生命周期 try { _repoManager.BeginTransaction(); var device = _mapper.Map(dto.Device); await _repoManager.Devices.AddAsync(device); var variableTable = _mapper.Map(dto.VariableTable); variableTable.DeviceId = device.Id; await _repoManager.VariableTables.AddAsync(variableTable); var menu = _mapper.Map(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(provider => { // ...数据库连接配置... return new SqlSugarClient(new ConnectionConfig() { ... }); }); // 注册仓储管理器 (Scoped 或 Transient) // 对于WPF,每个业务流程使用一个新的实例是安全的,因此用Transient services.AddTransient(); // 注册应用服务 services.AddTransient(); // 注意:不再需要单独注册每个仓储 } ```