SqlCommand'da Geçiş Dizisi Parametresi


144

Aşağıdaki gibi C # SQL commnd dizi parametresi geçmek çalışıyorum, ama çalışmıyor. Daha önce kimse tanıştı mı?

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
Aslında konu değil, ama bana öyle geliyor ki, bir tabloda sütun olarak Yaş olması kötü bir fikir, çünkü sürekli güncellenmesi gerekecek. İnsanlar yaşlanıyor değil mi? Belki de bunun yerine bir DateOfBirth sütunu almayı düşünmelisiniz?
Kjetil Watnedal

Yanıtlar:


169

Dizideki değerleri birer birer eklemeniz gerekir.

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

GÜNCELLEME: İşte Adam'ın cevabını önerilen düzenlemesiyle birlikte kullanan genişletilmiş ve tekrar kullanılabilir bir çözüm. Biraz geliştirdim ve aramayı daha da kolaylaştırmak için bir uzantı yöntemi yaptım.

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

}

Buna şöyle denir ...

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

Sql deyimindeki "{Age}" ifadesinin, AddArrayParameters'a gönderdiğimiz parametre adıyla aynı olduğuna dikkat edin. AddArrayParameters değeri doğru parametrelerle değiştirir.


11
Bu yöntemin sql enjeksiyonu gibi güvenlik sorunu var mı?
Yongwei Xing

7
Değerleri parametrelere koyduğunuz için sql enjeksiyonu riski yoktur.
Brian

Aradığım şey buydu ama bir sorum vardı. OP, SQL'e eklemek için aynı sütunlardan birkaçına sahip olsaydı, bunu nasıl yapardık? Misal. SEÇ * TabloDEN YERE Yaş = ({0}) VEYA Yaş = ({1}). (cmd.Parameters ile nasıl yaparız)
Cocoa Dev

1
@ T2Tom, Hata! Onardım. Teşekkürler.
Brian

1
Bunu beğendim ve yalnızca bir değişkeni yer tutucu dizesini var paramPlaceholder = "{" & paramNameRoot & "}"; Debug.Assert(cmd.CommandText.Contains(paramPlaceholder), "Parameter Name Root must exist in the Source Query"); ayıkladıktan sonra aşağıdaki değişikliği yaptı: Bu, paramNameRoot sorguyla eşleştirmeyi unutursa devs yardımcı olacaktır.
MCattle

37

Brian'ın bunu başka yerlerde kolayca kullanılabilir hale getirmesine katkıda bulunduğu cevabını genişletmek istedim.

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

Bu yeni işlevi aşağıdaki gibi kullanabilirsiniz:

SqlCommand cmd = new SqlCommand();

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

cmd.CommandText = sql;


Düzenleme: İşte herhangi bir türdeki değerler dizisi ile çalışan ve bir uzantı yöntemi olarak kullanılabilir genel bir varyasyon:

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

Daha sonra bu uzantı yöntemini aşağıdaki gibi kullanabilirsiniz:

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

AddArrayParameters'ı çağırmadan önce CommandText'i ayarladığınızdan emin olun.

Ayrıca parametre adınızın ifadenizdeki herhangi bir şeyle kısmen eşleşmediğinden emin olun (örn. @AgeOfChild)


1
İşte herhangi bir türdeki değer dizisi ile çalışan ve bir uzantı yöntemi olarak kullanılabilen genel bir varyasyon: public static void AddArrayParameters <T> (bu SqlCommand cmd, dize adı, IEnumerable <T> değerleri) {var names = string.Join (",", değerler.Seçin ((değer, i) => {var paramName = name + i; cmd.Parameters.AddWithValue (paramName, value); return paramName;})); cmd.CommandText = cmd.CommandText.Replace (ad, adlar); }
Adam Nemitoff

Bu cevapla ilgili küçük bir sorun AddWithValuefonksiyonu ile, düzeltmek için herhangi bir şans?
DavidG

Bu cevap yanlıştır çünkü zayıf ölçeklenebilirlik ve performansa sahiptir ve kötü kodlama uygulamalarını teşvik eder.
Igor Levicki

24

"Dapper" gibi bir araç kullanabiliyorsanız, bu basitçe:

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 bunu sizin için bireysel parametrelere açmayı halledecektir .


Dapper birçok bağımlılık çekiyor :(
mlt

@mlt ha? hayır değil; netfx üzerinde: "bağımlılık yok"; ns2.0'da, sadece "System.Reflection.Emit.Lightweight" - ve bir necroreapp hedefi eklediğimizde bunu kaldırabiliriz
Marc Gravell

Tartışmayı kaçırmak istemedim, ama yaptım ... Şimdiye kadar '{1,2,3}'bir işlevin stil argümanlarında olduğu gibi dizileri iyi işleyen Npgsql kullanıyorum (WHERE IN yan tümcesi değil), ancak düz ODBC kullanmamayı tercih ederim dizi güçlüğü. Bu durumda da Dapper ODBC'ye ihtiyacım olacağını düşünüyorum. İşte çekmek istediği şey. snipboard.io/HU0RpJ.jpg . Belki de Dapper hakkında daha fazla okumak gerekir ...
mlt

16

MS SQL Server 2008 ve üstünü kullanıyorsanız, http://www.sommarskog.se/arrays-in-sql-2008.html adresinde açıklanan gibi tablo değerli parametreleri kullanabilirsiniz .

1. Kullanacağınız her parametre türü için bir tablo türü oluşturun

Aşağıdaki komut, tamsayılar için bir tablo türü oluşturur:

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

2. Yardımcı yöntemleri uygulayın

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. Bu şekilde kullanın

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

1
Çizgi table.Rows.Add(id);bir sonuçlanır küçük kod kokusu SonarQube kullanıldığında. Ben foreach içinde bu alternatifi kullandı: var row = table.NewRow(); row["id"] = id; table.Rows.Add(row);.
pogosama

1
Bu, özellikle daha fazla sütun kabul edecek şekilde uyarlanmışsa, kabul edilen cevap olmalıdır.
Igor Levicki

10

Üzerinde bir yöntem olduğu için

SqlCommand.Parameters.AddWithValue(parameterName, value)

değiştirilecek bir parametreyi (adı) ve değer listesini kabul eden bir yöntem oluşturmak daha uygun olabilir. O değil Parametreler (gibi düzeyine AddWithValue ) ama komuta kendisinde bu yüzden onu çağırmak iyidir AddParametersWithValues ve sadece AddWithValues :

sorgu:

SELECT * from TableA WHERE Age IN (@age)

kullanımı:

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

uzantı yöntemi:

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
Birkaç cevapta bu uzantı yönteminin birkaç yinelemesi var gibi görünüyor. Bunu da kullandım, bu yüzden yukarı oy kullanıyorum :-)
Dan Forbes

parametre adı için statik bir dizin kullanmak daha iyidir
shmnff

6

Başka bir yol, IN operatörü ile sınırlamanın nasıl çözüleceğini önermek istiyorum.

Örneğin şu sorgumuz var

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

Kullanıcıları filtrelemek için birkaç kimlik iletmek istiyoruz. Ne yazık ki C # ile kolay bir şekilde yapmak mümkün değil. Ancak "string_split" işlevini kullanarak bunun için geçici çözüm var. Sorgumuzu aşağıdaki şekilde biraz yeniden yazmamız gerekiyor.

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

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

Şimdi, virgülle ayrılmış değerlerin bir parametre numaralandırmasını kolayca iletebiliriz.


uyumluluğunuzun güncel olması şartıyla en iyi ve bulduğum en temiz yol.
user1040975

4

WHERE..IN deyimine bir dizi öğeyi daraltılmış parametre olarak iletmek, sorgu biçim alacağından başarısız olur WHERE Age IN ("11, 13, 14, 16").

Ancak parametrenizi XML veya JSON'a serileştirilmiş bir dizi olarak iletebilirsiniz:

nodes()Yöntemi kullanarak :

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

OPENXMLYöntemi kullanarak :

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

Bu, SQL tarafında biraz daha fazla ve uygun bir XML'e (root ile) ihtiyacınız var.

OPENJSONYöntemi kullanma (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);

Son yöntem için Uyumluluk Düzeyi'nin 130+ üzerinde olması gerektiğini unutmayın.


0

Genel Bakış: Parametre tipini ayarlamak için DbType kullanın.

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

Kullanın .AddWithValue(), yani:

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

Alternatif olarak, bunu kullanabilirsiniz:

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

Toplam kod örneğiniz aşağıdaki gibi olacaktır:

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

Yaş alanının türü int değil nvchar'dır. Önemli mi?
Yongwei Xing

Olmamalı. Özellikle ikinci yöntemle. Türü açıkça belirtirsiniz.
Kyle Rosendo

Her iki yöntemi de kullanıyorum, hala çalışmıyor. Güvenlik sorununa yol açabilecek ipi manipüle etmek istemiyorum
Yongwei Xing

Seni gerçekten anlamıyorum. İşe yaramadığını söylediğinizde, bir istisna atıyor mu? Bu ne işe yarıyor?
Kyle Rosendo

1
istisna atmaz, hiçbir şey döndürmez. Ancak Studio Yönetimi'nde T-SQL çalıştırıyorum, birçok sonuç döndürüyor.
Yongwei Xing

-1

İşte Brian'ın başka birinin yararlı bulabileceği cevabının küçük bir çeşidi. Bir anahtar listesi alır ve parametre listesine bırakır.

//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(',') + ")";

Bu cevap yanlıştır çünkü zayıf ölçeklenebilirlik ve performansa sahiptir ve kötü kodlama uygulamalarını teşvik eder.
Igor Levicki

-3

Deneyin

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

-5

böyle dene

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
SqlConnection ve SqlCommnad'ı neden döngüye koymalıyım?
Yongwei Xing
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.