Здається, проблема полягає в тому, що ви неправильно зрозуміли, як асинхронізувати / очікувати роботу з Entity Framework.
Про структуру сутності
Отже, давайте розглянемо цей код:
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
і приклад його використання:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
Що там відбувається?
- Ми отримуємо
IQueryable
об’єкт (який ще не має доступу до бази даних)repo.GetAllUrls()
- За допомогою ми створюємо новий
IQueryable
об’єкт із заданою умовою.Where(u => <condition>
- Ми створюємо новий
IQueryable
об’єкт із зазначеним обмеженням підкачки.Take(10)
- Ми отримуємо результати з бази даних за допомогою
.ToList()
. Наш IQueryable
об'єкт компілюється в sql (як select top 10 * from Urls where <condition>
). А база даних може використовувати індекси, сервер sql надсилає вам лише 10 об’єктів з вашої бази даних (не всі мільярди URL-адрес, що зберігаються в базі даних)
Добре, давайте розглянемо перший код:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
З тим самим прикладом використання ми отримали:
- Ми завантажуємо в пам’ять усі мільярди URL-адрес, що зберігаються у вашій базі даних
await context.Urls.ToListAsync();
.
- У нас переповнення пам’яті. Правильний спосіб убити ваш сервер
Про асинхронізацію / очікування
Чому переважно використовувати async / await? Давайте подивимось на цей код:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
Що тут відбувається?
- Починаючи з рядка 1
var stuff1 = ...
- Ми надсилаємо запит на сервер sql, для якого ми хочемо отримати деякі речі1
userId
- Чекаємо (поточний потік заблоковано)
- Чекаємо (поточний потік заблоковано)
- .....
- Сервер SQL надішліть нам відповідь
- Переходимо до рядка 2
var stuff2 = ...
- Ми надсилаємо запит на сервер sql, для якого ми хочемо отримати деякі речі2
userId
- Чекаємо (поточний потік заблоковано)
- І знову
- .....
- Сервер SQL надішліть нам відповідь
- Ми робимо вигляд
Тож давайте розглянемо його асинхронну версію:
var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));
Що тут відбувається?
- Ми надсилаємо запит на сервер sql, щоб отримати stuff1 (рядок 1)
- Ми надсилаємо запит на сервер sql, щоб отримати stuff2 (рядок 2)
- Ми чекаємо відповідей від сервера sql, але поточний потік не заблокований, він може обробляти запити інших користувачів
- Ми робимо вигляд
Правильний спосіб це зробити
Так хороший код тут:
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();
}
Зверніть увагу, що ви повинні додати using System.Data.Entity
, щоб використовувати метод ToListAsync()
для IQueryable.
Зауважте, що якщо вам не потрібні фільтрація, підкачки та інше, вам не потрібно працювати IQueryable
. Ви можете просто використовувати await context.Urls.ToListAsync()
і працювати з матеріалізованим List<Url>
.