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

如何使用Dapper实现存储过程返回多表的一对多关系映射

Dapper存储过程多结果集一对多映射方案

写法问题分析

  • 第一种QueryAsync多映射写法不适用当前场景:
    Dapper的多映射重载是为处理单条SELECT语句返回的JOIN扁平结果设计的,只能拆分同一结果集的单行数据到多个对象,无法跨两个独立结果集做关联,所以用这个方法处理你的存储过程返回的多结果集是完全错误的。另外你第一个查询把主键别名设为了SpecID,但实体类主键属性是ID,会导致主键映射失败。
  • 第二种QueryMultiple写法逻辑正确,可做性能优化:
    你现在手动关联的思路是对的,只是循环中每次用Where过滤子项,数据量大时会有重复扫描的性能损耗,可以用ToLookup提前做分组优化。

正确实现方案

方案1:QueryMultiple 多结果集关联(推荐适配你当前存储过程)

首先修正第一个SQL的主键别名,和实体属性对应:

-- 把Spec.ID as SpecID改成Spec.ID as ID,匹配实体的ID属性
SELECT Spec.ID as ID,
       Spec.Def_Specification_GroupID,
       Spec.SpecTitle,
       Spec.HasItem,
       Spec.IsActive,
       SpecGroup.SpecGroup
FROM Def_Specification Spec
INNER JOIN Def_Specification_Group SpecGroup ON Spec.Def_Specification_GroupID = SpecGroup.ID
WHERE Spec.IsActive = 1

SELECT ID, Def_SpecificationID, SpecificationTitle 
FROM Def_Specification_Items

优化后的C#代码:

List<DefSpecificationAndGroupAndItems> specification;
using (var multi = await connection.QueryMultipleAsync("SP_DEF_SPECIFICATION_GET_ALL_BY_ACTIVE", new { Active = active },
    commandType: CommandType.StoredProcedure))
{
    var specs = (await multi.ReadAsync<DefSpecificationAndGroupAndItems>()).ToList();
    var specItems = (await multi.ReadAsync<DefSpecificationItems>()).ToList();
    
    // 提前把所有子项按父ID分组,避免循环里重复过滤
    var itemLookup = specItems.ToLookup(i => i.Def_SpecificationID);
    
    foreach (var spec in specs)
    {
        spec.DefSpecificationItems = itemLookup[spec.ID].ToList();
    }
    specification = specs;
}

方案2:多映射实现(需修改存储过程为单SELECT联查)

如果你要使用多映射,需要把存储过程改成单条SELECT返回JOIN后的扁平结果:

SELECT 
    Spec.ID as ID,
    Spec.Def_Specification_GroupID,
    Spec.SpecTitle,
    Spec.HasItem,
    Spec.IsActive,
    SpecGroup.SpecGroup,
    Item.ID,
    Item.Def_SpecificationID,
    Item.SpecificationTitle
FROM Def_Specification Spec
INNER JOIN Def_Specification_Group SpecGroup ON Spec.Def_Specification_GroupID = SpecGroup.ID
LEFT JOIN Def_Specification_Items Item ON Spec.ID = Item.Def_SpecificationID
WHERE Spec.IsActive = 1

对应的C#多映射代码,用字典去重父实体:

var specDict = new Dictionary<int, DefSpecificationAndGroupAndItems>();
await connection.QueryAsync<DefSpecificationAndGroupAndItems, DefSpecificationItems, DefSpecificationAndGroupAndItems>(
    "你的新存储过程名", 
    (spec, item) => 
    {
        // 去重父实体,避免同一规格被多次实例化
        if (!specDict.TryGetValue(spec.ID, out var existingSpec))
        {
            existingSpec = spec;
            specDict.Add(existingSpec.ID, existingSpec);
        }
        if(item != null) existingSpec.DefSpecificationItems.Add(item);
        return existingSpec;
    },
    new { Active = active },
    commandType: CommandType.StoredProcedure,
    splitOn: "ID" // 第二个ID是子项表的ID,作为拆分字段
);
var specification = specDict.Values.ToList();

内容的提问来源于stack exchange,提问作者1 VeSSuZ 1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 10:06:03