ChatGPT解决这个技术问题 Extra ChatGPT

实体框架可查询异步

我正在使用 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 来产生不必要的开销?

context.Urls 的类型是 DbSet;它实现了 IQueryable<URL>所以 .AsQueryable() 是多余的。 msdn.microsoft.com/en-us/library/gg696460(v=vs.113).aspx 假设您已遵循 EF 提供的模式,或使用了为您创建上下文的工具。

C
Corical

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

关于实体框架

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

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

及其用法示例:

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

那里会发生什么?

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

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

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

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

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

关于异步/等待

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

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

这里会发生什么?

从第 1 行开始 var stuff1 = ... 我们向 sql server 发送请求,要求我们为 userId 获取一些 stuff1 我们等待(当前线程被阻塞) 我们等待(当前线程被阻塞)..... Sql server 发送到我们响应我们移动到第 2 行 var stuff2 = ... 我们向 sql server 发送请求,我们希望为 userId 获取一些 stuff2 我们等待(当前线程被阻塞)然后再次..... 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();
}

请注意,您必须添加 using System.Data.Entity 才能将方法 ToListAsync() 用于 IQueryable。

请注意,如果您不需要过滤和分页等内容,则无需使用 IQueryable。您可以只使用 await context.Urls.ToListAsync() 并使用具体化的 List<Url>


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

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

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。
R
Rzassar

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