EFCore中如何删除自引用实体并递归删除所有层级的子文件夹?
问题原因说明
你之前配置的DeleteBehavior.ClientCascade仅会对已经加载到当前DbContext上下文中的关联实体执行级联删除,你只Include了一级ChildFolders,所以上下文里只有一级子文件夹,自然只能删除一级,就算多次调用ThenInclude也只能覆盖固定层级,无法适配无限层级的场景。
解决方案
方案1:递归加载所有后代后批量删除(纯EF Core实现)
该方案不需要写原生SQL,可适配所有EF Core支持的数据库,适合文件夹层级不深、数据量不大的场景。
首先实现递归获取所有后代文件夹ID的辅助方法:
private async Task<List<string>> GetAllDescendantFolderIds(string parentId) { // 先查当前父级的直接子文件夹ID var childIds = await _db.Folders .Where(f => f.ParentFolderId == parentId) .Select(f => f.Id) .ToListAsync(); var allDescendantIds = new List<string>(childIds); // 递归查询每个子文件夹的后代 foreach (var childId in childIds) { allDescendantIds.AddRange(await GetAllDescendantFolderIds(childId)); } return allDescendantIds; }
修改删除方法如下:
public async Task DeleteFolder(string folderId) { var folder = await _db.Folders.FindAsync(folderId); // 文件夹不存在或为根目录时直接返回 if (folder == null || folder.ParentFolderId == null) return; // 获取所有后代文件夹ID,再把当前文件夹加入删除列表 var allToDeleteIds = await GetAllDescendantFolderIds(folderId); allToDeleteIds.Add(folderId); // 查询所有要删除的文件夹实体,批量标记删除 var allToDeleteFolders = await _db.Folders .Where(f => allToDeleteIds.Contains(f.Id)) .ToListAsync(); _db.Folders.RemoveRange(allToDeleteFolders); await _db.SaveChangesAsync(); }
方案2:数据库端递归CTE删除(高性能实现)
该方案直接在数据库层执行递归删除,无需将实体加载到内存,性能极高,适合数据量大、层级深的场景,仅需要根据你使用的数据库调整CTE语法即可,以下是SQL Server的示例:
public async Task DeleteFolder(string folderId) { var folder = await _db.Folders.FindAsync(folderId); if (folder == null || folder.ParentFolderId == null) return; // 递归CTE查询所有要删除的文件夹ID,直接执行删除 var deleteSql = @" WITH RecursiveFolders AS ( SELECT Id FROM Folders WHERE Id = @FolderId UNION ALL SELECT f.Id FROM Folders f INNER JOIN RecursiveFolders rf ON f.ParentFolderId = rf.Id ) DELETE FROM Folders WHERE Id IN (SELECT Id FROM RecursiveFolders) "; await _db.Database.ExecuteSqlInterpolatedAsync($"EXEC {deleteSql} @FolderId = {folderId}"); }
内容的提问来源于stack exchange,提问作者M. Akar
相关产品推荐
相关产品推荐

