无法使用.Take()和.Skip()获取总计数

时间:2017-03-30 23:35:25

标签: c# entity-framework linq skip-take

我在使用Linq实现一些分页时遇到了一些麻烦,我已经在这里阅读了各种问题(例如thisthis),但我仍然收到错误;

  

System.InvalidOperationException:查询结果不能多​​次枚举。

我的(稍微混淆)代码是;

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()的调用是否枚举相同的内容?

编辑:我正在使用实体框架,如果这有任何区别

6 个答案:

答案 0 :(得分:1)

如果我没弄错的话,ExecuteFunction实际上会返回一个ObjectResult,这更复杂。如果你使函数可组合(你可以在Count()时执行一个单独的查询),你可能会得到不同的结果,但是我已经有一段时间了,因为我使用低级EF,所以我不是100%肯定会工作。< / p>

由于你不能完全执行有效的两个查询,最安全的选择是创建一个完全独立的计数 - 并且通过完全独立我的意思是一个单独的函数或存储过程,它只是计数,否则你可能最终(取决于你的功能)将行返回到EF并在内存中计算它们。或者,如果可能的话,将该函数重写为视图,这可能会使其更直接。

答案 1 :(得分:0)

您正在设置pagedThings =的东西。所以你正在研究同一个对象。如果你想做你上面尝试的事情,你需要将东西复制到一个新的集合,但我建议一般重构这个代码。

您可以查看此SO帖子,了解如何在不枚举列表的情况下获取计数: How to COUNT rows within EntityFramework without loading contents?

答案 2 :(得分:0)

一般来说,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提供商必须保护您不要进行多次昂贵的通话。您应该寻找一种只迭代结果一次的方法。例如,如上所述,您可以在已生成的List上使用.Count()。

答案 3 :(得分:0)

通常,我们称之为 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();
}

答案 4 :(得分:0)

这是我的代码实现。一些注意事项。 你可以在一个声明中处理Skip。 2.主要方法显示如何将多个页面传递给方法。

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;   }
}

答案 5 :(得分:0)

碰巧,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);
}