Compartilhar via


Paginação de grandes quantidades de dados com eficiência (VB)

por Scott Mitchell

Baixar PDF

A opção de paginação padrão de um controle de apresentação de dados não é adequada ao trabalhar com grandes quantidades de dados, pois seu controle de fonte de dados subjacente recupera todos os registros, mesmo que apenas um subconjunto de dados seja exibido. Nessas circunstâncias, devemos recorrer à paginação personalizada.

Introdução

Como discutimos no tutorial anterior, a paginação pode ser implementada de duas maneiras:

  • A paginação padrão pode ser implementada simplesmente marcando a opção Habilitar paginação na marca inteligente do controle Web de dados; no entanto, sempre que exibir uma página de dados, o ObjectDataSource recupera todos os registros, mesmo que apenas um subconjunto deles seja exibido na página
  • A paginação personalizada melhora o desempenho da paginação padrão, recuperando apenas os registros do banco de dados que precisam ser exibidos para a página específica de dados solicitada pelo usuário; no entanto, a paginação personalizada envolve um pouco mais de esforço para implementar do que a paginação padrão

Devido à facilidade de implementação, basta marcar uma caixa de seleção e pronto! A paginação padrão é uma opção atraente. Sua abordagem ingênua na recuperação de todos os registros, no entanto, o torna uma escolha implausível ao paginar quantidades suficientemente grandes de dados ou para sites com muitos usuários simultâneos. Em tais circunstâncias, devemos recorrer à paginação personalizada para fornecer um sistema responsivo.

O desafio da paginação personalizada é ser capaz de escrever uma consulta que retorne o conjunto preciso de registros necessários para uma página específica de dados. Felizmente, o Microsoft SQL Server 2005 fornece uma nova palavra-chave para classificar os resultados, o que nos permite escrever uma consulta que pode recuperar com eficiência o subconjunto adequado de registros. Neste tutorial, veremos como usar essa nova palavra-chave do SQL Server 2005 para implementar a paginação personalizada em um controle GridView. Embora a interface do usuário para paginação personalizada seja idêntica à da paginação padrão, passar de uma página para a próxima usando a paginação personalizada pode ser várias ordens de magnitude mais rápida do que a paginação padrão.

Observação

O ganho de desempenho exato exibido pela paginação personalizada depende do número total de registros que estão sendo paginados e da carga que está sendo colocada no servidor de banco de dados. No final deste tutorial, veremos algumas métricas aproximadas que mostram os benefícios em desempenho obtidos por meio da paginação personalizada.

Etapa 1: Entendendo o processo de paginação personalizada

Ao paginar dados, os registros precisos exibidos em uma página dependem da página de dados que está sendo solicitada e do número de registros exibidos por página. Por exemplo, imagine que quiséssemos percorrer os 81 produtos, exibindo 10 produtos por página. Ao visualizar a primeira página, queremos os produtos de 1 a 10; Ao visualizar a segunda página, estaríamos interessados nos produtos 11 a 20 e assim por diante.

Há três variáveis que determinam quais registros precisam ser recuperados e como a interface de paginação deve ser renderizada:

  • Índice de Linha Inicial o índice da primeira linha na página de dados a ser exibida; esse índice pode ser calculado multiplicando o índice da página pelos registros a serem exibidos por página e adicionando um. Por exemplo, ao paginar os registros 10 por vez, para a primeira página (cujo índice de página é 0), o Índice de Linha Inicial é 0 * 10 + 1 ou 1; para a segunda página (cujo índice de página é 1), o Índice da Linha Inicial é 1 * 10 + 1 ou 11.
  • Máximo de linhas : o número máximo de registros a serem exibidos por página. Essa variável é chamada de linhas máximas, pois para a última página pode haver menos registros retornados do que o tamanho da página. Por exemplo, ao paginar os 81 produtos 10 registros por página, a nona e última página terá apenas um registro. Nenhuma página, no entanto, mostrará mais registros do que o valor Máximo de Linhas.
  • Contagem Total de Registros : o número total de registros que estão sendo paginados. Embora essa variável não seja necessária para determinar quais registros recuperar para uma determinada página, ela determina a interface de paginação. Por exemplo, se houver 81 produtos sendo paginados, a interface de paginação saberá exibir nove números de página na interface do usuário de paginação.

Com a paginação padrão, o Índice de Linha Inicial é calculado como o produto do índice de página e o tamanho da página mais um, enquanto o Máximo de Linhas é simplesmente o tamanho da página. Como a paginação padrão recupera todos os registros do banco de dados ao renderizar qualquer página de dados, o índice de cada linha é conhecido, tornando a mudança para a linha Índice de Linha Inicial uma tarefa trivial. Além disso, a Contagem Total de Registros está prontamente disponível, pois é simplesmente o número de registros na DataTable (ou qualquer objeto que esteja sendo usado para armazenar os resultados do banco de dados).

Dadas as variáveis Índice de Linha Inicial e Máximo de Linhas, uma implementação de paginação personalizada deve retornar apenas o subconjunto preciso de registros começando no Índice de Linha Inicial e até o número máximo de linhas de registros depois disso. A paginação personalizada oferece dois desafios:

  • Devemos ser capazes de associar com eficiência um índice de linha a cada linha em todos os dados que estão sendo paginados para que possamos começar a retornar registros no Índice de Linha Inicial especificado
  • Precisamos fornecer o número total de registros que estão sendo paginados

Nas próximas duas etapas, examinaremos o script SQL necessário para responder a esses dois desafios. Além do script SQL, também precisaremos implementar métodos na DAL e na BLL.

Etapa 2: Retornando o número total de registros que estão sendo paginados

Antes de examinarmos como recuperar o subconjunto preciso de registros para a página que está sendo exibida, vamos primeiro examinar como retornar o número total de registros que estão sendo paginados. Essas informações são necessárias para configurar corretamente a interface do usuário de paginação. O número total de registros retornados por uma consulta SQL específica pode ser obtido usando a COUNT função agregada. Por exemplo, para determinar o número total de registros na Products tabela, podemos usar a seguinte consulta:

SELECT COUNT(*)
FROM Products

Vamos adicionar um método ao nosso DAL que retorna essas informações. Em particular, criaremos um método DAL chamado TotalNumberOfProducts() que executa a SELECT instrução mostrada acima.

Comece abrindo o arquivo Conjunto de Dados Digitado Northwind.xsd App_Code/DAL na pasta. Em seguida, clique com o botão direito do ProductsTableAdapter mouse no Designer e escolha Adicionar Consulta. Como vimos em tutoriais anteriores, isso nos permitirá adicionar um novo método à DAL que, quando invocado, executará uma instrução SQL ou procedimento armazenado específico. Assim como acontece com nossos métodos TableAdapter em tutoriais anteriores, para este, opte por usar uma instrução SQL ad hoc.

Usar uma instrução SQL ad hoc

Figura 1: Usar uma instrução SQL ad hoc

Na próxima tela, podemos especificar que tipo de consulta criar. Como essa consulta retornará um único valor escalar, o número total de registros na Products tabela, escolha a SELECT opção que retorna um valor único.

Configurar a consulta para usar uma instrução SELECT que retorna um único valor

Figura 2: Configurar a consulta para usar uma instrução SELECT que retorna um único valor

Depois de indicar o tipo de consulta a ser usada, devemos especificar a consulta.

Use a consulta SELECT COUNT(*) FROM Products

Figura 3: Usar a consulta SELECT COUNT(*) FROM Products

Por fim, especifique o nome do método. Como mencionado anteriormente, vamos usar TotalNumberOfProducts.

Nomeie o método DAL TotalNumberOfProducts

Figura 4: Nomear o método DAL TotalNumberOfProducts

Depois de clicar em Concluir, o assistente adicionará o TotalNumberOfProducts método ao DAL. Os métodos escalares de retorno na DAL retornam tipos anuláveis, caso o resultado da consulta SQL seja NULL. Nossa COUNT consulta, no entanto, sempre retornará um não-valorNULL ; independentemente disso, o método DAL retorna um número inteiro anulável.

Além do método DAL, também precisamos de um método na BLL. Abra o arquivo de ProductsBLL classe e adicione um TotalNumberOfProducts método que simplesmente chama o método DAL TotalNumberOfProducts :

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

O método DAL retorna TotalNumberOfProducts um inteiro anulável; no entanto, criamos o ProductsBLL método da classe para TotalNumberOfProducts que ele retorne um inteiro padrão. Portanto, precisamos fazer com que o ProductsBLL método da classe retorne TotalNumberOfProducts a parte do valor do inteiro anulável retornado pelo método DAL TotalNumberOfProducts . A chamada para GetValueOrDefault() retorna o valor do inteiro anulável, se ele existir; se o inteiro anulável for null, no entanto, ele retornará o valor inteiro padrão, 0.

Etapa 3: Retornando o subconjunto preciso de registros

Nossa próxima tarefa é criar métodos na DAL e na BLL que aceitem as variáveis Índice de Linha Inicial e Máximo de Linhas discutidas anteriormente e retornem os registros apropriados. Antes de fazermos isso, vamos primeiro examinar o script SQL necessário. O desafio que enfrentamos é que devemos ser capazes de atribuir com eficiência um índice a cada linha em todos os resultados que estão sendo paginados, para que possamos retornar apenas os registros começando no Índice de Linha Inicial (e até o número máximo de registros de registros).

Isso não é um desafio se já houver uma coluna na tabela do banco de dados que sirva como um índice de linha. À primeira vista, podemos pensar que o Products campo da ProductID tabela seria suficiente, já que o primeiro produto tem ProductID de 1, o segundo de 2 e assim por diante. No entanto, a exclusão de um produto deixa uma lacuna na sequência, anulando essa abordagem.

Há duas técnicas gerais usadas para associar com eficiência um índice de linha aos dados a serem percorridos, permitindo assim que o subconjunto preciso de registros seja recuperado:

  • Usando a palavra-chave do SQL Server 2005 nova ROW_NUMBER() no SQL Server 2005, a palavra-chave associa ROW_NUMBER() uma classificação a cada registro retornado com base em alguma ordenação. Essa classificação pode ser usada como um índice de linha para cada linha.

  • O uso de uma variável de tabela e SET ROWCOUNT a instrução do SET ROWCOUNT SQL Server podem ser usados para especificar quantos registros totais uma consulta deve processar antes de encerrar; variáveis de tabela são variáveis T-SQL locais que podem conter dados tabulares, semelhantes a tabelas temporárias. Essa abordagem funciona igualmente bem com o Microsoft SQL Server 2005 e o SQL Server 2000 (enquanto a abordagem só funciona com o ROW_NUMBER() SQL Server 2005).

    A ideia aqui é criar uma variável de tabela que tenha uma IDENTITY coluna e colunas para as chaves primárias da tabela cujos dados estão sendo paginados. Em seguida, o conteúdo da tabela cujos dados estão sendo paginados é despejado na variável de tabela, associando assim um índice de linha sequencial (por meio da IDENTITY coluna) para cada registro na tabela. Depois que a variável de tabela for preenchida, uma SELECT instrução na variável de tabela, unida à tabela subjacente, poderá ser executada para extrair os registros específicos. A SET ROWCOUNT instrução é usada para limitar de forma inteligente o número de registros que precisam ser despejados na variável de tabela.

    A eficiência dessa abordagem é baseada no número da página que está sendo solicitado, pois o SET ROWCOUNT valor é atribuído ao valor de Índice de Linha Inicial mais o Máximo de Linhas. Ao paginar páginas com números baixos, como as primeiras páginas de dados, essa abordagem é muito eficiente. No entanto, ele exibe um desempenho semelhante ao paginação padrão ao recuperar uma página perto do final.

Este tutorial implementa a paginação personalizada usando a ROW_NUMBER() palavra-chave. Para obter mais informações sobre como usar a variável de tabela e SET ROWCOUNT a técnica, consulte Um método mais eficiente para paginação por meio de grandes conjuntos de resultados.

A ROW_NUMBER() palavra-chave associou uma classificação a cada registro retornado em uma ordem específica usando a seguinte sintaxe:

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

ROW_NUMBER() Retorna um valor numérico que especifica a classificação de cada registro em relação à ordem indicada. Por exemplo, para ver a classificação de cada produto, ordenada do mais caro para o menos, poderíamos usar a seguinte consulta:

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

A Figura 5 mostra os resultados dessa consulta quando executada por meio da janela de consulta no Visual Studio. Observe que os produtos são ordenados por preço, juntamente com uma classificação de preço para cada linha.

A classificação de preço está incluída para cada registro retornado

Figura 5: A classificação de preço é incluída para cada registro retornado

Observação

ROW_NUMBER() é apenas uma das muitas novas funções de classificação disponíveis no SQL Server 2005. Para uma discussão mais completa sobre , juntamente com as outras funções de ROW_NUMBER()classificação, leia ROW_NUMBER documentação.

Ao classificar os resultados pela coluna especificada ORDER BY na OVER cláusula (UnitPrice, no exemplo acima), o SQL Server deve classificar os resultados. Essa é uma operação rápida se houver um índice clusterizado sobre a(s) coluna(s) pelas quais os resultados estão sendo ordenados ou se houver um índice de cobertura, mas pode ser mais caro de outra forma. Para ajudar a melhorar o desempenho de consultas suficientemente grandes, considere adicionar um índice não clusterizado para a coluna pela qual os resultados são ordenados. Consulte Funções de classificação e desempenho no SQL Server 2005 para obter uma visão mais detalhada das considerações de desempenho.

As informações de classificação retornadas por ROW_NUMBER() não podem ser usadas diretamente na WHERE cláusula. No entanto, uma tabela derivada pode ser usada para retornar o ROW_NUMBER() resultado, que pode aparecer na WHERE cláusula. Por exemplo, a consulta a seguir usa uma tabela derivada para retornar as colunas ProductName e UnitPrice, juntamente com o ROW_NUMBER() resultado, e usa uma WHERE cláusula para retornar apenas os produtos cuja classificação de preço está entre 11 e 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

Estendendo esse conceito um pouco mais, podemos utilizar essa abordagem para recuperar uma página específica de dados, considerando os valores desejados de Índice de Linha Inicial e Máximo de Linhas:

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>)

Observação

Como veremos mais adiante neste tutorial, o StartRowIndex fornecido pelo ObjectDataSource é indexado a partir de zero, enquanto o ROW_NUMBER() valor retornado pelo SQL Server 2005 é indexado a partir de 1. Portanto, a WHERE cláusula retorna os registros em que PriceRank é estritamente maior e StartRowIndex menor ou igual a StartRowIndex + MaximumRows.

Agora que discutimos como ROW_NUMBER() pode ser usado para recuperar uma página específica de dados, considerando os valores Índice de Linha Inicial e Máximo de Linhas, agora precisamos implementar essa lógica como métodos na DAL e na BLL.

Ao criar esta consulta, devemos decidir a ordem pela qual os resultados serão classificados; Vamos classificar os produtos pelo nome em ordem alfabética. Isso significa que, com a implementação de paginação personalizada neste tutorial, não poderemos criar um relatório paginado personalizado que também possa ser classificado. No próximo tutorial, porém, veremos como essa funcionalidade pode ser fornecida.

Na seção anterior, criamos o método DAL como uma instrução SQL ad-hoc. Infelizmente, o analisador T-SQL no Visual Studio usado pelo assistente TableAdapter não gosta da OVER sintaxe usada pela ROW_NUMBER() função. Portanto, devemos criar esse método DAL como um procedimento armazenado. Selecione o Gerenciador de Servidores no menu Exibir (ou pressione Ctrl+Alt+S) e expanda o NORTHWND.MDF nó. Para adicionar um novo procedimento armazenado, clique com o botão direito do mouse no nó Stored Procedures (Procedimentos armazenados) e escolha Add a New Stored Procedure (consulte a Figura 6).

Adicionar um novo procedimento armazenado para paginação pelos produtos

Figura 6: Adicionar um novo procedimento armazenado para paginação por meio dos produtos

Esse procedimento armazenado deve aceitar dois parâmetros de entrada inteiros - @startRowIndex e usar a ROW_NUMBER() função ordenada pelo ProductName campo, retornando apenas as linhas maiores que as especificadas @startRowIndex e menores ou iguais a @startRowIndex + @maximumRow @maximumRows s. Insira o script a seguir no novo procedimento armazenado e clique no ícone Salvar para adicionar o procedimento armazenado ao banco de dados.

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)

Depois de criar o procedimento armazenado, reserve um momento para testá-lo. Clique com o botão direito do mouse no nome do GetProductsPaged procedimento armazenado no Gerenciador de Servidores e escolha a opção Executar. Em seguida, @startRowIndex o Visual Studio solicitará os parâmetros de entrada e @maximumRow s (consulte a Figura 7). Experimente valores diferentes e examine os resultados.

Insira um valor para a classe <span=@startRowIndex e @maximumRows parâmetros" />

Figura 7: Insira um valor para os @startRowIndex parâmetros e @maximumRows

Depois de escolher esses valores de parâmetros de entrada, a janela Saída mostrará os resultados. A Figura 8 mostra os resultados ao passar 10 para os @startRowIndex parâmetros e @maximumRows .

Os registros que apareceriam na segunda página de dados são retornados

Figura 8: Os registros que apareceriam na segunda página de dados são retornados (clique para exibir a imagem em tamanho real)

Com esse procedimento armazenado criado, estamos prontos para criar o ProductsTableAdapter método. Abra o Northwind.xsd Conjunto de Dados Tipado, clique com o botão direito do ProductsTableAdaptermouse no e escolha a opção Adicionar Consulta. Em vez de criar a consulta usando uma instrução SQL ad hoc, crie-a usando um procedimento armazenado existente.

Criar o método DAL usando um procedimento armazenado existente

Figura 9: Criar o método DAL usando um procedimento armazenado existente

Em seguida, somos solicitados a selecionar o procedimento armazenado a ser invocado. Escolha o GetProductsPaged procedimento armazenado na lista suspensa.

Escolha o procedimento armazenado GetProductsPaged na lista suspensa

Figura 10: Escolher o procedimento armazenado GetProductsPaged na lista suspensa

A próxima tela pergunta que tipo de dados é retornado pelo procedimento armazenado: dados tabulares, um único valor ou nenhum valor. Como o GetProductsPaged procedimento armazenado pode retornar vários registros, indique que ele retorna dados tabulares.

Indique que o procedimento armazenado retorna dados tabulares

Figura 11: Indicar que o procedimento armazenado retorna dados tabulares

Por fim, indique os nomes dos métodos que deseja criar. Assim como em nossos tutoriais anteriores, vá em frente e crie métodos usando Preencher uma DataTable e Retornar uma DataTable. Nomeie o primeiro método FillPaged e o segundo GetProductsPaged.

Nomeie os métodos FillPaged e GetProductsPaged

Figura 12: Nomear os métodos FillPaged e GetProductsPaged

Além de criar um método DAL para retornar uma página específica de produtos, também precisamos fornecer essa funcionalidade na BLL. Assim como o método DAL, o método GetProductsPaged da BLL deve aceitar duas entradas inteiras para especificar o Índice da Linha Inicial e o Máximo de Linhas e deve retornar apenas os registros que se enquadram no intervalo especificado. Crie esse método BLL na ProductsBLL classe que simplesmente chama o método GetProductsPaged da DAL, da seguinte forma:

<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

Você pode usar qualquer nome para os parâmetros de entrada do método BLL, mas, como veremos em breve, optar por usar startRowIndex e maximumRows nos poupa de um pouco mais de trabalho ao configurar um ObjectDataSource para usar esse método.

Etapa 4: Configurando o ObjectDataSource para usar a paginação personalizada

Com os métodos BLL e DAL para acessar um subconjunto específico de registros concluídos, estamos prontos para criar um controle GridView que percorre seus registros subjacentes usando paginação personalizada. Comece abrindo a EfficientPaging.aspx PagingAndSorting página na pasta, adicione um GridView à página e configure-a para usar um novo controle ObjectDataSource. Em nossos tutoriais anteriores, geralmente tínhamos o ObjectDataSource configurado para usar o ProductsBLL método da GetProducts classe. Desta vez, no entanto, queremos usar o GetProductsPaged método, pois o GetProducts método retorna todos os produtos no banco de dados, enquanto GetProductsPaged retorna apenas um subconjunto específico de registros.

Configurar o ObjectDataSource para usar o ProductsBLL Método da GetProductsPaged classe

Figura 13: Configurar o ObjectDataSource para usar o método GetProductsPaged da classe ProductsBLL

Como estamos criando um GridView somente leitura, reserve um momento para definir a lista suspensa de método nas guias INSERT, UPDATE e DELETE como (Nenhum).

Em seguida, o assistente ObjectDataSource nos solicita as fontes dos valores do GetProductsPaged método e startRowIndex maximumRows dos parâmetros de entrada. Esses parâmetros de entrada serão definidos pelo GridView automaticamente, portanto, basta deixar a origem definida como Nenhum e clicar em Concluir.

Deixe as fontes de parâmetro de entrada como nenhuma

Figura 14: Deixar as fontes de parâmetro de entrada como nenhuma

Depois de concluir o assistente ObjectDataSource, o GridView conterá um BoundField ou CheckBoxField para cada um dos campos de dados do produto. Sinta-se à vontade para personalizar a aparência do GridView como achar melhor. Optei por exibir apenas os ProductNameCategoryName, , , SupplierNameQuantityPerUnit, e UnitPrice BoundFields. Além disso, configure o GridView para dar suporte à paginação marcando a caixa de seleção Habilitar Paginação em sua marca inteligente. Após essas alterações, a marcação declarativa GridView e ObjectDataSource deve ser semelhante à seguinte:

<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>

Se você visitar a página por meio de um navegador, no entanto, o GridView não será encontrado.

O GridView não é exibido

Figura 15: O GridView não é exibido

O GridView está ausente porque o ObjectDataSource está usando 0 como os valores para os parâmetros de GetProductsPaged startRowIndex entrada e maximumRows . Portanto, a consulta SQL resultante não está retornando registros e, portanto, o GridView não é exibido.

Para remediar isso, precisamos configurar o ObjectDataSource para usar a paginação personalizada. Isso pode ser feito nas seguintes etapas:

  1. Defina a propriedade ObjectDataSource EnablePaging como true isso indica ao ObjectDataSource que ele deve passar para os SelectMethod dois parâmetros adicionais: um para especificar o Índice de Linha Inicial (StartRowIndexParameterName) e outro para especificar o Máximo de Linhas (MaximumRowsParameterName).
  2. Definir o ObjectDataSource e as propriedades Da mesma forma, as propriedades e MaximumRowsParameterName indicam os nomes dos parâmetros de entrada passados MaximumRowsParameterName StartRowIndexParameterName para fins SelectMethod de paginação StartRowIndexParameterName personalizada. Por padrão, esses nomes de parâmetros são startIndexRow e maximumRows, e é por isso que, ao criar o GetProductsPaged método na BLL, usei esses valores para os parâmetros de entrada. Se você optar por usar nomes de parâmetro diferentes para o método da GetProductsPaged BLL, como startIndex e maxRows, por exemplo, você precisará definir o ObjectDataSource s StartRowIndexParameterName e MaximumRowsParameterName as propriedades adequadamente (como startIndex para StartRowIndexParameterName e maxRows para MaximumRowsParameterName).
  3. Defina a propriedade ObjectDataSource como SelectCountMethod o nome do método que retorna o número total de registros que estão sendo paginados (TotalNumberOfProducts) lembre-se de que o ProductsBLL método da classe retorna TotalNumberOfProducts o número total de registros que estão sendo paginados usando um método DAL que executa uma SELECT COUNT(*) FROM Products consulta. Essas informações são necessárias para o ObjectDataSource para renderizar corretamente a interface de paginação.
  4. Remova os startRowIndex elementos e maximumRows<asp:Parameter> da marcação declarativa do ObjectDataSource ao configurar o ObjectDataSource por meio do assistente, o Visual Studio adicionou automaticamente dois <asp:Parameter> elementos para os parâmetros de entrada do GetProductsPaged método. Ao definir EnablePaging como true, esses parâmetros serão passados automaticamente; se eles também aparecerem na sintaxe declarativa, o ObjectDataSource tentará passar quatro parâmetros para o GetProductsPaged método e dois parâmetros para o TotalNumberOfProducts método. Se você esquecer de remover esses <asp:Parameter> elementos, ao visitar a página por meio de um navegador, receberá uma mensagem de erro como: ObjectDataSource 'ObjectDataSource1' não foi possível encontrar um método não genérico 'TotalNumberOfProducts' que tenha parâmetros: startRowIndex, maximumRows.

Depois de fazer essas alterações, a sintaxe declarativa do ObjectDataSource deve ser semelhante à seguinte:

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

Observe que as EnablePaging propriedades e SelectCountMethod foram definidas e os <asp:Parameter> elementos foram removidos. A Figura 16 mostra uma captura de tela da janela Properties depois que essas mudanças forem feitas.

Para usar a paginação personalizada, configure o controle ObjectDataSource

Figura 16: Para usar a paginação personalizada, configure o controle ObjectDataSource

Depois de fazer essas alterações, visite esta página por meio de um navegador. Você deve ver 10 produtos listados, ordenados em ordem alfabética. Reserve um momento para percorrer os dados, uma página de cada vez. Embora não haja diferença visual da perspectiva do usuário final entre a paginação padrão e a paginação personalizada, a paginação personalizada é mais eficiente para percorrer grandes quantidades de dados, pois recupera apenas os registros que precisam ser exibidos para uma determinada página.

Os dados, ordenados pelo nome do produto, são paginados usando paginação personalizada

Figura 17: Os dados, ordenados pelo nome do produto, são paginados usando paginação personalizada (clique para exibir a imagem em tamanho real)

Observação

Com a paginação personalizada, o valor da contagem de SelectCountMethod páginas retornado pelo ObjectDataSource é armazenado no estado de exibição do GridView. Outras variáveis GridView, a PageIndexcoleção , EditIndex, DataKeys SelectedIndex, e assim por diante, são armazenadas no estado de controle, que é mantido independentemente do valor da propriedade do GridViewEnableViewState. Como o PageCount valor é mantido em postbacks usando o estado de exibição, ao usar uma interface de paginação que inclui um link para levá-lo à última página, é imperativo que o estado de exibição do GridView seja habilitado. (Se sua interface de paginação não incluir um link direto para a última página, você poderá desativar o estado de exibição.)

Clicar no link da última página causa um postback e instrui o GridView a atualizar sua PageIndex propriedade. Se o último link de página for clicado, o GridView atribuirá sua PageIndex propriedade a um valor um a menos que sua PageCount propriedade. Com o estado de exibição desabilitado, o PageCount valor é perdido entre postbacks e o PageIndex recebe o valor inteiro máximo. Em seguida, o GridView tenta determinar o índice da linha inicial multiplicando as PageSize propriedades e PageCount . Isso resulta em uma OverflowException vez que o produto excede o tamanho inteiro máximo permitido.

Implementar paginação e classificação personalizadas

Nossa implementação de paginação personalizada atual exige que a ordem pela qual os dados são paginados seja especificada estaticamente ao criar o GetProductsPaged procedimento armazenado. No entanto, você pode ter observado que a marca inteligente do GridView contém uma caixa de seleção Habilitar Classificação, além da opção Habilitar Paginação. Infelizmente, adicionar suporte de classificação ao GridView com nossa implementação de paginação personalizada atual classificará apenas os registros na página de dados exibida no momento. Por exemplo, se você configurar o GridView para também dar suporte à paginação e, ao exibir a primeira página de dados, classificar por nome de produto em ordem decrescente, ele inverterá a ordem dos produtos na página 1. Como mostra a Figura 18, isso mostra os Tigres de Carnarvon como o primeiro produto ao classificar em ordem alfabética inversa, o que ignora os outros 71 produtos que vêm depois dos Tigres de Carnarvon, em ordem alfabética; Somente os registros na primeira página são considerados na classificação.

Somente os dados mostrados na página atual são classificados

Figura 18: Somente os dados mostrados na página atual são classificados (clique para exibir a imagem em tamanho completo)

A classificação só se aplica à página atual de dados porque a classificação está ocorrendo depois que os dados foram recuperados do método da GetProductsPaged BLL e esse método retorna apenas esses registros para a página específica. Para implementar a classificação corretamente, precisamos passar a expressão sort para o GetProductsPaged método para que os dados possam ser classificados adequadamente antes de retornar a página específica de dados. Veremos como fazer isso em nosso próximo tutorial.

Implementando paginação e exclusão personalizadas

Se você habilitar a funcionalidade de exclusão em um GridView cujos dados são paginados usando técnicas de paginação personalizadas, descobrirá que, ao excluir o último registro da última página, o GridView desaparece em vez de diminuir adequadamente o GridView PageIndex. Para reproduzir esse bug, habilite a exclusão para o tutorial que acabamos de criar. Vá para a última página (página 9), onde você deve ver um único produto, pois estamos folheando 81 produtos, 10 produtos por vez. Exclua este produto.

Ao excluir o último produto, o GridView deve ir automaticamente para a oitava página, e essa funcionalidade é exibida com paginação padrão. Com a paginação personalizada, no entanto, depois de excluir o último produto na última página, o GridView simplesmente desaparece completamente da tela. O motivo exato pelo qual isso acontece está um pouco além do escopo deste tutorial; consulte Excluindo o último registro na última página de um GridView com paginação personalizada para obter os detalhes de baixo nível sobre a origem desse problema. Em resumo, é devido à seguinte sequência de etapas que são executadas pelo GridView quando o botão Excluir é clicado:

  1. Excluir o registro
  2. Obtenha os registros apropriados a serem exibidos para o especificado PageIndex e PageSize
  3. Verifique se o PageIndex não excede o número de páginas de dados na fonte de dados; se isso acontecer, diminua automaticamente a propriedade do GridView PageIndex
  4. Associe a página apropriada de dados ao GridView usando os registros obtidos na Etapa 2

O problema decorre do fato de que na Etapa 2 o PageIndex usado ao pegar os registros a serem exibidos ainda é o PageIndex da última página cujo único registro acabou de ser excluído. Portanto, na Etapa 2, nenhum registro é retornado, pois essa última página de dados não contém mais nenhum registro. Em seguida, na Etapa 3, o GridView percebe que sua PageIndex propriedade é maior que o número total de páginas na fonte de dados (já que excluímos o último registro na última página) e, portanto, diminui sua PageIndex propriedade. Na Etapa 4, o GridView tenta se associar aos dados recuperados na Etapa 2; no entanto, na Etapa 2, nenhum registro foi retornado, resultando em um GridView vazio. Com a paginação padrão, esse problema não surge porque, na Etapa 2 , todos os registros são recuperados da fonte de dados.

Para corrigir isso, temos duas opções. A primeira é criar um manipulador de eventos para o manipulador de eventos do RowDeleted GridView que determina quantos registros foram exibidos na página que acabou de ser excluída. Se houvesse apenas um registro, o registro que acabou de ser excluído deve ter sido o último e precisamos diminuir o GridView s PageIndex. Obviamente, queremos atualizar apenas se a operação de exclusão foi realmente bem-sucedida, o PageIndex que pode ser determinado garantindo que a e.Exception propriedade seja null.

Essa abordagem funciona porque atualiza a etapa 1, mas antes da PageIndex etapa 2. Portanto, na Etapa 2, o conjunto apropriado de registros é retornado. Para fazer isso, use um código como o seguinte:

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

Uma solução alternativa é criar um manipulador de eventos para o evento ObjectDataSource RowDeleted e definir a AffectedRows propriedade como um valor de 1. Depois de excluir o registro na Etapa 1 (mas antes de recuperar novamente os dados na Etapa 2), o GridView atualiza sua PageIndex propriedade se uma ou mais linhas foram afetadas pela operação. No entanto, a AffectedRows propriedade não é definida pelo ObjectDataSource e, portanto, essa etapa é omitida. Uma maneira de executar essa etapa é definir manualmente a AffectedRows propriedade se a operação de exclusão for concluída com êxito. Isso pode ser feito usando um código como o seguinte:

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

O código para ambos os manipuladores de eventos pode ser encontrado na classe code-behind do EfficientPaging.aspx exemplo.

Comparando o desempenho da paginação padrão e personalizada

Como a paginação personalizada recupera apenas os registros necessários, enquanto a paginação padrão retorna todos os registros de cada página que está sendo exibida, fica claro que a paginação personalizada é mais eficiente do que a paginação padrão. Mas quão mais eficiente é a paginação personalizada? Que tipo de ganhos de desempenho podem ser vistos ao passar da paginação padrão para a paginação personalizada?

Infelizmente, não há uma resposta única aqui. O ganho de desempenho depende de vários fatores, sendo os dois mais proeminentes o número de registros que estão sendo paginados e a carga colocada no servidor de banco de dados e nos canais de comunicação entre o servidor Web e o servidor de banco de dados. Para tabelas pequenas com apenas algumas dezenas de registros, a diferença de desempenho pode ser insignificante. No entanto, para tabelas grandes, com milhares a centenas de milhares de linhas, a diferença de desempenho é aguda.

Um artigo meu, "Paginação personalizada no ASP.NET 2.0 com SQL Server 2005", contém alguns testes de desempenho que executei para exibir as diferenças de desempenho entre essas duas técnicas de paginação ao paginar por meio de uma tabela de banco de dados com 50.000 registros. Nesses testes, examinei o tempo para executar a consulta no nível do SQL Server (usando o SQL Profiler) e na página ASP.NET usando ASP.NET recursos de rastreamento. Lembre-se de que esses testes foram executados na minha caixa de desenvolvimento com um único usuário ativo e, portanto, não são científicos e não imitam os padrões típicos de carregamento do site. Independentemente disso, os resultados ilustram as diferenças relativas no tempo de execução para paginação padrão e personalizada ao trabalhar com quantidades suficientemente grandes de dados.

Duração média (seg) Reads
Paginação padrão do SQL Profiler 1.411 383
Criador de perfil SQL de paginação personalizado 0,002 29
Paginação padrão ASP.NET rastreamento 2.379 N/A
Rastreamento de ASP.NET de paginação personalizado 0.029 N/A

Como você pode ver, a recuperação de uma determinada página de dados exigiu 354 leituras a menos em média e concluídas em uma fração do tempo. Na página ASP.NET, a página personalizada foi capaz de renderizar em cerca de 1/100 do tempo que levou ao usar a paginação padrão.

Resumo

A paginação padrão é fácil de implementar, basta marcar a caixa de seleção Habilitar Paginação na marca inteligente do controle Web de dados, mas essa simplicidade tem o custo do desempenho. Com a paginação padrão, quando um usuário solicita qualquer página de dados , todos os registros são retornados, mesmo que apenas uma pequena fração deles possa ser mostrada. Para combater essa sobrecarga de desempenho, o ObjectDataSource oferece uma opção de paginação alternativa de paginação personalizada.

Embora a paginação personalizada melhore os problemas de desempenho da paginação padrão, recuperando apenas os registros que precisam ser exibidos, ela é mais complicada para implementar a paginação personalizada. Primeiro, uma consulta deve ser escrita que acesse corretamente (e com eficiência) o subconjunto específico de registros solicitados. Isso pode ser feito de várias maneiras; o que examinamos neste tutorial é usar a nova ROW_NUMBER() função do SQL Server 2005 para classificar os resultados e, em seguida, retornar apenas os resultados cuja classificação está dentro de um intervalo especificado. Além disso, precisamos adicionar um meio para determinar o número total de registros que estão sendo paginados. Depois de criar esses métodos DAL e BLL, também precisamos configurar o ObjectDataSource para que ele possa determinar quantos registros totais estão sendo paginados e possa passar corretamente os valores Índice da Linha Inicial e Máximo de Linhas para a BLL.

Embora a implementação da paginação personalizada exija várias etapas e não seja tão simples quanto a paginação padrão, a paginação personalizada é uma necessidade ao paginar quantidades suficientemente grandes de dados. Como os resultados examinados mostraram, a paginação personalizada pode reduzir segundos do tempo de renderização da página ASP.NET e pode aliviar a carga no servidor de banco de dados em uma ou mais ordens de magnitude.

Boa programação!

Sobre o autor

Scott Mitchell, autor de sete livros ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Web da Microsoft desde 1998. Scott trabalha como consultor, instrutor e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.