Cómo usar un WebDataSource para mostrar millones de filas en un WebListBox

A continuación encontrarás traducido al Castellano el artículo escrito por Ricardo Cruz y publicado originalmente en el Blog oficial de Xojo.

Si te estás preguntando por qué deberías de utilizar un WebDataSource en tu WebListBox entonces este artículo te proporcionará los argumentos y consejos para que puedas tomar la decisión e implementarla.

¿Por qué necesito WebDataSource? ¿No es suficiente con AddRow?

Si estás creando un prototipo rápido o para pequeños conjuntos de datos que no van a aumentar en exceso, entonces sí: puedes simplemente utilizar un WebListBox en tu WebPage y rellenarlo usando AddRow. Si tu WebListBox va a mostrar una cantidad fija y reducida de datos (digamos que en torno a 500~1000 filas), entonces el uso de WebDataSource puede resultar excesivo.

Sin embargo, cuando se trata de mostrar en el WebListBox los datos procedentes de una base de datos de gran tamaño… entonces la cosa cambia. Estarías desperdiciando una valiosa cantidad de recursos del servidor duplicando en la memoria de tu app los mismos datos que tienes en tu base de datos; e incluso peor: los datos se duplicarían en cada sesión que estuviese utilizando dicho WebListBox.

Este es el WebListBox que crearemos en este tutorial:

Preparemos una base de datos con un millón de registros

En este ejemplo utilizaremos una humilde base de datos SQLite, pero recuerda que un WebDataSource puede ser cualquier otro objeto. Por ejemplo, podrías estar mostrando el resultado obtenido al llamar a una API externa, o bien al sistema de archivos de tu disco duro.

El siguiente SQL creará una tabla llamada “things” con tres columnas: “foo”, “bar” y “baz” con un millón de registros:

CREATE TABLE things (
  id    INTEGER PRIMARY KEY AUTOINCREMENT,
  foo TEXT,
  bar TEXT,
  baz TEXT
);
 
-- Hora de poblar la tabla
WITH RECURSIVE cnt(x) AS 
(
   SELECT
      1 
   UNION ALL
   SELECT
      x + 1 
   FROM
      cnt LIMIT 1000000
)
INSERT INTO things (foo, bar, baz)
SELECT
    'foo_' || x AS foo,
    'bar_' || x  AS bar,
    'baz_' || x AS baz
FROM cnt;
 
-- Por último, crearemos algunos índices para que la ordenación de las columnas sea más rápida
CREATE INDEX "idx_foo" ON things (
    foo, id, bar, baz
);
 
CREATE INDEX "idx_bar" ON things (
    bar, id, foo, baz
);
 
CREATE INDEX "idx_baz" ON things (
    baz, id, foo, bar
);
 
VACUUM;

Sólo hemos de almacenar este SQL en una constante de tipo String para que resulte más conveniente. Creemos una constante llamada kDatabaseSetupSQLite en nuestra clase Application y pega como su valor el anterior código SQL.

Configuración Inicial

La primera vez que ejecutemos la aplicación tendremos que asegurarnos de que nuestro archivo de base de datos esté creado y poblado con registros. No llevará mucho tiempo generar la base de datos pero, en cualquier caso, mejor hacerlo sólo una vez.

Crea un nuevo método en tu clase App llamado DBFile y que devuelva un FolderItem apuntando a la ubicación en la que queramos guardar la base de datos. Asegúrate de que su Scope (ámbito) sea Public (Público), dado que llamaremos posteriormente a dicho método desde el objeto Session. Introduce el siguiente código en el Editor de Código asociado con el nuevo método:

Return SpecialFolder.Desktop.Child("test-db.sqlite")

Nada del otro mundo; puedes utilizar cualquier otra ubicación que quieras. El archivo tendrá un tamaño en torno a los ~200 MB, de modo que recuerda eliminar dicho archivo cuando ya no lo necesites más.

Crea un nuevo método en el objeto App llamado SetupDatabase. Este será el encargado de crear y poblar sólo una vez la base de datos, de modo que si reinicias el servidor dicha información aun seguirá en su sitio:

Var db As New SQLiteDatabase
db.DatabaseFile = DBFile
db.WriteAheadLogging = True
 
// El archivo ya existe, de modo que no tenemos que crearlo de nuevo
If db.DatabaseFile.Exists Then
  Return
End If
 
db.CreateDatabase
db.Connect
db.ExecuteSQL(kDatabaseSetupSQLite)

Por último, implementa el evento App.Opening y añade una llamada al método SetupDatabase:

SetupDatabase

Con eso sería suficiente.

Prepara la Sesión

Es recomendado contar con una instancia de la base de datos por cada Sesión. Añade una nueva propiedad de ámbito Público llamada “Database” a tu objeto Session y cuyo tipo sea SQLiteDatabase.

Añade a continuación un manejador para el evento Opening que incluya el siguiente código:

Database = New SQLiteDatabase
Database.DatabaseFile = App.DBFile
Database.WriteAheadLogging = True
Database.Connect

Cada vez que un usuario llegue a tu aplicación web, se creará una conexión SQLiteDatabase aislada.

Implementar la interface WebDataSource

Desde Xojo 2024r2 hemos reducido la cantidad de métodos necesarios a la hora de implementar WebDataSource.

Crea una nueva clase llamada DatabaseDataSource. Luego, en el panel Inspector, selecciona la interface WebDataSource:

Dicha acción incluirá los tres métodos requeridos por dicha interface de clase: ColumnData, RowCount y RowData.

ColumnData

En este método hemos de devolver un array de WebListBoxColumnData. Contamos con cuatro columnas en este ejemplo, de modo que el código necesario sería el siguiente:

Var result() As WebListBoxColumnData
result.Add(New WebListBoxColumnData("ID", "id"))
result.Add(New WebListBoxColumnData("Foo", "foo"))
result.Add(New WebListBoxColumnData("Bar", "bar"))
result.Add(New WebListBoxColumnData("Baz", "baz"))
 
Return result

RowCount

Xojo necesita saber la cantidad de datos que tiene tu fuente de datos. Es suficiente con devolver un valor de tipo entero, pero en este caso consultaremos a nuestra base de datos:

Try
  Var rows As RowSet = Session.Database.SelectSQL("SELECT COUNT(*) AS counter FROM things")
  Return rows.Column("counter").IntegerValue
Catch DatabaseException
  Return 0
End Try

RowData

Aquí es donde tendrás que devolver los contenidos propiamente dichos de cada fila. Como puedes ver dispones de los parámetros rowCount y rowOffset, de modo que no tendrás que devolver un millón de registros. El control sólo cargará un subgrupo de los mismos. La cantidad se calculará de forma dinámica y variará en función de la altura del WebListBox y también de la altura de las filas.

También se proporciona el parámetro sortColumns. Si permites que tus usuarios ordenen las columnas, tendrás que utilizar dicho parámetro para saber la columna y la dirección. Por fortuna en este ejemplo podremos ordenar las columnas, y este este es el código que se encargará de ello para cumplir con la interface WebDataSource:

Var sql As String = "SELECT id, foo, bar, baz FROM things"
If sortColumns <> "" Then
  sql = sql + " ORDER BY " + sortColumns
End If
sql = sql + " LIMIT " + rowOffset.ToString + ", " + rowCount.ToString
 
Var result() As WebListBoxRowData
Var rows As RowSet = Session.Database.SelectSQL(sql)
 
// Esto no es necesario, sólo es para demostrar cómo usar generadores de celdas
Var style As New WebStyle
style.Bold = True
style.BackgroundColor = Color.Teal
style.ForegroundColor = Color.White
 
For Each row As DatabaseRow In rows
  Var newRowData As New WebListBoxRowData
  newRowData.PrimaryKey = row.Column("id").IntegerValue
  newRowData.Value("id") = row.Column("id")
  newRowData.Value("foo") = row.Column("foo")
  newRowData.Value("bar") = New WebListBoxStyleRenderer(style, row.Column("bar"))
  newRowData.Value("baz") = row.Column("baz")
  result.Add(newRowData)
Next
 
Return result

Diseñar la Interfaz de Usuario

Hemos llegado a la parte más sencilla. Crear la interfaz de usuario nos llevará menos de un minuto.

  1. Suelta un control DatabaseDataSource en tu WebPage
  2. Suelta un control WebListBox en tu WebPage
  3. Configura el DataSource en el evento Opening del WebListBox

Si has nombrado tu DataSource como “MyDataSource”, entonces esta será la línea de código requerida en el evento Opening del WebListBox:

Me.DataSource = MyDataSource

Este es un vídeo breve:

¡Eso es todo!

El control WebListBox combinado con un WebDataSource es una solución muy robusta y eficaz para mostrar un gran conjunto de datos.

Descarga el proyecto:

weblistbox-million-rows.xojo_binary_project

¡Feliz programación con Xojo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *