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

Dapperで匿名型を使う

Dapper便利ですよね。いまさら最近使い始めました。

オブジェクトにマッピングしてくれるのは楽でいいんだけど、ちょこっとSELECT発射したいときにオブジェクト定義するのは手間だよね。
dynamic? VSのIntelliSenseが効かないよね?

( ゚д゚)ハッ! 匿名型渡せばいいんだ。

とりあえず定義した匿名型をジェネリックで渡せるように、ラッパー拡張メソッドを作りました。

public static class DapperEx
{
  public static IEnumerable<T> Query<T>(this IDbConnection connection, T columnsType, string sql, object param = null)
  {
    return connection.Query<T>(sql, param);
  }
}
class DapperSample
{
  public static void DapperTest()
  {
    using (var connection = new SqlConnection(@"接続文字列"))
    {
      var rows = connection.Query(
        new { 
          Column1 = default(string), 
          Column2 = default(int), 
          Column3 = default(DateTime) 
        },
        "SELECT * FROM Table_1");

      foreach (var row in rows)
      {
        Console.WriteLine("{0}", row.Column2);
      }
    }
  }

}

なんだ簡単じゃん。

SELECT文で返ってくるカラム数と匿名型のプロパティ数が一致する場合はいいんですが、一致しないと例外が

型 'System.InvalidOperationException' のハンドルされていない例外が Dapper.dll で発生しました
追加情報:A parameterless default constructor or one matching signature 

発生する理由がわからなかったので、とりあえずGitからコードを取得してデバッグしてみるとに・・・

原因は、匿名型のコンストラクタにありました。
匿名型を使用すると、ビルド時にクラスが自動生成されます。その自動生成したコンストラクタは、引数なしではなく、定義したプロパティすべてを必要となっとる!

newobj instance void class '<>f__AnonymousType0`3'<string, int32, valuetype [mscorlib]System.DateTime>::.ctor(!0, !1, !2)

ILを見るとよくわかる。”ctor(!0, !1, !2)”の部分がコンストラクタ

せっかく楽しようとしたのにカラム数を一致させるのは手間なので。
どうせちょっとした開発ツールでしか使わないし・・・
”SELECT * FROM”の部分を動的に生成するようにしようか。

public static class DapperEx
{
    public static IEnumerable<T> Query<T>(this IDbConnection connection, T columnsType, string sql, object param = null)
    {
      if (sql.Contains("@Columns") == true)
      {
        var tyep = columnsType.GetType();
        if (sqlcache.ContainsKey(tyep) == false)
        {
          var names = new List<string>();
          foreach (var prop in tyep.GetProperties())
          {
            names.Add(prop.Name);
          }
          var colums = string.Join(",", names);

          sqlcache.TryAdd(tyep, colums);
        }
        sql = sql.Replace("@Columns", sqlcache[tyep]);
      }
      return connection.Query<T>(sql, param);
    }
}
class DapperSample
{
  public static void DapperTest()
  {
    using (var connection = new SqlConnection(@"接続文字列"))
    {
      var rows = connection.Query(
        new { 
          Column1 = default(string), 
          Column2 = default(int), 
          Column3 = default(DateTime) 
        },
        "SELECT @Columns FROM Table_1");

      foreach (var row in rows)
      {
        Console.WriteLine("{0}", row.Column2);
      }
    }
  }
}

”@Columns”をSQL文中に埋め込んでおいて、それを匿名型のプロパティを取得して連結して置換するだけ
なんか負けた感が半端ない。