개체에 대한 LINQ를 사용한 페이징


94

LINQ 쿼리에서 페이징을 어떻게 구현 하시겠습니까? 사실 당분간은 sql TOP 함수를 모방 할 수 있다면 만족할 것입니다. 그러나 나는 완전한 페이징 지원에 대한 필요성이 어차피 조만간 나올 것이라고 확신합니다.

var queryResult = from o in objects
                  where ...
                  select new
                      {
                         A = o.a,
                         B = o.b
                      }
                   ????????? TOP 10????????

답변:


233

SkipTake확장 방법을 찾고 있습니다. Skip결과에서 처음 N 개 요소를지나 이동하여 나머지를 반환합니다. Take결과의 처음 N 개 요소를 반환하고 나머지 요소를 모두 삭제합니다.

이러한 방법을 사용하는 방법에 대한 자세한 내용은 MSDN을 참조하십시오. http://msdn.microsoft.com/en-us/library/bb386988.aspx

pageNumber가 0에서 시작해야한다는 것을 이미 고려하고 있다고 가정하면 (댓글에서 제안한대로 1 당 감소) 다음과 같이 할 수 있습니다.

int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
  .Skip(numberOfObjectsPerPage * pageNumber)
  .Take(numberOfObjectsPerPage);

그렇지 않으면 @Alvin이 제안한대로

int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
  .Skip(numberOfObjectsPerPage * (pageNumber - 1))
  .Take(numberOfObjectsPerPage);

7
거대한 데이터베이스가있는 SQL에 대해 동일한 기술을 사용해야하는데, 먼저 전체 테이블을 메모리로 가져온 다음 원하지 않는 데이터를 버릴까요?
user256890 2010 년

1
그런데 내부에서 일어나는 일에 관심이 있다면 대부분의 LINQ 데이터베이스 드라이버는 실행중인 실제 SQL에 대한 디버그 출력 정보를 가져 오는 방법을 제공합니다.
David Pfeffer

Rob Conery는 시작하는 데 도움이 될 수있는 PagedList <T> 클래스에 대해 블로그를 작성했습니다. blog.wekeroad.com/blog/aspnet-mvc-pagedlistt
jrotello

49
이것은 pageNumber가 0이 아닌 경우 첫 페이지를 건너 뛰게됩니다. pageNumber가 1로 시작하면 ".Skip (numberOfObjectsPerPage * (pageNumber-1))"를 사용하십시오.
Alvin

결과 SQL은 데이터베이스에 부딪히는 SQL과 같을까요?
Faiz 2014

54

Skip및 사용 Take은 확실히 갈 길입니다. 이것을 구현한다면 아마도 페이징을 처리하는 (코드를 더 읽기 쉽게 만들기 위해) 나만의 확장 메서드를 작성할 것입니다. 물론 사용의 구현 캔 SkipTake:

static class PagingUtils {
  public static IEnumerable<T> Page<T>(this IEnumerable<T> en, int pageSize, int page) {
    return en.Skip(page * pageSize).Take(pageSize);
  }
  public static IQueryable<T> Page<T>(this IQueryable<T> en, int pageSize, int page) {
    return en.Skip(page * pageSize).Take(pageSize);
  }
}

하나 - 클래스는 두 개의 확장 메서드 정의 IEnumerable및 하나 IQueryable, 당신은 SQL에 개체 및 LINQ (데이터베이스 쿼리를 작성할 때, 컴파일러는 선택할 것까지 모두 LINQ와 함께 사용할 수있는 수단을 IQueryable버전).

페이징 요구 사항에 따라 몇 가지 추가 동작 (예 : 음수 pageSize또는 page값 처리)을 추가 할 수도 있습니다 . 다음은 쿼리에서이 확장 메서드를 사용하는 방법의 예입니다.

var q = (from p in products
         where p.Show == true
         select new { p.Name }).Page(10, pageIndex);

3
나는 이것이 전체 결과 집합을 반환하고 서버 대신 메모리 내에서 필터링 할 것이라고 믿습니다. 이것이 SQL 인 경우 데이터베이스에 대한 엄청난 성능 저하.
jvenema 2010 년

1
@jvenema 당신이 맞아요. 이것이 IEnumerable아니라 인터페이스를 사용하기 때문에 IQueryable전체 데이터베이스 테이블을 가져 오므로 주요 성능 저하가 발생합니다.
David Pfeffer

2
물론 IQueryabledatabse 쿼리에서도 작동하도록 오버로드를 쉽게 추가 할 수 있습니다 (답을 편집하고 추가했습니다). 완전히 일반적인 방식으로 코드를 작성할 수 없다는 것은 조금 안타깝습니다 (Haskell에서는 유형 클래스로 가능합니다). 원래 질문에는 LINQ to Objects가 언급되어 있으므로 오버로드를 하나만 작성했습니다.
Tomas Petricek 2010 년

나는 이것을 직접 구현하는 것에 대해 생각하고 있었다. 표준 구현의 일부가 아니라는 사실에 조금 놀랐습니다. 샘플 코드에 감사드립니다!
Michael Richardson

1
예를 들어야한다고 생각합니다. public static IQueryable <T> Page <T> (... etc
David Talbot

37

다음은 LINQ to 개체를 사용할 때 페이징에 대한 내 성능 접근 방식입니다.

public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int pageSize)
{
    Contract.Requires(source != null);
    Contract.Requires(pageSize > 0);
    Contract.Ensures(Contract.Result<IEnumerable<IEnumerable<T>>>() != null);

    using (var enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            var currentPage = new List<T>(pageSize)
            {
                enumerator.Current
            };

            while (currentPage.Count < pageSize && enumerator.MoveNext())
            {
                currentPage.Add(enumerator.Current);
            }
            yield return new ReadOnlyCollection<T>(currentPage);
        }
    }
}

그러면 다음과 같이 사용할 수 있습니다.

var items = Enumerable.Range(0, 12);

foreach(var page in items.Page(3))
{
    // Do something with each page
    foreach(var item in page)
    {
        // Do something with the item in the current page       
    }
}

이 쓰레기의 없음 SkipTake여러 페이지에 관심이 있다면 이는 매우 비효율적 일 수 없습니다.


1
그것은 (내부적으로 OFFSET 절을 사용하여) 건너 뛰기 방법을 지원하지 않습니다 푸른 SQL 데이터웨어 하우스와 엔티티 프레임 워크에서 작동
마이클 Freidgeim

4
이건 그냥 훔쳐서 내 공통 라이브러리에 넣어야 했어요, 감사합니다! 난 그냥 할 수있는 방법 이름 Paginate제거하는 nounverb모호성을.
Gabrielius


6

이것이 누구에게도 도움이 될지 모르겠지만 내 목적에 유용하다는 것을 알았습니다.

private static IEnumerable<T> PagedIterator<T>(IEnumerable<T> objectList, int PageSize)
{
    var page = 0;
    var recordCount = objectList.Count();
    var pageCount = (int)((recordCount + PageSize)/PageSize);

    if (recordCount < 1)
    {
        yield break;
    }

    while (page < pageCount)
    {
        var pageData = objectList.Skip(PageSize*page).Take(PageSize).ToList();

        foreach (var rd in pageData)
        {
            yield return rd;
        }
        page++;
    }
}

이를 사용하려면 linq 쿼리가 있고 페이지 크기와 함께 결과를 foreach 루프에 전달합니다.

var results = from a in dbContext.Authors
              where a.PublishDate > someDate
              orderby a.Publisher
              select a;

foreach(var author in PagedIterator(results, 100))
{
    // Do Stuff
}

따라서 이것은 한 번에 100 명의 작성자를 가져 오는 각 작성자에 대해 반복됩니다.


Count ()가 컬렉션을 열거하므로 List ()로 변환하고 인덱스로 반복 할 수도 있습니다.
Kaerber 2014

5

편집-필요하지 않으므로 Skip (0) 제거

var queryResult = (from o in objects where ...
                      select new
                      {
                          A = o.a,
                          B = o.b
                      }
                  ).Take(10);

2
Take / Skip 방법의 순서를 변경해야하지 않습니까? Take 후 Skip (0)이 의미가 없습니다. 쿼리 스타일로 예제를 제공 한 고맙습니다.
user256890 2010 년

2
아니, 그가 옳다. Take10, Skip0은 처음 10 개의 요소를 사용합니다. Skip0은 무의미하며 절대 수행해서는 안됩니다. 그리고 순서 TakeSkip문제 Skip-10, Take10은 요소 10-20을 취합니다. Take10, Skip10은 요소를 반환하지 않습니다.
David Pfeffer

Take를 호출하기 전에 쿼리를 괄호로 묶어야 할 수도 있습니다. (...에서 ... 선택) .Take (10). 문자열을 선택하여 구성을 호출했습니다. 괄호없이 Take는 쿼리 결과를 제한하는 대신 문자열의 처음 10
자를 반환했습니다.

3
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);

Batchsize는 분명히 정수입니다. 이것은 정수가 단순히 소수 자리를 삭제한다는 사실을 활용합니다.

나는이 응답에 대해 반 농담이지만 ​​원하는대로 수행 할 것이며 지연되기 때문에 그렇게해도 큰 성능 저하를 초래하지 않을 것입니다.

pages.First(p => p.Key == thePage)

이 솔루션은 LinqToEntities를위한 것이 아니며 이것이 좋은 쿼리로 바뀔 수 있는지조차 모릅니다.


3

Lukazoid의 답변과 유사하게 IQueryable에 대한 확장을 만들었습니다.

   public static IEnumerable<IEnumerable<T>> PageIterator<T>(this IQueryable<T> source, int pageSize)
            {
                Contract.Requires(source != null);
                Contract.Requires(pageSize > 0);
                Contract.Ensures(Contract.Result<IEnumerable<IQueryable<T>>>() != null);

                using (var enumerator = source.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        var currentPage = new List<T>(pageSize)
                        {
                            enumerator.Current
                        };

                        while (currentPage.Count < pageSize && enumerator.MoveNext())
                        {
                            currentPage.Add(enumerator.Current);
                        }
                        yield return new ReadOnlyCollection<T>(currentPage);
                    }
                }
            }

Skip 또는 Take가 지원되지 않는 경우 유용합니다.


1

이 확장 방법을 사용합니다.

public static IQueryable<T> Page<T, TResult>(this IQueryable<T> obj, int page, int pageSize, System.Linq.Expressions.Expression<Func<T, TResult>> keySelector, bool asc, out int rowsCount)
{
    rowsCount = obj.Count();
    int innerRows = rowsCount - (page * pageSize);
    if (innerRows < 0)
    {
        innerRows = 0;
    }
    if (asc)
        return obj.OrderByDescending(keySelector).Take(innerRows).OrderBy(keySelector).Take(pageSize).AsQueryable();
    else
        return obj.OrderBy(keySelector).Take(innerRows).OrderByDescending(keySelector).Take(pageSize).AsQueryable();
}

public IEnumerable<Data> GetAll(int RowIndex, int PageSize, string SortExpression)
{
    int totalRows;
    int pageIndex = RowIndex / PageSize;

    List<Data> data= new List<Data>();
    IEnumerable<Data> dataPage;

    bool asc = !SortExpression.Contains("DESC");
    switch (SortExpression.Split(' ')[0])
    {
        case "ColumnName":
            dataPage = DataContext.Data.Page(pageIndex, PageSize, p => p.ColumnName, asc, out totalRows);
            break;
        default:
            dataPage = DataContext.vwClientDetails1s.Page(pageIndex, PageSize, p => p.IdColumn, asc, out totalRows);
            break;
    }

    foreach (var d in dataPage)
    {
        clients.Add(d);
    }

    return data;
}
public int CountAll()
{
    return DataContext.Data.Count();
}

1
    public LightDataTable PagerSelection(int pageNumber, int setsPerPage, Func<LightDataRow, bool> prection = null)
    {
        this.setsPerPage = setsPerPage;
        this.pageNumber = pageNumber > 0 ? pageNumber - 1 : pageNumber;
        if (!ValidatePagerByPageNumber(pageNumber))
            return this;

        var rowList = rows.Cast<LightDataRow>();
        if (prection != null)
            rowList = rows.Where(prection).ToList();

        if (!rowList.Any())
            return new LightDataTable() { TablePrimaryKey = this.tablePrimaryKey };
        //if (rowList.Count() < (pageNumber * setsPerPage))
        //    return new LightDataTable(new LightDataRowCollection(rowList)) { TablePrimaryKey = this.tablePrimaryKey };

        return new LightDataTable(new LightDataRowCollection(rowList.Skip(this.pageNumber * setsPerPage).Take(setsPerPage).ToList())) { TablePrimaryKey = this.tablePrimaryKey };
  }

이것이 내가 한 일입니다. 일반적으로 1에서 시작하지만 IList에서는 0으로 시작합니다. 따라서 8 개의 페이징이 있음을 의미하는 152 개의 행이 있지만 IList에는 7 개만있는 경우 홉은 명확하게 할 수 있습니다.



1

두 가지 주요 옵션이 있습니다.

.NET> = 4.0 동적 LINQ :

  1. System.Linq.Dynamic을 사용하여 추가하십시오. 상단에.
  2. 사용하다: var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

NuGet으로 도 얻을 수 있습니다 .

.NET <4.0 확장 방법 :

private static readonly Hashtable accessors = new Hashtable();

private static readonly Hashtable callSites = new Hashtable();

private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(string name) {
    var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
    if(callSite == null)
    {
        callSites[name] = callSite = CallSite<Func<CallSite, object, object>>.Create(
                    Binder.GetMember(CSharpBinderFlags.None, name, typeof(AccessorCache),
                new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
    }
    return callSite;
}

internal static Func<dynamic,object> GetAccessor(string name)
{
    Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
    if (accessor == null)
    {
        lock (accessors )
        {
            accessor = (Func<dynamic, object>)accessors[name];
            if (accessor == null)
            {
                if(name.IndexOf('.') >= 0) {
                    string[] props = name.Split('.');
                    CallSite<Func<CallSite, object, object>>[] arr = Array.ConvertAll(props, GetCallSiteLocked);
                    accessor = target =>
                    {
                        object val = (object)target;
                        for (int i = 0; i < arr.Length; i++)
                        {
                            var cs = arr[i];
                            val = cs.Target(cs, val);
                        }
                        return val;
                    };
                } else {
                    var callSite = GetCallSiteLocked(name);
                    accessor = target =>
                    {
                        return callSite.Target(callSite, (object)target);
                    };
                }
                accessors[name] = accessor;
            }
        }
    }
    return accessor;
}
public static IOrderedEnumerable<dynamic> OrderBy(this IEnumerable<dynamic> source, string property)
{
    return Enumerable.OrderBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(this IEnumerable<dynamic> source, string property)
{
    return Enumerable.OrderByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(this IOrderedEnumerable<dynamic> source, string property)
{
    return Enumerable.ThenBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(this IOrderedEnumerable<dynamic> source, string property)
{
    return Enumerable.ThenByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.