C# 带2100+参数的ADO.net限制SQL查询

C# 带2100+参数的ADO.net限制SQL查询,c#,sql,ado.net,C#,Sql,Ado.net,我试图实现一个ADO.NET代码,该代码使用多个参数执行SQL查询。看起来SQL参数限制为2100,并且不接受超过此限制的值。我如何用我下面的代码实现这一点,使其接受更多的限制 我发现在验证在线文章时很难理解实现,这些文章涉及如何以子集或块的形式发送查询以满足我的请求 这是我的代码: using (Connection = new SqlConnection(CS)) { Connection.Open(); string query = "SELECT FamilyID, F

我试图实现一个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子句通常不是好的编码实践。您可能需要考虑将这些值放入单独的表中,例如

families (ID int PK, ...)
然后,您可以将查询重写为:

SELECT FamilyID, FullName, Alias
FROM TABLE (nolock)
WHERE FamilyID IN (SELECT ID FROM families);
您也可以使用EXISTS子句或join来表示上述内容,但这三种方法都可能只是优化为非常相似的查询计划。

使用带有2100甚至100个参数的WHERE IN子句通常不是好的编码实践。您可能需要考虑将这些值放入单独的表中,例如

families (ID int PK, ...)
然后,您可以将查询重写为:

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

您也可以使用EXISTS子句或join来表示上述内容,但这三种方法可能都只是针对非常相似的查询计划进行优化。

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

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);
}

您只需在代码中每隔2000个参数添加一个表加载调用:

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,我通常建议使用

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

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

将值分配给存储的proc或inline参数相当简单,但后面还有一个问题:

    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;
    }

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

对于这样的动态sql,我通常建议使用

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

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

将值分配给存储的proc或inline参数相当简单,但后面还有一个问题:

    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;
    }


其中typeName是您在数据库中键入的名称。

太棒了!我不得不为此做最小的代码更改,非常感谢您的解决方案Wesome!我必须为此做最小的代码更改,非常感谢您的解决方案谢谢您的建议Tim!我会记住这一点,然后再这样做。您需要以某种方式将这些ID从客户端移动到服务器,这就是问题所在。@AntonínLejsek True,但对临时表执行一次操作可能比每次查询一次要高效得多,这取决于您的查询工作方式。大多数数据库应用程序都会将记录插入表中。@NetMage在我看来,在典型使用中,每个查询的ID都是不同的,因此您必须为每个查询构建并填充临时表。是的,这是你无论如何都要做的事情。但是如果要用RBAR填充表,那么最好将其全部丢弃,只需在循环中填充数据,每次一个ID。这会更快更简单。谢谢你的建议,蒂姆!我会记住这一点,然后再这样做。您需要以某种方式将这些ID从客户端移动到服务器,这就是问题所在。@AntonínLejsek True,但对临时表执行一次操作可能比每次查询一次要高效得多,这取决于您的查询工作方式。大多数数据库应用程序都会将记录插入表中。@NetMage在我看来,在典型使用中,每个查询的ID都是不同的,因此您必须为每个查询构建并填充临时表。是的,这是你无论如何都要做的事情。但是如果要用RBAR填充表,那么最好将其全部丢弃,只需在循环中填充数据,每次一个ID。它会更快更简单。Nolock的意思是,你不在乎结果是否正确。例如,您可能会在结果中得到重复或缺少的行。你知道吗?@AntonínLejsek:不,对不起,我不知道。在查询中使用nolock是否会导致数据丢失?nolock的意思是,您不关心结果是否正确。例如,您可能会在结果中得到重复或缺少的行。你知道吗?@AntonínLejsek:不,对不起,我不知道。在查询中使用nolock是否会导致数据丢失?不幸的是,我不应该创建存储过程,因为它将在生产环境中运行。使用tvp不需要存储过程。它只是一个参数,您可以将其用作任何其他参数。服务器上唯一需要的设置是用户定义的
很遗憾,我不应该创建存储过程,因为它将在生产环境中运行。使用tvp不需要存储过程。它只是一个参数,您可以将其用作任何其他参数。服务器上唯一需要的设置是用户定义的类型。