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

如何用Dapper QueryMultiple将多结果集映射为强类型对象?

优化Dapper调用多结果集存储过程的方案

先解决你遇到的基础疑问

  1. 为什么grid.Read()看不到完整结果,grid.ReadSingle()可以?
    Read()返回的是结果集的行枚举,第一个结果集只有1行,所以Read()返回的是包含单个动态对象的枚举,直接Dump会显示枚举容器而非内部对象;ReadSingle()则直接取出这唯一的一行对象,所以能看到具体数据。
  2. 为什么grid.ReadSingle()["Status"]报错?
    你的存储过程第一个SELECT没有指定列名,SQL Server会默认给列命名为Column1和Column2,而非Status和Msg,所以用["Status"]找不到对应键值才会报错。

最优实现方案(分两种场景)

场景1:允许修改存储过程(推荐)

如果能修改存储过程,去掉第二个结果集的FOR XML RAW,直接返回表数据,同时给第一个结果集指定列名,这样Dapper可以直接映射到强类型,完全避免类型转换和XML解析:

修改后的存储过程
CREATE PROCEDURE [dbo].[dapper_test_sproc] 
AS 
BEGIN
    SET NOCOUNT ON ;

    DECLARE @Status INT = 1
    DECLARE @Msg VARCHAR(256) = 'Hello World'

    DECLARE @StatusLines TABLE
    (
      rule_code VARCHAR(20),
      rule_name VARCHAR(30),
      [status] INT,
      rule_error_msg VARCHAR(256)
    )

    INSERT INTO @StatusLines
    VALUES ('TEST1', 'TEST2', 1, 'TEST3')

    -- 给结果列指定明确名称
    SELECT  @Status AS Status,
            @Msg AS Message

    -- 直接返回表数据,不用XML
    SELECT  rule_code,
            rule_name,
            [status],
            rule_error_msg
    FROM    @StatusLines
END
对应的C#代码
public async Task dapper_test_sproc()
{
    using var connection = this.Connection;

    var grid = await connection.QueryMultipleAsync(
        new CommandDefinition(
            "dapper_test_sproc",
            commandType: CommandType.StoredProcedure,
            cancellationToken: this.QueryCancelToken));

    // 直接映射到强类型,无需类型转换
    var header = await grid.ReadSingleAsync<ProcedureResultHeader>();
    // 直接映射到实体列表
    var statusLines = await grid.ReadAsync<StatusLines>().ToListAsync();

    var validationMessage = new PasswordValidationStatusMessage
    {
        Error = !Convert.ToBoolean(header.Status),
        Message = header.Message
    };

    validationMessage.Dump();
    statusLines.Dump();
}

// 对应第一个结果集的强类型
public class ProcedureResultHeader
{
    public int Status { get; set; }
    public string Message { get; set; }
}

// 对应第二个结果集的实体类
public class StatusLines
{
    public string RuleCode { get; set; }
    public string RuleName { get; set; }
    public bool Status { get; set; }
    public string RuleErrorMsg { get; set; }
}

public class PasswordValidationStatusMessage
{
    public bool Error { get; set; }
    public string Message { get; set; }
}

场景2:无法修改存储过程

如果不能改存储过程,那至少给第一个结果集的列名通过Dapper特性映射,同时优化XML解析代码:

优化后的C#代码
public async Task dapper_test_sproc()
{
    using var connection = this.Connection;

    var grid = await connection.QueryMultipleAsync(
        new CommandDefinition(
            "dapper_test_sproc",
            commandType: CommandType.StoredProcedure,
            cancellationToken: this.QueryCancelToken));

    // 用强类型接收第一个结果集,通过ColumnAttribute映射默认列名
    var header = await grid.ReadSingleAsync<ProcedureResultHeader>();
    
    // 读取第二个结果集的XML字符串
    var xmlResult = await grid.ReadSingleAsync<XmlResult>();
    var statusLinesXML = xmlResult.XmlContent;

    var validationMessage = new PasswordValidationStatusMessage
    {
        Error = !Convert.ToBoolean(header.Status),
        Message = header.Message
    };

    // 优化XML解析代码,复用序列化器(避免重复创建)
    var serializer = new XmlSerializer(typeof(StatusLinesEnvelope));
    using var reader = new StringReader($"<root>{statusLinesXML}</root>");
    var statusLines = (StatusLinesEnvelope)serializer.Deserialize(reader);

    validationMessage.Dump();
    statusLines.Dump();
}

// 映射第一个结果集的默认列名(Column1、Column2)
public class ProcedureResultHeader
{
    [Column("Column1")]
    public int Status { get; set; }

    [Column("Column2")]
    public string Message { get; set; }
}

// 映射第二个结果集的XML列(默认列名是Column1)
public class XmlResult
{
    [Column("Column1")]
    public string XmlContent { get; set; }
}

// 原有的XML实体类保持不变
[XmlRoot(ElementName = "row", Namespace = "")]
public class StatusLines
{
    [XmlAttribute(AttributeName = "rule_code")]
    public string RuleCode { get; set; }

    [XmlAttribute(AttributeName = "rule_name")]
    public string RuleName { get; set; }

    [XmlAttribute(AttributeName = "status")]
    public bool Status { get; set; }

    [XmlAttribute(AttributeName = "rule_error_msg")]
    public string RuleErrorMsg { get; set; }
}

[XmlRoot(ElementName = "root")]
public class StatusLinesEnvelope
{
    [XmlElement(ElementName = "row")]
    public List<StatusLines> Row { get; set; }
}

public class PasswordValidationStatusMessage
{
    public bool Error { get; set; }
    public string Message { get; set; }
}

核心优化点

  1. 使用强类型接收结果集:避免动态类型的强制转换和索引访问,减少出错概率,代码更易读维护。
  2. 明确列名映射:无论是修改存储过程指定列名,还是用[Column]特性映射默认列名,都能让Dapper正确匹配属性。
  3. 避免重复创建序列化器:如果需要解析XML,尽量复用XmlSerializer实例(可静态初始化),提升性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 07:10:31