Partilhar via


Paginação eficiente de grandes quantidades de dados no Visual Basic

por Scott Mitchell

Descarregar 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 Ativar paginação na marca inteligente do controle da Web de dados; no entanto, sempre que visualizar 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 solicitados 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 pouco sofisticada na recuperação de todos os registos, no entanto, torna isto uma escolha implausível ao percorrer grandes volumes de dados ou para sites com numerosos utilizadores em simultâneo. Nessas 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 retorna o conjunto preciso de registros necessários para uma determinada página de dados. Felizmente, o Microsoft SQL Server 2005 fornece uma nova palavra-chave para resultados de classificação, 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 outra usando a paginação personalizada pode ser várias ordens de magnitude mais rápido 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 no desempenho obtidos por meio da paginação personalizada.

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

Ao paginar os 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 queríamos navegar pelos 81 produtos, exibindo 10 produtos por página. Ao visualizar a primeira página, queremos produtos de 1 a 10; Ao visualizar a segunda página, estaríamos interessados nos produtos de 11 a 20, e assim por diante.

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

  • Start Row Index o índice da primeira linha na página de dados a serem exibidos; Esse índice pode ser calculado multiplicando o índice de página pelos registros a serem exibidos por página e adicionando uma. Por exemplo, ao paginar registros 10 de cada vez, para a primeira página (cujo índice de página é 0), o Índice da 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 registos a apresentar por página. Essa variável é chamada de linhas máximas, já que para a última página pode haver menos registros retornados do que o tamanho da página. Por exemplo, ao navegar pelos 81 produtos com 10 registos por página, a nona e última página terá apenas um registo. Nenhuma página, no entanto, mostrará mais registros do que o valor do Número Máximo de Linhas.
  • Contagem Total de Registos o número total de registos que estão a ser paginados. Embora essa variável não seja necessária para determinar quais registros recuperar para uma determinada página, ela dita a interface de paginação. Por exemplo, se houver 81 produtos a serem paginados, a interface de paginação saberá exibir nove números de página.

Com a paginação padrão, o Índice da Linha Inicial é calculado como o produto do índice da página pelo 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 registos do banco de dados ao renderizar qualquer página de dados, o índice de cada linha é conhecido, tornando a mudança para a linha com Índice de Início 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 Número Máximo de Linhas, uma implementação de paginação personalizada deve apenas retornar o subconjunto exato de registros começando no Índice de Linha Inicial, até ao Número Máximo de Linhas de registros após isso. A paginação personalizada oferece dois desafios:

  • Devemos ser capazes de associar eficientemente um índice de linha a cada linha no conjunto completo de dados que está a ser paginado, para podermos começar a devolver os registos no Índice de Linha Inicial especificado.
  • Precisamos fornecer o número total de registos que estão a ser 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 no DAL e 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 de agregação. 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 por abrir o ficheiro Northwind.xsd Typed DataSet na pasta App_Code/DAL. Em seguida, clique com o botão direito do rato no ProductsTableAdapter no Designer e escolha Adicionar consulta. Como vimos em tutoriais anteriores, isso nos permitirá adicionar um novo método ao DAL que, quando invocado, executará uma instrução SQL específica ou um procedimento armazenado. Como 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 esta consulta retornará um único valor escalar, que é o número total de registros na tabela Products, escolha a opção SELECT 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 usado, devemos especificar a consulta em seguida.

Use a opção SELECT COUNT(*) da Consulta Produtos

Figura 3: Use a opção SELECT COUNT(*) da consulta Produtos

Finalmente, especifique o nome para o método. Como mencionado acima, vamos usar TotalNumberOfProducts.

Nomeie o método DAL TotalNumberOfProducts

Figura 4: Nomeie o método DAL TotalNumberOfProducts

Depois de clicar em Concluir, o assistente adicionará o TotalNumberOfProducts método ao DAL. Os métodos de retorno escalar no DAL retornam tipos anuláveis, caso o resultado da consulta SQL seja NULL. Nossa COUNT consulta, no entanto, sempre retornará um valor que não seja NULL; independentemente disso, o método DAL retorna um inteiro que pode ser nulo.

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

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

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

Etapa 3: Retornando o subconjunto preciso de registros

Nossa próxima tarefa é criar métodos na DAL e BLL que aceitem as variáveis Start Row Index e Maximum Rows discutidas anteriormente e retornem os registros apropriados. Antes de fazermos isso, vamos primeiro examinar o script SQL necessário. O desafio que enfrentamos é sermos capazes de atribuir eficientemente um índice a cada linha dos resultados em paginação, para que possamos retornar apenas esses registros começando no Índice da Linha Inicial (e até o número máximo 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 campo Products da tabela seria suficiente, pois o primeiro produto tem ProductID de 1, o segundo de 2, e assim por diante. No entanto, excluir um produto deixa uma lacuna na sequência, anulando essa abordagem.

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

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

  • Usando uma variável de tabela e SET ROWCOUNT A instrução do SET ROWCOUNT SQL Server pode ser usada 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 ROW_NUMBER() só funciona com o 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 (através da IDENTITY coluna) para cada registro na tabela. Uma vez que a variável de tabela tenha sido preenchida, uma SELECT instrução na variável de tabela, unida à tabela subjacente, pode 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 inseridos na variável de tabela.

    A eficiência dessa abordagem é baseada no número de página que está sendo solicitado, pois ao SET ROWCOUNT valor é atribuído o valor de Índice da Linha Inicial mais as Linhas Máximas. Esta abordagem é muito eficiente ao folhear páginas com números baixos, como as primeiras páginas de dados. No entanto, ele exibe um desempenho padrão semelhante à paginaçã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 a técnica SET ROWCOUNT, consulte Um método mais eficiente para paginar grandes conjuntos de resultados.

A ROW_NUMBER() palavra-chave associava 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() Devolve um valor numérico que especifica a classificação de cada registo no que diz respeito à ordenação indicada. Por exemplo, para ver a classificação de cada produto, encomendado do mais caro para o menos, podemos 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 pela 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 está 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 aprofundada de ROW_NUMBER(), juntamente com as outras funções de classificação, leia a documentação do ROW_NUMBER.

Ao classificar os resultados pela coluna especificada ORDER BY na OVER cláusula (UnitPrice, no exemplo acima), o SQL Server deve classificar os resultados. Esta é uma operação rápida se houver um índice agrupado na(s) coluna(s) pela qual os resultados estão sendo ordenados, ou se houver um índice de cobertura, mas pode ser mais caro caso contrário. Para ajudar a melhorar o desempenho de consultas suficientemente grandes, considere adicionar um índice não agrupado para a coluna pela qual os resultados são ordenados. Consulte Classificação de funções 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, dados 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 onde 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, dadas as Start Row Index e Maximum Rows, precisamos implementar essa lógica como métodos no DAL e BLL.

Ao criar esta consulta, devemos decidir a ordem pela qual os resultados serão classificados; deixe s classificar os produtos por seu 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 usado pelo assistente TableAdapter não aprecia a sintaxe OVER usada pela função ROW_NUMBER() no Visual Studio. Portanto, devemos criar esse método DAL como um procedimento armazenado. Selecione o Explorador de Servidores no menu Ver (ou pressione Ctrl+Alt+S) e expanda o nó NORTHWND.MDF. Para adicionar um novo procedimento armazenado, clique com o botão direito do mouse no nó Stored Procedures e escolha Add a New Stored Procedure (consulte a Figura 6).

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

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

Este procedimento armazenado deve aceitar dois parâmetros de entrada inteiros - @startRowIndex e @maximumRows usar a ROW_NUMBER() função ordenada pelo ProductName campo, retornando apenas as linhas maiores que o especificado @startRowIndex e menores ou iguais a @startRowIndex + @maximumRow s. Insira o seguinte script 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. O Visual Studio irá então solicitar-lhe os parâmetros de entrada, @startRowIndex e @maximumRows (consulte a Figura 7). Experimente valores diferentes e examine os resultados.

Insira um valor para o <span class= @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 o valor 10 para os parâmetros @startRowIndex 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 visualizar a imagem em tamanho real)

Com este procedimento armazenado criado, estamos prontos para criar o ProductsTableAdapter método. Abra o Northwind.xsd Typed DataSet, clique com o botão direito em ProductsTableAdapter 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 de Drop-Down

Figura 10: Escolha o procedimento armazenado GetProductsPaged na lista de Drop-Down

A próxima tela pergunta que tipo de dados são retornados 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

Finalmente, indique os nomes dos métodos que você deseja ter criado. Como em nossos tutoriais anteriores, vá em frente e crie métodos usando Fill a DataTable e Return a DataTable. Nomeie o primeiro método FillPaged e o segundo GetProductsPaged.

Nomeie os métodos FillPaged e GetProductsPaged

Figura 12: Nomeie os métodos FillPaged e GetProductsPaged

Além de criar um método DAL para retornar uma determinada página de produtos, também precisamos fornecer essa funcionalidade na BLL. Como o método DAL, o método GetProductsPaged da BLL deve aceitar duas entradas inteiras para especificar o Índice da Linha Inicial e as Linhas Máximas e deve retornar apenas os registros que estão dentro do intervalo especificado. Crie um método BLL na classe ProductsBLL que simplesmente chama para o método GetProductsPaged da DAL, como segue:

<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 salva de um pouco extra 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.aspxPagingAndSorting página na pasta, adicione um GridView à página e configure-o para usar um novo controle ObjectDataSource. Em nossos tutoriais anteriores, muitas vezes tínhamos o ObjectDataSource configurado para usar o método s de ProductsBLLGetProducts classe. Desta vez, no entanto, queremos usar o GetProductsPaged método em vez disso, uma vez que 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 método GetProductsPaged da classe ProductsBLL

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

Como estamos a criar um GridView somente leitura, dedique um momento para definir o menu de opções do método nas abas INSERT, UPDATE e DELETE como (Nenhum).

Em seguida, o assistente ObjectDataSource solicita-nos as fontes dos valores dos parâmetros de entrada para o método GetProductsPaged e para startRowIndexmaximumRows. Esses parâmetros de entrada serão definidos pelo GridView automaticamente, então basta deixar a fonte definida como Nenhum e clicar em Concluir.

Deixe as fontes de parâmetros de entrada como nenhuma

Figura 14: Deixe as fontes de parâmetros 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 ProductName, CategoryName, SupplierName, QuantityPerUnit, e UnitPrice BoundFields. Além disso, configure o GridView para oferecer suporte à paginação, assinalando a caixa de seleção "Ativar paginação" no seu tag 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 visitar a página por intermédio de um navegador, no entanto, o GridView não se encontra em lugar nenhum.

O GridView não é exibido

Figura 15: O GridView não é exibido

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

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

  1. EnablePaging
  2. Defina as propriedades StartRowIndexParameterName e MaximumRowsParameterName de ObjectDataSource de acordo com as propriedades StartRowIndexParameterName e MaximumRowsParameterName que indicam os nomes dos parâmetros de entrada passados para o SelectMethod para fins de paginação 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 optar por usar nomes de parâmetros diferentes para o método do BLL, como GetProductsPaged e startIndex, por exemplo, precisará definir as propriedades maxRows e StartRowIndexParameterName do ObjectDataSource de forma correspondente (por exemplo, startIndex para MaximumRowsParameterName e maxRows para StartRowIndexParameterName).
  3. Defina a propriedade do ObjectDataSource SelectCountMethod como o nome do método que retorna o número total de registos que estão a ser paginados por (TotalNumberOfProducts) Lembre-se de que o método ProductsBLL da classe TotalNumberOfProducts retorna o número total de registos que estão a ser paginados usando um método DAL que executa uma consulta SELECT COUNT(*) FROM Products. Essas informações são necessárias para o ObjectDataSource para renderizar corretamente a interface de paginação.
  4. Remova os startRowIndex e maximumRows<asp:Parameter> elementos da marcação declarativa de 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 através de um navegador, você receberá uma mensagem de erro como: ObjectDataSource 'ObjectDataSource1' não conseguiu encontrar um método não genérico 'TotalNumberOfProducts' que tenha parâmetros: startRowIndex, maximumRows.

Depois de fazer essas alterações, a sintaxe declarativa de ObjectDataSource deve ter a seguinte aparência:

<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 Propriedades depois que essas alterações foram 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 através de um navegador. Você deve ver 10 produtos listados, ordenados alfabeticamente. Reserve um momento para percorrer os dados, uma página de cada vez. Embora não haja diferença visual da perspetiva do usuário final entre a paginação padrão e a paginação personalizada, a paginação personalizada é mais eficiente por meio de 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 visualizar a imagem em tamanho real)

Observação

Com a paginação personalizada, o valor da contagem de páginas retornado pelo ObjectDataSource SelectCountMethod é armazenado no estado de exibição do GridView. Outras variáveis do GridView, como , , , e coleções similares, são armazenadas no estado de controle, que é mantido independentemente do valor da propriedade do GridView. Uma vez que o valor PageCount é mantido persistente em postbacks através do estado de exibição, ao usar uma interface de paginação que inclui um link para levá-lo à última página, é essencial que o estado de exibição do GridView esteja ativado. (Se a 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 a sua PageIndex propriedade. Se o link da última página for clicado, o GridView atribuirá à sua PageIndex propriedade um valor um a menos que a sua PageCount propriedade. Com o estado de exibição desativado, o valor PageCount é perdido entre os postbacks e o valor PageIndex é atribuído 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 requer que a ordem pela qual os dados são paginados seja especificada estaticamente ao criar o GetProductsPaged procedimento armazenado. No entanto, pode ter notado que o smart tag 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 visualizada no momento. Por exemplo, se você configurar o GridView para também oferecer suporte à paginação e, ao exibir a primeira página de dados, classificar por nome do produto em ordem decrescente, ele inverterá a ordem dos produtos na página 1. Como mostra a Figura 18, tal mostra Carnarvon Tigers como o primeiro produto ao classificar em ordem alfabética inversa, o que ignora os outros 71 produtos que vêm depois de Carnarvon Tigers, alfabeticamente; apenas os registos da 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 visualizar a imagem em tamanho real)

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 BLL s GetProductsPaged , e esse método só retorna esses registros para a página específica. Para implementar a classificação corretamente, precisamos passar a expressão de classificação 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 personalizada e exclusão

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 s 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, já que estamos paginando 81 produtos, 10 produtos de cada vez. Elimine este produto.

Ao excluir o último produto, o GridView deve ir automaticamente para a oitava página, e tal funcionalidade é exibida com a paginação padrão. Com a paginação personalizada, no entanto, depois de excluir esse último produto na última página, o GridView simplesmente desaparece da tela completamente. A razão precisa pela 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 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 registos apropriados a serem exibidos para os especificados 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 de PageIndex GridView
  4. Vincule 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 para exibir 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 registros. Em seguida, na Etapa 3, o GridView percebe que sua PageIndex propriedade é maior do 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 vincular 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 aparece 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 GridView que RowDeleted determina quantos registros foram exibidos na página que acabou de ser excluída. Se houve apenas um registro, então o registro que acabou de ser excluído deve ter sido o último e precisamos diminuir o GridView s PageIndex. Claro, só queremos atualizar o PageIndex se a operação de exclusão foi realmente bem-sucedida, o que pode ser determinado garantindo que a propriedade e.Exception seja null.

Essa abordagem funciona porque atualiza após 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 RowDeleted do ObjectDataSource e definir a propriedade AffectedRows como valor 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, esta 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 esses 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 para cada página que está sendo visualizada, 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 uma série de fatores, sendo os dois mais proeminentes o número de registros sendo paginados e a carga colocada no servidor de banco de dados e 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. Para tabelas grandes, com milhares a centenas de milhares de linhas, porém, a diferença de desempenho é significativa.

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 os recursos de rastreamento do ASP.NET. Tenha em mente 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 padrões típicos de carregamento de sites. 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) Leituras
Profiler SQL de Paginação Padrão 1.411 383
Profiler SQL Personalizado de Paginação 0.002 29
Rastreamento padrão de paginação do ASP.NET 2.379 N/A
Paginação Personalizada Rastreamento ASP.NET 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, uma página personalizada foi capaz de ser renderizada em cerca de 1/100 avos do tempo que demorou ao usar a paginação padrão.

Resumo

A paginação padrão é fácil de implementar; basta marcar a caixa de seleção Activar Paginação na tag inteligente do controlo de Web de dados, mas essa simplicidade tem um custo de 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 alternativa de paginação personalizada.

Embora a paginação personalizada resolva os problemas de desempenho da paginação padrão ao recuperar apenas os registros que precisam ser exibidos, a implementação da paginação personalizada é mais complexa. Primeiro, deve ser escrita uma consulta que acesse corretamente (e de forma eficiente) o subconjunto específico de registros solicitados. Isto pode ser conseguido 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 Start Row Index e Maximum Rows para o 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 através de quantidades suficientemente grandes de dados. Como os resultados examinados mostraram, a paginação personalizada pode perder 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.

Feliz Programação!

Sobre o Autor

Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.