在MariaDb中使用EntityFramework Core执行递归SQL时遇错求助
解决EF Core中自引用实体的树形结构查询问题
错误原因分析
你遇到的An item with the same key has already been added. Key: AppName错误,是因为存储过程返回的结果集中包含重复列名(比如递归查询时父节点与子节点的AppName重复输出),EF Core映射实体时无法处理重复键名导致的。
方案一:修正存储过程+DTO接收结果
1. 调整存储过程输出
修改存储过程,确保返回列名唯一,输出扁平化的树形结构数据(包含层级、父ID等信息):
CREATE PROCEDURE GET_DEPENDENCIES(IN rootId INT) BEGIN WITH RECURSIVE app_tree AS ( SELECT Id, AppName, CAST(NULL AS INT) AS ParentId, 0 AS Level FROM ApplicationModel WHERE Id = rootId UNION ALL SELECT child.Id, child.AppName, parent.Id AS ParentId, parent.Level + 1 AS Level FROM ApplicationModel child JOIN ApplicationModelApplicationModel link ON child.Id = link.SonId JOIN app_tree parent ON link.ParentId = parent.Id ) SELECT * FROM app_tree; END
2. 创建树形结构DTO
由于原始实体不匹配扁平化结果,创建专用DTO:
public class ApplicationTreeDto { public int Id { get; set; } public string AppName { get; set; } public int? ParentId { get; set; } public int Level { get; set; } public List<ApplicationTreeDto>? Children { get; set; } }
3. Service中调用并构建树形结构
// 调用存储过程获取扁平化数据 var flatData = await _dbContext.Database .SqlQueryRaw<ApplicationTreeDto>("CALL GET_DEPENDENCIES({0})", 1) .ToListAsync(); // 将扁平化数据转换为树形结构 var tree = BuildTree(flatData, 1); // 树形结构构建方法 private List<ApplicationTreeDto> BuildTree(List<ApplicationTreeDto> flatData, int rootId) { var rootNode = flatData.First(x => x.Id == rootId); var childNodes = flatData.Where(x => x.ParentId == rootId).ToList(); foreach (var child in childNodes) { child.Children = BuildTree(flatData, child.Id); } rootNode.Children = childNodes; return new List<ApplicationTreeDto> { rootNode }; }
方案二:无存储过程,EF Core+内存递归构建
适合数据量较小的场景,先加载全量数据再在内存中构建树形:
// 加载所有节点及子节点关联 var allApps = await _dbContext.ApplicationModels .Include(a => a.Son) .ToListAsync(); // 定位根节点(示例根ID为1) var root = allApps.First(a => a.Id == 1); // 如需整理为自定义树形结构,可复用方案一中的BuildTree逻辑,将allApps转换为DTO后处理
方案三:直接执行递归SQL+DTO映射
无需存储过程,直接在代码中编写递归SQL:
var rootId = 1; var recursiveSql = @" WITH RECURSIVE app_tree AS ( SELECT Id, AppName, CAST(NULL AS INT) AS ParentId, 0 AS Level FROM ApplicationModel WHERE Id = {0} UNION ALL SELECT child.Id, child.AppName, parent.Id AS ParentId, parent.Level + 1 AS Level FROM ApplicationModel child JOIN ApplicationModelApplicationModel link ON child.Id = link.SonId JOIN app_tree parent ON link.ParentId = parent.Id ) SELECT * FROM app_tree;"; var flatData = await _dbContext.Database .SqlQueryRaw<ApplicationTreeDto>(recursiveSql, rootId) .ToListAsync(); // 转换为树形结构,同方案一的BuildTree方法
关键注意事项
- 不要直接将递归SQL结果映射到原始
ApplicationModel实体:递归结果集的结构与实体导航属性不匹配,易引发列名冲突或映射错误。 - 大数据量场景优先用数据库端递归(存储过程/直接SQL),减少内存压力;小数据量可采用内存递归构建。
内容的提问来源于stack exchange,提问作者Tanc
相关产品推荐
相关产品推荐

