テーブル結合のLINQ(GroupJoin, SelectMany)と全データ取得後にプログラムで処理する速度の比較

仕事でプログラミングやってた時に少し気になったので、実験してみた。

複数のテーブルを結合して一括でデータを取得するのと、各テーブルのデータを取得した後、プログラム側でデータを成型することのどっちが速いか。

# 1. 結論
EntityFrameworkで結合したほうが(かなり)早かった。

# 2. 環境
IDE：Visual Studio 2019
DB:Microsoft SQL Server 2017

# 3. やったこと
以下のような処理の速度を比較。

1つめ。テーブルからデータをガバっと取り、プログラム側で外部結合っぽい処理をする。

``` C#
    class Program
    {
        static void Main(string[] args)
        {
            var context = new TestContext();
            var sw = new Stopwatch();

            var _studentReposiroty = new StudentsRepository(context);
            var _classReposiroty = new ClassesRepository(context);
            var _clabReposiroty = new ClabsRepository(context);
            var _userReposiroty = new UsersRepository(context);

            var viewList = new List<TestViewModel>();

            // プログラムでデータ成型
            sw.Start();

            // 特定の生徒データ取得
            var students = _studentReposiroty.List();

            // クラブとかのIdを取得
            var clabIds = students.Select(x => x.ClabId).ToList();
            var classIds = students.Select(x => x.ClassId).ToList();
            var userIds = students.Select(x => x.CreatedByUserId).ToList();

            // IDに紐づく名称のリストを(ID, Name)のタプルで取得
            var classes = _classReposiroty.ListNameById(classIds);
            var clabs = _clabReposiroty.ListNameById(clabIds);
            var users = _userReposiroty.ListNameById(userIds);

            // データ内にあるIDをNameに置き換える。対応するものがなかったら空文字。
            students.ForEach(r =>
            {
                var className = !string.IsNullOrEmpty(classes.Find(x => x.Item1.Equals(r.ClassId)).Item2) 
                    ? classes.Find(x => x.Item1.Equals(r.ClassId)).Item2 : string.Empty;

                var clabName = !string.IsNullOrEmpty(clabs.Find(x => x.Item1.Equals(r.ClabId)).Item2)
                    ? clabs.Find(x => x.Item1.Equals(r.ClabId)).Item2 : string.Empty;

                var userName = !string.IsNullOrEmpty(users.Find(x => x.Item1.Equals(r.CreatedByUserId)).Item2)
                    ? users.Find(x => x.Item1.Equals(r.CreatedByUserId)).Item2 : string.Empty;

                var testViewModel = new TestViewModel
                {
                    Id = r.Id,
                    Name = r.Name,
                    ClassName = className,
                    ClabName = clabName,
                    CreatedByUserName = userName,
                    Age = r.Age
                };

                viewList.Add(testViewModel);
            });

            sw.Stop();

            Console.WriteLine($"処理時間: {sw.ElapsedMilliseconds}ミリ秒");
            Console.WriteLine();

```

2つ目。LINQで一気にデータを取る。

```C#
            sw.Restart();

            var viewList2 = _studentReposiroty.GetTestViewModels();

            sw.Stop();
```

``` C#
        public List<TestViewModel> GetTestViewModels()
        {
            var returnList = _testContext.Students.GroupJoin(
                _testContext.Classes, s => s.ClassId, c => c.Id,
                (s, c) => new
                {
                    s = s,
                    c = c
                }
            ).SelectMany(
                x => x.c.DefaultIfEmpty(),
                (s, c) => new { s = s.s, c }
                )

            .GroupJoin(_testContext.Clabs, sc => sc.s.ClabId, clab => clab.Id, (sc, clab) => new { sc, clab })
            .SelectMany(x => x.clab.DefaultIfEmpty(), (sc, clab) => new { sc = sc.sc, clab })
            .GroupJoin(_testContext.Users, scc => scc.sc.s.CreatedByUserId, u => u.Id, (scc, u) => new { scc, u })
            .SelectMany(x => x.u.DefaultIfEmpty(), (scc, u) => new TestViewModel
            {
                Id = scc.scc.sc.s.Id,
                Name = scc.scc.sc.s.Name,
                ClabName = scc.scc.clab.Name ?? string.Empty,
                ClassName = scc.scc.sc.c.Name ?? string.Empty,
                CreatedByUserName = u.Name ?? string.Empty,
                Age = scc.scc.sc.s.Age
            }).ToList();

            return returnList;
        }

```

# 4．測定結果
前者：4189ミリ秒
後者：160ミリ秒

というわけで、後者のほうが全然早かった｡｡｡｡
ListじゃなくてHashSet使うともっと違うのかしら。
でもまあ、ミリ秒の世界だったら、正直HashSetでもっと早くなったところでどっちでもいいかなぁ｡｡｡｡