Entity Framework Querizable async


98

Saya sedang mengerjakan beberapa hal API Web menggunakan Entity Framework 6 dan salah satu metode pengontrol saya adalah "Dapatkan Semua" yang mengharapkan untuk menerima konten tabel dari database saya sebagai IQueryable<Entity>. Di repositori saya, saya bertanya-tanya apakah ada alasan menguntungkan untuk melakukan ini secara asinkron karena saya baru menggunakan EF dengan async.

Pada dasarnya itu intinya

 public async Task<IQueryable<URL>> GetAllUrlsAsync()
 {
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
 }

vs.

 public IQueryable<URL> GetAllUrls()
 {
    return context.Urls.AsQueryable();
 }

Akankah versi asinkron benar-benar menghasilkan manfaat kinerja di sini atau apakah saya menimbulkan biaya tambahan yang tidak perlu dengan memproyeksikan ke Daftar terlebih dahulu (menggunakan pikiran asinkron Anda) dan KEMUDIAN pergi ke IQuerable?


1
context.Urls adalah tipe DbSet <URL> yang mengimplementasikan IQuerizable <URL> sehingga .AsQueryable () redundan. msdn.microsoft.com/en-us/library/gg696460(v=vs.113).aspx Dengan asumsi Anda telah mengikuti pola yang disediakan EF, atau menggunakan perkakas yang membuat konteks untuk Anda.
Sean B

Jawaban:


225

Masalahnya adalah Anda salah paham tentang cara kerja async / await dengan Entity Framework.

Tentang Entity Framework

Jadi, mari kita lihat kode ini:

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}

dan contoh penggunaannya:

repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()

Apa yang terjadi disana?

  1. Kami mendapatkan IQueryableobjek (belum mengakses database) menggunakanrepo.GetAllUrls()
  2. Kami membuat IQueryableobjek baru dengan kondisi tertentu menggunakan.Where(u => <condition>
  3. Kami membuat IQueryableobjek baru dengan batas halaman yang ditentukan menggunakan.Take(10)
  4. Kami mengambil hasil dari database menggunakan .ToList(). IQueryableObjek kami dikompilasi menjadi sql (like select top 10 * from Urls where <condition>). Dan database dapat menggunakan indeks, server sql hanya mengirimi Anda 10 objek dari database Anda (tidak semua miliar url disimpan dalam database)

Oke, mari kita lihat kode pertama:

public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
}

Dengan contoh penggunaan yang sama yang kami dapatkan:

  1. Kami sedang memuat dalam memori semua miliar url yang disimpan dalam database Anda menggunakan await context.Urls.ToListAsync();.
  2. Kami mengalami kelebihan memori. Cara yang tepat untuk mematikan server Anda

Tentang async / await

Mengapa async / await lebih disukai digunakan? Mari kita lihat kode ini:

var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));

Apa yang terjadi di sini?

  1. Mulai di baris 1 var stuff1 = ...
  2. Kami mengirim permintaan ke sql server yang kami inginkan untuk mendapatkan beberapa barang1 userId
  3. Kami menunggu (utas saat ini diblokir)
  4. Kami menunggu (utas saat ini diblokir)
  5. .....
  6. Server Sql mengirimkan tanggapan kepada kami
  7. Kami pindah ke baris 2 var stuff2 = ...
  8. Kami mengirim permintaan ke server sql yang kami inginkan untuk mendapatkan beberapa barang2 userId
  9. Kami menunggu (utas saat ini diblokir)
  10. Dan lagi
  11. .....
  12. Server Sql mengirimkan tanggapan kepada kami
  13. Kami memberikan tampilan

Jadi mari kita lihat versi asinkronnya:

var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));

Apa yang terjadi di sini?

  1. Kami mengirim permintaan ke server sql untuk mendapatkan barang1 (baris 1)
  2. Kami mengirim permintaan ke server sql untuk mendapatkan barang2 (baris 2)
  3. Kami menunggu tanggapan dari server sql, tetapi utas saat ini tidak diblokir, dia dapat menangani kueri dari pengguna lain
  4. Kami memberikan tampilan

Cara yang tepat untuk melakukannya

Kode yang sangat bagus di sini:

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();
}

Catatan, Anda harus menambahkan using System.Data.Entityuntuk menggunakan metode ToListAsync()IQuerable.

Perhatikan, bahwa jika Anda tidak memerlukan pemfilteran dan paging dan sebagainya, Anda tidak perlu mengerjakannya IQueryable. Anda bisa menggunakan await context.Urls.ToListAsync()dan bekerja dengan terwujud List<Url>.


3
@Korijn melihat gambar i2.iis.net/media/7188126/… dari Pengantar arsitektur IIS Saya dapat mengatakan bahwa semua permintaan di IIS diproses secara asinkron
Viktor Lova

7
Karena Anda tidak bertindak pada hasil yang ditetapkan dalam GetAllUrlsByUsermetode ini, Anda tidak perlu menjadikannya asinkron. Cukup kembalikan Task dan simpan diri Anda sebagai mesin status yang tidak perlu agar tidak dibuat oleh compiler.
Johnathon Sullinger

1
@JohnathonSullinger Meskipun itu akan berhasil dalam aliran bahagia, bukankah itu memiliki efek samping bahwa pengecualian apa pun tidak akan muncul di sini dan menyebar ke tempat pertama yang menunggu? (Bukannya itu perlu buruk, tapi itu adalah perubahan dalam perilaku?)
Henry Been

9
Menarik, tidak ada yang memperhatikan bahwa contoh kode ke-2 dalam "About async / await" sama sekali tidak masuk akal, karena ini akan mengeluarkan pengecualian karena baik EF maupun EF Core tidak aman untuk thread, jadi mencoba menjalankan secara paralel hanya akan membuat pengecualian
Tseng

1
Meskipun jawaban ini benar, saya sarankan untuk menghindari penggunaan asyncdan awaitjika Anda TIDAK melakukan apa pun dengan daftar tersebut. Biarkan penelepon untuk awaititu. Saat Anda menunggu panggilan pada tahap ini, return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();Anda membuat pembungkus async tambahan saat Anda mendekompilasi assembly dan melihat IL.
Ali Khakpouri

10

Ada perbedaan besar dalam contoh yang Anda posting, versi pertama:

var urls = await context.Urls.ToListAsync();

Ini buruk , pada dasarnya tidak select * from table, mengembalikan semua hasil ke dalam memori dan kemudian menerapkannya wheredalam pengumpulan memori daripada melakukan select * from table where...terhadap database.

Metode kedua tidak akan benar-benar masuk ke database hingga kueri diterapkan ke IQueryable(mungkin melalui .Where().Select()operasi gaya linq yang hanya akan mengembalikan nilai db yang cocok dengan kueri.

Jika contoh Anda sebanding, asyncversinya biasanya akan sedikit lebih lambat per permintaan karena ada lebih banyak overhead di mesin status yang dihasilkan kompilator untuk memungkinkan asyncfungsionalitas tersebut.

Namun perbedaan utama (dan manfaat) adalah bahwa asyncversi ini memungkinkan lebih banyak permintaan bersamaan karena tidak memblokir utas pemrosesan sementara itu menunggu IO untuk menyelesaikan (kueri db, akses file, permintaan web, dll).


7
hingga kueri diterapkan ke IQuerizable .... baik IQueryable.Where dan IQueryable.Select memaksa kueri untuk dieksekusi. Sebelumnya menerapkan predikat dan yang terakhir menerapkan proyeksi. Ini tidak dijalankan sampai operator terwujud digunakan, seperti ToList, ToArray, Single atau First.
JJS

0

Singkat cerita,
IQueryabledirancang untuk menunda proses RUN dan pertama membangun ekspresi dalam hubungannya dengan IQueryableekspresi lain , lalu menafsirkan dan menjalankan ekspresi secara keseluruhan.
Tetapi ToList()metode (atau beberapa jenis metode seperti itu), diperlukan untuk menjalankan ekspresi secara instan "sebagaimana adanya".
Metode pertama Anda ( GetAllUrlsAsync), akan segera berjalan, karena IQueryablediikuti oleh ToListAsync()metode. maka itu berjalan secara instan (asynchronous), dan mengembalikan banyak IEnumerables.
Sementara metode kedua Anda ( GetAllUrls), tidak akan dijalankan. Sebaliknya, ia mengembalikan ekspresi dan PENELI metode ini bertanggung jawab untuk menjalankan ekspresi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.