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

ODP.NET OracleCommand はデフォルトではバインド変数に名前指定出来ない

OracleCommand の BindByName プロパティを true に設定すると、名前指定可能になります。
OracleCommandクラス
しかし、デフォルトは false です。
ドキュメント読んでなくて、全然気づかなかった。。普通に名前指定でいけてる感じで使ってました。
しかも、バインド変数名が 1つしかない場合は、複数回利用してても例外出ずに動いてたせいで、さらに気づくのが遅れた!

このコードはセーフ

var cmd = new OracleCommand("select SYSDATE from dual where :PARAM1 is null or :PARAM1 = '1'", conn);
cmd.Parameters.Add(new OracleParameter("PARAM1", DBNull.Value));
cmd.ExecuteScalor(); // 例外出ず

このコードはアウト

var cmd = new OracleCommand("select SYSDATE from dual where (:PARAM1 is null or :PARAM1 = '1') and (:PARAM2 is null or PARAM2 = '2')", conn);
cmd.Parameters.Add(new OracleParameter("PARAM1", DBNull.Value));
cmd.Parameters.Add(new OracleParameter("PARAM2", "2"));
cmd.ExecuteScalor(); // バインド変数とパラメータの数があってない例外発生!
// cmd.BindByName = true; にするか、Parameter を 4つ Add したら例外が出ない。

なんでこんな挙動なんだろ?


今回は IDbConnection インターフェイス (System.Data) を受け取って良しなにしてくれる Micro-ORM*1 を使っていました。なので Command を直接触る機会はありません!


というわけで、IDbConnection のメソッドだけ委譲した OracleConnectionクラス の Wrapper を作成して CreateCommand だけちょこちょこ編集する方法で回避しました。*2

コンパイル通るか未確認。雰囲気は伝わるよね?

public class OracleConnectionWrapper : IDbConnection {
  private readonly OracleConnection _conn;
  public OracleConnectionWrapper(OracleConnection conn) {
    Contract.Required(conn != null);
 
    _conn = conn;
  }
  // ...他の IDbConnection の実装は、_conn のメソッドをそのまま呼び出す

  IDbCommand IDbConnection.CreateCommand() { 
    var cmd = _conn.CreateCommand();
    cmd.BindByName = true;
    return cmd;
  }
}

こいつを ORM のコンストラクタに渡すと、バインド変数に名前指定出来ます。

*1:[http://dbexecutor.codeplex.com/:title]

*2:ちなみに OracleConnection は sealed なので継承出来ません。なので委譲する位しか手出せなさそう。