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

如何为返回结构动态变化的SQL Server存储过程生成DTO模型?

针对存储过程返回结构随表名动态变化的场景,给你几个实用的解决方案:

方案1:使用DataTable/DataSet(最直接)

直接用DataTable接收动态结果,它会自动根据返回的列构建结构,无需预定义DTO。

修改你的数据访问方法,新增一个非泛型的存储过程执行方法:

public DataTable ExecuteProcedure(string procedureName, Dictionary<string, object> parameters)
{
    DataTable result = new DataTable();
    using (SqlConnection conn = new SqlConnection("your_connection_string"))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(procedureName, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            foreach (var param in parameters)
            {
                cmd.Parameters.AddWithValue(param.Key, param.Value);
            }
            using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
            {
                adapter.Fill(result);
            }
        }
    }
    return result;
}

调用示例:

public DataTable GetDynamicTableData(string tableName)
{
    // 关键:先验证表名合法性,防止SQL注入
    if (!IsValidTableName(tableName))
    {
        throw new ArgumentException("Invalid table name");
    }
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("@TableName", tableName);

    return ExecuteProcedure("spGetDynamicTableData", parameters);
}

// 示例:验证表名是否在允许的列表中
private bool IsValidTableName(string tableName)
{
    var allowedTables = new List<string> { "UserDetails", "OrderInfo", "ProductData" };
    return allowedTables.Contains(tableName, StringComparer.OrdinalIgnoreCase);
}

优点:实现简单,无需额外依赖,完全适配动态结构;缺点:不够面向对象,数据访问需要通过列名索引,代码可读性稍差。

方案2:使用dynamic动态类型

如果用Dapper这类ORM,可以直接返回dynamic类型的列表,无需预定义DTO。

假设你的ExecuteProcedure是基于Dapper封装的,重载一个返回动态类型的版本:

public IList<dynamic> ExecuteProcedureDynamic(string procedureName, Dictionary<string, object> parameters)
{
    using (SqlConnection conn = new SqlConnection("your_connection_string"))
    {
        conn.Open();
        return conn.Query<dynamic>(procedureName, parameters, commandType: CommandType.StoredProcedure).ToList();
    }
}

调用示例:

public IList<dynamic> GetDynamicTableData(string tableName)
{
    if (!IsValidTableName(tableName))
    {
        throw new ArgumentException("Invalid table name");
    }
    var parameters = new Dictionary<string, object> { { "@TableName", tableName } };
    return ExecuteProcedureDynamic("spGetDynamicTableData", parameters);
}

使用时可以直接访问属性:

var data = GetDynamicTableData("UserDetails");
foreach (var item in data)
{
    Console.WriteLine(item.UserId + " " + item.UserName);
}

优点:语法接近强类型,使用方便;缺点:编译时无类型检查,拼写错误会导致运行时异常,IDE无智能提示。

方案3:使用ExpandoObject

ExpandoObject允许你动态添加属性,适合需要在代码中灵活操作属性的场景:

可以基于DataTable转换为ExpandoObject列表:

public IList<ExpandoObject> DataTableToExpando(DataTable table)
{
    var expandoList = new List<ExpandoObject>();
    foreach (DataRow row in table.Rows)
    {
        var expando = new ExpandoObject();
        var expandoDict = (IDictionary<string, object>)expando;
        foreach (DataColumn col in table.Columns)
        {
            expandoDict[col.ColumnName] = row[col] == DBNull.Value ? null : row[col];
        }
        expandoList.Add(expando);
    }
    return expandoList;
}

调用时先获取DataTable再转换:

public IList<ExpandoObject> GetDynamicTableData(string tableName)
{
    var dt = ExecuteProcedure("spGetDynamicTableData", new Dictionary<string, object> { { "@TableName", tableName } });
    return DataTableToExpando(dt);
}

优点:完全动态,可在运行时修改属性;缺点:性能略低于dynamic,同样无编译时类型检查。

关键注意事项:SQL注入防护

因为传入的是表名参数,直接拼接或传入未验证的表名会导致严重的SQL注入风险,必须做以下防护:

  • 白名单验证:只允许访问预先定义的合法表名(如方案1中的IsValidTableName方法);
  • 使用QUOTENAME函数:在存储过程中用QUOTENAME(@TableName)包裹表名,避免注入;
    存储过程示例:
    CREATE PROCEDURE spGetDynamicTableData
        @TableName NVARCHAR(128)
    AS
    BEGIN
        DECLARE @Sql NVARCHAR(MAX)
        SET @Sql = N'SELECT * FROM ' + QUOTENAME(@TableName)
        EXEC sp_executesql @Sql
    END
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:55:21