简体   繁体   English

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

[英]Trouble getting total count using .Take() and .Skip()

I'm having some trouble implementing some paging using Linq and I've read the various questions ( this and this for example) on here but I'm still getting the error; 我在使用Linq实现某些分页时遇到了一些麻烦,并且我在这里阅读了各种问题(例如, 这个这个 ),但是我仍然遇到错误。

System.InvalidOperationException: The result of a query cannot be enumerated more than once. System.InvalidOperationException:查询结果不能被多次枚举。

My (slightly obfuscated) code is; 我的代码(有点模糊)是;

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

As soon as the final .ToList() is called, the error is thrown but I can't see why - are the calls to things.Count() and pagedThings.ToList() enumerating the same thing? 一旦最后一个.ToList()被调用,就会引发错误,但是我不明白为什么-对things.Count()pagedThings.ToList()的调用是否枚举同一件事?

Edit: I'm using Entity Framework if that makes any difference 编辑:如果这有什么区别,我正在使用实体框架

ExecuteFunction actually returns an ObjectResult if I'm not mistaken, which is... more complicated. 如果我没有记错的话,ExecuteFunction实际上会返回一个ObjectResult,这更复杂了。 You may get different results if you make the function composable (which would execute a separate query when you Count()), but it's been a while since I worked with low level EF so I'm not 100% sure that would work. 如果使函数可组合(当您使用Count()时将执行一个单独的查询),则可能会得到不同的结果,但是由于我使用低级EF,已经有一段时间了,因此我不确定100%会可行。

Since you can't get out of executing what are effectively two queries, the safest bet is to make a completely separate one for counting - and by completely separate I mean a separate function or stored procedure, which just does the count, otherwise you may end up (depending on your function) returning rows to EF and counting them in memory. 由于您无法摆脱实际上执行两个查询的问题,因此最安全的选择是进行一个完全独立的计数操作-所谓完全独立是指一个单独的函数或存储过程,该操作仅用于计数,否则您可能会最后(取决于您的函数)将行返回到EF并在内存中对其进行计数。 Or rewrite the function as a view if at all possible, which may make it more straightforward. 或尽可能将函数重写为视图,这可能会使它更直接。

You are setting pagedThings = things. 您正在设置pagedThings =事物。 So you are working on the same object. 因此,您正在处理同一对象。 You would need to copy things to a new collection if you want to do what you're trying above, but I would recommend refactoring this code in general. 如果您想执行上面的尝试,则需要将内容复制到新的集合中,但是我建议通常重构此代码。

You can check out this SO post to get some ideas on how to get the count without enumerating the list: How to COUNT rows within EntityFramework without loading contents? 您可以查看这篇SO帖子,以获得有关如何在不枚举列表的情况下获得计数的一些想法: 如何在EntityFramework中计数行而不加载内容?

In general, Linq is able to do that. 通常,Linq能够做到这一点。 In LinqPad, I wrote the following code and successfully executed it: 在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();

} }

Note, as your error mentions, this will execute the query twice. 请注意,如您的错误所述,这将执行两次查询。 Once for Count() and once for ToList(). 一次用于Count(),一次用于ToList()。

It must be that the Linq provider that you are representing as Context.ExecuteFunction<Something>("function", params) is protecting you from making multiple expensive calls. 一定是您表示为Context.ExecuteFunction<Something>("function", params)的Linq提供程序可以防止您进行多次昂贵的调用。 You should look for a way to iterate over the results only once. 您应该寻找一种仅迭代一次结果的方法。 As written, for example, you could .Count() on the List that you had already generated. 如所写,例如,您可以在已经生成的列表上添加.Count()。

Normally, we call them pageIndex and pageSize . 通常,我们称它们为pageIndexpageSize

Please check pageIndex whether 0 as start index or 1 as start index depending on your requirement. 请检查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();
}

Here is my implementation of your code. 这是我对您代码的实现。 a few things to notice. 一些注意事项。 1. You can handle Skip in one statement. 1.您可以在一个语句中处理“跳过”。 2. The main method shows how to pass multiple pages into the method. 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;   }
}

As it happens, ExecuteFunction causes the enumeration to occur immediately, ultimately meaning the code could be re-ordered and the copying of the list was not required - it now looks like the below 碰巧的是, 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM