Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La opción de paginación predeterminada de un control de presentación de datos no es adecuada al trabajar con grandes cantidades de datos, ya que su control de origen de datos subyacente recupera todos los registros, aunque solo se muestre un subconjunto de datos. En tales circunstancias, se debe recurrir a la paginación personalizada.
Introducción
Como se ha explicado en el tutorial anterior, la paginación se puede implementar de una de estas dos maneras:
- La paginación predeterminada se puede implementar simplemente comprobando la opción Habilitar paginación en la etiqueta inteligente del control web de datos; sin embargo, siempre que vea una página de datos, ObjectDataSource recupera todos los registros, aunque solo se muestre un subconjunto de ellos en la página.
- La paginación personalizada mejora el rendimiento de la paginación predeterminada recuperando solo los registros de la base de datos que deben mostrarse para la página concreta de los datos solicitados por el usuario; sin embargo, la paginación personalizada implica un poco más de esfuerzo para implementar que la paginación predeterminada
Debido a la facilidad de implementación solo tiene que marcar una casilla y listo. la paginación predeterminada es una opción atractiva. Pero su enfoque sencillo para recuperar todos los registros, lo convierte en una opción poco plausible al paginar por cantidades grandes de datos o para sitios con muchos usuarios simultáneos. En esas circunstancias, debe recurrir a la paginación personalizada para proporcionar un sistema con capacidad de respuesta.
El desafío de la paginación personalizada es poder escribir una consulta que devuelva el conjunto preciso de registros necesarios para una página determinada de datos. Afortunadamente, Microsoft SQL Server 2005 proporciona una nueva palabra clave para clasificar resultados, lo que permite escribir una consulta que pueda recuperar eficazmente el subconjunto adecuado de registros. En este tutorial verá cómo usar esta nueva palabra clave de SQL Server 2005 para implementar la paginación personalizada en un control GridView. Aunque la interfaz de usuario para la paginación personalizada es idéntica a la de la paginación predeterminada, pasar de una página a la siguiente mediante paginación personalizada puede más rápido que la paginación predeterminada.
Nota:
La ganancia exacta de rendimiento mostrada por la paginación personalizada depende del número total de registros que se paginan y de la carga que se coloca en el servidor de bases de datos. Al final de este tutorial verá algunas métricas aproximadas que muestran las ventajas en el rendimiento obtenidos a través de la paginación personalizada.
Paso 1: Descripción del proceso de paginación personalizado
Al paginar por datos, los registros precisos mostrados en una página dependen de la página de datos que se solicita y del número de registros mostrados por página. Por ejemplo, imagine que quiere paginar por los 81 productos y mostrar 10 productos por página. Al ver la primera página, quiere los productos de 1 a 10; al ver la segunda página, los productos de 11 a 20, etc.
Hay tres variables que dictan qué registros deben recuperarse y cómo se debe representar la interfaz de paginación:
- Índice de fila de inicio el índice de la primera fila de la página de datos para mostrar; este índice se puede calcular multiplicando el índice de página por los registros para mostrar por página y sumando uno. Por ejemplo, al paginar los registros de 10 en 10, para la primera página (cuyo índice de página es 0), el índice de la fila inicial es 0 * 10 + 1, o 1; para la segunda página (cuyo índice de página es 1), el índice de la fila inicial es 1 * 10 + 1, o 11.
- Número máximo de filas el número máximo de registros que se van a mostrar por página. Esta variable se conoce como filas máximas, ya que para la última página puede haber menos registros devueltos que el tamaño de página. Por ejemplo, al paginar por los 81 productos, 10 registros por página, la novena y última página tendrá un solo registro. Pero ninguna página mostrará más registros que el valor Máximo de filas.
- Recuento total de registros del total de registros paginados. Aunque esta variable no es necesaria para determinar qué registros se van a recuperar para una página determinada, determina la interfaz de paginación. Por ejemplo, si se paginan 81 productos, la interfaz de paginación sabe que debe mostrar nueve números de página en la interfaz de usuario de paginación.
Con la paginación predeterminada, el índice de la fila inicial se calcula como el producto del índice de página y el tamaño de página más uno, mientras que el tamaño máximo de filas es simplemente el tamaño de página. Como la paginación predeterminada recupera todos los registros de la base de datos al representar cualquier página de datos, se conoce el índice de cada fila, por lo que pasar a la fila de Índice de la fila inicial es una tarea trivial. Además, el recuento total de registros está fácilmente disponible, ya que es simplemente el número de registros de DataTable (o del objeto que se use para guardar los resultados de la base de datos).
Dadas las variables Índice de la fila inicial y Número máximo de filas, una implementación de paginación personalizada solo debe devolver el subconjunto preciso de registros que comienzan en el índice de la fila inicial hasta el número máximo de filas de registros posteriores. La paginación personalizada proporciona dos desafíos:
- Debe poder asociar de forma eficaz un índice de fila a cada fila de todos los datos que se paginan para poder empezar a devolver registros en el índice de fila inicial especificado
- Es necesario proporcionar el número total de registros que se paginan
En los dos pasos siguientes se examinará el script SQL necesario para responder a estos dos desafíos. Además del script SQL, también es necesario implementar métodos en DAL y BLL.
Paso 2: Devolución del número total de registros paginados
Antes de examinar cómo recuperar el subconjunto preciso de registros de la página que se muestra, primero verá cómo devolver el número total de registros que se paginan. Esta información es necesaria para configurar correctamente la interfaz de usuario de paginación. El número total de registros devueltos por una consulta SQL determinada se puede obtener mediante la COUNT
función de agregado. Por ejemplo, para determinar el número total de registros de la tabla Products
, se puede utilizar la siguiente consulta:
SELECT COUNT(*)
FROM Products
Ahora se agregará un método a la DAL que devuelve esta información. En concreto, se crearás un método de DAL llamado TotalNumberOfProducts()
que ejecute la instrucción SELECT
mostrada anteriormente.
Para empezar, abra el archivo Northwind.xsd
de DataSet con tipo en la carpeta App_Code/DAL
. A continuación, haga clic con el botón derecho en ProductsTableAdapter
del Diseñador y seleccione Agregar consulta. Como ha visto en los tutoriales anteriores, esto nos permitirá agregar un nuevo método a la DAL que, cuando se invoca, ejecutará una instrucción SQL determinada o un procedimiento almacenado. Al igual que con los métodos TableAdapter de los tutoriales anteriores, para este opte por usar una instrucción SQL ad hoc.
Figura 1: Usar una instrucción SQL ad hoc
En la siguiente pantalla, puede especificar qué tipo de consulta se va a crear. Como esta consulta devolverá un único valor escalar, el número total de registros de la tabla Products
, elija la opción SELECT
, que devuelve un único valor.
Figura 2: Configurar la consulta para usar una instrucción SELECT que devuelve un valor único
Después de indicar el tipo de consulta que se va a usar, debe especificar la consulta.
Figura 3: Utilizar la consulta SELECT COUNT(*) FROM Products
Por último, especifique el nombre del método. Como se ha mencionado antes, se usará TotalNumberOfProducts
.
Figura 4: Nombre del método DAL TotalNumberOfProducts
Tras hacer clic en Finalizar, el asistente añadirá el método TotalNumberOfProducts
a la DAL. Los métodos de devolución escalares de la DAL devuelven tipos que aceptan valores NULL, en caso de que el resultado de la consulta SQL sea NULL
. Pero la consulta COUNT
siempre devolverá un valor distinto de NULL
; independientemente de ello, el método DAL devuelve un entero que admite un valor NULL.
Además del método la DAL, también necesita un método en la BLL. Abra el archivo de clase ProductsBLL
y agregue un método TotalNumberOfProducts
que simplemente llame al método TotalNumberOfProducts
de la DAL:
public int TotalNumberOfProducts()
{
return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}
El método TotalNumberOfProducts
de la DAL devuelve un entero que admite un valor NULL; pero se ha creado el método ProductsBLL
de la clase TotalNumberOfProducts
para que devuelva un entero estándar. Por tanto, necesita que el método ProductsBLL
de la clase TotalNumberOfProducts
devuelva la parte de valor del entero que admite un valor NULL devuelto por el método TotalNumberOfProducts
de la DAL. La llamada a GetValueOrDefault()
devuelve el valor del entero que admite un valor NULL, si existe; pero, si el entero que admite un valor NULL es null
, devuelve el valor entero predeterminado, 0.
Paso 3: Devolución del subconjunto preciso de registros
La siguiente tarea consiste en crear métodos en la DAL y BLL que acepten las variables Índice de la fila inicial y Número máximo de filas que se trataron anteriormente y devuelven los registros adecuados. Antes de hacerlo, primero verá el script SQL necesario. El desafío que se presenta es que debe poder asignar eficazmente un índice a cada fila de los resultados que se paginan por completo para poder devolver solo esos registros a partir del índice de fila inicial (y hasta el número máximo de registros).
Esto no es un desafío si ya hay una columna en la tabla de base de datos que actúa como índice de fila. A primera vista podría pensar que el campo Products
de la tabla ProductID
sería suficiente, ya que el primer producto tiene ProductID
de 1, el segundo un 2, y así sucesivamente. Pero la eliminación de un producto deja un hueco en la secuencia, lo que anula este enfoque.
Hay dos técnicas generales que se usan para asociar eficazmente un índice de fila con los datos a la página, lo que permite recuperar el subconjunto preciso de registros:
Uso de la palabra clave
ROW_NUMBER()
en SQL Server 2005 que es nueva en SQL Server 2005, la palabra claveROW_NUMBER()
asigna una clasificación a cada registro devuelto en función de algún orden. Esta clasificación se puede usar como índice de fila para cada fila.Usar una variable de tabla y
SET ROWCOUNT
Se puede usar la instrucción deSET ROWCOUNT
SQL Server para especificar el número total de registros que debe procesar una consulta antes de finalizar; las variables de tabla son variables T-SQL locales que pueden contener datos tabulares, como tablas temporales. Este enfoque funciona igualmente bien con Microsoft SQL Server 2005 y SQL Server 2000 (mientras que el enfoqueROW_NUMBER()
solo funciona con SQL Server 2005).La idea aquí es crear una variable de tabla que tenga una columna
IDENTITY
y columnas para las claves principales de la tabla cuyos datos se paginan. A continuación, el contenido de la tabla cuyos datos se están paginando se vuelca en la variable de tabla, y se asocia un índice de fila secuencial (mediante la columnaIDENTITY
) para cada registro de la tabla. Una vez que se rellena la variable de tabla, se puede ejecutar una instrucciónSELECT
sobre la variable de tabla, unida a la tabla subyacente, para extraer los registros concretos. La instrucciónSET ROWCOUNT
se utiliza para limitar de forma inteligente el número de registros que deben volcarse en la variable de tabla.La eficacia de este enfoque se basa en el número de página que se solicita, ya que al valor
SET ROWCOUNT
se le asigna el valor del Índice de la fila inicial más las filas máximas. Al paginar por páginas con números bajos, como las primeras páginas de datos, este enfoque es muy eficaz. Pero muestra un rendimiento similar al de la predeterminada paginación al recuperar una página cerca del final.
En este tutorial se implementa la paginación personalizada mediante la palabra clave ROW_NUMBER()
. Para obtener más información sobre el uso de la variable de tabla y la técnica SET ROWCOUNT
, consulte Navegación eficaz por páginas a través de grandes cantidades de datos.
La palabra clave ROW_NUMBER()
asocia una clasificación a cada registro devuelto sobre una ordenación determinada utilizando la siguiente sintaxis:
SELECT columnList,
ROW_NUMBER() OVER(orderByClause)
FROM TableName
ROW_NUMBER()
devuelve un valor numérico que especifica la clasificación de cada registro con respecto a la ordenación indicada. Por ejemplo, para ver la clasificación de cada producto, ordenado del más caro al menos caro, se podría utilizar la siguiente consulta:
SELECT ProductName, UnitPrice,
ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products
En la figura 5 se muestran los resultados de esta consulta cuando se ejecuta desde la ventana de consulta en Visual Studio. Tenga en cuenta que los productos se ordenan por precio, junto con una clasificación de precios para cada fila.
Figura 5: La clasificación de precios se incluye para cada registro devuelto
Nota:
ROW_NUMBER()
es solo una de las muchas nuevas funciones de clasificación disponibles en SQL Server 2005. Para obtener una explicación más exhaustiva de , junto con las otras funciones de ROW_NUMBER()
clasificación, lea Devolver resultados clasificados con Microsoft SQL Server 2005.
Al clasificar por orden de prioridad los resultados por la columna ORDER BY
especificada en la cláusula OVER
(UnitPrice
, en el ejemplo anterior), SQL Server debe ordenar los resultados. Se trata de una operación rápida si hay un índice agrupado sobre las columnas por las que se ordenan los resultados, o si hay un índice de cobertura, pero puede ser más costoso. Para ayudar a mejorar el rendimiento de las consultas suficientemente grandes, considere la posibilidad de agregar un índice no agrupado para la columna por la que se ordenan los resultados. Consulte Clasificación de funciones y rendimiento en SQL Server 2005 para obtener un vistazo más detallado a las consideraciones de rendimiento.
La información de clasificación devuelta por ROW_NUMBER()
no puede utilizarse directamente en la cláusula WHERE
. Pero se puede utilizar una tabla derivada para devolver el resultado ROW_NUMBER()
, que luego puede aparecer en la cláusula WHERE
. Por ejemplo, la siguiente consulta utiliza una tabla derivada para devolver las columnas ProductName y UnitPrice, junto con el resultado ROW_NUMBER()
, y después utiliza una cláusula WHERE
para devolver únicamente aquellos productos cuyo rango de precios se encuentre entre 11 y 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
Al ampliar este concepto un poco más, se puede usar este enfoque para recuperar una página específica de datos según los valores deseados de Índice de la fila inicial y Número máximo de filas:
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>)
Nota:
Como verá más adelante en este tutorial, el valor StartRowIndex
suministrado por el ObjectDataSource está indexado empezando por cero, mientras que el valor ROW_NUMBER()
devuelto por SQL Server 2005 está indexado empezando por 1. Por tanto, la cláusula WHERE
devuelve aquellos registros en los que PriceRank
es estrictamente mayor que StartRowIndex
y menor o igual que StartRowIndex
+ MaximumRows
.
Ahora que ha visto cómo se puede utilizar ROW_NUMBER()
para recuperar una página concreta de datos teniendo en cuenta los valores Índice de la fila inicial y Número máximo de filas, hay que implementar esta lógica como métodos en DAL y BLL.
Al crear esta consulta, debe decidir la ordenación por la que se clasificarán los resultados; los productos se ordenarán por su nombre en orden alfabético. Esto significa que con la implementación de paginación personalizada en este tutorial no podrá crear un informe paginado personalizado que también se pueda ordenar. Pero en el siguiente tutorial verá cómo se puede proporcionar esa funcionalidad.
En la sección anterior ha creado el método de la DAL como una instrucción SQL ad hoc. Desafortunadamente, al analizador T-SQL de Visual Studio utilizado por el asistente para TableAdapter no le gusta la sintaxis OVER
utilizada por la función ROW_NUMBER()
. Por tanto, debe crear este método de la DAL como un procedimiento almacenado. Seleccione el Explorador de servidores en el menú Ver (o presiones Ctrl+Alt+S) y expanda el nodo NORTHWND.MDF
. Para agregar un nuevo procedimiento almacenado, haga clic con el botón derecho en el nodo Procedimientos almacenados y elija Agregar un nuevo procedimiento almacenado (vea la figura 6).
Figura 6: Agregar un nuevo procedimiento almacenado para la paginación de productos
Este procedimiento almacenado debe aceptar dos parámetros de entrada enteros, @startRowIndex
y @maximumRows
, y utilizar la función ROW_NUMBER()
ordenada por el campo ProductName
, devolviendo solo aquellas filas mayores que el valor @startRowIndex
especificado y menores o iguales que @startRowIndex
+ @maximumRow
. Escriba el siguiente script en el nuevo procedimiento almacenado y, después, haga clic en el icono Guardar para agregarlo a la base de datos.
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)
Después de crear el procedimiento almacenado, dedique un momento a probarlo. Haga clic con el botón derecho en el nombre del procedimiento almacenado GetProductsPaged
en el Explorador de servidores y elija la opción Ejecutar. Visual Studio le pedirá los parámetros de entrada, @startRowIndex
y @maximumRow
(véase la figura 7). Pruebe valores diferentes y examine los resultados.
@startRowIndex y @maximumRows Parameters" />
Figura 7: Escribir un valor para los @startRowIndex parámetros y @maximumRows
Después de elegir estos valores de parámetros de entrada, la ventana Salida mostrará los resultados. En la figura 8 se muestran los resultados al pasar 10 para los parámetros @startRowIndex
y @maximumRows
.
Figura 8: Los registros que aparecerían en la segunda página de datos se devuelven (haga clic para ver la imagen de tamaño completo)
Con este procedimiento almacenado creado, ya puede crear el método ProductsTableAdapter
. Abra el conjunto de datos con tipo Northwind.xsd
, haga clic con el botón derecho del ratón en ProductsTableAdapter
y elija la opción Agregar consulta. En lugar de crear la consulta mediante una instrucción SQL ad hoc, créela mediante un procedimiento almacenado existente.
Figura 9: Crear el método DAL mediante un procedimiento almacenado existente
A continuación, se le pedirá que seleccione el procedimiento almacenado que se va a invocar. Elija el procedimiento almacenado GetProductsPaged
en la lista desplegable.
Figura 10: Seleccionar el procedimiento almacenado GetProductsPaged de la lista Drop-Down
A continuación, la siguiente pantalla le pregunta qué tipo de datos devuelve el procedimiento almacenado: datos tabulares, un valor único o ningún valor. Como el procedimiento almacenado GetProductsPaged
puede devolver varios registros, indique que devuelve datos tabulares.
Figura 11: Indicar que el procedimiento almacenado devuelve datos tabulares
Por último, indique los nombres de los métodos que quiera crear. Al igual que con los tutoriales anteriores, continúe y cree métodos mediante Fill a DataTable y Return a DataTable. Asigne el nombre FillPaged
al primer método y GetProductsPaged
al segundo.
Figura 12: Asignar un nombre a los métodos FillPaged y GetProductsPaged
Además de crear un método de la DAL para devolver una página determinada de productos, también es necesario proporcionar esa funcionalidad en la BLL. Al igual que el método DAL, el método GetProductsPaged para la BLL debe aceptar dos entradas enteras para especificar el índice de fila inicial y las filas máximas, y debe devolver solo los registros que se encuentran dentro del intervalo especificado. Cree un método de la BLL de este tipo en la clase ProductsBLL que simplemente llame al método GetProductsPaged de la DAL, de la siguiente manera:
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}
Puede usar cualquier nombre para los parámetros de entrada del método de la BLL, pero, como verá en breve, al elegir startRowIndex
y maximumRows
se ahorra un poco de trabajo extra a la hora de configurar una instancia de ObjectDataSource para usar este método.
Paso 4: Configuración de ObjectDataSource para usar la paginación personalizada
Con los métodos de la BLL y DAL para acceder a un subconjunto determinado de registros completados, ya puede crear un control GridView que pagine por sus registros subyacentes mediante la paginación personalizada. Para empezar, abra la página EfficientPaging.aspx
en la carpeta PagingAndSorting
, añada un control GridView a la página y configúrelo para que utilice un nuevo control ObjectDataSource. En los tutoriales anteriores, a menudo se configuraba ObjectDataSource para utilizar el método ProductsBLL
de la clase GetProducts
. Sin embargo, esta vez queremos usar el GetProductsPaged
método en su lugar, ya que el GetProducts
método devuelve todos los productos de la base de datos, mientras que GetProductsPaged
devuelve solo un subconjunto determinado de registros.
Figura 13: Configurar objectDataSource para usar el método GetProductsPaged de la clase ProductsBLL
Como se va crear un control GridView de solo lectura, tómese un momento para establecer la lista desplegable de métodos en las pestañas INSERT, UPDATE y DELETE en (None).
A continuación, el asistente para ObjectDataSource solicita los orígenes de los valores de los parámetros de entrada GetProductsPaged
y startRowIndex
del método maximumRows
. En realidad, estos parámetros de entrada los establece el control GridView automáticamente, así que simplemente deje el origen establecido en Ninguno y haga clic en Finalizar.
Figura 14: Dejar los orígenes de parámetros de entrada como Ninguno
Después de completar el asistente para ObjectDataSource, GridView contendrá un control BoundField o CheckBoxField para cada uno de los campos de datos del producto. No dude en adaptar la apariencia de GridView como prefiera. Aquí se ha optado por mostrar solo las instancias ProductName
, CategoryName
, SupplierName
, QuantityPerUnit
y UnitPrice
de BoundField. Además, configure GridView para admitir la paginación; para ello, active la casilla Habilitar paginación en su etiqueta inteligente. Tras estos cambios, el marcado declarativo de GridView y ObjectDataSource debería tener un aspecto similar al siguiente:
<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>
Pero si visita la página en un explorador, no encontrará el control GridView.
Figura 15: GridView no se muestra
Falta GridView porque ObjectDataSource utiliza actualmente 0 como valor para los dos parámetros de entrada GetProductsPaged
, startRowIndex
y maximumRows
. Por tanto, la consulta SQL resultante no devuelve ningún registro y, por tanto, no se muestra GridView.
Para solucionar esto, es necesario configurar ObjectDataSource para usar la paginación personalizada. Esto se puede realizar en los pasos siguientes:
-
Establezca la propiedad
EnablePaging
ObjectDataSource entrue
esto indica al ObjectDataSource que debe pasar a losSelectMethod
dos parámetros adicionales: uno para especificar el índice de fila de inicio (StartRowIndexParameterName
) y otro para especificar las filas máximas (MaximumRowsParameterName
). -
Establezca las propiedades
StartRowIndexParameterName
yMaximumRowsParameterName
de ObjectDataSource en consecuencia, las propiedadesStartRowIndexParameterName
yMaximumRowsParameterName
indican los nombres de los parámetros de entrada pasados alSelectMethod
para fines de paginación personalizada. De forma predeterminada, estos nombres de parámetros sonstartIndexRow
ymaximumRows
, por lo que, al crear el métodoGetProductsPaged
en la BLL, se han usado estos valores para los parámetros de entrada. Si decidiera usar otros nombres de parámetro para el métodoGetProductsPaged
de BLL, comostartIndex
ymaxRows
, por ejemplo, tendría que configurar las propiedadesStartRowIndexParameterName
yMaximumRowsParameterName
de ObjectDataSource en consecuencia (como startIndex paraStartRowIndexParameterName
y maxRows paraMaximumRowsParameterName
). -
ProductsBLL
ObjectDataSource necesita esta información para representar correctamente la interfaz de paginación. -
Elimine los elementos
startRowIndex
ymaximumRows
<asp:Parameter>
del marcado declarativo de ObjectDataSource cuando configure el ObjectDataSource a través del asistente, Visual Studio agregó automáticamente dos<asp:Parameter>
elementos para los parámetros de entrada delGetProductsPaged
método. Al establecerEnablePaging
entrue
, estos parámetros se pasarán automáticamente; si también aparecen en la sintaxis declarativa, ObjectDataSource intentará pasar cuatro parámetros al métodoGetProductsPaged
y dos parámetros al métodoTotalNumberOfProducts
. Si olvida quitar estos<asp:Parameter>
elementos, al visitar la página a través de un explorador obtendrá un mensaje de error como: ObjectDataSource 'ObjectDataSource1' no pudo encontrar un método no genérico 'TotalNumberOfProducts' que tenga parámetros: startRowIndex, maximumRows.
Después de realizar estos cambios, la sintaxis declarativa de ObjectDataSource debe ser similar a la siguiente:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
SelectMethod="GetProductsPaged" EnablePaging="True"
SelectCountMethod="TotalNumberOfProducts">
</asp:ObjectDataSource>
Observe que se han establecido las propiedades EnablePaging
y SelectCountMethod
, y se han eliminado los elementos <asp:Parameter>
. En la figura 16 se muestra una captura de pantalla de la ventana Propiedades después de realizar estos cambios.
Figura 16: Para usar la paginación personalizada, configure el control ObjectDataSource
Después de realizar estos cambios, visite esta página mediante un explorador. Debería ver 10 productos en la lista, ordenados alfabéticamente. Dedique un momento a recorrer los datos de una página a la vez. Aunque no hay ninguna diferencia visual desde la perspectiva del usuario final entre la paginación predeterminada y la paginación personalizada, la paginación personalizada es más eficaz en las páginas con grandes cantidades de datos, ya que solo recupera los registros que deben mostrarse para una página determinada.
Figura 17: Los datos ordenados por el nombre del producto se paginan mediante paginación personalizada (haga clic para ver la imagen de tamaño completo)
Nota:
Con la paginación personalizada, el valor del recuento de páginas devuelto por SelectCountMethod
de ObjectDataSource se almacena en el estado de visualización del GridView. Otras variables de GridView, la colección PageIndex
, EditIndex
, SelectedIndex
, DataKeys
, etc., se almacenan en el estado de control, que se conserva independientemente del valor de la propiedad de GridView EnableViewState
. Como el valor PageCount
se conserva entre postbacks utilizando el estado de visualización, cuando utilice una interfaz de paginación que incluya un enlace que le lleve a la última página, es imprescindible que el estado de visualización del control GridView esté activado. (Si la interfaz de paginación no incluye un vínculo directo a la última página, puede deshabilitar el estado de visualización).
Al hacer clic en el enlace de la última página se produce un postback y se ordena al GridView que actualice su propiedad PageIndex
. Si se hace clic en el enlace de la última página, el control GridView asigna a su propiedad PageIndex
un valor una unidad menos que el de su propiedad PageCount
. Con el estado de visualización desactivado, el valor PageCount
se pierde entre postbacks y a PageIndex
se le asigna en su lugar el valor entero máximo. A continuación, el control GridView intenta determinar el índice de la fila inicial multiplicando las propiedades PageSize
y PageCount
. Esto da como resultadoOverflowException
ya que el producto excede el tamaño entero máximo permitido.
Implementación de paginación y ordenación personalizadas
La implementación de paginación personalizada actual requiere que el orden de paginación de los datos se especifique estáticamente al crear el procedimiento almacenado GetProductsPaged
. Pero es posible que haya observado que la etiqueta inteligente de GridView contiene una casilla Habilitar ordenación además de la opción Habilitar paginación. Lamentablemente, si se agrega compatibilidad con la ordenación al control GridView con la implementación de paginación personalizada actual, solo se ordenarán los registros de la página de datos visualizada en ese momento. Por ejemplo, si configura GridView para admitir también la paginación y, después, al ver la primera página de datos, ordena por nombre de producto en orden descendente, revertirá el orden de los productos en la página 1. Como se muestra en la figura 18, Carnarvon Tigers es el primer producto cuando se ordena en orden alfabético inverso, lo que ignora los otros 71 productos que vienen después de Carnarvon Tigers, alfabéticamente; en la ordenación solo se tienen en cuenta los registros de la primera página.
Figura 18: Solo se ordenan los datos que se muestran en la página actual (haga clic para ver la imagen de tamaño completo).
La ordenación solo se aplica a la página actual de datos porque se produce después de que los datos se hayan recuperado del método GetProductsPaged
de la BLL, y este método solo devuelve los registros de la página específica. Para implementar la ordenación correctamente, es necesario pasar la expresión de ordenación al método GetProductsPaged
para que los datos puedan ordenarse adecuadamente antes de devolver la página específica de datos. Verá cómo hacerlo en el siguiente tutorial.
Implementación de paginación y eliminación personalizadas
Si habilita la funcionalidad de eliminación en un control GridView cuyos datos se paginan utilizando técnicas de paginación personalizadas, comprobará que al borrar el último registro de la última página, GridView desaparece en lugar de disminuir apropiadamente el valor PageIndex
. Para reproducir este error, habilite la eliminación para el tutorial que acaba de crear. Vaya a la última página (página 9), donde debería ver un solo producto, ya que se pagina por 81 productos, 10 productos a la vez. Elimine este producto.
Al eliminar el último producto, GridView debería ir automáticamente a la octava página y dicha funcionalidad se muestra con la paginación predeterminada. Pero con la paginación personalizada, después de eliminar ese último producto en la última página, GridView simplemente desaparece de la pantalla por completo. La razón precisa por la que esto sucede es un poco más allá del ámbito de este tutorial; vea Eliminar el último registro en la última página de gridView con paginación personalizada para obtener los detalles de bajo nivel en cuanto al origen de este problema. En resumen, se debe a la siguiente secuencia de pasos que realiza GridView cuando se hace clic en el botón Eliminar:
- Eliminar el registro
- Obtener los registros apropiados para mostrar para los valor
PageIndex
yPageSize
especificados - Comprobar que
PageIndex
no supera el número de páginas de datos del origen de datos; si es así, se disminuye automáticamente la propiedadPageIndex
de GridView - Enlazar la página adecuada de datos a GridView mediante los registros obtenidos en el paso 2
El problema proviene del hecho de que en el paso 2 la instancia de PageIndex
utilizada al obtener los registros para mostrar sigue siendo el valor PageIndex
de la última página cuyo único registro se acaba de borrar. Por lo tanto, en el paso 2, no se devuelven registros desde esa última página de datos ya no contiene ningún registro. Después, en el paso 3, GridView se da cuenta de que su propiedad PageIndex
es mayor que el número total de páginas del origen de datos (ya que se ha eliminado el último registro de la última página) y, por tanto, disminuye su propiedad PageIndex
. En el paso 4, GridView intenta enlazarse a los datos recuperados en el paso 2; pero en el paso 2 no se devolvieron registros, por lo que se creó un elemento GridView vacío. Con la paginación predeterminada, este problema no aparece porque en el paso 2 se recuperan todos los registros del origen de datos.
Para corregir esto, hay dos opciones. La primera es crear un controlador de eventos para el controlador de eventos RowDeleted
de GridView que determine cuántos registros se mostraron en la página que se acaba de eliminar. Si solo había un registro, entonces el registro que se acaba de eliminar debe haber sido el último y hay que disminuir PageIndex
de GridView. Por supuesto, solo quiere actualizar PageIndex
si la operación de eliminación ha tenido éxito realmente, lo que puede determinar si se asegura de que la propiedad e.Exception
es null
.
Este enfoque funciona porque actualiza el PageIndex
después del paso 1 pero antes del paso 2. Por tanto, en el paso 2 se devuelve el conjunto adecuado de registros. Para ello, use código como el siguiente:
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);
}
Una solución alternativa es crear un controlador de eventos para el evento RowDeleted
de ObjectDataSource y establecer la propiedad AffectedRows
en el valor 1. Después de eliminar el registro en el paso 1 (pero antes de volver a recuperar los datos en el paso 2), GridView actualiza su propiedad PageIndex
si una o más filas se han visto afectadas por la operación. Pero la propiedad AffectedRows
no es establecida por ObjectDataSource y, por tanto, se omite este paso. Una forma de hacer que se ejecute este paso es establecer manualmente la propiedad AffectedRows
si la operación de eliminación se completa con éxito. Esto se puede lograr mediante código como el siguiente:
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;
}
El código de ambos controladores de eventos se encuentra en la clase de código subyacente del ejemplo EfficientPaging.aspx
.
Comparación del rendimiento de la paginación predeterminada y personalizada
Dado que la paginación personalizada solo recupera los registros necesarios, mientras que la paginación predeterminada devuelve todos los registros de cada página que se está viendo, es claro que la paginación personalizada es más eficaz que la paginación predeterminada. ¿Pero cuánta eficacia más ofrece la paginación personalizada? ¿Qué tipo de mejoras de rendimiento se pueden ver al pasar de la paginación predeterminada a la paginación personalizada?
Desafortunadamente, no hay una respuesta única. La ganancia de rendimiento depende de varios factores, los dos más destacados son el número de registros que se paginan y la carga colocada en el servidor de base de datos y los canales de comunicación entre el servidor web y el servidor de base de datos. Para tablas pequeñas con solo unas docenas de registros, la diferencia de rendimiento puede ser insignificante. En el caso de las tablas grandes; pero con miles o cientos de miles de filas, la diferencia de rendimiento es considerable.
Mi artículo, "Paginación personalizada en ASP.NET 2.0 con SQL Server 2005", contiene algunas pruebas de rendimiento que he ejecutado para mostrar las diferencias de rendimiento entre estas dos técnicas de paginación al paginar en una tabla de base de datos con 50 000 registros. En estas pruebas he examinado tanto el tiempo para ejecutar la consulta en el nivel de SQL Server (mediante SQL Profiler) como en la página de ASP.NET mediante las características de seguimiento de ASP.NET. Tenga en cuenta que estas pruebas se ejecutaron en mi entorno de desarrollo con un solo usuario activo y, por tanto, no son científicas y no imitan los modelos de carga de sitios web típicos. Independientemente de los resultados, muestran las diferencias relativas en el tiempo de ejecución para la paginación predeterminada y personalizada cuando se trabaja con cantidades suficientemente grandes de datos.
Duración media (s) | Lee | |
---|---|---|
Paginación predeterminada de SQL Profiler | 1,411 | 383 |
SQL Profiler de paginación personalizada | 0,002 | 29 |
Seguimiento de ASP.NET paginación predeterminado | 2,379 | N/A |
Seguimiento de ASP.NET paginación personalizado | 0.029 | N/A |
Como puede ver, recuperar una página determinada de datos requería un promedio de 354 lecturas menos y se completaba en una fracción del tiempo. En la página ASP.NET, la página personalizada se pudo representar cerca del 1/100 del tiempo que tardó la paginación predeterminada.
Resumen
La paginación predeterminada es muy fácil de implementar, basta con activar la casilla Habilitar paginación en la etiqueta inteligente del control web de datos, pero esa simplicidad se produce a costa del rendimiento. Con la paginación predeterminada, cuando un usuario solicita cualquier página de datos, se devuelven todos los registros, aunque solo se muestre una pequeña fracción de ellos. Para combatir esta sobrecarga de rendimiento, ObjectDataSource ofrece una opción alternativa de paginación personalizada.
Aunque la paginación personalizada mejora los problemas de rendimiento de la paginación predeterminada recuperando solo los registros que deben mostrarse, es más complicado implementar la paginación personalizada. En primer lugar, se debe escribir una consulta que acceda correctamente (y eficazmente) al subconjunto específico de registros solicitados. Esto puede lograrse de varias formas; la que se examina en este tutorial consiste en utilizar la nueva función ROW_NUMBER()
de SQL Server 2005 para clasificar los resultados y, después, devolver solo aquellos cuya clasificación se encuentre dentro de un rango especificado. Además, es necesario agregar un medio para determinar el número total de registros que se paginan. Después de crear estos métodos para la DAL y BLL, también es necesario configurar ObjectDataSource para que pueda determinar cuántos registros totales se paginan y pueden pasar correctamente los valores Índice de la fila inicial y Número máximo de filas a la BLL.
Aunque la implementación de la paginación personalizada requiere una serie de pasos y no es tan simple como la paginación predeterminada, la paginación personalizada es una necesidad al paginar por cantidades grandes de datos. Como se ha mostrado en los resultados examinados, la paginación personalizada puede perder segundos del tiempo de representación de la página ASP.NET y aligerar la carga en el servidor de bases de datos por uno o varios órdenes de magnitud.
¡Feliz programación!
Acerca del autor
Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 en 24 horas. Se le puede contactar en mitchell@4GuysFromRolla.com.