C# 带2100+参数的ADO.net限制SQL查询
我试图实现一个ADO.NET代码,该代码使用多个参数执行SQL查询。看起来SQL参数限制为2100,并且不接受超过此限制的值。我如何用我下面的代码实现这一点,使其接受更多的限制 我发现在验证在线文章时很难理解实现,这些文章涉及如何以子集或块的形式发送查询以满足我的请求 这是我的代码: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
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不需要存储过程。它只是一个参数,您可以将其用作任何其他参数。服务器上唯一需要的设置是用户定义的类型。