【发布时间】:2021-02-22 18:23:37
【问题描述】:
我的问题和这个一样: Scalable Contains method for LINQ against a SQL backend
概要:用户向我的 asp.net 异步控制器方法发布了一个 long(id)列表。控制器需要为每个 id 从 SQL 数据库中提取两列,并将其作为 json 数组返回。由于我使用的是 EF/Linq,如上面的链接中所述,因此我使用 Contains 方法:
long[] ids; //Assume that the posted list of ids to the controller method
var courses = await db.Courses.AsNoTracking().Where(x => ids.Contains(x.id))
.Select(x => new JRecord { id = x.id, name = x.name, status = x.status})
.ToListAsync();
return Request.CreateResponse(HttpStatus.OK, courses);
EF 将 Contains 转换为 SQL IN 语句。问题是大多数时候,id 列表只有 100 个,这很好,但用户也可以选择几千个条目,这会导致查询非常慢或查询完全失败。
作者(在上面的链接中)发布了针对另一个问题的解决方案,其中 Linq Extension 只是将 IDs 数组拆分为块,将每个块作为单独的 & 较小的查询运行,然后将所有查询的结果合并回一个列表. 原因不是为了提高性能,而是为了确保在提供大量 id 时查询不会失败。
他的代码:https://stackoverflow.com/a/6852288/934257
public static IEnumerable<IEnumerable<T>> ToChunks<T>(this IEnumerable<T> enumerable, int chunkSize)
{
int itemsReturned = 0;
var list = enumerable.ToList(); // Prevent multiple execution of IEnumerable.
int count = list.Count;
while (itemsReturned < count)
{
int currentChunkSize = Math.Min(chunkSize, count - itemsReturned);
yield return list.GetRange(itemsReturned, currentChunkSize);
itemsReturned += currentChunkSize;
}
}
使用他的 Linq 扩展:
var courses = ids.ToChunks(1000)
.Select(chunk => Courses.Where(c => chunk.Contains(c.CourseID)))
.SelectMany(x => x).ToList();
我希望在我的场景中采用这个扩展,所以我可以使用一个简单的构造,例如 ToChunks(1000),它将 ID 数组拆分为 1000 个长度的部分,对每个 ID 部分运行异步 EF 查询并合并结果重新组合成一个列表。与手动拆分 ID 数组、创建 for 循环并在 ID 数组部分上单独运行查询并将结果合并回列表相比,这将是更清洁和可重用的解决方案。
【问题讨论】:
-
这里讲异步操作是没有意义的,除非源本身是异步的;你打算切换到
IAsyncEnumerable<T>吗?如果是这样,nuget.org/packages/System.Linq.Async 可能会提供您需要的一切,但是:这是一个很大的变化。再次强调:你不能只是挥动魔杖,让非异步方法变为异步 - 你所做的事情需要固有地支持异步 -
@MarcGravell 在上面的示例中,ids 是 List
,Courses 是表示 SQL Server 中表的 EF 模型。该方法会在 asp.net webapi async 控制器中调用,因此需要同时使数据检索异步。 -
主要问题是我的端点将获得一个可能包含 4000 个 ID 的列表,我需要在数据库中查找。传统的 Linq Contains() 性能不够,而且这种非异步的 Extension 方法将 id 分成块并创建多个 sql 查询。然而,由于答案很老,它使用非异步语义编写,因此不是今天的最佳解决方案。
-
什么是
list.GetRange,它有异步版本吗? -
没有。这是一个使用IEnumerable的扩展方法,扩展方法我不擅长,但我的理解是Async版本使用IQueryable接口。
标签: c# entity-framework linq