当我将GroupBy用作对EFCore的LINQ查询的一部分时,出现错误System.InvalidOperationException: Client-side GroupBy is not supported
。
这是因为EF Core 3.1试图尽可能在服务器端评估查询,而不是在客户端评估查询,并且该调用无法转换为SQL。
因此,以下语句不起作用,并产生上述错误:
var blogs = await context.Blogs
.Where(blog => blog.Url.Contains("dotnet"))
.GroupBy(t => t.BlobNumber)
.Select(b => b)
.ToListAsync();
现在显然解决方案是在调用GroupBy()之前使用.AsEnumerable()或.ToList(),因为它明确告诉EF Core您要进行客户端分组。关于此on GitHub和in the Microsoft docs的讨论。
var blogs = context.Blogs
.Where(blog => blog.Url.Contains("dotnet"))
.AsEnumerable()
.GroupBy(t => t.BlobNumber)
.Select(b => b)
.ToList();
但是,这不是异步的。如何使它异步?
如果我将AsEnumerable()更改为AsAsyncEnumerable(),则会收到错误消息。如果我改为尝试将AsEnumerable()更改为ToListAsync(),则GroupBy()命令将失败。
我正在考虑将其包装在Task.FromResult中,但这实际上是异步的吗?还是数据库查询仍然是同步的,只有随后的分组是异步的?
var blogs = await Task.FromResult(context.Blogs
.Where(blog => blog.Url.Contains("dotnet"))
.AsEnumerable()
.GroupBy(t => t.BlobNumber)
.Select(b => b)
.ToList());
或者如果那不起作用,还有另一种方法吗?
答案 0 :(得分:5)
我认为您唯一的办法就是做这样的事情
var blogs = await context.Blogs
.Where(blog => blog.Url.Contains("dotnet"))
.ToListAsync();
var groupedBlogs = blogs.GroupBy(t => t.BlobNumber).Select(b => b).ToList();
因为无论如何,GroupBy都会在客户端进行评估
答案 1 :(得分:3)
此查询未尝试从SQL / EF Core的角度对数据进行分组。没有涉及聚合。
它正在加载所有详细信息行,然后将其分批处理到客户端上的不同存储桶中。 EF Core不参与其中,这是一个纯粹的客户端操作。等效为:
var blogs = await context.Blogs
.Where(blog => blog.Url.Contains("dotnet"))
.ToListAsync();
var blogsByNum = blogs.ToLookup(t => t.BlobNumber);
加快分组速度
批处理/分组/查找操作完全是受CPU约束的,因此加速它的唯一方法是对其进行并行化,即使用所有CPU对数据进行分组,例如:
var blogsByNum = blogs.AsParallel()
.ToLookup(t => t.BlobNumber);
ToLookup
比GroupBy().ToList()
做更多或更少-它根据键将行分组到存储桶中
加载时分组
另一种方法是异步加载结果并将其放入存储桶中。为此,我们需要AsAsyncEnumerable()
。 ToListAsync()
一次返回所有结果,因此无法使用。
这种方法与ToLookup
的方法非常相似。
var blogs = await context.Blogs
.Where(blog => blog.Url.Contains("dotnet"));
var blogsByNum=new Dictionary<string,List<Blog>>();
await foreach(var blog in blogs.AsAsyncEnumerable())
{
if(blogsByNum.TryGetValue(blog.BlobNumber,out var blogList))
{
blogList.Add(blog);
}
else
{
blogsByNum[blog.BlobNumber=new List<Blog>(100){blog};
}
}
通过调用AsAsyncEnumerable()
执行查询。结果虽然异步到达,所以现在我们可以在迭代时将它们添加到存储桶中。
在列表构造函数中使用capacity
参数,以避免重新分配列表的内部缓冲区。
使用System.LINQ.Async
如果我们对IAsyncEnumerable <>本身进行LINQ操作,事情会容易得多。 This extension名称空间提供了这一点。它是由ReactiveX团队开发的。可通过NuGet获得,当前的主要版本是4.0。
有了这个,我们可以这样写:
var blogs = await context.Blogs
.Where(blog => blog.Url.Contains("dotnet"));
var blogsByNum=await blogs.AsAsyncEnumerable() individual rows asynchronously
.ToLookupAsync(blog=>blog.BlobNumber);
或
var blogsByNum=await blogs.AsAsyncEnumerable()
.GroupBy(blog=>blog.BlobNumber)
.Select(b=>b)
.ToListAsync();