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

Dapperで一対多の関係性をネストされたオブジェクトにマッピングする

Dapperを用いて一対多の関係をマッピングする方法の備忘録です。

データベースのテーブル定義は下記の通りです。

CREATE TABLE [dbo].[Users]
(
  [Id] INT NOT NULL PRIMARY KEY, 
  [Name] NVARCHAR(64) NULL
)

CREATE TABLE [dbo].[TodoItems]
(
  [Id] INT NOT NULL PRIMARY KEY, 
  [Title] NVARCHAR(64) NOT NULL, 
  [Done] BIT NOT NULL, 
  [UserId] INT NOT NULL
)

サンプルデータをなんとなくぶち込みます。

f:id:mrgchr:20170419203016p:plain

この場合、「各Userに対するTodoItems」は、下記の通りになります。

f:id:mrgchr:20170419203134p:plain

これをDapperを用いてマッピングします。
対応するC#のクラスは下記の通りです。

public class User
{
  public int Id { get; set; }

  public string Name { get; set; }

  public ICollection<TodoItem> TodoItems { get; set; }
}

public class TodoItem
{
  public int Id { get; set; }

  public string Title { get; set; }

  public bool Done { get; set; }
}

これをDapperを用いて一度のクエリ発行でマッピングするには下記のようにします。

var sql =
@"
Select 
  u.*,
  t.*
From Users as u
LEFT OUTER JOIN TodoItems As t On u.Id = t.UserId
";

using (var conn = new SqlConnection(connectionstring))
{
  var dict = new Dictionary<int, User>();

  var users =
    conn.Query<User, TodoItem, User>(sql,
      map: (u, t) =>
      {
        if (!dict.TryGetValue(u.Id, out User user))
        {
          user = u;
          dict.Add(user.Id, user);
        }

        if (user.TodoItems == null) user.TodoItems = new List<TodoItem>();

        if (t != null) user.TodoItems.Add(t);

        return user;
      });

  var usersWithTodoItems = dict.Values;
}

これにより、usersWithTodoItemsの各UserのTodoItemsプロパティにTodoItemsがマッピングされています。
一方で、users変数には、sqlの返すレコード、この場合は4つのUserが格納されています。こちらのTodoItemsプロパティにも同様にマッピングされています。

f:id:mrgchr:20170419203957p:plain