다음을 통해 공유


사용자 지정 페이징 데이터 정렬(C#)

작성자 : Scott Mitchell

PDF 다운로드

이전 자습서에서는 웹 페이지에서 데이터를 표시할 때 사용자 지정 페이징을 구현하는 방법을 알아보았습니다. 이 자습서에서는 사용자 지정 페이징 정렬에 대한 지원을 포함하도록 앞의 예제를 확장하는 방법을 알아보세요.

소개

기본 페이징에 비해 사용자 지정 페이징은 여러 크기로 데이터를 페이징하는 성능을 향상시킬 수 있으므로 사용자 지정 페이징은 대량의 데이터를 페이징할 때 사실상 페이징 구현을 선택할 수 있습니다. 그러나 사용자 지정 페이징 구현은 특히 혼합에 정렬을 추가할 때 기본 페이징을 구현하는 것보다 더 중요합니다. 이 자습서에서는 정렬 사용자 지정 페이징에 대한 지원을 포함하도록 앞의 예제에서 예제를 확장합니다.

참고

이 자습서는 이전 자습서를 기반으로 하므로 시작하기 전에 잠시 시간을 내어 이전 자습서의 웹 페이지(EfficientPaging.aspx)에서 요소 내 <asp:Content> 의 선언적 구문을 복사하여 페이지의 요소 SortParameter.aspx 간에 <asp:Content> 붙여넣습니다. 한 ASP.NET 페이지의 기능을 다른 페이지로 복제하는 방법에 대한 자세한 내용은 편집 및 삽입 인터페이스에 유효성 검사 컨트롤 추가 자습서의 1단계를 참조하세요.

1단계: 사용자 지정 페이징 기술 다시 살펴보겠습니다.

사용자 지정 페이징이 제대로 작동하려면 행 인덱스 시작 및 최대 행 매개 변수를 고려할 때 특정 레코드 하위 집합을 효율적으로 잡을 수 있는 몇 가지 기술을 구현해야 합니다. 이 목표를 달성하는 데 사용할 수 있는 몇 가지 기술이 있습니다. 이전 자습서에서는 Microsoft SQL Server 2005의 새 ROW_NUMBER() 순위 함수를 사용하여 이 작업을 수행하는 방법을 살펴보았습니다. 즉, 순위 함수는 ROW_NUMBER() 지정된 정렬 순서로 순위가 지정된 쿼리에서 반환되는 각 행에 행 번호를 할당합니다. 그런 다음 번호가 매겨진 결과의 특정 섹션을 반환하여 적절한 레코드 하위 집합을 가져옵니다. 다음 쿼리에서는 이 기술을 사용하여 11에서 20으로 번호가 매겨진 제품을 반환하는 방법을 보여 줍니다. 이때 에서 사전순으로 ProductName정렬된 결과의 순위를 지정합니다.

SELECT ProductID, ProductName, ...
FROM
   (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
        (ORDER BY ProductName) AS RowRank
    FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20

이 기술은 특정 정렬 순서(ProductName 이 경우 사전순으로 정렬됨)를 사용하여 페이징하는 데 적합하지만 다른 정렬 식으로 정렬된 결과를 표시하도록 쿼리를 수정해야 합니다. 이상적으로는 다음과 같이 절에서 매개 변수를 사용하도록 위의 쿼리를 OVER 다시 작성할 수 있습니다.

SELECT ProductID, ProductName, ...
FROM
   (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
        (ORDER BY @sortExpression) AS RowRank
    FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20

아쉽게도 매개 변수가 ORDER BY 있는 절은 허용되지 않습니다. 대신 입력 매개 변수를 허용 @sortExpression 하지만 다음 해결 방법 중 하나를 사용하는 저장 프로시저를 만들어야 합니다.

  • 사용할 수 있는 각 정렬 식에 대해 하드 코딩된 쿼리를 작성합니다. 그런 다음 T-SQL 문을 사용하여 IF/ELSE 실행할 쿼리를 결정합니다.
  • CASE 문을 사용하여 n 입력 매개 변수를 기반으로 @sortExpressio 동적 ORDER BY 식을 제공합니다. 자세한 내용은 T-SQL CASE에서 쿼리 결과를 동적으로 정렬하는 데 사용 섹션을 참조하세요.
  • 저장 프로시저에서 적절한 쿼리를 문자열로 만든 다음 시스템 저장 프로시저를 사용하여 sp_executesql 동적 쿼리를 실행합니다.

이러한 각 해결 방법은 몇 가지 단점이 있습니다. 첫 번째 옵션은 가능한 각 정렬 식에 대한 쿼리를 만들어야 하기 때문에 다른 두 옵션만큼 유지 관리할 수 없습니다. 따라서 나중에 GridView에 정렬 가능한 새 필드를 추가하려는 경우 다시 돌아가서 저장 프로시저를 업데이트해야 합니다. 두 번째 방법은 문자열이 아닌 데이터베이스 열을 기준으로 정렬할 때 성능 문제를 발생시키는 몇 가지 미묘한 차이를 가지며 첫 번째와 동일한 유지 관리 문제도 발생합니다. 또한 동적 SQL을 사용하는 세 번째 선택은 공격자가 선택한 입력 매개 변수 값을 전달하는 저장 프로시저를 실행할 수 있는 경우 SQL 삽입 공격에 대한 위험을 초래합니다.

이러한 접근 방식 중 어느 것도 완벽하지는 않지만 세 번째 옵션이 세 가지 중 최고라고 생각합니다. 동적 SQL을 사용하면 다른 두 가지가 사용하지 않는 수준의 유연성을 제공합니다. 또한 SQL 삽입 공격은 공격자가 선택한 입력 매개 변수를 전달하는 저장 프로시저를 실행할 수 있는 경우에만 악용될 수 있습니다. DAL은 매개 변수가 있는 쿼리를 사용하므로 ADO.NET 아키텍처를 통해 데이터베이스로 전송되는 매개 변수를 보호합니다. 즉, 공격자가 저장 프로시저를 직접 실행할 수 있는 경우에만 SQL 삽입 공격 취약성이 존재합니다.

이 기능을 구현하려면 Northwind 데이터베이스에 라는 GetProductsPagedAndSorted새 저장 프로시저를 만듭니다. 이 저장 프로시저는 @sortExpression결과를 정렬하고 절 @maximumRows@startRowIndex 의 텍스트 바로 다음에 ORDER BY 삽입하는 방법을 지정하는 세 개의 입력 매개 변수인 , 형식nvarchar(100의 입력 매개 변수 및 이전 자습서에서 OVER 검사한 저장 프로시저의 GetProductsPaged 동일한 두 정수 입력 매개 변수를 허용해야 합니다. 다음 스크립트를 GetProductsPagedAndSorted 사용하여 저장 프로시저를 만듭니다.

CREATE PROCEDURE dbo.GetProductsPagedAndSorted
(
    @sortExpression nvarchar(100),
    @startRowIndex int,
    @maximumRows int
)
AS
-- Make sure a @sortExpression is specified
IF LEN(@sortExpression) = 0
    SET @sortExpression = 'ProductID'
-- Issue query
DECLARE @sql nvarchar(4000)
SET @sql = 'SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
            UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
            CategoryName, SupplierName
            FROM (SELECT ProductID, ProductName, p.SupplierID, p.CategoryID,
                    QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
                    ReorderLevel, Discontinued,
                  c.CategoryName, s.CompanyName AS SupplierName,
                   ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowRank
            FROM Products AS p
                    INNER JOIN Categories AS c ON
                        c.CategoryID = p.CategoryID
                    INNER JOIN Suppliers AS s ON
                        s.SupplierID = p.SupplierID) AS ProductsWithRowNumbers
            WHERE     RowRank > ' + CONVERT(nvarchar(10), @startRowIndex) +
                ' AND RowRank <= (' + CONVERT(nvarchar(10), @startRowIndex) + ' + '
                + CONVERT(nvarchar(10), @maximumRows) + ')'
-- Execute the SQL query
EXEC sp_executesql @sql

저장 프로시저는 매개 변수의 값 @sortExpression 이 지정되었는지 확인하여 시작합니다. 누락된 경우 결과는 에 의해 ProductID순위가 매겨집니다. 다음으로 동적 SQL 쿼리가 생성됩니다. 여기서 동적 SQL 쿼리는 Products 테이블에서 모든 행을 검색하는 데 사용된 이전 쿼리와 약간 다릅니다. 이전 예제에서는 하위 쿼리를 사용하여 각 제품의 연결된 범주 및 공급자 이름을 얻었습니다. 이 결정은 데이터 액세스 계층 만들기 자습서에서 다시 이루어졌으며 TableAdapter가 이러한 쿼리에 대해 연결된 삽입, 업데이트 및 삭제 메서드를 자동으로 만들 수 없으므로 를 사용하는 JOIN 대신 수행되었습니다. 그러나 저장 프로시저는 GetProductsPagedAndSorted 범주 또는 공급자 이름으로 정렬할 결과에 s를 사용해야 JOIN 합니다.

이 동적 쿼리는 정적 쿼리 부분과 , @startRowIndex@maximumRows 매개 변수를 @sortExpression연결하여 빌드됩니다. @startRowIndex@maximumRows 는 정수 매개 변수이므로 올바르게 연결하려면 nvarchars로 변환해야 합니다. 이 동적 SQL 쿼리가 생성되면 를 통해 sp_executesql실행됩니다.

잠시 시간을 내어 , @startRowIndex@maximumRows 매개 변수에 대해 서로 다른 값을 사용하여 이 저장 프로시저를 @sortExpression테스트합니다. 서버 Explorer 저장 프로시저 이름을 마우스 오른쪽 단추로 클릭하고 실행을 선택합니다. 그러면 입력 매개 변수를 입력할 수 있는 저장 프로시저 실행 대화 상자가 표시됩니다(그림 1 참조). 결과를 범주 이름으로 정렬하려면 매개 변수 값에 CategoryName을 @sortExpression 사용합니다. 공급자의 회사 이름을 기준으로 정렬하려면 CompanyName을 사용합니다. 매개 변수 값을 제공한 후 확인을 클릭합니다. 결과는 출력 창에 표시됩니다. 그림 2는 내림차순으로 를 주문할 때 11~20위의 제품을 반품할 때의 UnitPrice 결과를 보여 줍니다.

저장 프로시저의 세 가지 입력 매개 변수에 대해 다른 값 사용해 보기

그림 1: 저장 프로시저의 세 가지 입력 매개 변수에 대해 다른 값 사용해 보기

저장 프로시저의 결과가 출력 창에 표시됩니다.

그림 2: 저장 프로시저의 결과가 출력 창에 표시됩니다(전체 크기 이미지를 보려면 클릭).

참고

절에서 지정된 ORDER BYOVER 로 결과의 순위를 지정할 때 SQL Server 결과를 정렬해야 합니다. 열에 클러스터형 인덱스가 있는 경우 또는 결과 순서가 지정되거나 포함 인덱스가 있는 경우 빠른 작업이지만, 그렇지 않으면 비용이 더 많이 들 수 있습니다. 충분히 큰 쿼리의 성능을 향상시키려면 결과가 정렬되는 열에 대해 클러스터되지 않은 인덱스를 추가하는 것이 좋습니다. 자세한 내용은 SQL Server 2005의 순위 함수 및 성능을 참조하세요.

2단계: 데이터 액세스 및 비즈니스 논리 계층 보강

저장 프로시저를 GetProductsPagedAndSorted 만든 다음 단계는 애플리케이션 아키텍처를 통해 해당 저장 프로시저를 실행할 수 있는 수단을 제공하는 것입니다. 이렇게 하려면 DAL과 BLL 모두에 적절한 메서드를 추가해야 합니다. 먼저 DAL에 메서드를 추가해 보겠습니다. Northwind.xsd 형식화된 데이터 세트를 열고 를 마우스 오른쪽 단추로 클릭하고 ProductsTableAdapter상황에 맞는 메뉴에서 쿼리 추가 옵션을 선택합니다. 이전 자습서에서 했던 것처럼 이 경우 기존 저장 프로시저 GetProductsPagedAndSorted인 를 사용하도록 이 새 DAL 메서드를 구성하려고 합니다. 먼저 새 TableAdapter 메서드가 기존 저장 프로시저를 사용하도록 함을 나타냅니다.

기존 저장 프로시저 사용 선택

그림 3: 기존 저장 프로시저 사용 선택

사용할 저장 프로시저를 지정하려면 다음 화면의 GetProductsPagedAndSorted 드롭다운 목록에서 저장 프로시저를 선택합니다.

GetProductsPagedAndSorted 저장 프로시저 사용

그림 4: GetProductsPagedAndSorted 저장 프로시저 사용

이 저장 프로시저는 레코드 집합을 결과로 반환하므로 다음 화면에서 테이블 형식 데이터를 반환함을 나타냅니다.

저장 프로시저가 테이블 형식 데이터를 반환함을 나타냅니다.

그림 5: 저장 프로시저가 테이블 형식 데이터를 반환함을 나타냅니다.

마지막으로 DataTable 채우기 및 DataTable 반환 패턴을 모두 사용하는 DAL 메서드를 만들고 메서드 및 GetProductsPagedAndSorted의 이름을 각각 지정 FillPagedAndSorted 합니다.

메서드 이름 선택

그림 6: 메서드 이름 선택

이제 DAL을 확장했으므로 BLL로 전환할 준비가 되었습니다. ProductsBLL 클래스 파일을 열고 새 메서드 GetProductsPagedAndSorted를 추가합니다. 이 메서드는 세 개의 입력 매개 변수 sortExpression, 및 startRowIndex를 수락해야 하며 maximumRows 다음과 같이 DAL 메서드 GetProductsPagedAndSorted 를 호출하기만 하면 됩니다.

[System.ComponentModel.DataObjectMethodAttribute(
    System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPagedAndSorted(
    string sortExpression, int startRowIndex, int maximumRows)
{
    return Adapter.GetProductsPagedAndSorted
        (sortExpression, startRowIndex, maximumRows);
}

3단계: SortExpression 매개 변수에 전달할 ObjectDataSource 구성

저장 프로시저를 활용하는 GetProductsPagedAndSorted 메서드를 포함하도록 DAL 및 BLL을 보강한 후에는 페이지에서 새 BLL 메서드를 사용하도록 ObjectDataSource SortParameter.aspx 를 구성하고 사용자가 결과를 정렬하도록 요청한 열에 따라 매개 변수를 전달 SortExpression 하기만 하면 됩니다.

먼저 ObjectDataSource를 SelectMethod 에서 GetProductsPaged 로 변경합니다 GetProductsPagedAndSorted. 이 작업은 데이터 원본 구성 마법사, 속성 창 또는 선언적 구문을 통해 직접 수행할 수 있습니다. 다음으로 ObjectDataSource 속성SortParameterName에 대한 값을 제공해야 합니다. 이 속성이 설정되면 ObjectDataSource는 GridView의 SortExpression 속성을 SelectMethod에 전달하려고 시도합니다. 특히 ObjectDataSource는 이름이 속성 값과 같은 입력 매개 변수를 SortParameterName 찾습니다. BLL의 GetProductsPagedAndSorted 메서드에는 라는 sortExpression정렬 식 입력 매개 변수가 있으므로 ObjectDataSource의 SortExpression 속성을 sortExpression 로 설정합니다.

이러한 두 가지를 변경한 후 ObjectDataSource의 선언적 구문은 다음과 유사하게 표시됩니다.

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPagedAndSorted" EnablePaging="True"
    SelectCountMethod="TotalNumberOfProducts" SortParameterName="sortExpression">
</asp:ObjectDataSource>

참고

이전 자습서와 마찬가지로 ObjectDataSource에 SelectParameters 컬렉션에 sortExpression, startRowIndex 또는 maximumRows 입력 매개 변수가 포함되어 있지 않은지 확인합니다.

GridView에서 정렬을 사용하도록 설정하려면 GridView의 스마트 태그에서 정렬 사용 확인란을 검사 GridView의 속성을 true 로 설정하고 각 열의 AllowSorting 헤더 텍스트를 LinkButton으로 렌더링합니다. 최종 사용자가 LinkButtons 헤더 중 하나를 클릭하면 포스트백이 계속되고 다음 단계가 발생합니다.

  1. GridView는 헤더 링크를 클릭한 필드의 SortExpression 값으로 속성을SortExpression 업데이트합니다.
  2. ObjectDataSource는 BLL의 GetProductsPagedAndSorted 메서드를 호출하여 GridView의 속성을 메서드의 SortExpressionsortExpression 입력 매개 변수 값으로 전달합니다maximumRows(적절한 startRowIndex 및 입력 매개 변수 값과 함께).
  3. BLL은 DAL의 GetProductsPagedAndSorted 메서드를 호출합니다.
  4. DAL은 저장 프로시저를 GetProductsPagedAndSorted 실행하여 매개 변수(및 입력 매개 변수 값과 @maximumRows 함께@startRowIndex)를 전달 @sortExpression 합니다.
  5. 저장 프로시저는 BLL에 적절한 데이터 하위 집합을 반환합니다. 이 하위 집합은 ObjectDataSource로 반환됩니다. 그러면 이 데이터가 GridView에 바인딩되고 HTML로 렌더링되고 최종 사용자에게 전송됩니다.

그림 7은 을 오름차순으로 UnitPrice 정렬할 때 결과의 첫 번째 페이지를 보여줍니다.

결과는 UnitPrice별로 정렬됩니다.

그림 7: 결과가 UnitPrice별로 정렬됩니다(전체 크기 이미지를 보려면 클릭).

현재 구현에서는 제품 이름, 범주 이름, 단위당 수량 및 단가별로 결과를 올바르게 정렬할 수 있지만 공급자 이름으로 결과를 정렬하려고 하면 런타임 예외가 발생합니다(그림 8 참조).

다음 런타임 예외에서 공급자 결과를 기준으로 결과 정렬 시도

그림 8: 다음 런타임 예외에서 공급자 결과를 기준으로 결과 정렬 시도

이 예외는 GridView의 BoundField의 SupplierName 가 로 설정되어 있기 때문에 SortExpression 발생합니다SupplierName. 그러나 테이블의 공급자 이름은 Suppliers 실제로 CompanyName 이 열 이름을 SupplierName로 별칭으로 지정했습니다. 그러나 함수에서 사용하는 ROW_NUMBER() 절은 OVER 별칭을 사용할 수 없으며 실제 열 이름을 사용해야 합니다. 따라서 BoundField를 SupplierNameSortExpression SupplierName에서 CompanyName으로 변경합니다(그림 9 참조). 그림 10에서 보여 주듯이 이 변경 후 공급자가 결과를 정렬할 수 있습니다.

SupplierName BoundField의 SortExpression을 CompanyName으로 변경

그림 9: SupplierName BoundField의 SortExpression을 CompanyName으로 변경

이제 공급자별로 결과를 정렬할 수 있습니다.

그림 10: 이제 공급업체별로 결과를 정렬할 수 있습니다(전체 크기 이미지를 보려면 클릭).

요약

이전 자습서에서 검사한 사용자 지정 페이징 구현에서는 디자인 타임에 결과를 정렬할 순서를 지정해야 했습니다. 즉, 구현한 사용자 지정 페이징 구현이 동시에 정렬 기능을 제공할 수 없다는 의미입니다. 이 자습서에서는 결과를 정렬할 수 있는 입력 매개 변수를 포함 @sortExpression 하도록 저장 프로시저를 첫 번째 프로시저에서 확장하여 이러한 제한을 극복했습니다.

이 저장 프로시저를 만들고 DAL 및 BLL에서 새 메서드를 만든 후 GridView의 현재 SortExpression 속성을 BLL SelectMethod에 전달하도록 ObjectDataSource를 구성하여 정렬 및 사용자 지정 페이징을 모두 제공하는 GridView를 구현할 수 있었습니다.

행복한 프로그래밍!

저자 정보

7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 연구해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 샘스 자신을 가르친다 ASP.NET 2.0 24 시간. 그는 에서 찾을 수있는 그의 블로그를 통해 또는 에 mitchell@4GuysFromRolla.comhttp://ScottOnWriting.NET도달 할 수 있습니다.

특별 감사

이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 Carlos Santos였습니다. 예정된 MSDN 문서를 검토하시겠습니까? 그렇다면 에 줄을 놓습니다 mitchell@4GuysFromRolla.com.