【问题标题】:Entity Framework Queryable async实体框架可查询异步
【发布时间】:2014-12-27 21:14:39
【问题描述】:

我正在使用 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();
 }

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

异步版本实际上会在这里产生性能优势,还是我会通过首先投影到列表(请注意使用异步)然后转到 IQueryable 来产生不必要的开销?

【问题讨论】:

标签: c# entity-framework async-await


【解决方案1】:

问题似乎是您误解了 async/await 如何与实体框架一起工作。

关于实体框架

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

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

及其用法示例:

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

那里会发生什么?

  1. 我们正在使用repo.GetAllUrls() 获取IQueryable 对象(尚未访问数据库)
  2. 我们使用.Where(u =&gt; &lt;condition&gt;创建具有指定条件的新IQueryable对象
  3. 我们使用.Take(10) 创建具有指定分页限制的新IQueryable 对象
  4. 我们使用.ToList() 从数据库中检索结果。我们的IQueryable 对象被编译为sql(如select top 10 * from Urls where &lt;condition&gt;)。并且数据库可以使用索引,sql server 只向您发送数据库中的 10 个对象(并非所有十亿个 url 都存储在数据库中)

好,我们来看第一个代码:

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

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

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

关于异步/等待

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

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

这里发生了什么?

  1. 从第一行开始var stuff1 = ...
  2. 我们向 sql server 发送请求,要求我们为userId 获取一些东西1
  3. 我们等待(当前线程被阻塞)
  4. 我们等待(当前线程被阻塞)
  5. .....
  6. Sql 服务器向我们发送响应
  7. 我们移动到第2行var stuff2 = ...
  8. 我们向 sql server 发送请求,要求我们为userId 获取一些东西2
  9. 我们等待(当前线程被阻塞)
  10. 再一次
  11. .....
  12. Sql 服务器向我们发送响应
  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&lt;Url&gt;

【讨论】:

  • @Korijn 看图片i2.iis.net/media/7188126/… from Introduction to IIS architecture 可以说IIS中的所有请求都是异步处理的
  • 由于您没有对GetAllUrlsByUser 方法中的结果集进行操作,因此您不需要使其异步。只需返回任务并为自己保存一个不必要的状态机,以免编译器生成。
  • @JohnathonSullinger 虽然这会在愉快的流程中工作,但这是否不会产生任何异常不会出现在这里并传播到第一个等待的地方的副作用? (不是说那一定很糟糕,但这是行为的改变?)
  • 有趣的是没有人注意到“关于异步/等待”中的第二个代码示例完全是无意义的,因为它会抛出异常,因为 EF 和 EF Core 都不是线程安全的,所以尝试运行并行只会抛出异常
  • 虽然这个答案是正确的,但如果你没有对列表做任何事情,我建议避免使用asyncawait。让来电者await吧。当您在此阶段等待调用 return await GetAllUrls().Where(u =&gt; u.User.Id == userId).ToListAsync(); 时,您在反编译程序集并查看 IL 时创建了一个额外的异步包装器。
【解决方案2】:

你发布的例子有很大的不同,第一个版本:

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

不好,它基本上是 select * from table,将所有结果返回到内存中,然后将 where 应用于内存集合中的结果,而不是对数据库执行 select * from table where...

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

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

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

【讨论】:

  • 直到将查询应用于 IQueryable.... IQueryable.Where 和 IQueryable.Select 都不会强制执行查询。先验应用谓词,后者应用投影。在使用具体化运算符之前不会执行它,例如 ToList、ToArray、Single 或 First。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 2021-12-25
    • 2013-03-17
    相关资源
    最近更新 更多