问题似乎是您误解了异步/等待如何与实体框架一起工作。
关于实体框架
因此,让我们看一下这段代码:
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 Server仅从数据库发送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 server,我们希望获得一些东西
userId
- 我们等待(当前线程被阻止)
- 我们等待(当前线程被阻止)
- .....
- SQL Server发送给我们回应
- 我们移至第2行
var stuff2 = ...
- 我们发送请求到sql server,我们想要获取一些东西
userId
- 我们等待(当前线程被阻止)
- 然后再次
- .....
- SQL Server发送给我们回应
- 我们渲染视图
因此,让我们看一下它的异步版本:
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>
。