实体框架可查询异步

Jes*_*ter 87 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();
 }
Run Code Online (Sandbox Code Playgroud)

VS

 public IQueryable<URL> GetAllUrls()
 {
    return context.Urls.AsQueryable();
 }
Run Code Online (Sandbox Code Playgroud)

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

Vik*_*ova 203

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

关于实体框架

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

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}
Run Code Online (Sandbox Code Playgroud)

以及它的使用示例:

repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
Run Code Online (Sandbox Code Playgroud)

那里发生了什么?

  1. 我们使用的是获取IQueryable对象(尚未访问数据库)repo.GetAllUrls()
  2. 我们IQueryable使用指定条件创建一个新对象.Where(u => <condition>
  3. 我们IQueryable使用指定的分页限制创建一个新对象.Take(10)
  4. 我们使用从数据库检索结果.ToList().我们的IQueryable对象被编译为sql(like 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();
}
Run Code Online (Sandbox Code Playgroud)

使用相同的用法示例:

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

关于async/await

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

var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

  1. 从第1行开始 var stuff1 = ...
  2. 我们向sql server发送请求,我们想要获取一些stuff1 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));
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

  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();
}
Run Code Online (Sandbox Code Playgroud)

注意,using System.Data.Entity为了使用ToListAsync()IQueryable的方法,必须添加.

请注意,如果您不需要过滤和分页,那么您无需使用IQueryable.您可以使用await context.Urls.ToListAsync()和使用物化List<Url>.

  • 第一段没有表现出欢迎的态度 (44认同)
  • @Danny对不起我的态度.但是我注意到有时候从"完全错误"开始更容易引起注意,因为人们不喜欢这种态度.这通常会迫使他们仔细阅读所有内容,因为他们想要找到为什么这个家伙如此粗鲁.如果你有一个更好的欢迎态度,你可以建议. (23认同)
  • 有趣的是没有人注意到"关于异步/等待"中的第二个代码示例是完全无意义的,因为它会引发异常,因为EF和EF Core都不是线程安全的,因此尝试并行运行只会引发异常 (9认同)
  • 由于您没有对`GetAllUrlsByUser`方法中的结果集执行操作,因此您无需将其设置为异步.只需返回任务并保存自己不必要的状态机,使其不被编译器生成. (6认同)
  • @Korijn从[IIS架构简介]中查看图片http://i2.iis.net/media/7188126/introduction-to-iis-architecture-101-HTTPRequestWorkerProc.png?cdn_id=2012-08-15-001 http://www.iis.net/learn/get-started/introduction-to-iis/introduction-to-iis-architecture)我可以说IIS中的所有请求都是以异步方式处理的 (3认同)

Tre*_*ley 9

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

var urls = await context.Urls.ToListAsync();
Run Code Online (Sandbox Code Playgroud)

这很糟糕,它基本上是这样select * from table,将所有结果返回到内存中,然后where在内存集合中应用它而不是select * from table where...对数据库进行操作.

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

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

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

  • 直到查询应用于IQueryable .... IQueryable.Where和IQueryable.Select强制查询执行.先验应用谓词,后者应用投影.在使用实现操作符之前,它不会执行,例如ToList,ToArray,Single或First. (6认同)