C# 实体框架可查询异步

C# 实体框架可查询异步,c#,entity-framework,async-await,C#,Entity Framework,Async Await,我正在使用Entity Framework 6开发一些Web API,我的控制器方法之一是“全部获取”,它希望以IQueryable的形式从数据库接收表的内容。在我的存储库中,我想知道是否有任何有利的理由异步执行此操作,因为我不熟悉将EF与async一起使用 基本上可以归结为 public async Task<IQueryable<URL>> GetAllUrlsAsync() { var urls = await context.Urls.ToListAs

我正在使用Entity Framework 6开发一些Web API,我的控制器方法之一是“全部获取”,它希望以
IQueryable
的形式从数据库接收表的内容。在我的存储库中,我想知道是否有任何有利的理由异步执行此操作,因为我不熟悉将EF与async一起使用

基本上可以归结为

 public async Task<IQueryable<URL>> GetAllUrlsAsync()
 {
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
 }
public异步任务GetAllUrlsAsync()
{
var url=await context.url.ToListAsync();
返回URL.AsQueryable();
}
vs

public IQueryable GetAllUrls()
{
返回context.url.AsQueryable();
}

异步版本真的会在这里带来性能上的好处吗?还是我会通过先投影到列表(使用async mind you)然后转到IQueryable而招致不必要的开销?

在您发布的示例中有很大的不同,第一个版本:

var urls = await context.Urls.ToListAsync();
这是不好的,它基本上是从表中选择*,将所有结果返回到内存中,然后将
where
应用到内存集合中,而不是对数据库执行
select*from table where…

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

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


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

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

关于实体框架 那么,让我们看看这段代码:

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
使用我们得到的相同用法示例:

  • 我们正在使用
    wait context.url.ToListAsync();
    将存储在数据库中的所有十亿个URL加载到内存中
  • 内存溢出。正确的方法可以杀死你的服务器
  • 关于异步/等待 为什么更喜欢使用async/await?让我们看看下面的代码:

    public IQueryable<URL> GetAllUrls()
    {
        return context.Urls.AsQueryable();
    }
    
    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();
    }
    
    使用System.Data.Entity;
    公共可查询的GetAllUrls()
    {
    返回context.url.AsQueryable();
    }
    公共异步任务GetAllUrlsByUser(int userId){
    return wait wait GetAllUrls().Where(u=>u.User.Id==userId.toListSync();
    }
    
    注意,您必须使用System.Data.Entity添加
    ,才能将方法
    ToListSync()
    用于IQueryable

    请注意,如果您不需要过滤、分页等功能,则不需要使用
    IQueryable
    。您只需使用
    wait context.url.tolistsync()
    ,并使用物化的
    列表

    长话短说,
    IQueryable
    旨在推迟运行过程,首先与其他
    IQueryable
    表达式一起构建表达式,然后将表达式作为一个整体进行解释和运行。
    但是
    ToList()
    method(或类似的几种方法)不能立即“按原样”运行表达式。
    您的第一个方法(
    GetAllusAsync
    )将立即运行,因为它是
    IQueryable
    ,后跟
    ToListSync()
    方法。因此它立即(异步)运行,并返回一组
    IEnumerable
    s。

    同时,您的第二种方法(
    GetAllUrls
    ),将不会运行。相反,它返回一个表达式,此方法的调用方负责运行该表达式。

    @Korijn查看来自的图片,我可以说IIS中的所有请求都是以异步方式处理的,因为您不处理
    GetAllUrlsByUser
    方法中的结果集,所以不需要使其异步。只需重新urn该任务并为自己保存一个不必要的状态机,使其不被编译器生成。@JohnathonSullinger尽管这样做可以顺利进行,但这难道不会产生任何异常不会在此处出现并传播到具有等待的第一个位置的副作用吗?(并不是说这很糟糕,但这是行为上的一个变化?)有趣的是,没有人注意到“About async/await”中的第二个代码示例是完全没有意义的,因为它会抛出一个异常,因为EF和EF核心都不是线程安全的,所以尝试并行运行只会抛出一个异常虽然这个答案是正确的,但如果您没有对列表执行任何操作,我建议避免使用
    async
    wait
    。让调用方等待它。在此阶段等待调用时
    return wait GetAllUrls()。其中(u=>u.User.Id==userId)。toListSync();
    您正在创建一个额外的异步wra
    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();
    }