読者です 読者をやめる 読者になる 読者になる

DapperのSQLプレースホルダをコーディングミスに強くする方法を考える

(追記アリ)
DapperのSQLプレースホルダでコーディングミスをしたので対応策の備忘録です。

文字列でのパラメータ指定が大嫌いなんです

DapperやADO.NETSQL中に"@“から始まる文言を用いることでプレースホルダを表現できます。
例えば下記の例では”@userId"がプレースホルダとなり、"new {userId = 1}“にてパラメータを指定しています。

var sql =
@"
Select
  u.*
From Users as u
Where u.Id = @userId
";

using (var conn = new SqlConnection(connectionstring))
{
  var user = conn.QueryFirstOrDefault<User>(sql, new { userId = 1});
}

今回は、この無名クラスのuserIdをタイポして実行時エラーとなりました。

System.Data.SqlClient.SqlException: 'Must declare the scalar variable "@userId".'

すぐに気が付くようなエラーなので大したことではなかったのですが、やはり実行時エラーは気に入りません。
静的型付言語を使っている以上コンパイル時に最大限エラーを発見したいものです。

対応策1. nameofとDynamicParametersを使う

SQL文の中のプレースホルダが文字列になっているのがそもそもの原因です。
nameofを使ってコンパイラの監視下に置きましょう。

//userId = 1 が与えられているとする

var sql =
$@"
Select
  u.*
From Users as u
Where u.Id = @{nameof(userId)}
";

var param = new DynamicParameters();
param.Add(nameof(userId), value: userId);

using (var conn = new SqlConnection(connectionstring))
{
  var user = conn.QueryFirstOrDefault<User>(sql, param: param);
}

対応策2. nameofと別の型を使う

あるいはこんな風に書いても動作します。
入力パラメータ専用のDTOを作成するか共用するかは設計次第でしょうか。

//userId = 1 が与えられているとする

var u = new User() { Id = userId };
var sql =
$@"
Select
  u.*
From Users as u
Where u.Id = @{nameof(User.Id)}
";

using (var conn = new SqlConnection(connectionstring))
{
  var user = conn.QueryFirstOrDefault<User>(sql, param: u);
}

(追記)対応策3. nameofと無名クラスを使う

すっかり忘れていましたが、こういう方法もありました。

//userId = 1 が与えられているとする

var sql =
$@"
Select
  u.*
From Users as u
Where u.Id = @{nameof(userId)}
";


using (var conn = new SqlConnection(connectionstring))
{
  var user = conn.QueryFirstOrDefault<User>(sql, param: new { userId });
}

まとめ

nameof演算子は便利ですね。