在 Entity Framework Core 中执行查询并返回数据为
IAsyncEnumerable<T>
的正确方法是什么?
我得出的结论是,我应该从以下 StackOverflow 帖子中将查询数据作为
IAsyncEnumerable<T>
返回:
Stephen Cleary(.NET 异步大师)在被问及有关在 EF Core 中使用异步与同步方法时建议尽可能使用异步方法:https://stackoverflow.com/a/62313515/216440
StackOverflow 问题“我应该从存储库返回 Task
IAsyncEnumerable<T>
),则进行流式传输(即返回Task<IEnumerable<T>>
):https://stackoverflow.com/a/69748549/216440
好的,我的查询应该返回
IAsyncEnumerable<T>
但我该怎么做?
关于 避免 DbContext 线程问题的 MS EF Core 文档说:
Entity Framework Core 不支持在同一 DbContext 实例上运行多个并行操作。
结果
始终立即等待 EF Core 异步方法。
我尝试了以下方法:
public async IAsyncEnumerable<EmployeeManager> GetEmployeeManagerByCompanyAsync(int companyId, CancellationToken cancellationToken = default)
{
var employeeManagers = await _dbContext.EmployeePosition
.Where(ep => ep.CompanyId == companyId)
.Select(ep => new EmployeeManager(ep.EmployeeId, ep.ManagerId))
.AsAsyncEnumerable();
return employeeManagers;
}
这会产生编译器错误
'IAsyncEnumerable<EmployeeManager>' does not contain a definition for 'GetAwaiter'
。
This StackOverflow 答案建议解决这个问题,我应该执行以下操作:
public async IAsyncEnumerable<EmployeeManager> GetEmployeeManagerByCompanyAsync(int companyId, CancellationToken cancellationToken = default)
{
var employeeManagers = await _dbContext.EmployeePosition
.Where(ep => ep.CompanyId == companyId)
.Where(ep => !ep.Deleted)
.Select(ep => new EmployeeManager(ep.EmployeeId, ep.ManagerId))
.AsAsyncEnumerable();
await foreach(var employeeManager in employeeManagers)
{
yield return employeeManager;
}
}
这消除了编译器错误,但必须迭代查询结果只是为了返回
IAsyncEnumerable<T>
似乎有点冗长。有没有更好的方法将查询结果返回为 IAsyncEnumerable<T>
?
IAsyncEnumerable<T>
不能与 async
方法结合使用。它在 ToListAsync<T>()
等内部使用来服务 await
/async
,因此返回 IAsyncEnumerable<T>
将是:
public IAsyncEnumerable<EmployeeManager> GetEmployeeManagerByCompanyAsync(int companyId)
{
var employeeManagers = _dbContext.EmployeePosition
.Where(ep => ep.CompanyId == companyId)
.Select(ep => new EmployeeManager(ep.EmployeeId, ep.ManagerId))
.AsAsyncEnumerable();
return employeeManagers;
}
围绕取消令牌的任何检查都将在使用异步枚举时发生。让它
async
只是:
public async Task<IEnumerable<EmployeeManager>> GetEmployeeManagerByCompanyAsync(int companyId, CancellationToken cancellationToken = default)
{
var employeeManagers = await _dbContext.EmployeePosition
.Where(ep => ep.CompanyId == companyId)
.Select(ep => new EmployeeManager(ep.EmployeeId, ep.ManagerId))
.ToListAsync();
return employeeManagers;
}
...它将在内部合并
IAsyncEnumerable<T>
。
我不建议对实体进行混杂以表示部分填充的容器:
.Select(ep => new EmployeeManager(ep.EmployeeId, ep.ManagerId))
返回实体或返回专用的缩减摘要DTO。避免减少和传递不完整的分离实体作为数据的部分包装器的主要原因是,当您可能有需要 EmployeeManager 的函数但可以传递实际跟踪或分离的 EmployeeManager 或减少仅包含这两个 ID 的 EmployeeManager 实例时,可以避免混淆。如果您想传递 DbContext 未跟踪/缓存的完整实例,请使用 AsNoTracking()
而不是新建副本,以避免引入不完整数据的错误。