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

작성자 : Scott Mitchell

PDF 다운로드

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

소개

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

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

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

사용자 지정 페이징의 과제는 특정 데이터 페이지에 필요한 정확한 레코드 집합을 반환하는 쿼리를 작성할 수 있다는 것입니다. 다행히 Microsoft SQL Server 2005는 순위 결과에 대한 새로운 키워드(keyword) 제공하므로 적절한 레코드 하위 집합을 효율적으로 검색할 수 있는 쿼리를 작성할 수 있습니다. 이 자습서에서는 이 새로운 SQL Server 2005 키워드(keyword) 사용하여 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과 1로 계산되는 반면 최대 행은 단순히 페이지 크기입니다. 기본 페이징은 데이터 페이지를 렌더링할 때 데이터베이스에서 모든 레코드를 검색하므로 각 행의 인덱스를 알 수 있으므로 행 인덱스 시작 행으로 이동하는 작업은 간단한 작업입니다. 또한 Total Record Count는 DataTable의 레코드 수(또는 데이터베이스 결과를 보유하는 데 사용되는 개체)이므로 쉽게 사용할 수 있습니다.

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

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

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

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

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

SELECT COUNT(*)
FROM Products

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

먼저 폴더에서 Northwind.xsd 형식화된 DataSet 파일을 App_Code/DAL 엽니다. 그런 다음, Designer 에서 를 마우스 오른쪽 단추로 클릭하고 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의 스칼라 반환 메서드는 nullable 형식을 반환합니다 NULL. 그러나 쿼리 COUNT 는 항상 비값NULL 을 반환합니다. DAL 메서드는 nullable 정수를 반환합니다.

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

Public Function TotalNumberOfProducts() As Integer
    Return Adapter.TotalNumberOfProducts().GetValueOrDefault()
End Function

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

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

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

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

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

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

  • 테이블 변수 사용 및 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 값에 행 인덱스 시작 값과 최대 행이 할당되기 때문입니다. 데이터의 처음 몇 페이지와 같이 번호가 낮은 페이지를 페이징하는 경우 이 방법은 매우 효율적입니다. 그러나 끝 부근의 페이지를 검색할 때 기본 페이징과 유사한 성능이 표시됩니다.

이 자습서에서는 키워드(keyword) 사용하여 사용자 지정 페이징을 ROW_NUMBER() 구현합니다. 테이블 변수 및 SET ROWCOUNT 기술 사용에 대한 자세한 내용은 대규모 결과 집합을 통해 페이징하기 위한 보다 효율적인 방법을 참조하세요.

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

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()자세한 내용은 ROW_NUMBER 설명서를 참조하세요.

위의 예제에서 절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 가 보다 크고 보다 작거나 같은 레코드 PriceRankStartRowIndex 를 반환합니다 StartRowIndex + MaximumRows.

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

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

이전 섹션에서는 임시 SQL 문으로 DAL 메서드를 만들었습니다. 안타깝게도 TableAdapter 마법사에서 사용하는 Visual Studio의 T-SQL 파서는 함수에서 사용하는 ROW_NUMBER() 구문을 좋아하지 OVER 않습니다. 따라서 이 DAL 메서드를 저장 프로시저로 만들어야 합니다. 보기 메뉴에서 서버 Explorer 선택하거나 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)

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

<span class=@startRowIndex 및 @maximumRows Parameters" /> 값을 입력합니다.

그림 7: 및 @maximumRows 매개 변수에 @startRowIndex 대한 값을 입력합니다.

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

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

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

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

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

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

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

Drop-Down 목록에서 GetProductsPaged 저장 프로시저를 선택합니다.

그림 10: Drop-Down 목록에서 GetProductsPaged 저장 프로시저 선택

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

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

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

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

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

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

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

<System.ComponentModel.DataObjectMethodAttribute( _
    System.ComponentModel.DataObjectMethodType.Select, False)> _
Public Function GetProductsPaged(startRowIndex As Integer, maximumRows As Integer) _
    As Northwind.ProductsDataTable
    Return Adapter.GetProductsPaged(startRowIndex, maximumRows)
End Function

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

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

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

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

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

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

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

입력 매개 변수 원본을 없음으로 둡니다.

그림 14: 입력 매개 변수 원본을 없음으로 둡니다.

ObjectDataSource 마법사를 완료한 후 GridView에는 각 제품 데이터 필드에 대한 BoundField 또는 CheckBoxField가 포함됩니다. 원하는 대로 GridView의 모양을 자유롭게 조정할 수 있습니다. , , , QuantityPerUnitCategoryNameSupplierNameUnitPrice BoundFields만 ProductName표시하도록 선택했습니다. 또한 스마트 태그에서 페이징 사용 확인란을 선택하여 페이징을 지원하도록 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가 현재 및 maximumRows 입력 매개 변수 모두에 대한 값으로 0을 사용하고 있기 때문에 GridView가 GetProductsPagedstartRowIndex 없습니다. 따라서 결과 SQL 쿼리는 레코드를 반환하지 않으므로 GridView가 표시되지 않습니다.

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

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

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

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

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

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

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

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

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

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

참고

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

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

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

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

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

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

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

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

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

마지막 제품을 삭제하면 GridView가 자동으로 여덟 번째 페이지로 이동 해야 하며 이러한 기능은 기본 페이징과 함께 표시됩니다. 그러나 사용자 지정 페이징을 사용하면 마지막 페이지에서 마지막 제품을 삭제한 후 GridView가 화면에서 완전히 사라집니다. 이 문제가 발생하는 정확한 이유는 이 자습서의 scope 약간 넘어가기 때문입니다. 이 문제의 원인에 대한 낮은 수준의 세부 정보는 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 Sub GridView1_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
    Handles GridView1.RowDeleted
    ' If we just deleted the last row in the GridView, decrement the PageIndex
    If e.Exception Is Nothing AndAlso GridView1.Rows.Count = 1 Then
        ' we just deleted the last row
        GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1)
    End If
End Sub

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

Protected Sub ObjectDataSource1_Deleted( _
    sender As Object, e As ObjectDataSourceStatusEventArgs) _
    Handles ObjectDataSource1.Deleted
    ' 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 TypeOf e.ReturnValue Is Boolean AndAlso CType(e.ReturnValue, Boolean) = True Then
        e.AffectedRows = 1
    End If
End Sub

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

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

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

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

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

평균 기간(초) Reads
기본 페이징 SQL Profiler 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수있는 자신의 블로그를 통해 도달 할 수 있습니다.