You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 11:40:24