[C#] DapperでListプロパティを持つクラスに一発でマッピングさせる方法

## 例えば

もし、以下のようなメンバークラスがあったとして
（説明のためにしょっぱくなっていますがご勘弁ください!!）

```csharp
    public class Member
    {
        public int Id { get; set; }
        public string Name { get; set; }
        
        public string ClassCode { get; set; }
        public string ClassName { get; set; }
        
        public string GradeCode { get; set; }
        public string GradeName { get; set; }
    }
```

それを以下のようなListプロパティを持つ学年、組、生徒に一発でマッピングさせたい場合、この記事はお役に立てると思います。

```csharp
    public class Grade
    {
        public string GradeCode { get; set; }
        public string GradeName { get; set; }
        public List<Class> Classes { get; set; }
    }

    public class Class
    {
        public string ClassCode { get; set; }
        public string ClassName { get; set; }
        public List<Student> Students { get; set; }
   }

    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

```

## やり方

まずは完成形をご覧ください。

```csharp
var grades = new List<Grade>();  // 最終的にこのListにマッピングします

using (var db = new MyDbContext())
{
    db.Database.Connection.Query<Student, Class, Grade, Student>(
        "select * from members",
        (s, c, g) =>
        {
            if (!grades.Contains(g))
            {
                c.Students = new List<Student>() { s };
                g.Classes = new List<Class>() { c };
                grades.Add(g);
                return s;
            }

            Grade grade = grades.Single(gl => gl.Equals(g));
            if (!grade.Classes.Contains(c))
            {
                c.Students = new List<Student>() { s };
                grade.Classes.Add(c);
                return s;
            }

            Class clazz = grade.Classes.Single(cl => cl.Equals(c));
            clazz.Students.Add(s);

            return s;
        },
        splitOn: "ClassCode,GradeCode");
}
```

- ポイント
    1. Dapperの戻り値でマップさせられないので、内部のFuncでコネコネして作成する
    2. Func内のreturnはダミーで意味はありません
    3. Equalメソッドをoverrideして等価判定を簡潔に表現する（本題とは直接は関係ないですが）

ポイント3のサンプルは以下の通りです。

```csharp
    public class Grade
    {
        public string GradeCode { get; set; }
        public string GradeName { get; set; }
        public List<Class> Classes { get; set; }

        // コードと名称の値が一致したら等価と判定する
        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
                return false;

            Grade g = (Grade)obj;
            return (GradeCode == g.GradeCode) && (GradeName == g.GradeName);
        }

        // Equalsをoverrideすると併せて書かないと警告がでます
        // 同じくコードと名称のみハッシュ対象とします
        public override int GetHashCode()
        {
            var hashCode = 780226712;
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(GradeCode);
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(GradeName);
            return hashCode;
        }
    }
```

## 具体例

- Memberデータ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134221/899c9303-9fac-5000-6b33-1fd5dd7107f0.png)


- 結果

```csharp
foreach (var grade in grades)
{
    Console.WriteLine("------------");
    Console.WriteLine(grade.GradeName);
    var classes = grade.Classes.Select(c => c);
    foreach (var clazz in classes)
    {
        Console.WriteLine("--------");
        Console.WriteLine(clazz.ClassName);
        var students = clazz.Students.Select(s => s);
        foreach (var student in students)
        {
            Console.WriteLine(student.Name);
        }
    }
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134221/59c16814-28df-2784-a0f9-46574c9edb11.png)

意図した通りにマッピングさせられました。

## 参考サイト

[How do I map lists of nested objects with Dapper](https://stackoverflow.com/questions/7508322/how-do-i-map-lists-of-nested-objects-with-dapper)

本ソースをgithubに公開しております。（しょっぱいです。）
[githubサンプルソース](https://github.com/Makohan/DapperForNestedListObject)