SQL 查询 ADO.net 限制为 2100+ 个参数

SQL query on ADO.net limitation with 2100+ parameters

我正在尝试实现一个 ADO.NET 代码,该代码使用多个参数执行 SQL 查询。看起来 SQL 参数限制是 2100 并且不接受超过这个限制。我如何使用下面的代码实现这个接受超过限制。

在验证有关如何以子集或块的形式发送查询以满足我的请求的在线文章时,我发现很难理解这些实现。

这是我的代码:

using (Connection = new SqlConnection(CS))
{
    Connection.Open();

    string query = "SELECT FamilyID, FullName, Alias FROM TABLE (nolock) WHERE FamilyID IN ({0})";

    var stringBuiler = new StringBuilder();
    var familyIds = new List<string>();

    string line;

    while ((line = TextFileReader.ReadLine()) != null)
    {
        line = line.Trim();

        if (!familyIds.Contains(line) & !string.IsNullOrEmpty(line))
        {
            familyIds.Add(line);
        }
    }

    var sqlCommand = new SqlCommand
    {
        Connection = Connection,
        CommandType = CommandType.Text
    };

    var index = 0; // Reset the index
    var idParameterList = new List<string>();

    foreach (var familyId in familyIds)
    {
        var paramName = "@familyId" + index;
        sqlCommand.Parameters.AddWithValue(paramName, familyId);
        idParameterList.Add(paramName);
        index++;
    }

    sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));

    var dt = new DataTable();

    using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
    {
        dt.Load(sqlReader);
    }

    try
    {
        if (dt.Rows.Count > 0)
        {
            OutputdataGridView.DataSource = lstDownloadOwnerOutput;
            OutputdataGridView.ColumnHeadersDefaultCellStyle.Font = new Font(DataGridView.DefaultFont, FontStyle.Bold);
            OutputdataGridView.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
            Gridviewdisplaylabel.Text = "Total no of rows: " + this.OutputdataGridView.Rows.Count.ToString();
        }
        else if (dt.Rows.Count == 0)
        {
            MessageBox.Show("Data returned blank!!!");
        }
    }
    catch (Exception Ex)
    {
        if (Connection != null)
        {
            Connection.Close();
        }
        MessageBox.Show(Ex.Message);
    }
}

具有 2100 个甚至 100 个参数的 WHERE IN 子句通常不是好的编码习惯。您可能需要考虑将这些值放入一个单独的真正 table,例如

families (ID int PK, ...)

然后,您可以将查询重写为:

SELECT FamilyID, FullName, Alias
FROM TABLE (nolock)
WHERE FamilyID IN (SELECT ID FROM families);

您也可以使用 EXISTS 子句或连接来表达上述内容,但是这三种方法都可能只是优化到一个非常相似的查询计划。

您可以在代码中每 2000 个参数添加一个 table 加载调用:

var index = 0; // Reset the index
var idParameterList = new List<string>();
var dt = new DataTable();

foreach (var familyId in familyIds) {
    var paramName = "@familyId" + index;
    sqlCommand.Parameters.AddWithValue(paramName, familyId);
    idParameterList.Add(paramName);
    index++;
    if (index > 2000) {
        sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));

        using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
            dt.Load(sqlReader);

        sqlCommand.Parameters.Clear();
        idParameterList.Clear();
        index = 0;
    }
}
if (index > 0) {
    sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));

    using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
        dt.Load(sqlReader);
}

像这样的动态sql,我一般推荐使用Table-Valued Parameter

它确实需要一些设置:您必须在数据库中创建一个用户定义的类型来保存值,但这是一个相当简单的操作:

CREATE TYPE PrimaryKeyType AS TABLE ( VALUE INT NOT NULL );  

我们通常将这些与存储过程结合使用:

CREATE PROCEDURE dbo.getFamily(@PrimaryKeys PrimaryKeyType READONLY)
AS
SELECT FamilyID, FullName, Alias 
  FROM TABLE (nolock) INNER JOIN @PrimaryKeys ON TABLE.FamilyID = @PrimaryKeys.Value
GO

但是,如果您愿意,也可以使用内联 SQL。

为存储过程或内联参数赋值相当简单,但有一个问题(稍后详述):

    public static void AssignValuesToPKTableTypeParameter(DbParameter parameter, ICollection<int> primaryKeys)
    {
        // Exceptions are handled by the caller

        var sqlParameter = parameter as SqlParameter;
        if (sqlParameter != null && sqlParameter.SqlDbType == SqlDbType.Structured)
        {
            // The type name may look like DatabaseName.dbo.PrimaryKeyType,
            // so remove the database name if it is present
            var parts = sqlParameter.TypeName.Split('.');
            if (parts.Length == 3)
            {
                sqlParameter.TypeName = parts[1] + "." + parts[2];
            }
        }

        if (primaryKeys == null)
        {
            primaryKeys = new List<int>();
        }

        var table = new DataTable();

        table.Columns.Add("Value", typeof(int));

        foreach (var wPrimaryKey in primaryKeys)
        {
            table.Rows.Add(wPrimaryKey);
        }

        parameter.Value = table;
    }

这里要注意的是参数的命名。参见上述方法中删除数据库名称的代码即可解决此问题。

如果你有动态SQL,你可以使用以下方法生成正确的参数:

    public static SqlParameter CreateTableValuedParameter(string typeName, string parameterName)
    {
        // Exceptions are handled by the caller

        var oParameter = new SqlParameter();

        oParameter.ParameterName = parameterName;
        oParameter.SqlDbType = SqlDbType.Structured;
        oParameter.TypeName = typeName;

        return oParameter;
    }

其中 typeName 是您的类型在数据库中的名称。