问题似乎是您误解了 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 server 只向您发送数据库中的 10 个对象(并非所有十亿个 url 都存储在数据库中)
好,我们来看第一个代码:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
使用我们得到的相同用法示例:
- 我们正在使用
await context.Urls.ToListAsync(); 将存储在您数据库中的所有十亿个 URL 加载到内存中。
- 内存溢出。杀死服务器的正确方法
关于异步/等待
为什么首选使用 async/await?我们来看看这段代码:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
这里发生了什么?
- 从第一行开始
var stuff1 = ...
- 我们向 sql server 发送请求,要求我们为
userId 获取一些东西1
- 我们等待(当前线程被阻塞)
- 我们等待(当前线程被阻塞)
- .....
- Sql 服务器向我们发送响应
- 我们移动到第2行
var stuff2 = ...
- 我们向 sql server 发送请求,要求我们为
userId 获取一些东西2
- 我们等待(当前线程被阻塞)
- 再一次
- .....
- 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 server 发送请求以获取 stuff1(第 1 行)
- 我们向 sql server 发送请求以获取 stuff2(第 2 行)
- 我们等待来自 sql server 的响应,但是当前线程没有被阻塞,他可以处理来自其他用户的查询
- 我们渲染视图
正确的方法
这里的代码很好:
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>。