批量操作
本文档中引用的文件
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application.Contracts/Services/IMyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs
目录
简介
本文说明在 MyEntityNameAppService 中实现的批量删除与克隆功能的技术细节。重点分析 DeleteBatchAsync 和 CloneAsync 方法的内部逻辑,包括如何通过仓储接口 IMyEntityNameRepository 批量获取实体、处理事务一致性以及记录操作日志。同时探讨克隆过程中核心属性的复制机制、新唯一标识的生成方式,以及审计字段(如创建人、创建时间)的正确重置。此外,还讨论了批量操作的性能优化策略,如分批处理、异步执行和数据库批量支持,并提供调用示例和异常处理建议。
核心组件分析
本节分析实现批量操作的核心类和接口。
图示来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application.Contracts/Services/IMyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application.Contracts/Services/IMyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs
批量删除功能实现
DeleteManyAsync 方法实现了对多个实体的批量删除功能。该方法接收一个 IEnumerable<Guid> 类型的 ID 集合,遍历每个 ID 并调用 DeleteAsync 方法进行删除。
该方法虽然名为“批量”,但实际上是通过循环逐个删除实体,而非使用数据库层面的批量删除操作。这意味着每次删除都会触发一次数据库事务,可能影响性能。然而,这种方式能够更好地控制错误处理——如果某个删除失败,可以捕获异常并决定是否继续后续删除。
图示来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
克隆功能实现
CloneAsync 方法实现了实体的克隆功能。其核心逻辑如下:
- 获取当前最大排序值
sort,用于为新克隆的实体分配连续的排序号。 - 遍历传入的 ID 列表,通过
FindAsync获取原始实体。 - 对每个实体,生成新的名称:原始名称 + 克隆标记(由
MyEntityNameConsts.CloneTag定义),并检查新名称是否已存在,若存在则追加更多标记以确保唯一性。 - 调用实体的
Clone方法创建新实例,传入新的 GUID、新名称和递增的排序号。 - 通过
InsertAsync将新实体插入数据库。
克隆过程中,MyEntityName 类的 Clone 方法仅复制了核心业务属性(Code、Name、Remark、Sort),而审计字段(如 CreatorId、CreationTime)由基类 FullAuditedAggregateRoot<Guid> 在插入时自动重置为当前用户和当前时间,确保了审计信息的正确性。
图示来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs
事务一致性与操作日志
尽管 DeleteManyAsync 和 CloneAsync 方法本身未显式使用事务包装,但由于它们运行在 ABP 框架的上下文中,默认情况下每个仓储操作(如 InsertAsync、DeleteAsync)都会在独立的事务中执行,保证了单个操作的原子性。
对于 CloneAsync,虽然每个插入是独立事务,但方法内部通过顺序执行确保了逻辑上的连续性。若需保证所有克隆操作的完全原子性(即全部成功或全部失败),应考虑在应用服务层使用 IUnitOfWorkManager 显式开启一个跨多个操作的事务。
操作日志方面,ABP 框架的审计功能(通过继承 FullAuditedAggregateRoot<Guid>)自动记录了每个实体的创建人、创建时间、最后修改人和最后修改时间。这些信息在克隆时被正确重置,体现了框架对审计字段的良好支持。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs
性能优化策略
当前实现存在以下性能优化空间:
- 批量数据库操作:
DeleteManyAsync使用循环删除,可优化为使用原生 SQL 或 EF Core 的ExecuteDeleteAsync方法进行真正的批量删除,减少数据库往返次数。 - 并行处理:对于非顺序依赖的操作(如独立实体的克隆),可考虑使用
Parallel.ForEachAsync或Task.WhenAll并行执行,提升吞吐量。 - 分批处理:当处理大量实体时,应实现分页或分批机制,避免一次性加载过多数据导致内存溢出。
- 缓存利用:频繁查询最大排序号或检查名称是否存在时,可引入缓存层减少数据库压力。
目前的 EfCoreMyEntityNameRepository 实现了基本的查询优化,如使用 Specification 模式和动态排序,但未提供批量操作的专用接口。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs
调用示例与异常处理
调用示例
// 批量删除
var idsToDelete = new List<Guid> { id1, id2, id3 };
await myEntityNameAppService.DeleteManyAsync(idsToDelete);
// 批量克隆
var idsToClone = new List<Guid> { id1, id2 };
var clonedEntities = await myEntityNameAppService.CloneAsync(idsToClone);
异常处理
- 名称冲突:
CloneAsync内部通过循环检查名称唯一性,避免NameAlreadyExists异常。 - 输入验证:
CheckCreateOrUpdateDtoAsync方法确保输入数据的有效性,防止空值或超长字符串。 - 并发冲突:实体更新时通过
SetConcurrencyStampIfNotNull支持乐观锁,防止并发修改覆盖。
建议在调用端捕获 UserFriendlyException 并向用户展示友好提示。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
高并发环境下的挑战与解决方案
挑战
- 排序冲突:多个用户同时调整排序可能导致
AdjustSortAsync计算出错。 - 名称竞争:高并发克隆时,名称唯一性检查可能存在时间窗口,导致重复名称。
- 性能瓶颈:循环删除/插入在高负载下响应缓慢。
解决方案
- 分布式锁:对排序调整等敏感操作加锁,确保串行执行。
- 数据库约束:在数据库层面添加唯一索引,作为最后一道防线防止名称重复。
- 异步队列:将批量操作放入后台任务队列(如 Hangfire),避免阻塞主线程。
- 乐观锁增强:在
AdjustSortAsync中加入版本号检查,提高并发安全性。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
结论
MyEntityNameAppService 中的批量删除与克隆功能实现了基本的业务需求,利用 ABP 框架的仓储模式和审计功能确保了数据一致性和可追溯性。克隆逻辑合理,能正确处理唯一标识和审计字段的重置。然而,当前的批量删除为逐条执行,存在性能瓶颈。建议在高并发场景下引入批量数据库操作、并行处理和分布式锁机制以提升性能和数据安全性。未来可考虑将 DeleteManyAsync 优化为真正的批量操作,并为 CloneAsync 提供事务性保证。