DDD中Category聚合根实现与自关联关系校验问题咨询
DDD Category聚合根自关联实现修正方案
现有实现的问题
- 导航属性不全:仅声明了父分类
ParentCategory,未维护当前分类的子分类集合,无法获取当前分类下的所有子分类完成重名校验 - 方法命名错误:
AddParentCategory的实际逻辑是为当前分类添加子分类,命名与逻辑相悖,极易引发后续业务逻辑错误 - 边界异常未处理:未判断父分类为空的场景,访问
ParentCategory.Name时会触发空引用异常 - 校验逻辑缺失:现有校验仅判断了当前分类的父分类名称与新分类名是否重复,未遍历当前分类的子分类列表做重复判断
修正后实现代码
public class Category : AggregateRoot { // 维护子分类私有集合,对外仅暴露只读视图,保证聚合内数据一致性 private readonly List<Category> _subCategories = new(); public IReadOnlyList<Category> SubCategories => _subCategories.AsReadOnly(); // 支持创建无父级的顶级分类 public static Result<Category> Create(string name, Category parentCategory = null) { var result = new Result<Category>(); var nameResult = Name.Create(name); result.WithErrors(nameResult.Errors); if (result.IsFailed) return result; var category = new Category(nameResult.Value, parentCategory); result.WithValue(category); return result; } private Category() : base() { } private Category(Name name, Category parentCategory) : this() { Name = name; ParentCategory = parentCategory; ParentId = parentCategory?.Id; // 自动同步父分类的子集合 parentCategory?._subCategories.Add(this); } public Name Name { get; private set; } public virtual Category ParentCategory { get; private set; } // 冗余存储父分类ID,适配持久化查询需求 public int? ParentId { get; private set; } // 修正方法命名,符合添加子分类的实际逻辑 public Result<Category> AddSubCategory(string subCategoryName) { var result = new Result<Category>(); // 校验同父级下子分类名称是否重复 var isDuplicate = _subCategories.Any(c => c.Name.Value.Equals(subCategoryName, StringComparison.OrdinalIgnoreCase)); if (isDuplicate) { var errorMessage = string.Format(Validations.Repetitive, DataDictionary.CategoryName); result.WithError(errorMessage); return result; } var subCategoryResult = Create(subCategoryName, this); return subCategoryResult.IsFailed ? subCategoryResult.ToResult() : subCategoryResult; } // 可选扩展:修改父分类方法,内置循环引用校验 public Result ChangeParent(Category newParent) { var result = new Result(); // 校验避免循环引用 if (newParent != null && IsDescendantOf(newParent, this)) { result.WithError("不能将子分类设置为当前分类的父级,避免循环关联"); return result; } // 同步原有父分类的子集合 ParentCategory?._subCategories.Remove(this); // 更新父级关联 ParentCategory = newParent; ParentId = newParent?.Id; // 同步新父分类的子集合 newParent?._subCategories.Add(this); return result; } // 辅助方法:判断目标分类是否是当前分类的后代 private bool IsDescendantOf(Category target, Category ancestor) { var current = target; while (current.ParentCategory != null) { if (current.ParentCategory == ancestor) return true; current = current.ParentCategory; } return false; } }
补充说明
- 同父级下子分类不重名的校验逻辑放在聚合内即可,符合DDD聚合内业务规则封装原则
- 若需要全局分类名唯一,需要通过领域服务调用
ICategoryRepository查询全量分类数据完成校验,单个聚合根无法持有全局数据 - 若使用EF Core等ORM框架做持久化,需要单独配置自关联外键:将
ParentId作为外键关联到Category的主键Id,同时配置ParentCategory和_subCategories的父子映射关系
内容的提问来源于stack exchange,提问作者MrJahanbin
相关产品推荐
相关产品推荐

