Entity Framework でクエリが実行されるタイミングを理解する
はじめに
C# の OR マッパー Entity Framework Core では LINQ to Entities を使用してオブジェクトを操作するようにデータストア(例えばリレーショナルデータベース)にアクセスすることができます。
IQueryable<Book> booksQuery = dbContext.Books
.Where(book => book.AuthorName == "Robert C.Martin")
.OrderBy(book => book.PublishYear);
クエリ変数の型がIQueryable<Book>
であることから分かるように、クエリ変数は結果を保持しておらず、代わりにクエリのコマンドが格納されるだけです。
IQueryable<T>
からクエリが実際に実行されるタイミングは
-
foreach
によるループ処理をする時 -
Single
Max
など一つの値を得る時 -
ToList
などで列挙する時
の3つです。
※追記:いただいたコメントによると以下の方法で簡単に見分けられるそうです。
LINQメソッドが即時実行か遅延実行か見分ける簡単な方法は、返り値の型を見ることです。
返り値の型がIEnumerableかSystem.Linq名前空間のインターフェイスなら遅延実行。
foreach
ループによるクエリの実行
IQueryable<T>
(のExspression
プロパティ)に格納されたコマンドは、foreach
によってループ処理されるときに実際に実行されます。
using (var dbContext = new BooksDbContext()) {
// まだクエリは実行されていない。query は結果のデータでなく式を持っている状態
IQueryable<string> query = dbContext.Books
.Where(b => b.PublishYear < 1970)
.Select(b => b.Title);
// foreach ループで初めてクエリが流れてデータストアからデータを取得する
foreach (var title in query) {
Console.WriteLine(title);
}
}
1つの値を返す LINQ によるクエリの実行
1つの値だけを返すクエリは即時に実行されます。
Average
Max
Min
Count
First
FirstOrDefault
Single
...などです。
using (var dbContext = new BooksDbContext()) {
// データストアからデータを取得した結果が変数に格納される
double averagePrice = dbContext.Books
.Where(b => b.AuthorName == "アーサー・C・クラーク")
.Average(b => b.Price);
Console.WriteLine(averagePrice);
// データストアからデータを取得した結果が変数に格納される
Book book = dbContext.Books.FirstOrDefault(b => b.ID == 1);
Console.WriteLine(book.Title);
}
結果を列挙する時のクエリの実行
ToList
ToDictionary
ToArray
などシーケンス(IEnumerable<T>
)を返すメソッドはクエリを即時実行します。
(※追記AsEnumerable
は即時実行ではありませんでした。コメントありがとうございます。)
using (var dbContext = new BooksDbContext()) {
// クエリが即時実行され、変数に結果が List<T> として格納される
List<string> titles = dbContext.Books
.Select(b => b.Title)
.ToList();
Console.WriteLine(titles[2]); // List<T>(既に結果取得済み)になっている
}
using (var dbContext = new BooksDbContext()) {
// クエリが即時実行され、変数に結果が Dictionary<TKey, TValue> として格納される
IDictionary<int, string> dic = dbContext.Books
.Where(book => book.Price > 3000)
.ToDictionary(book => book.ID, Book => Book.Title);
if (dic.TryGetValue(key: 1, out string title)) { // used C# 7.3
Console.WriteLine(title);
}
}
クエリの合成
クエリの実行を遅延できるので、クエリを合成することも可能です。
以下の例ではforeach
までクエリの実行が遅延されるのでWhere
によるフィルタリング処理も含んだクエリが実行されます。
using (var dbContext = new BooksDbContext()) {
// クエリはまだ実行されず、式を保持している状態
var query = dbContext.Books.Select(book => book.Title);
// まだクエリが実行されていないので取得結果をフィルタリングするわけでなく、
// 式に対してフィルタリング処理を追加できる
if (!string.IsNullOrWhiteSpace(serchString)) {
query = query.Where(title => title.Contains(serchString));
}
// ここでクエリが実行される
foreach (var title in query) {
Console.WriteLine(title);
}
}
LINQ to Entities から LINQ to Objects に切り替わるタイミングの見逃し
上記のように便利な LINQ to Entities ですが、クエリが実行されるタイミングを把握していないと、うっかり非効率なクエリを書いてしまうことがあります。
以下はC#
をタイトルに含む書籍のタイトル一覧を取得するクエリです。
// SQL は実行されておらず式を保持している状態
var query = dbContext.Books
.Select(book => book.Title)
.Where(title => title.Contains("C#"));
// foreach による反復処理時に SQL が発行される
foreach (var title in query) {
Console.WriteLine(title);
}
クエリに途中にAsEnumerable
が入るとどうでしょうか。
以下の例ではforeach
による列挙でクエリが実行される際に、DB から書籍のタイトルを全て取得するクエリが実行されAsEnumerable()
以降のフィルタリングは LINQ to Objects によってメモリ内でが行われます。
(※追記:記事内で「遅延実行と即時実行」と「LINQ to Entities と LINQ to Objectsが切り替わるタイミング」がごっちゃになっていましたので表現を修正しました。詳しくはコメント欄をご確認ください。)
// クエリはまだ実行されていない
var query = dbContext.Books
.Select(book => book.Title)
.AsEnumerable() // <- IEnumerable<T> に切り替わる
.Where(title => title.Contains("C#"));
// クエリが実行される
foreach (var title in query) {
Console.WriteLine(title);
}
通信量が増え、1つ目のクエリと比べ、はるかに遅くなる可能性が高いです。
上記の例では途中にAsEnumerable
が挟まっているのでわかりやすいですが、クエリが複雑になったりメソッドが分割されているときは注意が必要です。
また、それ以外でも、データストアからデータを取得するメソッドを定義した際、結果を返すのではなく、IQueryable<T>
を返してしまい、実際にクエリが実行されるときにはデータストアとの接続が切れていて例外をスローすることも起こります。(実体験)
やはり、LINQ to Entities でいつクエリが実行されるかを把握しておくことは重要だと思います。
2年目プログラマーです。
エンジニアの方とつながれるととても嬉しいです!
twitter: のさ@nosa_programmer
Author And Source
この問題について(Entity Framework でクエリが実行されるタイミングを理解する), 我々は、より多くの情報をここで見つけました https://qiita.com/Nossa/items/a2f36718d8c6ae6a4dc3著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .