私はEntity Framework 6を使用していくつかのWeb APIに取り組んでおり、コントローラーメソッドの1つは、データベースのテーブルの内容をIQueryable<Entity>
として受け取ることを期待する「Get All」です。私のリポジトリでは、非同期でEFを使用するのが初めてなので、非同期にこれを行う有利な理由があるかどうか疑問に思っています。
基本的には
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
対
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
非同期バージョンは実際にここでパフォーマンス上の利点をもたらしますか、最初にリストに投影し(非同期を使用して)、次にIQueryableに行くことで不要なオーバーヘッドが発生しますか?
問題は、Entity Frameworkでasync/awaitがどのように機能するかを誤解しているように思われます。
それでは、このコードを見てみましょう。
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
そして、その使用例:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
そこで何が起きますか?
repo.GetAllUrls()
を使用してIQueryable
オブジェクト(まだデータベースにアクセスしていない)を取得しています.Where(u => <condition>
を使用して、指定された条件で新しいIQueryable
オブジェクトを作成します.Take(10)
を使用して、指定されたページング制限を持つ新しいIQueryable
オブジェクトを作成します.ToList()
を使用してデータベースから結果を取得します。 IQueryable
オブジェクトはsqlにコンパイルされます(select top 10 * from Urls where <condition>
など)。データベースはインデックスを使用できます。SQLサーバーはデータベースから10個のオブジェクトのみを送信します(データベースに保存されている10億個のURLすべてではありません)さて、最初のコードを見てみましょう。
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
同じ使用例で得たもの:
await context.Urls.ToListAsync();
を使用して、データベースに保存されている10億のURLすべてをメモリにロードしています。なぜasync/awaitの使用が推奨されるのですか?このコードを見てみましょう:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
そこで何が起こるの?
var stuff1 = ...
userId
のstuff1を取得するリクエストをSQLサーバーに送信しますvar stuff2 = ...
userId
のstuff2を取得するリクエストをSQLサーバーに送信しますそれでは、非同期バージョンを見てみましょう。
var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));
そこで何が起こるの?
ここに良いコード:
using System.Data.Entity;
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
public async Task<List<URL>> GetAllUrlsByUser(int userId) {
return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();
}
IQueryableのメソッドToListAsync()
を使用するには、using System.Data.Entity
を追加する必要があることに注意してください。
フィルタリングとページングなどが必要ない場合は、IQueryable
を使用する必要がないことに注意してください。 await context.Urls.ToListAsync()
を使用して、実体化されたList<Url>
を操作するだけです。
最初のバージョンであるあなたが投稿した例には大きな違いがあります:
var urls = await context.Urls.ToListAsync();
これはbadであり、基本的にselect * from table
を実行し、すべての結果をメモリに返し、データベースに対してselect * from table where...
を実行するのではなく、メモリコレクション内の結果に対してwhere
を適用します。 。
2番目のメソッドは、クエリがIQueryable
に適用されるまで(おそらく、クエリに一致するdb値のみを返すlinq .Where().Select()
スタイル操作を介して)データベースにヒットしません。
あなたの例が比較可能である場合、コンパイラがasync
機能を許可するために生成するステートマシンにより多くのオーバーヘッドがあるため、async
バージョンは通常リクエストごとにわずかに遅くなります。
ただし、主な違い(および利点)は、IOが完了するのを待っている間、処理スレッドをブロックしないため、async
バージョンはより多くの同時要求を許可することです(dbクエリ、ファイルアクセス、ウェブリクエストなど)。