다음을 통해 공유


대량의 데이터를 효율적으로 페이징(C#)

스콧 미첼

PDF 다운로드

데이터 프레젠테이션 컨트롤의 기본 페이징 옵션은 데이터 하위 집합만 표시되더라도 기본 데이터 원본 컨트롤이 모든 레코드를 검색하므로 대량의 데이터로 작업할 때 적합하지 않습니다. 이러한 상황에서는 사용자 지정 페이징으로 전환해야 합니다.

소개

이전 자습서에서 설명한 대로 페이징은 다음 두 가지 방법 중 하나로 구현할 수 있습니다.

  • 데이터 웹 컨트롤의 스마트 태그에서 페이징 사용 옵션을 확인하여 기본 페이징을 구현할 수 있습니다. 그러나 데이터 페이지를 볼 때마다 ObjectDataSource는 해당 하위 집합만 페이지에 표시되더라도 모든 레코드를 검색합니다.
  • 사용자 지정 페이징 은 사용자가 요청한 데이터의 특정 페이지에 대해 표시해야 하는 데이터베이스에서 해당 레코드만 검색하여 기본 페이징의 성능을 향상시킵니다. 그러나 사용자 지정 페이징에는 기본 페이징보다 구현하는 데 약간의 노력이 필요합니다.

구현의 용이성으로 인해 확인란을 선택하고 완료했습니다! 기본 페이징은 매력적인 옵션입니다. 그러나 모든 레코드를 검색하는 순진한 접근 방식은 충분히 많은 양의 데이터를 페이징하거나 동시 사용자가 많은 사이트에 페이징할 때 믿을 수 없는 선택입니다. 이러한 상황에서는 반응형 시스템을 제공하기 위해 사용자 지정 페이징으로 전환해야 합니다.

사용자 지정 페이징의 과제는 특정 데이터 페이지에 필요한 정확한 레코드 집합을 반환하는 쿼리를 작성할 수 있다는 것입니다. 다행히 Microsoft SQL Server 2005는 순위 결과에 대한 새로운 키워드를 제공하므로 적절한 레코드 하위 집합을 효율적으로 검색할 수 있는 쿼리를 작성할 수 있습니다. 이 자습서에서는 이 새 SQL Server 2005 키워드를 사용하여 GridView 컨트롤에서 사용자 지정 페이징을 구현하는 방법을 알아보세요. 사용자 지정 페이징에 대한 사용자 인터페이스는 기본 페이징의 경우와 동일하지만 사용자 지정 페이징을 사용하여 한 페이지에서 다음 페이지로 단계별로 단계별로 실행하면 기본 페이징보다 몇 가지 크기가 더 빠를 수 있습니다.

참고 항목

사용자 지정 페이징에 의해 표시되는 정확한 성능 향상은 페이징되는 총 레코드 수와 데이터베이스 서버에 배치되는 부하에 따라 달라집니다. 이 자습서의 끝부분에는 사용자 지정 페이징을 통해 얻은 성능의 이점을 보여주는 몇 가지 대략적인 메트릭을 살펴보겠습니다.

1단계: 사용자 지정 페이징 프로세스 이해

데이터를 페이징할 때 페이지에 표시되는 정확한 레코드는 요청되는 데이터의 페이지와 페이지당 표시되는 레코드 수에 따라 달라집니다. 예를 들어 페이지당 10개의 제품을 표시하는 81개 제품을 페이징하고 싶다고 상상해 보십시오. 첫 번째 페이지를 볼 때 1부터 10까지의 제품을 원합니다. 두 번째 페이지를 볼 때 11부터 20까지의 제품에 관심이 있을 것입니다.

검색해야 하는 레코드와 페이징 인터페이스를 렌더링하는 방법을 나타내는 세 가지 변수가 있습니다.

  • 표시할 데이터 페이지의 첫 번째 행 인덱스 행 인덱스 시작. 페이지 인덱 스와 페이지당 표시할 레코드를 곱한 후 인덱스 하나를 추가하여 이 인덱스가 계산될 수 있습니다. 예를 들어 한 번에 레코드 10을 페이징하는 경우 첫 번째 페이지(페이지 인덱스가 0)에 대해 시작 행 인덱스는 0 * 10 + 1 또는 1입니다. 페이지 인덱스가 1인 두 번째 페이지의 경우 시작 행 인덱스는 1 * 10 + 1 또는 11입니다.
  • 최대 행은 페이지당 표시할 최대 레코드 수입니다. 이 변수를 최대 행이라고 합니다. 마지막 페이지의 경우 페이지 크기보다 더 적은 레코드가 반환될 수 있기 때문에 이 변수를 최대 행이라고 합니다. 예를 들어 페이지당 81개 제품 10개의 레코드를 페이징하는 경우 9번째 및 마지막 페이지에는 하나의 레코드만 포함됩니다. 하지만 최대 행 값보다 더 많은 레코드가 표시되는 페이지는 없습니다.
  • 총 레코드 수는 페이징되는 총 레코드 수입니다. 이 변수는 지정된 페이지에 대해 검색할 레코드를 결정하는 데 필요하지 않지만 페이징 인터페이스를 지시합니다. 예를 들어 페이징을 통해 페이징되는 제품이 81개인 경우 페이징 인터페이스는 페이징 UI에 9개의 페이지 번호를 표시하는 것을 알고 있습니다.

기본 페이징을 사용하면 시작 행 인덱스가 페이지 인덱스의 곱과 페이지 크기 및 1을 더한 값으로 계산되는 반면 최대 행은 단순히 페이지 크기입니다. 기본 페이징은 데이터 페이지를 렌더링할 때 데이터베이스에서 모든 레코드를 검색하므로 각 행에 대한 인덱스가 알려지므로 행 인덱스 시작 행으로 이동하는 것은 간단한 작업입니다. 또한 DataTable의 레코드 수(또는 데이터베이스 결과를 보유하는 데 사용되는 개체)이므로 총 레코드 수를 쉽게 사용할 수 있습니다.

시작 행 인덱스 및 최대 행 변수를 고려할 때 사용자 지정 페이징 구현은 행 인덱스 시작부터 시작하여 최대 최대 행 수의 레코드까지만 정확한 레코드 하위 집합을 반환해야 합니다. 사용자 지정 페이징은 다음 두 가지 과제를 제공합니다.

  • 지정된 행 인덱스 시작에서 레코드 반환을 시작할 수 있도록 행 인덱스와 페이징되는 전체 데이터의 각 행을 효율적으로 연결할 수 있어야 합니다.
  • 페이징되는 총 레코드 수를 제공해야 합니다.

다음 두 단계에서는 이러한 두 가지 문제에 대응하는 데 필요한 SQL 스크립트를 검사합니다. SQL 스크립트 외에도 DAL 및 BLL에서 메서드를 구현해야 합니다.

2단계: 페이징되는 총 레코드 수 반환

표시되는 페이지에 대한 레코드의 정확한 하위 집합을 검색하는 방법을 살펴보기 전에 먼저 페이징되는 총 레코드 수를 반환하는 방법을 살펴보겠습니다. 페이징 사용자 인터페이스를 제대로 구성하려면 이 정보가 필요합니다. 집계 함수를 사용하여 COUNT 특정 SQL 쿼리에서 반환된 총 레코드 수를 가져올 수 있습니다. 예를 들어 테이블의 총 레코드 Products 수를 확인하려면 다음 쿼리를 사용할 수 있습니다.

SELECT COUNT(*)
FROM Products

이 정보를 반환하는 메서드를 DAL에 추가해 보겠습니다. 특히 위에 표시된 문을 실행하는 DAL 메서드 TotalNumberOfProducts()SELECT 만듭니다.

먼저 폴더에서 Northwind.xsd Typed DataSet 파일을 App_Code/DAL 엽니다. 그런 다음 디자이너에서 마우스 오른쪽 단추를 클릭하고 ProductsTableAdapter 쿼리 추가를 선택합니다. 이전 자습서에서 살펴본 것처럼 이를 통해 DAL에 호출될 때 특정 SQL 문 또는 저장 프로시저를 실행하는 새 메서드를 추가할 수 있습니다. 이전 자습서의 TableAdapter 메서드와 마찬가지로 이 메서드는 임시 SQL 문을 사용하도록 선택합니다.

임시 SQL 문 사용

그림 1: 임시 SQL 문 사용

다음 화면에서 만들 쿼리 유형을 지정할 수 있습니다. 이 쿼리는 단일 스칼라 값을 반환하므로 테이블의 총 레코드 Products 수는 singe 값 옵션을 반환하는 레코드를 선택합니다 SELECT .

단일 값을 반환하는 SELECT 문을 사용하도록 쿼리 구성

그림 2: 단일 값을 반환하는 SELECT 문을 사용하도록 쿼리 구성

사용할 쿼리 유형을 표시한 후에는 쿼리를 지정해야 합니다.

SELECT COUNT(*) FROM Products 쿼리 사용

그림 3: SELECT COUNT(*) FROM Products 쿼리 사용

마지막으로 메서드의 이름을 지정합니다. 앞에서 설명한 TotalNumberOfProducts대로 .

DAL 메서드 TotalNumberOfProducts의 이름을 지정합니다.

그림 4: DAL 메서드 TotalNumberOfProducts 이름 지정

마침을 클릭하면 마법사가 DAL에 TotalNumberOfProducts 메서드를 추가합니다. SQL 쿼리의 결과가 같을 경우 DAL의 스칼라 반환 메서드는 NULLnullable 형식을 반환합니다. 그러나 쿼리 COUNT 는 항상 값NULL 이 아닌 값을 반환합니다. DAL 메서드는 nullable 정수를 반환합니다.

DAL 메서드 외에도 BLL에 메서드가 필요합니다. ProductsBLL 클래스 파일을 열고 DAL TotalNumberOfProducts 메서드를 TotalNumberOfProducts 호출하는 메서드를 추가합니다.

public int TotalNumberOfProducts()
{
    return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}

DAL 메서드 TotalNumberOfProducts 는 nullable 정수를 반환합니다. 그러나 표준 정수를 반환할 수 있도록 클래스의 TotalNumberOfProducts 메서드를 만들었습니다ProductsBLL. 따라서 클래스의 TotalNumberOfProducts 메서드가 DAL TotalNumberOfProducts 메서드에서 반환된 nullable 정수의 값 부분을 반환하도록 해야 합니다ProductsBLL. nullable 정수의 값(있는 경우)을 반환하는 GetValueOrDefault() 호출입니다 null. nullable 정수가면 기본 정수 값인 0을 반환합니다.

3단계: 레코드의 정확한 하위 집합 반환

다음 작업은 DAL 및 BLL에서 앞에서 설명한 행 인덱스 시작 및 최대 행 변수를 수락하고 적절한 레코드를 반환하는 메서드를 만드는 것입니다. 이렇게 하기 전에 먼저 필요한 SQL 스크립트를 살펴보겠습니다. 시작 행 인덱스에서 시작하여 최대 레코드 수까지의 레코드만 반환할 수 있도록 페이징되는 전체 결과의 각 행에 인덱스만 효율적으로 할당할 수 있어야 합니다.

행 인덱스 역할을 하는 열이 데이터베이스 테이블에 이미 있는 경우에는 문제가 되지 않습니다. 언뜻 보기에 첫 번째 제품은 1, 두 번째 제품은 ProductID 2 등으로 테이블 ProductID 필드로 충분할 것이라고 생각할 Products 수 있습니다. 그러나 제품을 삭제하면 순서에 차이가 생기고 이 접근 방식이 무효화됩니다.

행 인덱스를 페이지 간 데이터와 효율적으로 연결하는 데 사용되는 두 가지 일반적인 기술이 있으므로 레코드의 정확한 하위 집합을 검색할 수 있습니다.

  • SQL Server 2005에 새로 추가된 SQL Server 2005 ROW_NUMBER()ROW_NUMBER() 워드를 사용하여 키워드는 일부 순서에 따라 반환된 각 레코드와 순위를 연결합니다. 이 순위는 각 행에 대한 행 인덱스로 사용할 수 있습니다.

  • 테이블 변수 및 SET ROWCOUNT SQL Server 문을SET ROWCOUNT 사용하여 쿼리가 종료하기 전에 처리해야 하는 총 레코드 수를 지정할 수 있습니다. 테이블 변수는 임시 테이블과 유사한 테이블 형식 데이터를 보유할 수 있는 로컬 T-SQL 변수입니다. 이 방법은 Microsoft SQL Server 2005 및 SQL Server 2000 ROW_NUMBER() 모두에서 동일하게 작동하지만 이 방법은 SQL Server 2005에서만 작동합니다.

    여기서는 데이터가 페이징되는 테이블의 기본 키에 대한 열과 열이 있는 테이블 변수 IDENTITY 를 만드는 것이 좋습니다. 다음으로 데이터가 페이징되는 테이블의 내용이 테이블 변수에 덤프되어 테이블의 각 레코드에 대한 순차 행 인덱스(열을 통해 IDENTITY )를 연결합니다. 테이블 변수가 채워 SELECT 지면 기본 테이블과 조인된 테이블 변수의 문을 실행하여 특정 레코드를 끌어올 수 있습니다. 이 SET ROWCOUNT 문은 테이블 변수에 덤프해야 하는 레코드 수를 지능적으로 제한하는 데 사용됩니다.

    이 방법의 효율성은 요청되는 페이지 번호를 기반으로 하며 SET ROWCOUNT , 이 값에는 시작 행 인덱스 값과 최대 행 값이 할당됩니다. 데이터의 처음 몇 페이지와 같이 번호가 낮은 페이지를 페이징하는 경우 이 방법은 매우 효율적입니다. 그러나 끝부분에 있는 페이지를 검색할 때 기본 페이징과 유사한 성능이 표시됩니다.

이 자습서에서는 키워드를 사용하여 사용자 지정 페이징을 구현합니다 ROW_NUMBER() . 테이블 변수 및 SET ROWCOUNT 기술을 사용하는 방법에 대한 자세한 내용은 많은 양의 데이터를 효율적으로 페이징하는 방법을 참조 하세요.

키워드는 ROW_NUMBER() 다음 구문을 사용하여 특정 순서를 통해 반환된 각 레코드와 순위를 연결했습니다.

SELECT columnList,
       ROW_NUMBER() OVER(orderByClause)
FROM TableName

ROW_NUMBER() 는 지정된 순서와 관련하여 각 레코드의 순위를 지정하는 숫자 값을 반환합니다. 예를 들어 가장 비싼 제품에서 최소로 정렬된 각 제품의 순위를 보려면 다음 쿼리를 사용할 수 있습니다.

SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products

그림 5는 Visual Studio의 쿼리 창을 통해 실행할 때 이 쿼리의 결과를 보여 있습니다. 제품은 각 행에 대한 가격 순위와 함께 가격별로 정렬됩니다.

반환된 각 레코드에 대한 가격 순위가 포함됩니다.

그림 5: 반환된 각 레코드에 대한 가격 순위 포함

참고 항목

ROW_NUMBER() 는 SQL Server 2005에서 사용할 수 있는 많은 새로운 순위 함수 중 하나일 뿐입니다. 다른 순위 함수와 함께 보다 자세한 내용은 ROW_NUMBER()Microsoft SQL Server 2005를 사용하여 순위가 지정된 결과 반환을 참조하세요.

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

반환된 ROW_NUMBER() 순위 정보는 절에서 WHERE 직접 사용할 수 없습니다. 그러나 파생 테이블을 사용하여 결과를 반환 ROW_NUMBER() 한 다음 절에 WHERE 표시할 수 있습니다. 예를 들어 다음 쿼리는 파생 테이블을 사용하여 결과와 함께 ROW_NUMBER() ProductName 및 UnitPrice 열을 반환한 다음, 절을 사용하여 WHERE 가격 순위가 11에서 20 사이인 제품만 반환합니다.

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank BETWEEN 11 AND 20

이 개념을 좀 더 확장하면 원하는 행 인덱스 시작 및 최대 행 값에 따라 데이터의 특정 페이지를 검색하는 이 방법을 활용할 수 있습니다.

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank > <i>StartRowIndex</i> AND
    PriceRank <= (<i>StartRowIndex</i> + <i>MaximumRows</i>)

참고 항목

이 자습서 StartRowIndex 의 뒷부분에서 볼 수 있듯이 ObjectDataSource에서 제공하는 인덱스는 0부터 인덱싱되는 반면 ROW_NUMBER() SQL Server 2005에서 반환된 값은 1부터 인덱싱됩니다. 따라서 절은 WHERE 엄격하게 보다 크고 작 StartRowIndex 거나 같은 레코드 PriceRank 를 반환합니다 + StartRowIndexMaximumRows.

이제 시작 행 인덱스 및 최대 행 값에 따라 데이터의 특정 페이지를 검색하는 데 사용할 수 있는 방법을 ROW_NUMBER() 설명했으므로 이제 DAL 및 BLL에서 이 논리를 메서드로 구현해야 합니다.

이 쿼리를 만들 때 결과가 순위가 매겨질 순서를 결정해야 합니다. 에서는 이름을 기준으로 제품을 사전순으로 정렬해 보겠습니다. 즉, 이 자습서의 사용자 지정 페이징 구현에서는 정렬할 수 있는 것보다 사용자 지정 페이징된 보고서를 만들 수 없습니다. 하지만 다음 자습서에서는 이러한 기능을 제공하는 방법을 알아봅니다.

이전 섹션에서는 DAL 메서드를 임시 SQL 문으로 만들었습니다. 안타깝게도 TableAdapter 마법사에서 사용하는 Visual Studio의 T-SQL 파서는 함수에서 사용하는 ROW_NUMBER() 구문을 좋아하지 OVER 않습니다. 따라서 이 DAL 메서드를 저장 프로시저로 만들어야 합니다. 보기 메뉴에서 서버 탐색기를 선택하거나 Ctrl+Alt+S를 누르고 노드를 확장합니다 NORTHWND.MDF . 새 저장 프로시저를 추가하려면 저장 프로시저 노드를 마우스 오른쪽 단추로 클릭하고 새 저장 프로시저 추가를 선택합니다(그림 6 참조).

제품을 통해 페이징을 위한 새 저장 프로시저 추가

그림 6: 제품을 통해 페이징을 위한 새 저장 프로시저 추가

이 저장 프로시저는 두 개의 정수 입력 매개 변수 @startRowIndex 를 허용해야 하며 @maximumRows 필드에 의해 ProductName 정렬된 함수를 사용하여 ROW_NUMBER() 지정된 @startRowIndex 행보다 크고 s보다 작거나 같은 행만 반환합니다 + @startRowIndex@maximumRow. 새 저장 프로시저에 다음 스크립트를 입력한 다음 저장 아이콘을 클릭하여 저장 프로시저를 데이터베이스에 추가합니다.

CREATE PROCEDURE dbo.GetProductsPaged
(
    @startRowIndex int,
    @maximumRows int
)
AS
    SELECT     ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
               UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
               CategoryName, SupplierName
FROM
   (
       SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
              UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
              (SELECT CategoryName
               FROM Categories
               WHERE Categories.CategoryID = Products.CategoryID) AS CategoryName,
              (SELECT CompanyName
               FROM Suppliers
               WHERE Suppliers.SupplierID = Products.SupplierID) AS SupplierName,
              ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank
        FROM Products
    ) AS ProductsWithRowNumbers
WHERE RowRank > @startRowIndex AND RowRank <= (@startRowIndex + @maximumRows)

저장 프로시저를 만든 후 잠시 시간을 내어 테스트합니다. 서버 탐색기에서 GetProductsPaged 저장 프로시저 이름을 마우스 오른쪽 단추로 클릭하고 실행 옵션을 선택합니다. 그러면 Visual Studio에서 입력 매개 변수 @startRowIndex 를 묻는 메시지를 표시합니다 @maximumRow (그림 7 참조). 다른 값을 시도하고 결과를 검사합니다.

<span 클래스=에 대한 값을 입력합니다.@startRowIndex 및 @maximumRows 매개 변수" />

그림 7: 매개 변수 값 @startRowIndex @maximumRows 입력

이러한 입력 매개 변수 값을 선택하면 출력 창에 결과가 표시됩니다. 그림 8에서는 매개 변수와 @maximumRows 매개 변수 모두에 대해 10을 전달할 때의 @startRowIndex 결과를 보여 있습니다.

두 번째 데이터 페이지에 표시되는 레코드가 반환됩니다.

그림 8: 두 번째 데이터 페이지에 표시되는 레코드가 반환됩니다(전체 크기 이미지를 보려면 클릭).

이 저장 프로시저를 만들면 메서드를 만들 준비가 된 것입니다 ProductsTableAdapter . Northwind.xsd 형식화된 데이터 세트를 열고 마우스 오른쪽 단추로 ProductsTableAdapter클릭하고 쿼리 추가 옵션을 선택합니다. 임시 SQL 문을 사용하여 쿼리를 만드는 대신 기존 저장 프로시저를 사용하여 만듭니다.

기존 저장 프로시저를 사용하여 DAL 메서드 만들기

그림 9: 기존 저장 프로시저를 사용하여 DAL 메서드 만들기

다음으로 호출할 저장 프로시저를 선택하라는 메시지가 표시됩니다. GetProductsPaged 드롭다운 목록에서 저장 프로시저를 선택합니다.

드롭다운 목록에서 GetProductsPaged 저장 프로시저 선택

그림 10: 드롭다운 목록에서 GetProductsPaged 저장 프로시저 선택

그런 다음, 다음 화면에서는 저장 프로시저에서 반환되는 데이터 종류(테이블 형식 데이터, 단일 값 또는 값 없음)를 묻습니다. 저장 프로시저는 GetProductsPaged 여러 레코드를 반환할 수 있으므로 테이블 형식 데이터를 반환함을 나타냅니다.

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

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

마지막으로 만들려는 메서드의 이름을 나타냅니다. 이전 자습서와 마찬가지로 DataTable 채우기와 DataTable 반환을 모두 사용하여 메서드를 만듭니다. 첫 번째 메서드와 두 번째 GetProductsPaged메서드 FillPaged 의 이름을 지정합니다.

메서드 FillPaged 및 GetProductsPaged의 이름을 지정합니다.

그림 12: 메서드 FillPaged 및 GetProductsPaged 이름 지정

제품의 특정 페이지를 반환하는 DAL 메서드를 만든 것 외에도 BLL에서 이러한 기능을 제공해야 합니다. DAL 메서드와 마찬가지로 BLL의 GetProductsPaged 메서드는 시작 행 인덱스와 최대 행을 지정하기 위해 두 개의 정수 입력을 허용해야 하며 지정된 범위 내에 속하는 레코드만 반환해야 합니다. 다음과 같이 DAL의 GetProductsPaged 메서드를 호출하는 이러한 BLL 메서드를 ProductsBLL 클래스에 만듭니다.

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

BLL 메서드의 입력 매개 변수에 어떤 이름도 사용할 수 있지만, 곧 확인할 수 있듯이 이 메서드를 사용하도록 ObjectDataSource를 구성할 때 추가 작업에서 사용하기 startRowIndex maximumRows 로 선택하고 저장합니다.

4단계: 사용자 지정 페이징을 사용하도록 ObjectDataSource 구성

특정 레코드 하위 집합에 액세스하기 위한 BLL 및 DAL 메서드가 완료되면 사용자 지정 페이징을 사용하여 기본 레코드를 페이지로 처리하는 GridView 컨트롤을 만들 준비가 된 것입니다. 먼저 폴더에서 EfficientPaging.aspx PagingAndSorting 페이지를 열고, 페이지에 GridView를 추가하고, 새 ObjectDataSource 컨트롤을 사용하도록 구성합니다. 이전 자습서에서는 종종 클래스의 GetProducts 메서드를 사용하도록 ObjectDataSource를 ProductsBLL 구성했습니다. 그러나 이번에는 메서드가 데이터베이스의 모든 제품을 반환하는 반면 GetProductsPaged 레코드의 특정 하위 집합만 반환하므로 이 메서드를 대신 GetProducts 사용 GetProductsPaged 하려고 합니다.

ProductsBLL 클래스의 GetProductsPaged 메서드를 사용하도록 ObjectDataSource 구성

그림 13: ProductsBLL 클래스의 GetProductsPaged 메서드를 사용하도록 ObjectDataSource 구성

읽기 전용 GridView를 만들므로 잠시 시간을 내어 INSERT, UPDATE 및 DELETE 탭의 메서드 드롭다운 목록을 (없음)으로 설정합니다.

다음으로 ObjectDataSource 마법사는 메서드 startRowIndex maximumRows 및 입력 매개 변수 값의 원본을 GetProductsPaged 묻는 메시지를 표시합니다. 이러한 입력 매개 변수는 실제로 GridView에서 자동으로 설정되므로 원본을 None으로 설정하고 마침을 클릭합니다.

입력 매개 변수 원본을 없음으로 유지

그림 14: 입력 매개 변수 원본을 없음으로 유지

ObjectDataSource 마법사를 완료한 후 GridView에는 각 제품 데이터 필드에 대한 BoundField 또는 CheckBoxField가 포함됩니다. 원하는 대로 GridView의 모양을 자유롭게 조정할 수 있습니다. , , SupplierNameQuantityPerUnitUnitPrice BoundFields만 ProductNameCategoryName표시하도록 선택했습니다. 또한 스마트 태그에서 페이징 사용 확인란을 선택하여 페이징을 지원하도록 GridView를 구성합니다. 이러한 변경 후 GridView 및 ObjectDataSource 선언적 태그는 다음과 유사하게 표시됩니다.

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged"
    TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

그러나 브라우저를 통해 페이지를 방문하는 경우 GridView를 찾을 수 있는 위치는 없습니다.

GridView가 표시되지 않음

그림 15: GridView가 표시되지 않음

ObjectDataSource가 현재 0을 입력 매개 변수와 maximumRows 입력 매개 변수의 값으로 사용하므로 GridView가 GetProductsPaged startRowIndex 없습니다. 따라서 결과 SQL 쿼리는 레코드를 반환하지 않으므로 GridView가 표시되지 않습니다.

이 문제를 해결하려면 사용자 지정 페이징을 사용하도록 ObjectDataSource를 구성해야 합니다. 이 작업은 다음 단계에서 수행할 수 있습니다.

  1. ObjectDataSource의 EnablePaging 속성을 이 값으로 true 설정하면 두 개의 추가 매개 변수인 시작 행 인덱스()를 지정하는 매개 변수와 최대 행(StartRowIndexParameterNameMaximumRowsParameterName)을 지정하는 매개 변수로 전달 SelectMethod 되어야 합니다.
  2. ObjectDataSource StartRowIndexParameterNameMaximumRowsParameterName 속성을 설정합니다. 따라서 StartRowIndexParameterName 속성 및 MaximumRowsParameterName 속성은 사용자 지정 페이징을 위해 전달된 SelectMethod 입력 매개 변수의 이름을 나타냅니다. 기본적으로 이러한 매개 변수 이름은 startIndexRow maximumRowsBLL에서 메서드를 GetProductsPaged 만들 때 입력 매개 변수에 대해 이러한 값을 사용하는 이유입니다. 예를 들어 BLL GetProductsPaged 메서드와 같은 maxRowsstartIndex 다른 매개 변수 이름을 사용하도록 선택한 경우 ObjectDataSource StartRowIndexParameterNameMaximumRowsParameterName 속성을 적절하게 설정해야 합니다(예: startIndex for StartRowIndexParameterName 및 maxRows forMaximumRowsParameterName).
  3. ObjectDataSource 속성을 SelectCountMethod 페이징되는TotalNumberOfProductsProductsBLL 총 레코드 수를 반환하는 메서드의 이름으로 설정합니다. 클래스의 TotalNumberOfProducts 메서드는 쿼리를 실행하는 DAL 메서드를 사용하여 페이징되는 총 레코드 수를 반환합니다.SELECT COUNT(*) FROM Products 이 정보는 페이징 인터페이스를 올바르게 렌더링하기 위해 ObjectDataSource에서 필요합니다.
  4. 마법사를 startRowIndex 통해 ObjectDataSource를 구성할 때 ObjectDataSource의 선언적 태그에서 요소 및 maximumRows<asp:Parameter> 요소를 제거하면 Visual Studio에서 메서드의 입력 매개 변수에 대해 GetProductsPaged<asp:Parameter> 개의 요소가 자동으로 추가되었습니다. 로 설정 EnablePaging true하면 이러한 매개 변수가 자동으로 전달됩니다. 선언적 구문에도 표시되면 ObjectDataSource는 메서드에 4개의 매개 변수와 두 개의 매개 변수 GetProductsPaged 를 메서드에 TotalNumberOfProducts 전달하려고 시도합니다. 이러한 <asp:Parameter> 요소를 제거하는 것을 잊어버린 경우 브라우저를 통해 페이지를 방문할 때 다음과 같은 오류 메시지가 표시됩니다. ObjectDataSource 'ObjectDataSource1'에서 startRowIndex, maximumRows 매개 변수가 있는 제네릭이 아닌 메서드 'TotalNumberOfProducts'를 찾을 수 없습니다.

이러한 변경 후 ObjectDataSource의 선언적 구문은 다음과 같이 표시됩니다.

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

EnablePaging SelectCountMethod 속성이 설정 <asp:Parameter> 되었으며 요소가 제거되었습니다. 그림 16은 이러한 변경이 이루어진 후 속성 창 스크린샷을 보여줍니다.

사용자 지정 페이징을 사용하려면 ObjectDataSource 컨트롤을 구성합니다.

그림 16: 사용자 지정 페이징을 사용하려면 ObjectDataSource 컨트롤을 구성합니다.

변경한 후 브라우저를 통해 이 페이지를 방문합니다. 사전순으로 정렬된 10개의 제품이 표시됩니다. 한 번에 한 페이지씩 데이터를 단계별로 실행합니다. 기본 페이징과 사용자 지정 페이징 간의 최종 사용자 관점에서는 시각적 차이가 없지만, 사용자 지정 페이징은 지정된 페이지에 대해 표시해야 하는 레코드만 검색하므로 많은 양의 데이터를 통해 페이지를 보다 효율적으로 페이징합니다.

제품 이름으로 정렬된 데이터는 사용자 지정 페이징을 사용하여 페이징됩니다.

그림 17: 제품 이름으로 정렬된 데이터가 사용자 지정 페이징을 사용하여 페이징됩니다(전체 크기 이미지를 보려면 클릭).

참고 항목

사용자 지정 페이징을 사용하면 ObjectDataSource SelectCountMethod 에서 반환된 페이지 수 값이 GridView의 뷰 상태에 저장됩니다. 다른 GridView 변수 , , 컬렉션 등은 GridView EnableViewState 속성 값에 관계없이 유지되는 제어 상태로 저장됩니다. DataKeys SelectedIndexEditIndexPageIndex PageCount 값은 뷰 상태를 사용하여 포스트백 간에 유지되므로 마지막 페이지로 이동하도록 링크가 포함된 페이징 인터페이스를 사용하는 경우 GridView의 뷰 상태를 사용하도록 설정해야 합니다. (페이징 인터페이스에 마지막 페이지에 대한 직접 링크가 포함되지 않은 경우 보기 상태를 사용하지 않도록 설정할 수 있습니다.)

마지막 페이지 링크를 클릭하면 포스트백이 발생하고 GridView에서 해당 PageIndex 속성을 업데이트하도록 지시합니다. 마지막 페이지 링크를 클릭하면 GridView는 속성 PageIndex 보다 작은 값에 해당 PageCount 속성을 할당합니다. 뷰 상태를 사용하지 않도록 설정하면 PageCount 값이 포스트백에서 손실되고 PageIndex 대신 최대 정수 값이 할당됩니다. 다음으로 GridView는 시작 행 인덱스와 속성을 곱하여 시작 행 인덱스를 PageSize PageCount 확인하려고 시도합니다. 이렇게 하면 제품이 허용되는 최대 정수 크기를 초과하기 때문에 발생 OverflowException 합니다.

사용자 지정 페이징 및 정렬 구현

현재 사용자 지정 페이징 구현에서는 저장 프로시저를 만들 GetProductsPaged 때 데이터가 페이징되는 순서를 정적으로 지정해야 합니다. 그러나 GridView의 스마트 태그에는 페이징 사용 옵션 외에 정렬 사용 확인란이 포함되어 있을 수 있습니다. 아쉽게도 현재 사용자 지정 페이징 구현을 사용하여 GridView에 정렬 지원을 추가하면 현재 표시된 데이터 페이지의 레코드만 정렬됩니다. 예를 들어 페이징도 지원하도록 GridView를 구성한 다음 데이터의 첫 페이지를 볼 때 제품 이름을 내림차순으로 정렬하면 1페이지의 제품 순서가 반대로 바뀝니다. 그림 18에서 보여 주듯이, 이러한 카나본 타이거스는 사전순으로 카나본 타이거스 이후 오는 71개의 다른 제품을 무시하는 역순으로 정렬할 때 첫 번째 제품으로 표시됩니다. 첫 번째 페이지의 레코드만 정렬에서 고려됩니다.

현재 페이지에 표시된 데이터만 정렬됩니다.

그림 18: 현재 페이지에 표시된 데이터만 정렬됩니다(전체 크기 이미지를 보려면 클릭).

정렬은 BLL GetProductsPaged 메서드에서 데이터를 검색한 후에 정렬이 수행되므로 데이터의 현재 페이지에만 적용되며, 이 메서드는 특정 페이지에 대한 레코드만 반환합니다. 정렬을 올바르게 구현하려면 데이터의 특정 페이지를 반환하기 전에 데이터를 적절하게 순위를 지정할 수 있도록 정렬 식을 GetProductsPaged 메서드에 전달해야 합니다. 이 작업을 수행하는 방법은 다음 자습서에서 확인할 수 있습니다.

사용자 지정 페이징 및 삭제 구현

사용자 지정 페이징 기술을 사용하여 데이터가 페이징되는 GridView에서 기능 삭제를 사용하도록 설정하면 마지막 페이지에서 마지막 레코드를 삭제할 때 GridView가 GridView를 PageIndex적절하게 감소시키는 대신 사라집니다. 이 버그를 재현하려면 방금 만든 자습서에 대해 삭제를 사용하도록 설정합니다. 81개 제품, 한 번에 10개 제품을 페이징하고 있으므로 단일 제품을 볼 수 있는 마지막 페이지(9페이지)로 이동합니다. 이 제품을 삭제합니다.

마지막 제품을 삭제하면 GridView 는 자동으로 8번째 페이지로 이동해야 하며 이러한 기능은 기본 페이징과 함께 표시됩니다. 그러나 사용자 지정 페이징을 사용하면 마지막 페이지에서 마지막 제품을 삭제하면 GridView가 화면에서 완전히 사라집니다. 이 문제가 발생하는 정확한 이유는 이 자습서의 범위를 약간 벗어나는 것입니다. 이 문제의 원인에 대한 하위 수준 세부 정보는 사용자 지정 페이징이 있는 GridView에서 마지막 페이지의 마지막 레코드 삭제를 참조하세요. 요약하면 삭제 단추를 클릭할 때 GridView에서 수행하는 다음 단계 시퀀스 때문입니다.

  1. 레코드 삭제
  2. 지정된 PageIndex 및 에 대해 표시할 적절한 레코드를 가져옵니다. PageSize
  3. PageIndex 데이터 원본의 데이터 페이지 수를 초과하지 않는지 확인합니다. 이 경우 GridView의 PageIndex 속성이 자동으로 감소합니다.
  4. 2단계에서 가져온 레코드를 사용하여 GridView에 적절한 데이터 페이지 바인딩

문제는 2 PageIndex 단계에서 표시할 레코드를 잡을 때 사용된 레코드가 유일한 PageIndex 레코드가 삭제된 마지막 페이지라는 사실에서 비롯됩니다. 따라서 2 단계에서는 데이터의 마지막 페이지에 더 이상 레코드가 포함되지 않으므로 레코드가 반환되지 않습니다. 그런 다음, 3단계에서 GridView는 해당 PageIndex 속성이 데이터 원본의 총 페이지 수보다 크므로(마지막 페이지에서 마지막 레코드를 삭제했으므로) 해당 속성이 감소한다는 것을 PageIndex 인식합니다. 4단계에서 GridView는 2단계에서 검색된 데이터에 자신을 바인딩하려고 합니다. 그러나 2단계에서는 레코드가 반환되지 않아 GridView가 비어 있습니다. 기본 페이징의 경우 2 단계에서 모든 레코드가 데이터 원본에서 검색되기 때문에 이 문제가 나타나지 않습니다.

이 문제를 해결하려면 두 가지 옵션이 있습니다. 첫 번째는 방금 삭제된 페이지에 표시된 레코드 수를 결정하는 GridView의 RowDeleted 이벤트 처리기에 대한 이벤트 처리기를 만드는 것입니다. 레코드가 하나만 있는 경우 방금 삭제된 레코드가 마지막 레코드여야 하며 GridView를 PageIndex감소시켜야 합니다. 물론 삭제 작업이 실제로 성공한 경우에만 업데이트 PageIndex 하려고 합니다. 이 작업은 속성nulle.Exception 있는지 확인하여 확인할 수 있습니다.

이 방법은 1단계 이후와 2단계 이전을 PageIndex 업데이트하기 때문에 작동합니다. 따라서 2단계에서는 적절한 레코드 집합이 반환됩니다. 이렇게 하려면 다음과 같은 코드를 사용합니다.

protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // If we just deleted the last row in the GridView, decrement the PageIndex
    if (e.Exception == null && GridView1.Rows.Count == 1)
        // we just deleted the last row
        GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1);
}

다른 해결 방법은 ObjectDataSource 이벤트에 RowDeleted 대한 이벤트 처리기를 만들고 속성을 1 값으로 설정하는 AffectedRows 것입니다. 1단계에서 레코드를 삭제한 후(하지만 2단계에서 데이터를 다시 검색하기 전에) 하나 이상의 행이 작업의 영향을 받은 경우 GridView에서 해당 PageIndex 속성을 업데이트합니다. 그러나 AffectedRows 속성은 ObjectDataSource에 의해 설정되지 않으므로 이 단계는 생략됩니다. 이 단계를 실행하는 한 가지 방법은 삭제 작업이 성공적으로 완료되면 속성을 수동으로 설정하는 AffectedRows 것입니다. 이 작업은 다음과 같은 코드를 사용하여 수행할 수 있습니다.

protected void ObjectDataSource1_Deleted(
    object sender, ObjectDataSourceStatusEventArgs e)
{
    // If we get back a Boolean value from the DeleteProduct method and it's true,
    // then we successfully deleted the product. Set AffectedRows to 1
    if (e.ReturnValue is bool && ((bool)e.ReturnValue) == true)
        e.AffectedRows = 1;
}

이러한 두 이벤트 처리기의 코드는 예제의 EfficientPaging.aspx 코드 숨김 클래스에서 찾을 수 있습니다.

기본 및 사용자 지정 페이징의 성능 비교

사용자 지정 페이징은 필요한 레코드만 검색하지만 기본 페이징은 보고 있는 각 페이지에 대한 모든 레코드를 반환하므로 사용자 지정 페이징이 기본 페이징보다 더 효율적임이 분명합니다. 하지만 사용자 지정 페이징이 얼마나 효율적일까요? 기본 페이징에서 사용자 지정 페이징으로 이동하여 어떤 종류의 성능 향상을 볼 수 있나요?

불행하게도, 여기에 모든 대답에 맞는 하나의 크기가 없습니다. 성능 향상은 여러 가지 요인에 따라 달라집니다. 가장 눈에 띄는 두 가지는 페이징되는 레코드 수와 데이터베이스 서버에 배치된 부하 및 웹 서버와 데이터베이스 서버 간의 통신 채널입니다. 레코드가 몇 개뿐인 작은 테이블의 경우 성능 차이가 무시할 수 있습니다. 하지만 수천~수십만 개의 행이 있는 대형 테이블의 경우 성능 차이가 심각합니다.

"SQL Server 2005를 사용한 ASP.NET 2.0의 사용자 지정 페이징" 문서에는 50,000개의 레코드가 있는 데이터베이스 테이블을 페이징할 때 이 두 페이징 기술 간의 성능 차이를 나타내기 위해 실행한 몇 가지 성능 테스트가 포함되어 있습니다. 이 테스트에서는 SQL Server 수준(SQL Profiler 사용)과 ASP.NET 추적 기능을 사용하여 ASP.NET 페이지에서 쿼리를 실행하는 시간을 모두 검사했습니다. 이러한 테스트는 단일 활성 사용자로 개발 상자에서 실행되었으므로 비과학적이며 일반적인 웹 사이트 부하 패턴을 모방하지 않습니다. 그럼에도 불구하고 결과는 충분히 많은 양의 데이터로 작업할 때 기본 및 사용자 지정 페이징에 대한 실행 시간의 상대적 차이를 보여 줍니다.

평균 기간(초) 읽기
기본 페이징 SQL 프로파일러 1.411 383
사용자 지정 페이징 SQL 프로파일러 0.002 29
기본 페이징 ASP.NET 추적 2.379 해당 사항 없음
사용자 지정 페이징 ASP.NET 추적 0.029 해당 사항 없음

보듯이 데이터의 특정 페이지를 검색하려면 평균 읽기가 354회 더 적고 시간의 일부로 완료되었습니다. ASP.NET 페이지에서 사용자 지정 페이지는 기본 페이징을 사용하는 데 걸린 시간의 1/100 가깝게 렌더링할 수 있었습니다.

요약

기본 페이징은 데이터 웹 컨트롤의 스마트 태그에서 페이징 사용 확인란을 선택하여 구현하기 위한 cinch이지만 이러한 단순성은 성능에 따라 발생합니다. 기본 페이징을 사용하면 일부만 표시되더라도 사용자가 모든 데이터 페이지를 요청하면 모든 레코드가 반환됩니다. 이러한 성능 오버헤드를 방지하기 위해 ObjectDataSource는 대체 페이징 옵션 사용자 지정 페이징을 제공합니다.

사용자 지정 페이징은 표시해야 하는 레코드만 검색하여 기본 페이징 성능 문제를 개선하지만 사용자 지정 페이징을 구현하는 데 더 많이 관련됩니다. 먼저 요청된 레코드의 특정 하위 집합에 올바르게(효율적으로) 액세스하는 쿼리를 작성해야 합니다. 이 작업은 여러 가지 방법으로 수행할 수 있습니다. 이 자습서에서 검사한 것은 SQL Server 2005의 새 ROW_NUMBER() 함수를 사용하여 결과의 순위를 지정한 다음 순위가 지정된 범위 내에 속하는 결과만 반환하는 것입니다. 또한 페이징되는 총 레코드 수를 결정하는 수단을 추가해야 합니다. 이러한 DAL 및 BLL 메서드를 만든 후에는 페이징되는 총 레코드 수를 확인하고 시작 행 인덱스 및 최대 행 값을 BLL에 올바르게 전달할 수 있도록 ObjectDataSource를 구성해야 합니다.

사용자 지정 페이징을 구현하려면 여러 단계가 필요하며 기본 페이징만큼 간단하지는 않지만, 사용자 지정 페이징은 충분히 많은 양의 데이터를 페이징할 때 필요합니다. 조사 결과에서 볼 수 있듯이 사용자 지정 페이징은 ASP.NET 페이지 렌더링 시간에서 몇 초를 벗어날 수 있으며 데이터베이스 서버의 부하를 광석 단위로 한 번 더 밝게 할 수 있습니다.

행복한 프로그래밍!

작성자 정보

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