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

如何在C#中使用Dapper实现正确的一对多数据结构?

Dapper实现一对多关联查询去重及集合封装问题

我用C#结合Dapper查询unit_properties、categories、users和app_files关联表,想要实现UnitProperty与AppFile的一对多关联(单个UnitProperty对应多个UnitImages),但当前执行后返回重复的UnitProperty对象(同一unitId对应多条记录),希望修改代码让UnitImages属性成为包含多个AppFile的集合。

现有代码

string query = @"SELECT DISTINCT
         up.id as unitId,
         af.id as fileId,
         af.reference_id as referenceId,
         af.filename as fileName,
         af.filepath as filePath,
         c.id as categoryId
         u.id as userId
     FROM unit_properties up
     INNER JOIN categories c ON up.category_id = c.id
     INNER JOIN users u ON up.agent_id = u.id
     LEFT JOIN app_files af ON af.reference_id = up.id;";

var unitProperties = await connection.QueryAsync<UnitProperty, AppFile, Category, User, UnitProperty>(
    query,
    (unit, appFile, category, user) =>
    {
        unit.UnitImages = appFile;
        unit.UnitCategory = category;
        unit.UnitAgent = user;

        return unit;
    },
    splitOn: "referenceId, categoryId, userId"
);

当前返回结果(存在重复行)

[
   {
      "unitId":1,
      "unitImages":{
         "fileId":0,
         "referenceId":1,
         "fileName":"exterior1",
         "filePath":"exterior1"
      },
      "unitAgent":{
         "userId":1
      },
      "unitCategory":{
         "categoryId":1
      }
   },
   // 另一个相同unitId的重复对象
]

期望结果

{
"unitId":1,
"unitImages":[
   { 
    "fileId":0,
    "referenceId":1,
    "fileName":"exterior1",
    "filePath":"exterior1"
   },
   { 
    "fileId":1,
    "referenceId":1,
    "fileName":"exterior2",
    "filePath":"exterior2"
   }
],
"unitAgent":{
   "userId":1
},
"unitCategory":{
   "categoryId":1
}
}

代码修改建议

1. 修正SQL语法错误

原SQL中c.id as categoryId后缺少逗号,导致u.id as userId无法被正确解析,同时去掉冗余的DISTINCT(后续通过代码去重更高效):

SELECT
    up.id as unitId,
    af.id as fileId,
    af.reference_id as referenceId,
    af.filename as fileName,
    af.filepath as filePath,
    c.id as categoryId,
    u.id as userId
FROM unit_properties up
INNER JOIN categories c ON up.category_id = c.id
INNER JOIN users u ON up.agent_id = u.id
LEFT JOIN app_files af ON af.reference_id = up.id;

2. 调整实体类定义

确保UnitProperty中的UnitImages是集合类型(比如List<AppFile>):

public class UnitProperty
{
    public int unitId { get; set; }
    public List<AppFile> UnitImages { get; set; }
    public Category UnitCategory { get; set; }
    public User UnitAgent { get; set; }
}

public class AppFile
{
    public int fileId { get; set; }
    public int referenceId { get; set; }
    public string fileName { get; set; }
    public string filePath { get; set; }
}

public class Category
{
    public int categoryId { get; set; }
}

public class User
{
    public int userId { get; set; }
}

3. 使用字典聚合去重,实现一对多映射

通过Dictionary跟踪已创建的UnitProperty对象,将关联的AppFile添加到对应集合中:

var unitDict = new Dictionary<int, UnitProperty>();

await connection.QueryAsync<UnitProperty, AppFile, Category, User, UnitProperty>(
    query,
    (unit, appFile, category, user) =>
    {
        // 检查是否已存在当前unitId的对象
        if (!unitDict.TryGetValue(unit.unitId, out var existingUnit))
        {
            existingUnit = unit;
            // 初始化集合,避免空引用
            existingUnit.UnitImages = new List<AppFile>();
            existingUnit.UnitCategory = category;
            existingUnit.UnitAgent = user;
            unitDict.Add(existingUnit.unitId, existingUnit);
        }

        // 处理LEFT JOIN可能返回的null,避免添加空元素
        if (appFile != null)
        {
            existingUnit.UnitImages.Add(appFile);
        }

        return existingUnit;
    },
    // 调整splitOn顺序:从UnitProperty到AppFile的拆分点是fileId,之后是Category的categoryId,最后是User的userId
    splitOn: "fileId, categoryId, userId"
);

// 最终结果取字典中的值,已去重且聚合了所有关联的AppFile
var unitProperties = unitDict.Values.ToList();

关键说明

  • 用Dictionary<int, UnitProperty>基于unitId去重,确保每个UnitProperty仅被创建一次。
  • 初始化UnitImages集合,避免空引用异常。
  • 处理LEFT JOIN可能返回的appFile为null的情况,防止无效元素进入集合。
  • 修正splitOn参数,确保Dapper正确识别不同实体的字段边界,映射准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:32:14