Lewati Parameter Array di SqlCommand

144

Saya mencoba meneruskan parameter array ke perintah SQL di C # seperti di bawah ini, tetapi tidak berfungsi. Adakah yang pernah bertemu sebelumnya?

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(',');
Yongwei Xing
sumber
11
Bukan topik yang sebenarnya, tetapi bagi saya sepertinya memiliki Umur sebagai kolom dalam tabel adalah ide yang buruk, karena itu perlu diperbarui terus-menerus. Orang bertambah tua kan? Mungkin Anda harus mempertimbangkan memiliki kolom DateOfBirth saja?
Kjetil Watnedal
pertanyaan dengan jawaban yang baik di sini: stackoverflow.com/questions/83471/…
Adam Butler

Jawaban:

168

Anda perlu menambahkan nilai-nilai dalam array satu per satu.

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

PEMBARUAN: Berikut adalah solusi yang diperluas dan dapat digunakan kembali yang menggunakan jawaban Adam bersama dengan edit yang disarankannya. Saya memperbaikinya sedikit dan membuatnya menjadi metode ekstensi untuk membuatnya lebih mudah untuk dipanggil.

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

}

Ini disebut seperti ini ...

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

Perhatikan "{Age}" dalam pernyataan sql sama dengan nama parameter yang kami kirim ke AddArrayParameters. AddArrayParameters akan mengganti nilai dengan parameter yang benar.

Brian
sumber
11
Apakah metode ini memiliki masalah keamanan, seperti injeksi sql?
Yongwei Xing
7
Karena Anda memasukkan nilai ke dalam parameter, tidak ada risiko injeksi sql.
Brian
Ini yang saya cari tetapi saya punya pertanyaan. Jika OP memiliki beberapa kolom yang sama untuk ditambahkan ke SQL, bagaimana kita melakukan ini? Contoh. PILIH * DARI TableA DI MANA Umur = ({0}) ATAU Usia = ({1}). (bagaimana kita melakukannya dengan cmd.Parameters)
Cocoa Dev
1
@ T2Tom, Aduh. Aku telah memperbaikinya. Terima kasih.
Brian
1
Saya suka ini, dan hanya membuat modifikasi berikut setelah mengekstraksi string placeholder ke variabel: var paramPlaceholder = "{" & paramNameRoot & "}"; Debug.Assert(cmd.CommandText.Contains(paramPlaceholder), "Parameter Name Root must exist in the Source Query"); Ini akan membantu devs jika mereka lupa untuk mencocokkan paramNameRoot dengan kueri.
MCattle
37

Saya ingin memperluas jawaban yang diberikan Brian untuk membuatnya mudah digunakan di tempat lain.

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

Anda dapat menggunakan fungsi baru ini sebagai berikut:

SqlCommand cmd = new SqlCommand();

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

cmd.CommandText = sql;


Sunting: Berikut adalah variasi umum yang berfungsi dengan array nilai jenis apa pun dan dapat digunakan sebagai metode ekstensi:

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

Anda kemudian dapat menggunakan metode ekstensi ini sebagai berikut:

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

Pastikan Anda mengatur CommandText sebelum memanggil AddArrayParameters.

Juga pastikan nama parameter Anda tidak akan cocok dengan apa pun di pernyataan Anda (mis. @AgeOfChild)

J Adam Rogers
sumber
1
Berikut ini adalah variasi umum yang bekerja dengan array nilai jenis apa pun dan dapat digunakan sebagai metode ekstensi: public static void AddArrayParameters <T> (cmd Perintah SqlCommand ini, nama string, nilai IEnumerable <T>) {var names = string.Join (",", values.Select ((value, i) => {var paramName = name + i; cmd.Parameters.AddWithValue (paramName, value); return paramName;})); cmd.CommandText = cmd.CommandText.Replace (nama, nama); }
Adam Nemitoff
Masalah kecil dengan jawaban ini adalah dengan AddWithValuefungsinya, ada kemungkinan Anda bisa memperbaikinya?
DavidG
Jawaban ini salah karena memiliki skalabilitas dan kinerja yang buruk dan mempromosikan praktik pengkodean yang buruk.
Igor Levicki
24

Jika Anda dapat menggunakan alat seperti "dapper", ini bisa dengan mudah:

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 akan menangani pembatalan ini ke parameter individual untuk Anda .

Marc Gravell
sumber
Dapper menarik banyak dependensi :(
mlt
@mlt ya? tidak, itu tidak; di netfx: "tidak ada dependensi"; pada ns2.0, cukup "System.Reflection.Emit.Lightweight" - dan kita mungkin bisa menghapusnya jika kita menambahkan target necroreapp
Marc Gravell
Saya tidak bermaksud membajak diskusi, tetapi saya lakukan ... Sejauh ini saya menggunakan Npgsql yang menangani array dengan baik seperti dalam '{1,2,3}'argumen style ke fungsi (bukan klausa WHERE IN), tapi saya lebih suka menggunakan ODBC polos jika tidak kerumitan array. Saya kira saya perlu Dapper ODBC juga dalam kasus ini. Inilah yang ingin ia tarik. snipboard.io/HU0RpJ.jpg . Mungkin saya harus membaca lebih lanjut tentang Dapper ...
mlt
16

Jika Anda menggunakan MS SQL Server 2008 dan di atasnya, Anda dapat menggunakan parameter bernilai tabel seperti dijelaskan di sini http://www.sommarskog.se/arrays-in-sql-2008.html .

1. Buat tipe tabel untuk setiap tipe parameter yang akan Anda gunakan

Perintah berikut membuat tipe tabel untuk bilangan bulat:

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

2. Terapkan metode pembantu

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. Gunakan seperti ini

cmd.CommandText = "select * from TableA where Age in (select id from @age)"; 
cmd.AddParameter("@age", new [] {1,2,3,4,5});
Gregor Slavec
sumber
1
Baris table.Rows.Add(id);menghasilkan bau kode kecil saat menggunakan SonarQube. Saya menggunakan alternatif ini di dalam foreach: var row = table.NewRow(); row["id"] = id; table.Rows.Add(row);.
pogosama
1
Ini harus menjadi jawaban yang diterima, terutama jika itu disesuaikan untuk menerima lebih banyak kolom.
Igor Levicki
10

Karena ada metode pada

SqlCommand.Parameters.AddWithValue(parameterName, value)

mungkin lebih nyaman untuk membuat metode yang menerima parameter (nama) untuk diganti dan daftar nilai. Ini bukan pada level Parameter (seperti AddWithValue ) tetapi pada perintah itu sendiri sehingga lebih baik untuk menyebutnya AddParametersWithValues dan bukan hanya AddWithValues :

pertanyaan:

SELECT * from TableA WHERE Age IN (@age)

pemakaian:

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

metode ekstensi:

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));
    }
}
tridy
sumber
1
Sepertinya beberapa iterasi dari metode ekstensi ini ada di beberapa jawaban. Saya menggunakan yang ini, jadi saya memilihnya :-)
Dan Forbes
lebih baik menggunakan indeks statis untuk nama parameter
shmnff
6

Saya ingin mengusulkan cara lain, bagaimana mengatasi keterbatasan dengan operator IN.

Sebagai contoh, kami memiliki permintaan berikut

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

Kami ingin memberikan beberapa ID untuk memfilter pengguna. Sayangnya itu tidak mungkin dilakukan dengan C # dengan cara mudah. Tapi saya punya sumber solusi untuk ini dengan menggunakan fungsi "string_split". Kami perlu menulis ulang sedikit permintaan kami untuk mengikuti.

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

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

Sekarang kita dapat dengan mudah melewati satu enumerasi parameter nilai yang dipisahkan oleh koma.

pengguna2399170
sumber
terbaik dan cara terbersih yang saya temukan, asalkan kompatibilitas Anda terkini.
user1040975
4

Melewati larik item sebagai parameter yang diciutkan ke klausa WHERE..IN akan gagal karena kueri akan berbentuk WHERE Age IN ("11, 13, 14, 16").

Tapi Anda bisa meneruskan parameter Anda sebagai array yang diserialisasi ke XML atau JSON:

Menggunakan nodes()metode:

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

Menggunakan OPENXMLmetode:

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

Itu sedikit lebih di sisi SQL dan Anda perlu XML yang tepat (dengan root).

Menggunakan OPENJSONmetode (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);

Perhatikan bahwa untuk metode terakhir Anda juga harus memiliki Tingkat Kompatibilitas di 130+.

Lukasz Matysiak
sumber
0

Gambaran Umum: Gunakan DbType untuk mengatur tipe parameter.

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()
Singa Emas
sumber
-1

Gunakan .AddWithValue(), jadi:

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

Atau, Anda bisa menggunakan ini:

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

Sampel kode total Anda akan melihat sebagai berikut:

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 });
Kyle Rosendo
sumber
Jenis bidang Usia adalah nvchar tidak int. Apakah itu penting?
Yongwei Xing
Seharusnya tidak. Apalagi dengan metode kedua. Anda menentukan jenisnya secara eksplisit.
Kyle Rosendo
Saya menggunakan kedua metode, itu masih tidak berhasil. Saya tidak ingin memanipulasi string yang dapat menyebabkan masalah keamanan
Yongwei Xing
Saya tidak benar-benar mengerti Anda. Ketika Anda mengatakan itu tidak berhasil, apakah itu melempar pengecualian? Apa fungsinya?
Kyle Rosendo
1
tidak membuang pengecualian, tidak mengembalikan apa pun. Tapi saya menjalankan T-SQL di Studio Management, ia mengembalikan banyak hasil.
Yongwei Xing
-1

Berikut adalah varian kecil dari jawaban Brian yang mungkin berguna bagi orang lain. Mengambil Daftar kunci dan memasukkannya ke daftar parameter.

//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(',') + ")";
Jeff
sumber
Jawaban ini salah karena memiliki skalabilitas dan kinerja yang buruk dan mempromosikan praktik pengkodean yang buruk.
Igor Levicki
-3

mencoba

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

coba seperti ini

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(',');
     } 
} 
Ballin
sumber
2
Mengapa menempatkan SqlConnection dan SqlCommnad di loop?
Yongwei Xing