如何在C# ASP.NET中正确映射关联查询结果到DTO?
C# ASP.NET Web API关联查询字段映射问题
我正在开发一个C# ASP.NET简易Web API,需要对数据库执行关联查询,但查询返回的部分字段(如DefaultArtefactName)不存在于现有的实体模型中,导致无法完成映射。
现有实体模型:
public class Artefact { public Guid Id { get; set; } public string Status { get; set; } public Guid Completed_By { get; set; } public DateTime Completed_At { get; set; } public Guid PhaseId { get; set; } public int DefaultArtefactId { get; set; } }
public class DefaultArtefact { public int Id { get; set; } public string Name { get; set; } public string Artefact_Type { get; set; } public string Phase { get; set; } }
关联查询方法:
public async Task<IEnumerable<Artefact>> GetArtefactsFromProject(Guid projectId) { const string query = @"SELECT a.Id, a.Status, a.Completed_By, a.Completed_At, da.Name AS DefaultArtefactName, da.Artefact_Type FROM artefact a JOIN defaultartefacts da ON a.DefaultArtefactId = da.Id JOIN Phase ph ON a.PhaseId = ph.Id JOIN Project p ON ph.ProjectId = p.Id WHERE p.Id = @ProjectId"; using var connection = CreateConnection(); return await connection.QueryAsync<Artefact>(query, new { ProjectId = projectId}); }
当前模型与数据库表一一对应,我希望通过关联查询同时获取Artefact信息和DefaultArtefact的名称,但由于Artefact模型没有DefaultArtefactName字段,无法完成映射。我纠结的点在于:是为每个关联查询新建对应的模型,还是使用DAL中现有方法先遍历Artefacts,通过ID获取名称后在业务逻辑层映射到包含所需字段的新DTO?
两种方案分析与建议
方案1:为关联查询新建专用模型(或DTO)
- 做法:创建包含查询返回所有字段的类,作为查询的映射目标,比如:
修改查询方法的返回类型:public class ArtefactWithDefaultName { public Guid Id { get; set; } public string Status { get; set; } public Guid Completed_By { get; set; } public DateTime Completed_At { get; set; } public Guid PhaseId { get; set; } public int DefaultArtefactId { get; set; } public string DefaultArtefactName { get; set; } public string Artefact_Type { get; set; } }public async Task<IEnumerable<ArtefactWithDefaultName>> GetArtefactsFromProject(Guid projectId) { const string query = @"SELECT a.Id, a.Status, a.Completed_By, a.Completed_At, da.Name AS DefaultArtefactName, da.Artefact_Type FROM artefact a JOIN defaultartefacts da ON a.DefaultArtefactId = da.Id JOIN Phase ph ON a.PhaseId = ph.Id JOIN Project p ON ph.ProjectId = p.Id WHERE p.Id = @ProjectId"; using var connection = CreateConnection(); return await connection.QueryAsync<ArtefactWithDefaultName>(query, new { ProjectId = projectId}); } - 优势:
- 一次数据库查询完成所有数据获取,避免N+1查询问题(遍历Artefact再逐个查DefaultArtefact会触发多次数据库请求),性能更优。
- 模型与查询结果直接对应,映射逻辑清晰,维护成本低。
- 劣势:如果有多个不同的关联查询场景,会新增多个专用模型,类数量增多。
方案2:先查Artefact再补全名称(业务层映射)
- 做法:先用现有DAL方法获取所有Artefact,收集所有
DefaultArtefactId后批量查询对应的名称,最后在业务层映射到目标DTO:// DAL新增批量查询方法 public async Task<Dictionary<int, string>> GetDefaultArtefactNamesByIds(IEnumerable<int> ids) { const string query = "SELECT Id, Name FROM defaultartefacts WHERE Id IN @Ids"; using var connection = CreateConnection(); var result = await connection.QueryAsync<(int Id, string Name)>(query, new { Ids = ids }); return result.ToDictionary(x => x.Id, x => x.Name); } // 业务层逻辑 public async Task<IEnumerable<ArtefactDto>> GetArtefactsWithNames(Guid projectId) { var artefacts = await _artefactDal.GetArtefactsFromProject(projectId); var defaultIds = artefacts.Select(a => a.DefaultArtefactId).Distinct(); var nameMap = await _artefactDal.GetDefaultArtefactNamesByIds(defaultIds); return artefacts.Select(a => new ArtefactDto { Id = a.Id, Status = a.Status, Completed_By = a.Completed_By, Completed_At = a.Completed_At, PhaseId = a.PhaseId, DefaultArtefactId = a.DefaultArtefactId, DefaultArtefactName = nameMap.TryGetValue(a.DefaultArtefactId, out var name) ? name : null }); } - 优势:复用现有实体模型,不需要新增过多专用类。
- 劣势:需要至少两次数据库查询,性能不如一次关联查询;业务层需要额外的映射逻辑,代码量增加。
推荐方案
优先选择方案1,原因如下:
- 性能是Web API的核心指标之一,一次关联查询的效率远高于多次查询,数据量越大差距越明显。
- 专用模型的新增是合理的,它代表了业务场景下的特定数据视图,符合单一职责原则,后续维护时能清晰对应到查询场景。
- 若担心类数量过多,可以将这类专用模型统一放在
ViewModels或Dtos文件夹下分类管理,避免项目结构混乱。
内容的提问来源于stack exchange,提问作者Coder
相关产品推荐
相关产品推荐

