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

解决C#中SQL多列结果转换为值元组的类型转换错误

解决SQL OUTPUT子句返回多列转换为C#值元组的类型转换错误

问题描述

编写了如下C#异步方法,通过执行带OUTPUT子句的SQL插入语句获取PurchaseOrderId和PONum,期望以值元组(int purchaseOrderId, string PONum)形式返回结果:

public async Task<(int purchaseOrderId, string PONum)> AddPurchaseOrder(PurchaseOrderMaster purchaseOrderMaster)
{
    try
    {
        var sqlQuery = new StringBuilder();
        var paramSearch = new DynamicParameters();

        _ = sqlQuery.Append($@"INSERT INTO PurchaseOrder (PONum, VendorID, WHId, PODate, ReferenceNum, EstArrivalDate, POType, SubTotal, TAX, ShippingHandling, Total, EnteredBy, EnteredOn, IsDelete)
                OUTPUT inserted.PurchaseOrderId, inserted.PONum
                VALUES (FLOOR(RAND() * 900000) + 100000, @VendorID, @WHId, @PODate, @ReferenceNum, @EstArrivalDate, @POType, @SubTotal, @TAX, @ShippingHandling, @Total, @EnteredBy, GETUTCDATE(), 'N');
        ");

        paramSearch.Add("@VendorID", purchaseOrderMaster.VendorID);
        paramSearch.Add("@WHId", purchaseOrderMaster.WHId);
        paramSearch.Add("@PODate", purchaseOrderMaster.PODate);
        paramSearch.Add("@ReferenceNum", purchaseOrderMaster.ReferenceNum);
        paramSearch.Add("@EstArrivalDate", purchaseOrderMaster.EstArrivalDate);
        paramSearch.Add("@POType", purchaseOrderMaster.POType);
        paramSearch.Add("@SubTotal", purchaseOrderMaster.SubTotal);
        paramSearch.Add("@TAX", purchaseOrderMaster.TAX);
        paramSearch.Add("@ShippingHandling", purchaseOrderMaster.ShippingHandling);
        paramSearch.Add("@Total", purchaseOrderMaster.Total);
        paramSearch.Add("@EnteredBy", purchaseOrderMaster.EnteredBy);

        return await dbHelper.ExecuteScalarAsync<(int, string)>(sqlQuery.ToString(), paramSearch).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        throw new BadRequestException(ex.Message);
    }
}

执行时出现类型转换错误:

{"Invalid cast from 'System.Int32' to 'System.ValueTuple`2[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'."}

问题原因

错误根源在于ExecuteScalarAsync方法的特性:它仅返回查询结果的第一列第一行的值,也就是这里的PurchaseOrderId(int类型),无法直接将单个int值转换为包含int和string的复合值元组,因此抛出类型转换异常。

解决方案

使用Dapper的QueryFirstAsync方法替代ExecuteScalarAsync,该方法支持将多列查询结果自动映射到值元组。修改后的代码如下:

public async Task<(int purchaseOrderId, string PONum)> AddPurchaseOrder(PurchaseOrderMaster purchaseOrderMaster)
{
    try
    {
        var sqlQuery = new StringBuilder();
        var paramSearch = new DynamicParameters();

        _ = sqlQuery.Append($@"INSERT INTO PurchaseOrder (PONum, VendorID, WHId, PODate, ReferenceNum, EstArrivalDate, POType, SubTotal, TAX, ShippingHandling, Total, EnteredBy, EnteredOn, IsDelete)
                OUTPUT inserted.PurchaseOrderId, inserted.PONum
                VALUES (FLOOR(RAND() * 900000) + 100000, @VendorID, @WHId, @PODate, @ReferenceNum, @EstArrivalDate, @POType, @SubTotal, @TAX, @ShippingHandling, @Total, @EnteredBy, GETUTCDATE(), 'N');
        ");

        paramSearch.Add("@VendorID", purchaseOrderMaster.VendorID);
        paramSearch.Add("@WHId", purchaseOrderMaster.WHId);
        paramSearch.Add("@PODate", purchaseOrderMaster.PODate);
        paramSearch.Add("@ReferenceNum", purchaseOrderMaster.ReferenceNum);
        paramSearch.Add("@EstArrivalDate", purchaseOrderMaster.EstArrivalDate);
        paramSearch.Add("@POType", purchaseOrderMaster.POType);
        paramSearch.Add("@SubTotal", purchaseOrderMaster.SubTotal);
        paramSearch.Add("@TAX", purchaseOrderMaster.TAX);
        paramSearch.Add("@ShippingHandling", purchaseOrderMaster.ShippingHandling);
        paramSearch.Add("@Total", purchaseOrderMaster.Total);
        paramSearch.Add("@EnteredBy", purchaseOrderMaster.EnteredBy);

        // 替换为QueryFirstAsync,直接映射到具名值元组
        return await dbHelper.QueryFirstAsync<(int purchaseOrderId, string PONum)>(sqlQuery.ToString(), paramSearch).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        throw new BadRequestException(ex.Message);
    }
}

补充说明

  • QueryFirstAsync<T>会自动匹配SQL返回列名和值元组的成员名,只要两者名称一致即可完成映射(这里inserted.PurchaseOrderId对应元组的purchaseOrderId,inserted.PONum对应PONum)。
  • 如果SQL列名和元组成员名不匹配,可以在SQL中给列添加别名,例如:OUTPUT inserted.PurchaseOrderId AS purchaseOrderId, inserted.PONum AS PONum,确保映射准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:41:03