LINQ(to Entities)の遅延実行に入門!
2019-08-13
azblob://2022/11/11/eyecatch/2019-08-13-introduction-to-lazy-execution-of-linq-to-entities-000.jpg

.NET エンジニアの皆様、データベースとのアクセスを効率的にできていますか?

プログラムからデータベースにアクセスする手段は多種多様に存在しています。
私はその中でも、LINQ to Entities を使用する方法が好きです。
非常にお手軽にスタイリッシュにDBアクセス処理を書くことができます。
正直、データベースの知識がほとんど無くても使えるんじゃないかと思います。

しかし、データベースにどのようにアクセスが発生するか意識していなければ恐ろしい目に遭いますので、最低限のところを押さえてから使えると良いです。

遅延実行

LINQには「遅延実行」という仕組みがあります。
「後でこんなこと実行したいな~」というリクエストを埋め込むイメージです。
処理が書かれた場所では何もしないので、特に学生の時にCPUの仕組みから勉強している方だと、なんだか気持ち悪い仕組みだと思えるかもしれません。
でも、「これがいいんですよ、これが」というところをお伝えします。

データベースアクセスのイメージ

例えばこんな単純なモデルがあったとします。

public class SampleData
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Status { get; set; }
}

そうすると、テーブルはこんなのですよね。

IdNameStatus
1A0
2B1
3C1

LINQでテーブルのデータを全件取りたいときはどうしますか?
DBコンテキストが _context だとしたら、こんな感じですね。

var entities = await _context.SampleDatas.ToListAsync();

こうすると、DBに対してSELECT文が飛びます。SQLは下記のイメージ(*)です。

SELECT Id, Name, Status FROM SampleDatas

(*) 実際はもう少し違う形のSQLが飛んでいて、[出力]ウィンドウから確認可能です。SQLで飛ばしている命令が伝わりやすいよう、一部加工したSQLを示します

良いんじゃないですか?
次に、Status が 1 のデータの検索 を実施したいとしたらどうしますか?

var entities = await _context.SampleDatas.ToListAsync();
var status1 = entities.Where(x => x.Status == 1);

こんな感じ?
こうすると、DBに対して飛ぶSELECT文はさっきと変わりません。
つまり、
(1) テーブルのデータを全件取ってきて、
(2) それをメモリ上に展開し、
(3) 最後に Where で欲しいものを抽出する
という流れになってしまいます。

この動作には2つの問題があります。

  • 必要ないデータ(Status != 1のデータ)をDBから取ってきていて無駄な通信が発生している
  • 必要ないデータ(Status != 1のデータ)がメモリ上に展開されていて無駄なメモリ消費が発生している

検索処理はDB側に任せるべきですよね。
本来はどうするべきでしょうか?

遅延実行の活用

ここで冒頭の遅延実行について思い出しましょう。
「後でこんなこと実行したいな~」というリクエストを埋め込むイメージで、「Status == 1のデータで絞り込みたいな~」という処理を ToListAsync() の前に実行すれば良さそうではないですか?
下記のように書いてみます。

var status1 = await _context.SampleDatas
    .Where(x => x.Status == 1)
    .ToListAsync();

C# の文法の観点で見る限りは実行順が入れ替わっただけですが、 Where()ToListAsync() の前に挟んだことで、 「Status == 1のデータで絞り込みたいな~」という遅延実行の処理を事前に実行してみました。
こうするとSQLは下記のイメージのものが飛ぶようになりました。

SELECT Id, Name, Status
FROM SampleDatas
WHERE Status = 1

これなら、
(1) テーブルのデータのうち、Status = 1 のものを取ってくる
だけの処理になるので、無駄なくDBアクセスできていますね!

このように絞り込み処理などは積極的に遅延実行の仕組みを使用して、データベース側にお任せしましょう。
ただし、データベースで実行できないような検索・加工処理もあるので、必要に応じて一旦 ToListAsync() を実行してからメモリ上で処理実施するなど、データの規模や要件などを踏まえて工夫してコーディングしてください。