使用 .Take() 和 .Skip() 获取总数时遇到问题
Trouble getting total count using .Take() and .Skip()
我在使用 Linq 实现一些分页时遇到了一些问题,我已经阅读了这里的各种问题(例如 this and this),但我仍然遇到错误;
System.InvalidOperationException: The result of a query cannot be enumerated more than once.
我的(有点混淆)代码是;
public List<Thing> GetThings(ObjectParameter[] params, int count, int pageIndex)
{
var things = from t in Context.ExecuteFunction<Something>("function", params)
select new Thing
{
ID = t.ID
});
var pagedThings = things;
if (pageIndex == 0)
pagedThings = things.Take(count);
else if (pageIndex > 0)
pagedThings = things.Skip(count * pageIndex).Take(count);
var countOfThings = things.Count();
return pagedThings.ToList();
}
一旦调用最终的 .ToList()
,就会抛出错误,但我不明白为什么 - 对 things.Count()
和 pagedThings.ToList()
的调用枚举的是同一件事吗?
编辑:我正在使用 Entity Framework 如果这有什么不同
您正在设置 pagedThings = things。所以你正在处理同一个对象。如果您想执行上述操作,则需要将内容复制到新集合中,但我一般建议重构此代码。
您可以查看此 SO post 以了解如何在不枚举列表的情况下获取计数:
How to COUNT rows within EntityFramework without loading contents?
一般来说,Linq 是可以做到的。在LinqPad中,我写了如下代码并成功执行:
void Main()
{
var sampleList = new List<int>();
for (int i = 0; i < 100; i++){
sampleList.Add(i);
}
var furtherQuery = sampleList.Take(3).Skip(4);
var count = furtherQuery.Count();
var cache = furtherQuery.ToList();
}
请注意,正如您的错误所述,这将执行查询两次。一次用于 Count() 一次用于 ToList()。
一定是您代表 Context.ExecuteFunction<Something>("function", params)
的 Linq 提供程序保护您免于进行多次昂贵的调用。您应该寻找一种仅对结果进行一次迭代的方法。例如,如所写,您可以在已经生成的列表上使用 .Count()。
ExecuteFunction 实际上 returns 一个 ObjectResult 如果我没记错的话,这……更复杂。如果您使函数可组合(当您 Count() 时会执行单独的查询),您可能会得到不同的结果,但自从我使用低级 EF 以来已经有一段时间了,所以我不能 100% 确定它会起作用。
由于您无法不执行实际上是两个查询的操作,因此最安全的做法是创建一个完全独立的查询 - 完全独立的意思是一个单独的函数或存储过程,它只执行计数,否则您可能最终(取决于您的函数)将行返回到 EF 并在内存中对它们进行计数。或者尽可能将函数重写为视图,这可能会使其更直接。
通常我们称它们为pageIndex和pageSize.
请根据您的要求检查pageIndex是0作为起始索引还是1作为起始索引。
public List<Thing> GetThings(ObjectParameter[] params, int pageIndex, int pageSize)
{
if (pageSize <= 0)
pageSize = 1;
if (pageIndex < 0)
pageIndex = 0;
var source = Context.ExecuteFunction<Something>("function", params);
var total = source.Count();
var things = (from t in source select new Thing { ID = t.ID })
.Skip(pageIndex * pageSize).Take(pageSize).ToList();
return things.ToList();
}
这是我对您的代码的实现。有几件事要注意。
1.您可以在一条语句中处理Skip。
2. main方法展示了如何将多个页面传入该方法。
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Thing> thingList = new List<Thing>();
for (int i = 0; i < 99; i++)
{
thingList.Add(new Thing(i));
}
int count = 20;
int pageIndex = 0;
int numberPages = (int)Math.Ceiling(thingList.Count * 1.0/ (count ));
for( ; pageIndex < numberPages; pageIndex ++)
{
var myPagedThings = GetThings(thingList, count, pageIndex);
foreach( var item in myPagedThings)
{
Console.WriteLine(item.ID );
}
}
}
public static IEnumerable<Thing> GetThings(List<Thing> myList, int count, int pageIndex)
{
var things = (
from t in myList
select new Thing{ID = t.ID}).ToList();
return things.Skip(count * pageIndex).Take(count);
}
}
public class Thing
{
public int ID
{ get; set; }
public Thing (){}
public Thing(int id)
{ this.ID = id; }
}
碰巧,ExecuteFunction
导致枚举立即发生,最终意味着代码可以重新排序并且不需要复制列表 - 现在看起来像下面
public ThingObjects GetThings(ObjectParameter[] params, int count, int pageIndex)
{
var things = from t in Context.ExecuteFunction<Something>("function", params)
select new Thing
{
ID = t.ID
}).ToList();
var countOfThings = things.Count;
if (pageIndex >= 0)
things = things.Skip(count * pageIndex).Take(count);
return new ThingObjects(things, countOfThings);
}
我在使用 Linq 实现一些分页时遇到了一些问题,我已经阅读了这里的各种问题(例如 this and this),但我仍然遇到错误;
System.InvalidOperationException: The result of a query cannot be enumerated more than once.
我的(有点混淆)代码是;
public List<Thing> GetThings(ObjectParameter[] params, int count, int pageIndex)
{
var things = from t in Context.ExecuteFunction<Something>("function", params)
select new Thing
{
ID = t.ID
});
var pagedThings = things;
if (pageIndex == 0)
pagedThings = things.Take(count);
else if (pageIndex > 0)
pagedThings = things.Skip(count * pageIndex).Take(count);
var countOfThings = things.Count();
return pagedThings.ToList();
}
一旦调用最终的 .ToList()
,就会抛出错误,但我不明白为什么 - 对 things.Count()
和 pagedThings.ToList()
的调用枚举的是同一件事吗?
编辑:我正在使用 Entity Framework 如果这有什么不同
您正在设置 pagedThings = things。所以你正在处理同一个对象。如果您想执行上述操作,则需要将内容复制到新集合中,但我一般建议重构此代码。
您可以查看此 SO post 以了解如何在不枚举列表的情况下获取计数: How to COUNT rows within EntityFramework without loading contents?
一般来说,Linq 是可以做到的。在LinqPad中,我写了如下代码并成功执行:
void Main()
{
var sampleList = new List<int>();
for (int i = 0; i < 100; i++){
sampleList.Add(i);
}
var furtherQuery = sampleList.Take(3).Skip(4);
var count = furtherQuery.Count();
var cache = furtherQuery.ToList();
}
请注意,正如您的错误所述,这将执行查询两次。一次用于 Count() 一次用于 ToList()。
一定是您代表 Context.ExecuteFunction<Something>("function", params)
的 Linq 提供程序保护您免于进行多次昂贵的调用。您应该寻找一种仅对结果进行一次迭代的方法。例如,如所写,您可以在已经生成的列表上使用 .Count()。
ExecuteFunction 实际上 returns 一个 ObjectResult 如果我没记错的话,这……更复杂。如果您使函数可组合(当您 Count() 时会执行单独的查询),您可能会得到不同的结果,但自从我使用低级 EF 以来已经有一段时间了,所以我不能 100% 确定它会起作用。
由于您无法不执行实际上是两个查询的操作,因此最安全的做法是创建一个完全独立的查询 - 完全独立的意思是一个单独的函数或存储过程,它只执行计数,否则您可能最终(取决于您的函数)将行返回到 EF 并在内存中对它们进行计数。或者尽可能将函数重写为视图,这可能会使其更直接。
通常我们称它们为pageIndex和pageSize.
请根据您的要求检查pageIndex是0作为起始索引还是1作为起始索引。
public List<Thing> GetThings(ObjectParameter[] params, int pageIndex, int pageSize)
{
if (pageSize <= 0)
pageSize = 1;
if (pageIndex < 0)
pageIndex = 0;
var source = Context.ExecuteFunction<Something>("function", params);
var total = source.Count();
var things = (from t in source select new Thing { ID = t.ID })
.Skip(pageIndex * pageSize).Take(pageSize).ToList();
return things.ToList();
}
这是我对您的代码的实现。有几件事要注意。 1.您可以在一条语句中处理Skip。 2. main方法展示了如何将多个页面传入该方法。
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Thing> thingList = new List<Thing>();
for (int i = 0; i < 99; i++)
{
thingList.Add(new Thing(i));
}
int count = 20;
int pageIndex = 0;
int numberPages = (int)Math.Ceiling(thingList.Count * 1.0/ (count ));
for( ; pageIndex < numberPages; pageIndex ++)
{
var myPagedThings = GetThings(thingList, count, pageIndex);
foreach( var item in myPagedThings)
{
Console.WriteLine(item.ID );
}
}
}
public static IEnumerable<Thing> GetThings(List<Thing> myList, int count, int pageIndex)
{
var things = (
from t in myList
select new Thing{ID = t.ID}).ToList();
return things.Skip(count * pageIndex).Take(count);
}
}
public class Thing
{
public int ID
{ get; set; }
public Thing (){}
public Thing(int id)
{ this.ID = id; }
}
碰巧,ExecuteFunction
导致枚举立即发生,最终意味着代码可以重新排序并且不需要复制列表 - 现在看起来像下面
public ThingObjects GetThings(ObjectParameter[] params, int count, int pageIndex)
{
var things = from t in Context.ExecuteFunction<Something>("function", params)
select new Thing
{
ID = t.ID
}).ToList();
var countOfThings = things.Count;
if (pageIndex >= 0)
things = things.Skip(count * pageIndex).Take(count);
return new ThingObjects(things, countOfThings);
}