Здається, проблема полягає в тому, що ви неправильно зрозуміли, як асинхронізувати / очікувати роботу з 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>.