2019年4月24日水曜日

C#(ADO.NET)でIN句にSqlParameterをわたす方法


オフィス狛 技術部のHammarです。

SQLでIN句を使ってデータを取得するというのはよくある取得条件ですが、実際のプログラムで実装する場合には、SQLインジェクション対策といったセキュリティの観点から、IN句の中はプレースホルダを使って動的に値を受け渡したいという場面があると思います。

基本的にはIN句の中を作成し、そのままパラメータとして渡すという方法でいけるのですが、C#(ADO.NET)を使って同様の実装をすると、意図したデータが取得できずにうまくいかずハマってしまいましたので、今回はその対応方法を書きたいと思います。

DB環境としてSQLServerを対象にしています。
まず通常のIN句をそのままパラメータで渡す方法として、下記のように記述します。
// 配列データ
string[] array = new string[3];
array[0] = "name1";
array[1] = "name2";
array[2] = "name3";

// DB接続
string connect = 【DB接続内容】

// 取得SQL
string query =
   "SELECT * FROM TestTable "
       + "WHERE Name IN (@param) ";
// IN句作成
string paramValue = string.Join(",", array);

using (SqlConnection connection = new SqlConnection(connect))
{
   SqlCommand command = new SqlCommand(query, connection);
   // パラメータセット
   command.Parameters.Add("@param", SqlDbType.VarChar).Value = paramValue; 
   connection.Open();
   // SQL実行
   SqlDataReader reader = command.ExecuteReader();
   while (reader.Read())
   {
       Console.WriteLine("\t{0}\t{1}\t{2}", reader[0], reader[1], reader[2]);
   }
   connection.Close();
}

上記を実行すると、結果は何も取得できません。 どうやらSqlParameterでわたす値は1つの値と見なされるなしく、IN句のカンマ区切りで渡した内容も1つの値となるため、検索条件がおかしくなり意図しない結果として返ってくるようです。

これを知らなかったので、いくらIN句の値をいろいろ変えてやってみたんですが、全く値が取れずにかなりハマりました。

結果「SqlParameterでわたす値は1つの値と見なされる」ということなので、IN句の中で渡すパラメータを1つずつ設定してあげないといけないようです。 SQLはこんな感じになります。

string query =
   "SELECT * FROM TestTable "
       + "WHERE Name IN (@param1, @param2, @param3) ";

当然IN句の中は動的に変える必要があるので、@param自体も動的に作成する必要があるということになります。
で、最終的にはこのような感じになりました。

// DB接続
string connect = 【DB接続内容】
// 取得SQL作成
string query =
   "SELECT * FROM TestTable WHERE Name IN ( ";
for (int i = 0; i < array.Count(); i++)
{
   if (i < (array.Count() - 1))
   {
       query += "@param" + i + ", ";
   }
   else
   {
       query += "@param" + i;
   }
}
query += ")";

using (SqlConnection connection = new SqlConnection(connect))
{
   SqlCommand command = new SqlCommand(query, connection);
   string[] paramNames = new string[array.Count()];
   // パラメータ作成
   for (int j = 0; j < array.Count(); j++)
   {
       paramNames[j] = "@param" + j;
       command.Parameters.Add(paramNames[j], SqlDbType.VarChar).Value = array[j];
   }
   connection.Open();
   // SQL実行
   SqlDataReader reader = command.ExecuteReader();
   while (reader.Read())
   {
       Console.WriteLine("\t{0}\t{1}\t{2}", reader[0], reader[1], reader[2]);
   }
   connection.Close();
}

上記のようにSQLとパラメータのIN句部分をそれぞれ動的に生成することで対応します。

結局スマートな書き方というか、そういう書き方ではなく、どちらかというとまあ当然の書き方というか、IN句をループして動的処理を自作しなければいけないので、ちょっときれいな感じではないですが、IN句はよく使われると思うのでご参考になれば。

ちなみにいろいろ調べてみると、LIKE句をうまく使ってやることもできるようで自分も試してみたのですが、なぜか最初の1件だけしか取得できずうまくいかなかったので、今回のようなスタンダードな書き方に落ち着きました。

0 件のコメント:

コメントを投稿