实体框架可查询异步


96

我正在使用Entity Framework 6处理一些Web API,而我的控制器方法之一是“获取全部”,期望从数据库中以形式接收表的内容IQueryable<Entity>。在我的存储库中,我想知道是否有任何有利的原因来异步执行此操作,因为我是初次使用带有异步功能的EF。

基本上可以归结为

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

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

异步版本实际上会在这里带来性能上的好处吗?还是我先投影到一个列表(使用异步方法,然后转到IQueryable)会导致不必要的开销?


1
context.Urls是DbSet <URL>类型,它实现了IQueryable <URL>,因此.AsQueryable()是冗余的。 msdn.microsoft.com/zh-cn/library/gg696460(v=vs.113).aspx 假定您遵循EF提供的模式,或者使用了为您创建上下文的工具。
肖恩·B

Answers:


222

问题似乎是您误解了异步/等待如何与实体框架一起工作。

关于实体框架

因此,让我们看一下这段代码:

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

及其用法示例:

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

那里发生了什么?

  1. 我们正在使用以下IQueryable对象(尚未访问数据库)repo.GetAllUrls()
  2. 我们使用以下命令创建一个IQueryable具有指定条件的新对象.Where(u => <condition>
  3. 我们使用以下命令创建一个IQueryable具有指定分页限制的新对象.Take(10)
  4. 我们使用检索数据库中的结果.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();
}

使用相同的用法示例,我们得到:

  1. 我们正在使用中将存储在数据库中的所有十亿个url加载到内存中await context.Urls.ToListAsync();
  2. 我们内存溢出。杀死服务器的正确方法

关于异步/等待

为什么首选使用async / await?让我们看一下这段代码:

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

发生什么事了?

  1. 从第1行开始 var stuff1 = ...
  2. 我们发送请求到sql server,我们希望获得一些东西 userId
  3. 我们等待(当前线程被阻止)
  4. 我们等待(当前线程被阻止)
  5. .....
  6. SQL Server发送给我们回应
  7. 我们移至第2行 var stuff2 = ...
  8. 我们发送请求到sql server,我们想要获取一些东西 userId
  9. 我们等待(当前线程被阻止)
  10. 然后再次
  11. .....
  12. SQL Server发送给我们回应
  13. 我们渲染视图

因此,让我们看一下它的异步版本:

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

发生什么事了?

  1. 我们将请求发送到sql server以获取stuff1(第1行)
  2. 我们向sql server发送请求以获取stuff2(第2行)
  3. 我们等待来自sql server的响应,但是当前线程未被阻止,他可以处理来自其他用户的查询
  4. 我们渲染视图

正确的做法

很好的代码在这里:

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>


3
@Korijn看着图片i2.iis.net/media/7188126/…来自IIS体系结构简介我可以说IIS中的所有请求都以异步方式处理
Viktor Lova,2014年

7
由于您未对方法中的结果集进行GetAllUrlsByUser操作,因此无需使其异步。只需返回Task并为自己保存不必要的状态机,以免编译器生成该状态机。
Johnathon Sullinger

1
@JohnathonSullinger尽管这可以顺畅地工作,但那不会产生任何异常不会在这里浮出水面并传播到第一个等待的地方的副作用吗?(那不是必要的,但这是行为上的改变吗?)
亨利

9
有趣的是,没有人注意到“关于异步/等待”中的第二个代码示例完全是毫无意义的,因为EF和EF Core都不是线程安全的,因此它将引发异常,因此尝试并行运行只会引发异常

1
尽管此答案是正确的,但我建议您避免使用asyncawait如果您对列表不做任何事情,则建议避免使用。让调用者来await。当您在此阶段等待调用时,将在return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();反汇编程序集并查看IL时创建一个额外的异步包装器。
阿里·卡普里

10

您发布的示例(第一个版本)有很大的不同:

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

这样做不好,基本上是这样做的select * from table,将所有结果返回到内存中,然后将其应用于where内存收集中的结果,而不是select * from table where...针对数据库。

第二种方法将不会实际访问数据库,除非将查询应用于IQueryable(可能通过linq .Where().Select()样式操作),该操作只会返回与查询匹配的db值。

如果您的示例具有可比性,则async每个请求的版本通常会稍慢一些,因为编译器生成以允许该async功能的状态机中的开销更大。

但是主要区别(和好处)是,该async版本允许更多并发请求,因为它在等待IO完成(数据库查询,文件访问,Web请求等)时不会阻塞处理线程。


7
直到将查询应用于IQueryable...。IQueryable.Where和IQueryable.Select都不强制执行查询。前者应用谓词,后者应用投影。直到使用实例化运算符(例如ToList,ToArray,Single或First)后,它才会执行。
JJS

0

长话短说,
IQueryable旨在推迟RUN过程,首先与其他IQueryable表达式一起构建表达式,然后将表达式作为一个整体进行解释和运行。
但是ToList()方法(或类似的几种方法)可以立即“按原样”运行表达式。
您的第一个方法(GetAllUrlsAsync)将立即运行,因为它IQueryable后面是ToListAsync()方法。因此它立即运行(异步),并返回一堆IEnumerables。
同时,您的第二种方法(GetAllUrls)将不会运行。而是返回一个表达式,此方法的CALLER负责运行该表达式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.