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

Dapper联表查询用ViewModel接收返回null、传object正常问题

Dapper 一对多JOIN查询映射嵌套ViewModel返回null问题

问题复现

定义的实体类

// Customer Model
public class CustomerDto
{
    public int ID { get; set; }
    public string? Name { get; set; }
}
// Customer Contact Details
public class ContactdetailsDto
{
    public int ID { get; set; }
    public string? Name { get; set; }
}

定义的ViewModel

public class CustomersViewModel
{
    public CustomerDto? CustomerDto { get; set; }
    public List<CustomerContactDetailsDto>? CustomerContactDetailsDto { get; set; }
}

问题查询代码

List<CustomersViewModel> customerData = await Task.FromResult(dapper.GetAll<CustomersViewModel >($"SELECT * FROM [dbo].[Customer] LEFT JOIN [Customer].[ContactDetails] ON [dbo].[Customer].[CompanyID] = [Customer].[ContactDetails].[CompanyID] WHERE [dbo].[Customer].[CompanyID] = '{companyID}'", null, commandType: CommandType.Text)); 

异常表现

  • 上述代码返回的CustomersViewModel所有属性值均为null
  • 将泛型参数替换为object类型时,可以正常读取到Name等字段值
  • 尝试将SELECT *替换为显式指定两张表的Name字段,问题未解决
  • 同SQL语句在SQL Studio中执行可正常返回结果

问题根因

  • Dapper默认的单泛型参数映射逻辑,不会自动将JOIN查询返回的平铺结果集,拆分为嵌套子对象+子集合的层级结构。当传入CustomersViewModel作为映射目标类型时,Dapper只会查找结果集中与CustomersViewModel一级属性名完全匹配的列,也就是需要存在名为CustomerDto、CustomerContactDetailsDto的列才会给这两个属性赋值。但JOIN返回的列是ID、Name、CompanyID这类业务字段,和一级属性名完全不匹配,自然所有属性都会映射为null。换成object时Dapper会返回动态ExpandoObject,直接把所有返回列作为动态对象的属性,所以可以正常读取字段值。
  • 代码存在类型命名不一致问题:ViewModel中声明的集合元素类型为CustomerContactDetailsDto,但实际定义的联系详情类名为ContactdetailsDto,即使映射逻辑正确,这里也会触发类型不匹配错误。
  • 当前写法用字符串插值拼接SQL参数,存在明确的SQL注入风险。

修复方案

  1. 先统一实体命名,补全实体中缺失的关联字段,保证类型引用一致:
// 统一类名和ViewModel引用匹配
public class CustomerContactDetailsDto
{
    public int ID { get; set; }
    public string? Name { get; set; }
    // 补全关联字段
    public int CompanyID { get; set; }
}

public class CustomerDto
{
    public int ID { get; set; }
    public string? Name { get; set; }
    // 补全关联字段
    public int CompanyID { get; set; }
}
  1. 使用Dapper原生的多映射(MultiMapping)功能手动实现一对多关系映射,替换原有的单泛型GetAll调用,同时改用参数化查询避免注入风险:
// 编写参数化SQL,显式指定返回字段避免列名冲突
string sql = @"SELECT 
    c.ID, 
    c.Name, 
    c.CompanyID,
    d.ID, 
    d.Name, 
    d.CompanyID
FROM [dbo].[Customer] c 
LEFT JOIN [Customer].[ContactDetails] d ON c.CompanyID = d.CompanyID 
WHERE c.CompanyID = @CompanyID";

// 用字典缓存主客户对象,合并重复的客户行
var customerCache = new Dictionary<int, CustomersViewModel>();

// 调用多映射重载QueryAsync
var mappedResult = await dbConnection.QueryAsync<CustomerDto, CustomerContactDetailsDto, CustomersViewModel>(
    sql,
    (customer, contact) => 
    {
        // 缓存中不存在当前客户时,初始化ViewModel实例
        if (!customerCache.TryGetValue(customer.ID, out var customerEntry))
        {
            customerEntry = new CustomersViewModel
            {
                CustomerDto = customer,
                CustomerContactDetailsDto = new List<CustomerContactDetailsDto>()
            };
            customerCache.Add(customer.ID, customerEntry);
        }
        // 关联的联系方式不为空时加入集合
        if (contact != null)
        {
            customerEntry.CustomerContactDetailsDto.Add(contact);
        }
        return customerEntry;
    },
    new { CompanyID = companyID }, // 传入参数,禁止字符串拼接SQL
    splitOn: "ID" // 指定拆分列:遇到第二个ID列时开始映射第二个实体ContactDetails
);

// 去重后得到最终的一对多结果
List<CustomersViewModel> customerData = mappedResult.Distinct().ToList();
  1. 如果你使用的GetAll是第三方封装的Dapper扩展方法,需要确认该方法是否支持嵌套对象/一对多自动映射,如果不支持直接使用Dapper原生的多映射API即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 20:48:16