【问题标题】:Linq paging - how to solve performance problem?Linq 分页 - 如何解决性能问题?
【发布时间】:2011-04-06 14:26:15
【问题描述】:

编辑: 实体框架似乎是问题所在,在问题Entity Framework & Linq performance problem 中进一步讨论。

我支持一个早已离开的人编写的 PagedList(使用 linq/generics)类 - 它有 2 行性能非常差 - 在只有 2000 行的数据集上运行需要长达一分钟。

两个有问题的行是:

TotalItemCount = source.Count();

我可能可以通过将计数作为参数传递来解决这个问题。但是另一条慢的线是这个sn-p中的AddRange

IQueryable<T> a = source.Skip<T>((index) * pageSize).Take<T>(pageSize);
AddRange(a.AsEnumerable());

我不明白为什么AddRange 这么慢或者我可以做些什么来改进它?

整个类源码列表是

public class PagedList<T> : List<T>, IPagedList<T>
{
    public PagedList(IEnumerable<T> source, int index, int pageSize)
     : this(source, index, pageSize, null)
    {
    }

    public PagedList(IEnumerable<T> source, int index, int pageSize, int? totalCount)
    {
        Initialize(source.AsQueryable(), index, pageSize, totalCount);
    }

    public PagedList(IQueryable<T> source, int index, int pageSize)
     : this(source, index, pageSize, null)
    {
    }

    public PagedList(IQueryable<T> source, int index, int pageSize, int? totalCount)
    {
        Initialize(source, index, pageSize, totalCount);
    }

    #region IPagedList Members

    public int PageCount { get; private set; }
    public int TotalItemCount { get; private set; }
    public int PageIndex { get; private set; }
    public int PageNumber { get { return PageIndex + 1; } }
    public int PageSize { get; private set; }
    public bool HasPreviousPage { get; private set; }
    public bool HasNextPage { get; private set; }
    public bool IsFirstPage { get; private set; }
    public bool IsLastPage { get; private set; }

    #endregion

    protected void Initialize(IQueryable<T> source, int index, 
            int pageSize, int? totalCount)
    {
        //### argument checking
        if (index < 0)
        {
            throw new ArgumentOutOfRangeException("PageIndex cannot be below 0.");
        }
        if (pageSize < 1)
        {
            throw new ArgumentOutOfRangeException("PageSize cannot be less than 1.");
        }

        //### set source to blank list if source is null to prevent exceptions
        if (source == null)
        {
            source = new List<T>().AsQueryable();
        }

        //### set properties
        if (!totalCount.HasValue)
        {
            TotalItemCount = source.Count();
        }
        PageSize = pageSize;
        PageIndex = index;
        if (TotalItemCount > 0)
        {
            PageCount = (int)Math.Ceiling(TotalItemCount / (double)PageSize);
        }
        else
        {
            PageCount = 0;
        }
        HasPreviousPage = (PageIndex > 0);
        HasNextPage = (PageIndex < (PageCount - 1));
        IsFirstPage = (PageIndex <= 0);
        IsLastPage = (PageIndex >= (PageCount - 1));

        //### add items to internal list
        if (TotalItemCount > 0)
        {
            IQueryable<T> a = source.Skip<T>((index) * pageSize).Take<T>(pageSize);
            AddRange(a.AsEnumerable());
        }
    }
}

【问题讨论】:

  • 数据从何而来? PS:“我不明白 AddRange 为什么这么慢”---AddRange 不慢,但是延迟的 linq 表达式评估很慢。
  • @zerkms 抱歉,它的 a.AsEnumerable() 很慢,而不是 AddRange
  • @robert 是这样调用的:var pagedModel = new PagedList&lt;T&gt;(data, currentPage - 1, rowsPerPage);PagedList 继承 List,所以AddRangethis.AddRange 相同。并不是说我自己 100% 理解代码
  • 好的,谢谢。我终于知道这是一个本地电话。

标签: c# linq performance entity-framework pagination


【解决方案1】:

您为什么不直接将a 设为 IEnumerable?这样你就完全不用打电话给AsEnumerable()了。

尽管仔细想想,查询直到AsEnumerable 被调用后才真正执行,所以这里可能不会有任何区别。

【讨论】:

  • a 已经通过派生自 IEnumerable 的 IQueryable 成为 IEnumerable。我怀疑 OP 可能会投入 AsEnumerable 来尝试隔离瓶颈。
  • 那是我继承的代码,所以不能说为什么会这样。
【解决方案2】:

可能不是 AddRange 很慢,可能是对源的查询。对 AsEnumerable() 的调用将立即返回,但查询实际上不会执行,直到实际枚举序列时在 AddRange 调用内部。

你可以通过改变这一行来证明这一点:

AddRange(a.AsEnumerable());

到:

T[] aa = a.ToArray();
AddRange(aa);

您可能会看到 ToArray() 调用占用了大部分时间,因为这是实际执行查询的时间。现在为什么这很慢是任何人的猜测。如果您使用 LINQ to SQL 或 Entity Framework,您可以尝试分析数据库以查看瓶颈在哪里。但它可能不是 PagedList 类。

【讨论】:

  • 当我单步执行代码时,它不会立即返回,这是一个很大的延迟。是的,它使用 EF。我将启动 sql profiler 并查看它的内容。而data 参数只是一个简单的 EF getAll() 查询(因此没有复杂的连接等会导致糟糕的 sql 性能)
  • 现在这很奇怪:sql profiler 显示选择了 1958 行,它对另一个表(主子表的子表)的全部内容进行了 3916 次(即 1958*2)次单独选择。这 3916 个选择中没有 where 子句
  • 我应该问一个单独的问题,因为这似乎是一个 EF 问题吗?它所做的只是var data =_service.GetAll(),然后是data.Skip((index) * pageSize).Take(pageSize)。这不应该有性能问题
  • 是的,我肯定会建议打开另一个问题。看看您是否可以使用 LINQPad 之类的东西在应用程序之外重现问题也是一个好主意。这样您就可以只发布有问题的 LINQ 查询,这应该有助于隔离问题。
  • 但就其价值而言,这听起来像是急切加载关系数据的问题。在 EF 中可能有更好的方法,但我没有丰富的经验。
猜你喜欢
  • 2017-05-25
  • 2021-07-26
  • 1970-01-01
  • 2021-06-12
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多