如何在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
相关产品推荐
相关产品推荐

