28 septiembre 2007

La potencia de los ClientDataSet (III)

Después de crear la base de datos y la capa de acceso a datos dentro del DataModule vamos a crear la lógica de negocio.

Antes de seguir tenemos que hacer doble clic en los componente IBQuery de la capa de acceso a datos y pulsar la combinación de teclas CTRL + A para introducir todos los campos en el módulo de datos. Y en cada una de ellas hay que seleccionar el campo ID y quitar la propiedad Required ya que el propio motor de bases de datos va a meter el campo ID con un disparador.

Pero tenemos un problema, y es que no hemos seleccionado donde esta la base de datos. Para ello hacemos doble clic en el objeto BaseDatos situado módulo de datos AccesoDatos. Seleccionamos una base de datos Remota (Remote) con IP 127.0.0.1. Y la base de datos donde la tengamos, por ejemplo:

D:\Desarrollo\Delphi7\ClientDataSet\BaseDatos.fdb

De este modo, al hacer CTRL + A sobre las tablas conectará automáticamente sobre la base de datos para traerse los campos. No se os olvide luego desconectarla.

CREANDO LA LOGICA DE NEGOCIO

La capa de lógica de negocio también la vamos a implementar dentro de un objeto DataModule y va a constar de los siguientes componentes:


- Un DataModule llamado LogicaNegocio.

- Dos componentes DataSetProvider llamados DSPLstClientes y DSPClientes.

- Dos componentes ClientDataSet llamados TLstClientes y TClientes.

Ahora vamos a configurar cada uno de estos componentes:

- Enlazamos el DataModule LogicaNegocio con el DataModule AccesoDatos en la sección uses.

- Al componente DSPLstClientes le asignamos en su propiedad DataSet el componente AccesoDatos.LstClientes.

- Al componente DSPClientes le asignamos en su propiedad DataSet el componente AccesoDatos.Clientes.

- El componente TLstClientes lo vamos a vincular con DSPLstClientes mediante su propiedad ProviderName.

- El componente TClientes lo vamos a vincular con DSPClientes mediante su propiedad ProviderName.

- Debemos hacer doble clic en ambos ClientDataSets y pulsar la combinación de teclas CTRL + A para meter todos los campos.

CONTROLANDO EL NUMERO DE REGISTROS CARGADOS EN MEMORIA

Los componentes ClientDataSet tienen una propiedad llamada PacketRecord la cual determina cuandos registros se van a almacenar en memoria. Por defecto tiene configurado -1 lo que significa que se van a cargar todos los registros en la tabla. Como eso no me interesa en el listado general del formulario principal lo que vamos a hacer es poner esta propiedad a 100.

Por eso he ordenado la lista por el campo ID descendentemente para que se vean sólo los últimos 100 registros insertados. Una de las cosas que más me gustan de los componentes ClientDataSet es que se trae los 100 últimos registros y desconecta la tabla y la transacción quitándole trabajo al motor de bases de datos. Si el usuario que maneja el programa llega hasta el registro número 100 el propio componente conecta automáticamente con el servidor, se trae otros 100 registros y vuelve desconectar.

Lo único en lo que hay que tener cuidado es no acumular demasiados registros en memoria ya que puede relentizar el programa e incluso el sistema operativo si el PC no tiene mucha potencia.

El componente ClientDataSet llamado TClientes lo dejamos como está en -1 ya que sólo lo vamos a utilizar para dar de alta un registro o modificarlo.

ENVIANDO LAS TRANSACCIONES AL SERVIDOR

Cuando se utilizan los clásicos métodos Insert, Append, Post y Delete con los objetos de la clase TClientDataSet el resultado de las operaciones con registros no tiene lugar en la base de datos hasta que envíamos la transacción al servidor con el método ApplyUpdates.

Por ello vamos a utilizar el evento OnAfterPost para enviar la transacción al servidor en el caso que haya sucedido alguna modificación en el registro:

procedure TLogicaNegocio.TClientesAfterPost( DataSet: TDataSet );
begin
if TClientes.ChangeCount > 0 then
begin
TClientes.ApplyUpdates( 0 );
TClientes.Refresh;

if TLstClientes.Active then
TLstClientes.Refresh;
end;
end;

Después de enviar la transacción hemos refrescado la tabla TClientes y también la tabla TLstClientes para que se actualicen los cambios en la rejilla. Por último, cuando en el listado de clientes se elimine un registro también hay que enviar la trasacción al servidor en su evento OnAfterDelete:

procedure TLogicaNegocio.TLstClientesAfterDelete( DataSet: TDataSet );
begin
TLstClientes.ApplyUpdates( 0 );
end;

Con esto ya tenemos controlada la inserción, modificación y eliminación de registros hacia el motor de bases de datos.

ESTABLECIENDO REGLAS DE NEGOCIO EN NUESTRAS TABLAS

Las reglas de negocio definidas en el módulo de datos le quitan mucho trabajo al formulario que esté vinculado con la tabla. En un primer ejemplo vamos a hacer que cuando se demos de alta un cliente su importe pendiente sea cero. Esto se hace en el evento OnNewRecord del componente TClientes:

procedure TLogicaNegocio.TClientesNewRecord( DataSet: TDataSet );
begin
TClientesIMPORTEPTE.AsFloat := 0;
end;

Otra de las malas costumbres que solemos cometer en los programas es controlar los datos que introduce o no el usuario en el registro, cuya lógica la hacemos en el formulario. Esto tiene el inconveniente en que si en otro formulario hay que acceder a la misma tabla hay que volver a controlar las acciones del usuario.

Para evitar esto, tenemos que definir también en la capa de lógica de negocio las reglas sobre las tablas y los campos, lo que se llama comunmente validación de campos. El componente ClientDataSet dispone de la propiedad Constraints donde pueden definirse tantas reglas como queramos. Para verlo con un ejemplo, vamos a hacer que el usuario no pueda guardar el cliente si no ha introducido su nombre.

Para definir una regla en un ClientDataSet hay que hacer lo siguiente (con TClientes):

- Pulsamos el botón [...] en la propiedad Constraints.

- Pulsamos el botón Add New.

- En la propiedad CustomConstraint definimos la condición de error mediante SQL:

NOMBRE IS NOT NULL

- En el campo ErrorMessage del mismo Constraint ponemos el mensaje de error:

No ha introducido el nombre del cliente

Con esta regla definida, si en cualquier parte de nuestro programa hacemos un Post de la tabla clientes y esta vacío el campo NOMBRE el programa lanzará un mensaje de error sin tener que programar nada. Antes teníamos que hacer esto en el botón Aceptar del formulario para validar los campos. Con los Constraints las validadiones las hacemos en sin tener que programar.

Ahora vamos a definir otra regla la cual establece que un cliene no puede tener un importe pendiente superior a 2000 €. Creamos un nuevo Constraint con la propiedad CustomConstrait definida con:

IMPORTEPTE <= 2000

y con la propiedad ErrorMessage que tenga:

El importe pendiente no puede ser superior a 2000 €

Se pueden definir tantas reglas como deseemos. En el próximo artículo vamos a hacer los formularios de mantenimiento de clientes.

Pruebas realizadas en Firebird 2.0 y Delphi 7.

7 comentarios:

yazoo dijo...

Hola y felicidades por tu excelente blog y la calidad de los artículos. Recurro a ti por lo siguiente, he seguido los ejemplos aqui descritos en delphi 2010 y resulta que los contraints del los ClientDataset no se evaluan. La verdad le he dado mil vueltas y no doy con la solución. Estaría muy agradecido si me dieras una mano.

Gracias.

Administrador dijo...

Pues tienes razón. He probado los constraints en los ClientDataSet y da igual lo que ponga de expresión que se lo pasa por el forro.

Debe ser un error de Delphi 2010. Y no es el único que he encontrado.

Estamos apañados.

yazoo dijo...

FibClientDataset hereda el mismo bug. Gracias.

Deseo furtivo dijo...

Muy interesante tu blog, tengo una pregunta acerca de los cds, cuando los trabajo por internet y los activo cds.active:= true , se toma un tiempo de casi un minuto, pero solo la primera vez, despues ya no demora, esto a que se debe, trabajo con el firebird 2.5 y delphi 7.

drkirocorp dijo...

Se que esta entrada tiene algo de tiempo pero; es verdad, los contraints no se evalúan y lo que yo hago es validar los datos en el evento BeforePost del ClientDataSet con código delphi (ObjectPascal). y lo que se comenta de la propiedad PacketRecords no solo basta con ponerlo a 100 o 20 o lo que sea, también hay que interceptar eventos y solicitar el siguiente o anterior paquete de registros, saludos!

alschopenhauer dijo...

Tengo una duda:
Primero: se configuran los componentes de acceso a datos:
En éste caso a Interbase mediante IBQuery->IBDatabase
Segundo: se enlazan los clientDataSets mediante su DSProvider, a los componentes de
acceso a datos.
Y
Tercero: Los ClientDataset son "Tablas en memoria"

A partir de éstas consideraciones, me pregunto si éste programa (o cualquiera que ocupe ClientDatasets aunue con otros componentes de acceso a datos como los BDE) no estará ocupando el "DOBLE" de memoria porque los componentes Query ocupan memoria al ejecutar su SQL y luego los ClientDataset por definición también.
¿Qué me puedes decir al respecto?

Administrador dijo...

Tu piensa que hoy en día, el PC más malo del mercado tiene 4 GB de RAM. Lo que ocupan estos componentes es una miseria comparado con lo que chupa un antivirus con una pedazo de base de datos en meroria.

Los ClientDataSet crean una caché intermedia entre lo que hay en la base de datos y los componentes DataSet tracicionales. Eso evita muchos accesos al disco duro y a la red.

Si quieres ver cosas lentas y que ocupan mucha memoria, instálate Android Studio o Visual Studio Community y vas a ver lo que es desperdiciar memoria de verdad. Yo tengo 16 GB de RAM y se mueren de asco estos entornos.

Después de ver muchos lenguajes y entornos de desarrollo, te puedo asegurar que no hay nada más rápido y eficiente que Delphi, aunque tenga una tecnología algo primitiva.

Saludos.

Publicidad