实体框架可查询异步

时间:2014-10-31 14:02:42

标签: c# entity-framework async-await

我正在使用Entity Framework 6处理一些Web API,我的一个控制器方法是“Get All”,它希望从我的数据库中收到IQueryable<Entity>表的内容。在我的存储库中,我想知道是否有任何有利的理由以异步方式执行此操作,因为我刚接触使用EF与异步。

基本上归结为

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

VS

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

异步版本实际上是否会在这里产生性能优势,或者我首先投射到List(使用async mind you)然后转到IQueryable会产生不必要的开销吗?

3 个答案:

答案 0 :(得分:185)

问题似乎是您误解了async / await如何与Entity Framework一起使用。

关于实体框架

那么,让我们看一下这段代码:

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

及其使用示例:

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

那里发生了什么?

  1. 我们正在使用IQueryable
  2. 获取repo.GetAllUrls()个对象(尚未访问数据库)
  3. 我们使用IQueryable
  4. 创建一个具有指定条件的新.Where(u => <condition>对象
  5. 我们使用IQueryable
  6. 创建一个具有指定分页限制的新.Take(10)对象
  7. 我们使用.ToList()从数据库中检索结果。我们的IQueryable对象被编译为sql(如select top 10 * from Urls where <condition>)。并且数据库可以使用索引,sql server只向您发送数据库中的10个对象(不是存储在数据库中的所有亿个URL)
  8. 好的,让我们看看第一个代码:

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

    使用相同的使用示例:

    1. 我们使用await context.Urls.ToListAsync();在内存中加载存储在您数据库中的所有十亿个网址。
    2. 我们内存溢出了。杀死服务器的正确方法
    3. 关于async / await

      为什么首选使用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. 的一些东西1
      4. 我们等待(当前线程被阻止)
      5. 我们等待(当前线程被阻止)
      6. .....
      7. Sql server发送给我们回复
      8. 我们转到第2行var stuff2 = ...
      9. 我们向sql server发送请求,我们想要获取userId
      10. 的一些东西2
      11. 我们等待(当前线程被阻止)
      12. 再次
      13. .....
      14. Sql server发送给我们回复
      15. 我们渲染视图
      16. 让我们看看它的异步版本:

        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. 我们渲染视图
        5. 正确的方法

          这里的代码很好:

          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>

答案 1 :(得分:9)

您发布的示例与第一个版本存在巨大差异:

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

这是错误,它基本上是select * from table,将所有结果返回到内存中,然后对内存集合中的where应用select * from table where...而不是IQueryable反对数据库。

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

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

然而,主要区别(和好处)是{{1}}版本允许更多并发请求,因为它在等待IO完成时不会阻止处理线程(db查询,文件访问,Web请求)等)。

答案 2 :(得分:0)

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