Truyền tham số mảng trong SqlCommand


144

Tôi đang cố gắng truyền tham số mảng cho SQL commnd trong C # như bên dưới, nhưng nó không hoạt động. Có ai gặp nó trước?

string sqlCommand = "SELECT * from TableA WHERE Age IN (@Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;
sqlComm.Parameters.Add("@Age", SqlDbType.NVarChar);
StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
     if (item.Selected)
     {
         sb.Append(item.Text + ",");
     }
}

sqlComm.Parameters["@Age"].Value = sb.ToString().TrimEnd(',');

11
Không thực sự là chủ đề, nhưng dường như với tôi, việc Age như một cột trong bảng là một ý tưởng tồi, vì nó sẽ cần phải được cập nhật liên tục. Mọi người già đi phải không? Có lẽ bạn nên xem xét việc có một cột DateOfBirth thay thế?
Kjetil Watnedal

câu hỏi với câu trả lời hay ở đây: stackoverflow.com/questions/83471/ trộm
Adam Butler

Câu trả lời:


168

Bạn sẽ cần thêm các giá trị trong mảng một lần.

var parameters = new string[items.Length];
var cmd = new SqlCommand();
for (int i = 0; i < items.Length; i++)
{
    parameters[i] = string.Format("@Age{0}", i);
    cmd.Parameters.AddWithValue(parameters[i], items[i]);
}

cmd.CommandText = string.Format("SELECT * from TableA WHERE Age IN ({0})", string.Join(", ", parameters));
cmd.Connection = new SqlConnection(connStr);

CẬP NHẬT: Đây là một giải pháp mở rộng và có thể sử dụng lại, sử dụng câu trả lời của Adam cùng với chỉnh sửa được đề xuất của anh ấy. Tôi đã cải thiện nó một chút và biến nó thành một phương thức mở rộng để làm cho nó dễ gọi hơn.

public static class SqlCommandExt
{

    /// <summary>
    /// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
    /// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN ({paramNameRoot}))
    /// </summary>
    /// <param name="cmd">The SqlCommand object to add parameters to.</param>
    /// <param name="paramNameRoot">What the parameter should be named followed by a unique value for each value. This value surrounded by {} in the CommandText will be replaced.</param>
    /// <param name="values">The array of strings that need to be added as parameters.</param>
    /// <param name="dbType">One of the System.Data.SqlDbType values. If null, determines type based on T.</param>
    /// <param name="size">The maximum size, in bytes, of the data within the column. The default value is inferred from the parameter value.</param>
    public static SqlParameter[] AddArrayParameters<T>(this SqlCommand cmd, string paramNameRoot, IEnumerable<T> values, SqlDbType? dbType = null, int? size = null)
    {
        /* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually. 
         * Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
         * IN statement in the CommandText.
         */
        var parameters = new List<SqlParameter>();
        var parameterNames = new List<string>();
        var paramNbr = 1;
        foreach (var value in values)
        {
            var paramName = string.Format("@{0}{1}", paramNameRoot, paramNbr++);
            parameterNames.Add(paramName);
            SqlParameter p = new SqlParameter(paramName, value);
            if (dbType.HasValue)
                p.SqlDbType = dbType.Value;
            if (size.HasValue)
                p.Size = size.Value;
            cmd.Parameters.Add(p);
            parameters.Add(p);
        }

        cmd.CommandText = cmd.CommandText.Replace("{" + paramNameRoot + "}", string.Join(",", parameterNames));

        return parameters.ToArray();
    }

}

Nó được gọi như thế này ...

var cmd = new SqlCommand("SELECT * FROM TableA WHERE Age IN ({Age})");
cmd.AddArrayParameters("Age", new int[] { 1, 2, 3 });

Lưu ý "{Age}" trong câu lệnh sql giống như tên tham số mà chúng tôi đang gửi tới AddArrayParameter. AddArrayParameter sẽ thay thế giá trị bằng các tham số chính xác.


11
Liệu phương pháp này có vấn đề bảo mật, như tiêm sql?
Yongwei Xing

7
Bởi vì bạn đang đặt các giá trị vào các tham số, không có rủi ro tiêm sql.
Brian

Đây là những gì tôi đang tìm kiếm nhưng tôi đã có một câu hỏi. Nếu OP có một vài cột giống nhau để thêm vào SQL, chúng ta sẽ làm điều này như thế nào? Thí dụ. CHỌN * TỪ TableA WHERE Age = ({0}) HOẶC Tuổi = ({1}). (làm thế nào chúng ta sẽ làm điều đó với cmd.Parameter)
Ca cao Dev

1
@ T2Tom, Rất tiếc. Tôi sửa nó rồi. Cảm ơn.
Brian

1
Tôi thích điều này và chỉ thực hiện sửa đổi sau khi trích xuất chuỗi giữ chỗ thành một biến: var paramPlaceholder = "{" & paramNameRoot & "}"; Debug.Assert(cmd.CommandText.Contains(paramPlaceholder), "Parameter Name Root must exist in the Source Query"); Điều này sẽ giúp các nhà phát triển nếu họ quên khớp paramNameRoot với truy vấn.
MCattle

37

Tôi muốn mở rộng câu trả lời mà Brian đã đóng góp để làm cho điều này dễ sử dụng ở những nơi khác.

/// <summary>
/// This will add an array of parameters to a SqlCommand. This is used for an IN statement.
/// Use the returned value for the IN part of your SQL call. (i.e. SELECT * FROM table WHERE field IN (returnValue))
/// </summary>
/// <param name="sqlCommand">The SqlCommand object to add parameters to.</param>
/// <param name="array">The array of strings that need to be added as parameters.</param>
/// <param name="paramName">What the parameter should be named.</param>
protected string AddArrayParameters(SqlCommand sqlCommand, string[] array, string paramName)
{
    /* An array cannot be simply added as a parameter to a SqlCommand so we need to loop through things and add it manually. 
     * Each item in the array will end up being it's own SqlParameter so the return value for this must be used as part of the
     * IN statement in the CommandText.
     */
    var parameters = new string[array.Length];
    for (int i = 0; i < array.Length; i++)
    {
        parameters[i] = string.Format("@{0}{1}", paramName, i);
        sqlCommand.Parameters.AddWithValue(parameters[i], array[i]);
    }

    return string.Join(", ", parameters);
}

Bạn có thể sử dụng chức năng mới này như sau:

SqlCommand cmd = new SqlCommand();

string ageParameters = AddArrayParameters(cmd, agesArray, "Age");
sql = string.Format("SELECT * FROM TableA WHERE Age IN ({0})", ageParameters);

cmd.CommandText = sql;


Chỉnh sửa: Đây là một biến thể chung hoạt động với một loạt các giá trị thuộc bất kỳ loại nào và có thể sử dụng như một phương thức mở rộng:

public static class Extensions
{
    public static void AddArrayParameters<T>(this SqlCommand cmd, string name, IEnumerable<T> values) 
    { 
        name = name.StartsWith("@") ? name : "@" + name;
        var names = string.Join(", ", values.Select((value, i) => { 
            var paramName = name + i; 
            cmd.Parameters.AddWithValue(paramName, value); 
            return paramName; 
        })); 
        cmd.CommandText = cmd.CommandText.Replace(name, names); 
    }
}

Sau đó, bạn có thể sử dụng phương thức mở rộng này như sau:

var ageList = new List<int> { 1, 3, 5, 7, 9, 11 };
var cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM MyTable WHERE Age IN (@Age)";    
cmd.AddArrayParameters("Age", ageList);

Hãy chắc chắn rằng bạn đã đặt CommandText trước khi gọi AddArrayParameter.

Ngoài ra, hãy đảm bảo tên tham số của bạn sẽ không khớp một phần với bất kỳ thứ gì khác trong câu lệnh của bạn (ví dụ @AgeOfChild)


1
Đây là một biến thể chung hoạt động với một loạt các giá trị thuộc bất kỳ loại nào và có thể sử dụng như một phương thức mở rộng: public static void AddArrayParameter <T> (giá trị này của SqlCommand cmd, tên chuỗi, IEnumerable <T> value) {var name = string.Join (",", value.Select ((value, i) => {var paramName = name + i; cmd.Parameter.AddWithValue (paramName, value); return paramName;})); cmd.CommandText = cmd.CommandText.Replace (tên, tên); }
Adam Nemitoff

Vấn đề nhỏ với câu trả lời này là với AddWithValuechức năng, bất kỳ cơ hội nào bạn có thể khắc phục điều đó?
DavidG

Câu trả lời này là sai bởi vì nó có khả năng mở rộng và hiệu suất kém và nó thúc đẩy thực hành mã hóa xấu.
Igor Levicki

24

Nếu bạn có thể sử dụng một công cụ như "dapper", thì đây có thể chỉ đơn giản là:

int[] ages = { 20, 21, 22 }; // could be any common list-like type
var rows = connection.Query<YourType>("SELECT * from TableA WHERE Age IN @ages",
          new { ages }).ToList();

Dapper sẽ xử lý việc hủy kết nối này với các tham số riêng cho bạn .


Dapper thu hút rất nhiều sự phụ thuộc :(
mlt

@mlt hả? không, nó không; trên netfx: "không phụ thuộc"; trên ns2.0, chỉ cần "System.Reflection.Emit.Light weight" - và chúng tôi có thể loại bỏ điều đó nếu chúng tôi thêm mục tiêu necror Ứng dụng lại
Marc Gravell

Tôi không có ý định chiếm quyền điều khiển cuộc thảo luận, nhưng tôi đã làm ... Cho đến nay tôi sử dụng Npgsql xử lý các mảng tốt như trong các '{1,2,3}'đối số kiểu cho một hàm (không phải là mệnh đề WHERE IN), nhưng tôi không nên sử dụng ODBC đơn giản nếu không rắc rối mảng. Tôi đoán tôi cũng sẽ cần Dapper ODBC trong trường hợp này. Đây là những gì nó muốn kéo. snipboard.io/HU0RpJ.jpg . Có lẽ tôi nên đọc thêm về Dapper ...
mlt

16

Nếu bạn đang sử dụng MS SQL Server 2008 trở lên, bạn có thể sử dụng các tham số có giá trị bảng như được mô tả ở đây http://www.sommarskog.se/arrays-in-sql-2008.html .

1. Tạo một loại bảng cho từng loại tham số bạn sẽ sử dụng

Lệnh sau tạo một kiểu bảng cho số nguyên:

create type int32_id_list as table (id int not null primary key)

2. Thực hiện các phương thức trợ giúp

public static SqlCommand AddParameter<T>(this SqlCommand command, string name, IEnumerable<T> ids)
{
  var parameter = command.CreateParameter();      

  parameter.ParameterName = name;
  parameter.TypeName = typeof(T).Name.ToLowerInvariant() + "_id_list";
  parameter.SqlDbType = SqlDbType.Structured;
  parameter.Direction = ParameterDirection.Input;

  parameter.Value = CreateIdList(ids);

  command.Parameters.Add(parameter);
  return command;
}

private static DataTable CreateIdList<T>(IEnumerable<T> ids)
{
  var table = new DataTable();
  table.Columns.Add("id", typeof (T));

  foreach (var id in ids)
  {
    table.Rows.Add(id);
  }

  return table;
}

3. Sử dụng nó như thế này

cmd.CommandText = "select * from TableA where Age in (select id from @age)"; 
cmd.AddParameter("@age", new [] {1,2,3,4,5});

1
Dòng này table.Rows.Add(id);dẫn đến mùi mã nhỏ khi sử dụng SonarQube. Tôi đã sử dụng thay thế này trong foreach : var row = table.NewRow(); row["id"] = id; table.Rows.Add(row);.
pogosama

1
Đây phải là câu trả lời được chấp nhận, đặc biệt nếu nó được điều chỉnh để chấp nhận nhiều cột hơn.
Igor Levicki

10

Vì có một phương pháp trên

SqlCommand.Parameters.AddWithValue(parameterName, value)

có thể thuận tiện hơn khi tạo một phương thức chấp nhận một tham số (tên) để thay thế và một danh sách các giá trị. Nó không ở cấp độ Tham số (như AddWithValue ) mà là bản thân lệnh nên tốt hơn là gọi nó là AddParameterWithValues và không chỉ AddWithValues :

truy vấn:

SELECT * from TableA WHERE Age IN (@age)

sử dụng:

sqlCommand.AddParametersWithValues("@age", 1, 2, 3);

phương pháp mở rộng:

public static class SqlCommandExtensions
{
    public static void AddParametersWithValues<T>(this SqlCommand cmd,  string parameterName, params T[] values)
    {
        var parameterNames = new List<string>();
        for(int i = 0; i < values.Count(); i++)
        {
            var paramName = @"@param" + i;
            cmd.Parameters.AddWithValue(paramName, values.ElementAt(i));
            parameterNames.Add(paramName);
        }

        cmd.CommandText = cmd.CommandText.Replace(parameterName, string.Join(",", parameterNames));
    }
}

1
Có vẻ như một số lần lặp lại của phương thức mở rộng này tồn tại qua một vài câu trả lời. Mặc dù vậy, tôi đã sử dụng cái này, vì vậy tôi đang bỏ phiếu cho nó :-)
Dan Forbes

tốt hơn là sử dụng một chỉ mục tĩnh cho tên tham số
shmnff

6

Tôi muốn đề xuất một cách khác, cách giải quyết giới hạn với toán tử IN.

Ví dụ: chúng tôi có truy vấn sau

select *
from Users U
WHERE U.ID in (@ids)

Chúng tôi muốn vượt qua một số ID để lọc người dùng. Thật không may, không thể làm với C # một cách dễ dàng. Nhưng tôi có cách giải quyết khác cho việc này bằng cách sử dụng chức năng "string_split". Chúng tôi cần phải viết lại một chút truy vấn của chúng tôi để làm theo.

declare @ids nvarchar(max) = '1,2,3'

SELECT *
FROM Users as U
CROSS APPLY string_split(@ids, ',') as UIDS
WHERE U.ID = UIDS.value

Bây giờ chúng ta có thể dễ dàng truyền một tham số liệt kê các giá trị được phân tách bằng dấu phẩy.


cách tốt nhất và sạch nhất tôi đã tìm thấy, miễn là khả năng tương thích của bạn là hiện tại.
dùng1040975

4

Truyền một mảng các mục dưới dạng tham số được thu gọn cho mệnh đề WHERE..IN sẽ không thành công do truy vấn sẽ có dạng WHERE Age IN ("11, 13, 14, 16").

Nhưng bạn có thể chuyển tham số của mình dưới dạng một mảng được tuần tự hóa sang XML hoặc JSON:

Sử dụng nodes()phương pháp:

StringBuilder sb = new StringBuilder();

foreach (ListItem item in ddlAge.Items)
  if (item.Selected)
    sb.Append("<age>" + item.Text + "</age>"); // actually it's xml-ish

sqlComm.CommandText = @"SELECT * from TableA WHERE Age IN (
    SELECT Tab.col.value('.', 'int') as Age from @Ages.nodes('/age') as Tab(col))";
sqlComm.Parameters.Add("@Ages", SqlDbType.NVarChar);
sqlComm.Parameters["@Ages"].Value = sb.ToString();

Sử dụng OPENXMLphương pháp:

using System.Xml.Linq;
...
XElement xml = new XElement("Ages");

foreach (ListItem item in ddlAge.Items)
  if (item.Selected)
    xml.Add(new XElement("age", item.Text);

sqlComm.CommandText = @"DECLARE @idoc int;
    EXEC sp_xml_preparedocument @idoc OUTPUT, @Ages;
    SELECT * from TableA WHERE Age IN (
    SELECT Age from OPENXML(@idoc, '/Ages/age') with (Age int 'text()')
    EXEC sp_xml_removedocument @idoc";
sqlComm.Parameters.Add("@Ages", SqlDbType.Xml);
sqlComm.Parameters["@Ages"].Value = xml.ToString();

Đó là một chút nữa về phía SQL và bạn cần một XML thích hợp (có root).

Sử dụng OPENJSONphương thức (SQL Server 2016+):

using Newtonsoft.Json;
...
List<string> ages = new List<string>();

foreach (ListItem item in ddlAge.Items)
  if (item.Selected)
    ages.Add(item.Text);

sqlComm.CommandText = @"SELECT * from TableA WHERE Age IN (
    select value from OPENJSON(@Ages))";
sqlComm.Parameters.Add("@Ages", SqlDbType.NVarChar);
sqlComm.Parameters["@Ages"].Value = JsonConvert.SerializeObject(ages);

Lưu ý rằng đối với phương pháp cuối cùng, bạn cũng cần phải có Mức tương thích ở mức 130+.


0

Tổng quan: Sử dụng DbType để đặt loại tham số.

var parameter = new SqlParameter();
parameter.ParameterName = "@UserID";
parameter.DbType = DbType.Int32;
parameter.Value = userID.ToString();

var command = conn.CreateCommand()
command.Parameters.Add(parameter);
var reader = await command.ExecuteReaderAsync()

-1

Sử dụng .AddWithValue(), vì vậy:

sqlComm.Parameters.AddWithValue("@Age", sb.ToString().TrimEnd(','));

Ngoài ra, bạn có thể sử dụng điều này:

sqlComm.Parameters.Add(
    new SqlParameter("@Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar }
    );

Tổng số mẫu mã của bạn sẽ xem xét sau đây:

string sqlCommand = "SELECT * from TableA WHERE Age IN (@Age)";
SqlConnection sqlCon = new SqlConnection(connectString);
SqlCommand sqlComm = new SqlCommand();
sqlComm.Connection = sqlCon;
sqlComm.CommandType = System.Data.CommandType.Text;
sqlComm.CommandText = sqlCommand;
sqlComm.CommandTimeout = 300;

StringBuilder sb = new StringBuilder();
foreach (ListItem item in ddlAge.Items)
{
     if (item.Selected)
     {
         sb.Append(item.Text + ",");
     }
}

sqlComm.Parameters.AddWithValue("@Age", sb.ToString().TrimEnd(','));

// OR

// sqlComm.Parameters.Add(new SqlParameter("@Age", sb.ToString().TrimEnd(',')) { SqlDbType = SqlDbType. NVarChar });

Loại trường của trường là nvchar không int. Có vấn đề gì không?
Yongwei Xing

Nó không nên. Đặc biệt với phương pháp thứ hai. Bạn xác định loại rõ ràng.
Kyle Rosendo

Tôi sử dụng cả hai phương pháp, nó vẫn không hoạt động. Tôi không muốn thao túng chuỗi có thể dẫn đến sự cố bảo mật
Yongwei Xing

Tôi không thực sự hiểu bạn. Khi bạn nói nó không hoạt động, nó có ném ngoại lệ không? Nó làm gì?
Kyle Rosendo

1
Nó không ném ngoại lệ, nó không trả lại gì cả. Nhưng tôi chạy T-SQL trong Studio Management, nó trả về nhiều kết quả.
Yongwei Xing

-1

Đây là một biến thể nhỏ của câu trả lời của Brian mà người khác có thể thấy hữu ích. Lấy một danh sách các khóa và thả nó vào danh sách tham số.

//keyList is a List<string>
System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
string sql = "SELECT fieldList FROM dbo.tableName WHERE keyField in (";
int i = 1;
foreach (string key in keyList) {
    sql = sql + "@key" + i + ",";
    command.Parameters.AddWithValue("@key" + i, key);
    i++;
}
sql = sql.TrimEnd(',') + ")";

Câu trả lời này là sai bởi vì nó có khả năng mở rộng và hiệu suất kém và nó thúc đẩy thực hành mã hóa xấu.
Igor Levicki

-3

thử

sqlComm.Parameters["@Age"].Value = sb.ToString().Replace(","," ");

-5

thử nó như thế này

StringBuilder sb = new StringBuilder(); 
foreach (ListItem item in ddlAge.Items) 
{ 
     if (item.Selected) 
     { 
          string sqlCommand = "SELECT * from TableA WHERE Age IN (@Age)"; 
          SqlConnection sqlCon = new SqlConnection(connectString); 
          SqlCommand sqlComm = new SqlCommand(); 
          sqlComm.Connection = sqlCon; 
          sqlComm.CommandType = System.Data.CommandType.Text; 
          sqlComm.CommandText = sqlCommand; 
          sqlComm.CommandTimeout = 300; 
          sqlComm.Parameters.Add("@Age", SqlDbType.NVarChar);
          sb.Append(item.Text + ","); 
          sqlComm.Parameters["@Age"].Value = sb.ToString().TrimEnd(',');
     } 
} 

2
Tại sao phải đặt SqlConnection và SqlCommnad trong vòng lặp?
Yongwei Xing
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.