Compartilhar via


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

por Scott Mitchell

Baixar PDF

A opção de paginação padrão de um controle de apresentação de dados é inadequada ao trabalhar com grandes quantidades de dados, pois seu controle de fonte de dados subjacente recupera todos os registros, embora 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 uma das duas maneiras:

  • A paginação padrão pode ser implementada simplesmente verificando a opção Habilitar Paginação na marca inteligente do controle web de dados; no entanto, sempre que uma página de dados é exibida, 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 dos 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 ingênua na recuperação de todos os registros, no entanto, torna-a uma escolha implausível ao paginar por meio de quantidades suficientemente grandes de dados ou para sites com muitos usuários simultâneos. Nessas circunstâncias, devemos recorrer à paginação personalizada para fornecer um sistema responsivo.

O desafio da paginação personalizada é poder escrever uma consulta que retorna o conjunto preciso de registros necessários para uma página de dados específica. Felizmente, o Microsoft SQL Server 2005 fornece uma nova palavra-chave para os resultados da classificação, o que nos permite escrever uma consulta que possa recuperar com eficiência o subconjunto adequado de registros. Neste tutorial, veremos como usar esse novo SQL Server 2005 palavra-chave para implementar a paginação personalizada em um controle GridView. Embora a interface do usuário para paginação personalizada seja idêntica à paginação padrão, passar de uma página para outra usando paginação personalizada pode ser várias ordens de magnitude mais rápidas 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, examinaremos algumas métricas aproximadas que mostram os benefícios no desempenho obtidos por meio da paginação personalizada.

Etapa 1: Noções básicas sobre o processo de paginação personalizado

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 queríamos percorrer os 81 produtos, exibindo 10 produtos por página. Ao exibir a primeira página, queremos produtos de 1 a 10; ao exibir a segunda página, estaríamos interessados em produtos de 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:

  • Inicie o Índice de Linhas o índice da primeira linha na página de dados a ser exibida; esse índice pode ser calculado multiplicando o índice de página pelos registros a serem exibidos por página e adicionando um. Por exemplo, ao paginar os registros 10 de cada 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 de 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 é conhecida como 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, porém, 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 paginado. 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 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 para cada linha é conhecido, tornando, assim, mover para a linha Iniciar Índice de Linha 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 manter os resultados do banco de dados).

Considerando as variáveis Índice de Linha Inicial e Linhas Máximas, uma implementação de paginação personalizada só deve retornar 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 paginado

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 na BLL.

Etapa 2: retornando o número total de registros que estão sendo paginado

Antes de examinarmos como recuperar o subconjunto preciso de registros da 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 abrindo o Northwind.xsd arquivo Typed DataSet na App_Code/DAL pasta . Em seguida, clique com o ProductsTableAdapter botão direito do mouse no 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. 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 escolherá o que retorna uma opção SELECT de valor de singe.

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.

Usar 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 acima, 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 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 diferenteNULL ; independentemente disso, o método DAL retorna um 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 para baixo o método da DAL:TotalNumberOfProducts

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

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

Etapa 3: retornando o subconjunto preciso de registros

Nossa próxima tarefa é criar métodos no DAL e na BLL que aceitam as variáveis Índice de Linha Inicial e Linhas Máximas discutidas anteriormente e retornar os registros apropriados. Antes de fazermos isso, vamos primeiro examinar o script SQL necessário. O desafio enfrentado é 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 esses registros começando no Índice de Linha inicial (e até o número máximo de registros de registros).

Isso não será um desafio se já houver uma coluna na tabela de banco de dados que serve 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 1, o segundo a 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 com eficiência um índice de linha aos dados a serem paginados, permitindo assim que o subconjunto preciso de registros seja recuperado:

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

  • Usando uma variável de tabela e SET ROWCOUNT SQL Server instrução sSET ROWCOUNT 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 SQL Server 2000 (enquanto a ROW_NUMBER() abordagem só funciona com 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 paginado. 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 inteligentemente o número de registros que precisam ser despejados na variável de tabela.

    A eficiência dessa abordagem baseia-se no número de página que está sendo solicitado, pois o SET ROWCOUNT valor é atribuído ao valor de Índice de Linha inicial mais as Linhas Máximas. Ao paginar por páginas de baixa numeração, como as primeiras páginas de dados, essa abordagem é muito eficiente. No entanto, ele exibe o desempenho padrão semelhante à paginação ao recuperar uma página próxima ao final.

Este tutorial implementa a paginação personalizada usando o ROW_NUMBER() palavra-chave. Para obter mais informações sobre como usar a variável de tabela e SET ROWCOUNT a técnica, confira Paginação eficiente por meio de grandes quantidades de dados.

O ROW_NUMBER() palavra-chave associou uma classificação a cada registro retornado em uma ordenação 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 para cada registro em relação à ordenação indicada. Por exemplo, para ver a classificação de cada produto, ordenada do mais caro para o mínimo, 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 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 é 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 obter uma discussão mais detalhada sobre ROW_NUMBER(), juntamente com as outras funções de classificação, leia Retornando resultados classificados com o Microsoft SQL Server 2005.

Ao classificar os resultados pela coluna especificada ORDER BY na OVER cláusula (UnitPriceno exemplo acima), SQL Server deve classificar os resultados. Essa é uma operação rápida se houver um índice clusterizado sobre as colunas pelas quais 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 clusterizado 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 um pouco mais esse conceito, podemos utilizar essa abordagem para recuperar uma página específica de dados, considerando os valores desejados índice de linha inicial e linhas máximas:

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 posteriormente neste tutorial, o StartRowIndex fornecido pelo ObjectDataSource é indexado a partir de zero, enquanto o ROW_NUMBER() valor retornado por SQL Server 2005 é indexado a partir de 1. Portanto, a WHERE cláusula retorna esses registros em PriceRank que é estritamente maior que StartRowIndex e menor ou igual a + StartRowIndexMaximumRows .

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 Linhas Máximas, agora precisamos implementar essa lógica como métodos na DAL e na BLL.

Ao criar essa consulta, devemos decidir a ordenação pela qual os resultados serão classificados; vamos 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 do que também pode 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 servidor Explorer 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ó Procedimentos Armazenados e escolha Adicionar um Novo Procedimento Armazenado (consulte a Figura 6).

Adicionar um novo procedimento armazenado para paginação por meio dos 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 s.@maximumRows 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, tire um momento para testá-lo. Clique com o botão direito do mouse no nome do GetProductsPaged procedimento armazenado no servidor Explorer 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 o <span class=@startRowIndex and @maximumRows Parameters" />

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 Digitado, 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 de Drop-Down

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

Em seguida, a tela seguinte 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.

Indicar 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 você deseja criar. Assim como acontece com nossos tutoriais anteriores, vá em frente e crie métodos usando o Fill a DataTable e o 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 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 de inteiro para especificar o Índice de Linha Inicial e As Linhas Máximas e deve retornar apenas os registros que se enquadram no intervalo especificado. Crie esse método BLL na classe ProductsBLL que simplesmente chama para baixo o método GetProductsPaged da DAL, da seguinte maneira:

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

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 salvar de um bit extra de trabalho ao configurar um ObjectDataSource para usar esse método.

Etapa 4: Configurando o ObjectDataSource para usar 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 página na PagingAndSorting 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 ProductsBLL método da classe s GetProducts . 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 método GetProductsPaged da classe ProductsBLL

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

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

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

Deixe as fontes de parâmetro de entrada como Nenhuma

Figura 14: deixe 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. Fique à vontade para adaptar a aparência do GridView conforme você achar melhor. Optei por exibir apenas os ProductNameCampos Limitados , CategoryName, SupplierName, QuantityPerUnite UnitPrice . 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>

No entanto, se você visitar a página por meio de um navegador, 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á atualmente usando 0 como os valores dos parâmetros de GetProductsPagedstartRowIndex entrada e maximumRows . Portanto, a consulta SQL resultante não retorna registros 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. Defina a propriedade trueObjectDataSource como EnablePaging essa indica para ObjectDataSource que ela deve passar para os SelectMethod dois parâmetros adicionais: um para especificar o Índice de Linha inicial (StartRowIndexParameterName) e outro para especificar as Linhas Máximas (MaximumRowsParameterName).
  2. Defina as propriedades ObjectDataSource e StartRowIndexParameterNameMaximumRowsParameterName De acordo com as StartRowIndexParameterName propriedades e MaximumRowsParameterName indicam os nomes dos parâmetros de entrada passados para o SelectMethod para fins de paginação personalizados. Por padrão, esses nomes de parâmetro 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âmetros diferentes para o método S da GetProductsPaged BLL, como startIndex e maxRows, por exemplo, você precisará definir as propriedades e MaximumRowsParameterName objectDataSource StartRowIndexParameterName 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 de classe s TotalNumberOfProducts retorna 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 pelo ObjectDataSource para renderizar corretamente a interface de paginação.
  4. Remova os startRowIndex elementos e maximumRows<asp:Parameter> da marcação declarativa ObjectDataSource ao configurar ObjectDataSource por meio do assistente, o Visual Studio adicionou automaticamente dois <asp:Parameter> elementos para os GetProductsPaged parâmetros de entrada do método. Ao definir EnablePaging como true, esses parâmetros serão passados automaticamente; se eles também aparecerem na sintaxe declarativa, 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}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPaged" 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 do janela Propriedades após essas alterações terem sido 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 paginação padrão e 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 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 de contagem de páginas retornado pelo ObjectDataSource é SelectCountMethod armazenado no estado de exibição do GridView. Outras variáveis GridView , PageIndex, SelectedIndexEditIndex, DataKeys coleção e assim por diante são armazenadas no estado de controle, que é persistente independentemente do valor da propriedade gridViewEnableViewState. Como o PageCount valor é persistente entre postbacks usando o estado de exibição, ao usar uma interface de paginação que inclui um link para levá-lo para a última página, é imperativo que o estado de exibição do GridView seja habilitado. (Se a interface de paginação não incluir um link direto para a última página, você poderá desabilitar 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 link da última página for clicado, o GridView atribuirá sua PageIndex propriedade a um valor menor 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 de linha inicial multiplicando as PageSize propriedades e PageCount . Isso resulta em um, uma OverflowException vez que o produto excede o tamanho máximo permitido do inteiro.

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, talvez você tenha observado que a marca inteligente 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 do produto em ordem decrescente, ele reverterá 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 dos Tigres Carnarvon, em ordem alfabética; somente esses 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 real)

A classificação só se aplica à página atual de dados porque a classificação está ocorrendo depois que os dados são recuperados do método S 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 de classificação para o GetProductsPaged método para que os dados possam ser classificados adequadamente antes de retornar a página de dados específica. Veremos como fazer isso em nosso próximo tutorial.

Implementando paginação e exclusão personalizadas

Se você habilitar a exclusão de funcionalidade em um GridView cujos dados são paginados usando técnicas de paginação personalizadas, você descobrirá que, ao excluir o último registro da última página, o GridView desaparecerá 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 paginando 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. No entanto, com a paginação personalizada, 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 detalhes de baixo nível sobre a origem desse problema. Em resumo, isso ocorre devido à seguinte sequência de etapas executadas pelo GridView quando o botão Excluir é clicado:

  1. Excluir o registro
  2. Obter 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, decremente automaticamente a propriedade GridView PageIndex
  4. Associar a página de dados apropriada 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 a última página de dados não contém mais registros. 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, portanto, 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 RowDeleted que determina quantos registros foram exibidos na página que acabou de ser excluída. Se houvesse apenas um registro, o registro excluído deve ter sido o último e precisamos diminuir o GridView.PageIndex É claro que só queremos atualizar o PageIndex se a operação de exclusão tiver sido realmente bem-sucedida, o que pode ser determinado garantindo que a e.Exception propriedade seja null.

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

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

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 atualizará sua PageIndex propriedade se uma ou mais linhas forem 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 código como o seguinte:

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

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 para cada página que está sendo exibida, é 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 passando da paginação padrão para a paginação personalizada?

Infelizmente, não há um tamanho que se encaixe em todas as respostas 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 dúzias 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 é 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 uma tabela de banco de dados com 50.000 registros. Nesses testes, examinei o tempo para executar a consulta no nível 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 de carga típicos 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 (s) Reads
Paginação padrão do SQL Profiler 1.411 383
Paginação personalizada do SQL Profiler 0.002 29
Rastreamento de ASP.NET de paginação padrão 2.379 N/A
Rastreamento de ASP.NET de Paginação Personalizada 0.029 N/A

Como você pode ver, a recuperação de uma página específica 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 necessário ao usar a paginação padrão.

Resumo

A paginação padrão é um cinch para implementar apenas marcar caixa de seleção Habilitar Paginação na marca inteligente do controle web de dados, mas essa simplicidade vem ao 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, embora 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 melhore os problemas de desempenho da paginação padrão recuperando apenas os registros que precisam ser exibidos, é mais envolvido implementar a paginação personalizada. Primeiro, uma consulta deve ser gravada que acesse corretamente (e com eficiência) o subconjunto específico dos registros solicitados. Isso pode ser feito de várias maneiras; a que examinamos neste tutorial é usar SQL Server nova ROW_NUMBER() função do 2005 para classificar os resultados e, em seguida, retornar apenas os resultados cuja classificação se enquadra em um intervalo especificado. Além disso, precisamos adicionar um meio para determinar o número total de registros que estão sendo paginado. 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 pode passar corretamente os valores Índice de Linha Inicial e Linhas Máximas 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 em 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 um minério a mais pedidos de magnitude.

Programação feliz!

Sobre o autor

Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. 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.