Guías técnicas Grupo Danysoft: Aplicaciones de Bases de Datos con Delphi III Equipo Grupo Danysoft julio de 2003 - (902) 123146 www.danysoft.com Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de datos con Delphi Este documento se ha realizado utilizando Doc-To-Help ®, distribuido por : Danysoft Internacional Avda de España 17 28100 Alcobendas – Madrid Tfno. 902.123146 Fax. 902.123145 http://www.danysoft.com http://www.danyshop.com [email protected] www.danysoft.com - Página 2/7 Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de Datos con Delphi Aplicaciones de bases de datos con Delphi Una estrategia para su desarrollo (3ra. entrega) Otro punto fuerte de los componentes ClientDataSet y DataSetProvider es la reconciliación de errores de actualización. Borland provee un mecanismo completo que nos permite indicarle al usuario con precisión él o los errores ocurridos al intentar aplicar sus modificaciones. Incluso podemos permitirle que realice las acciones que crea oportunas para intentar resolver el problema que generó el error y volver a aplicar las actualizaciones. El registro de cambios El ClientDataSet mantiene los cambios realizados por el usuario en memoria, más concretamente en su propiedad Delta. Para cada registro modificado, el ClientDataSet almacena la siguiente información en el registro de cambios: • Si el registro fue insertado, el registro insertado completo. • Si el registro fue eliminado, el registro eliminado completo. • Si el registro fue modificado, el registro original completo y el registro modificado pero solamente los campos que han sido modificados. La propiedad ChangeCount indica la cantidad de registros en el registro de cambios. Si el valor de ChangeCount es cero entonces lo s datos del ClientDataSet no han sido modificados. La propiedad UpdateStatus indica el estado del registro actual. Los posibles estados son: usInserted, usDeleted, usModified y usUnmodified. La propiedad StatusFilter nos permite filtrar los registros visibles dependiendo del valor de la propiedad UpdateStatus. Por defecto están visibles los registros insertados, modificados y, por supuesto, los no modificados. Los datos originales son mantenidos en la propiedad Data. La estructura de los datos contenidos en la propiedad Data es igual a la estructura de los datos mantenidos en la propiedad Delta. Esto significa que podemos asignarle el valor de la propiedad Delta de un primer ClientDataSet a la propiedad Data de un segundo ClientDataSet y tener acceso, desde el segundo ClientDataSet, al registro de cambios del primer ClientDataSet. Errores de actualización Lo descrito en los párrafos anteriores es utilizado por el DataSetProvider para resolver las actualizaciones. El proceso de actualización comienza con una llamada al método ApplyUpdates del ClientDataSet. Este método recibe un parámetro que indica la cantidad máxima de errores de actualización tolerados antes de finalizar el proceso de actualización. El valor -1 indica que el proceso de actualización debe ser completado sin importar la cantidad de errores ocurridos. El valor 0 indica que el proceso de actualización debe ser finalizado ante el primer error. El valor 1 ante el segundo y así sucesivamente. El comportamiento del proceso de actualización depende entonces del parámetro que le pasamos al método ApplyUpdates y de la cantidad de registros en el registro de cambios. La siguiente tabla muestra como se comporta el DataSetProvider en distintos escenarios. En dicha tabla puede verse claramente como la cantidad de registros actualizados depende no sólo de la cantidad de errores de actualización ocurridos sino también del valor que le pasamos a ApplyUpdates. ApplyUpdates Cantidad de registros Error en registro Nº Registros actualizados www.danysoft.com - Página 3/7 Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de datos con Delphi -1 0 1 3 3 3 1y3 2 2 1 (el 2º) 1 (el 1º) 2 (el 1º y el 3º) En la segunda entrega vimos la secuencia de eventos que genera el DataSetProvider durante el proceso de actualización. Cuando ocurre un error al intentar actualizar un registro el DataSetProvider genera el evento OnUpdateError, en el que recibimos información detallada acerca del error ocurrido, el registro que generó el error y el tipo de actualización que se intentó realizar. En este evento podemos indicarle al DataSetProvider la acción que queremos que lleve a cabo con el registro en cuestión. Dependiendo del valor pasado a ApplyUpdates, el DataSetProvider continuará con el proceso de actualización o lo cancelará. Cuando ocurre un error de actualización el DataSetProvider finaliza la transacción actual deshaciendo los cambios realizados, es decir, la finaliza haciendo Rollback. Debido a que las relaciones maestro/detalle son actualizadas en el contexto de la misma transacción, si, por ejemplo, ocurre un error al actualizar un registro del detalle, los cambios al maestro serán cancelados. Sin embargo, si en el registro de cambios hay varios registros del maestro con sus correspondientes detalles, los registros actualizados correctamente no serán cancelados ya que fueron actualizados en el contexto de otra transacción. Reconciliación de errores de actualización Al finalizar el proceso de actualización, ya sea porque se comp letó o porque finalizó debido a que se superó la cantidad máxima de errores de actualización tolerados, el DataSetProvider le informa al ClientDataSet lo ocurrido. Para cada registro hay tres situaciones posibles: que el registro haya sido actualizado, que el registro no haya sido actualizado debido a un error de actualización o que el registro no haya sido actualizado porque el proceso de actualización finalizó antes de intentar actualizarlo. A continuación se describe lo que ocurre en cada caso: • Los registros que fueron actualizados son quitados del registro de cambios y mezclados con los registros originales. • Para cada uno de los registros que no fueron actualizados debido a un error de actualización, el ClientDataSet genera el evento OnReconcileError. En este evento recibimos información detallada acerca del error ocurrido, el registro que generó el error y el tipo de actualización que se intentó realizar. Aquí también podemos indicarle al ClientDataSet la acción que queremos que lleve a cabo con el registro en cuestión. Es importante escribir un manejador para este evento ya que de lo contrario corremos el riesgo de no enterarnos si han ocurrido errores de actualización. Esto se debe a que el ClientDataSet no genera excepciones por errores de actualizació n. • Los registros que no fueron actualizados porque el proceso de actualización finalizó antes de intentar actualizarlos, son mantenidos en el registro de cambios. Para estos registros no se genera ningún evento. El método ApplyUpdates devuelve un valor entero que indica la cantidad de errores de actualización ocurridos. Por ejemplo, el valor 0 indica que no hubo errores de actualización. En lugar de escribir un manejador para el evento OnReconcileError podemos evaluar el valor devuelto por ApplyUpdates para saber si ocurrieron errores de actualización. Para reconciliar los errores de actualización Borland provee la unidad RecError, conteniendo la función HandleReconcileError, que le presenta al usuario un www.danysoft.com - Página 4/7 Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de Datos con Delphi formulario con información del error y la posibilidad de realizar acciones correctivas para intentar actualizar nuevamente el registro que generó el error. La imagen de arriba muestra el formulario presentado por la función HandleReconcileError para el caso de un registro que no pudo ser actualizado debido a un problema de concurrencia. En este caso, el usuario intentó modificar el campo OnOrder de la tabla Parts del alias DBDemos pero otro usuario modificó el mismo registro asignándole otro valor a dicho campo. El ClientDataSet nos permite acceder al valor ingresado por el usuario (propiedad Value), el valor original obtenido por el usuario (propiedad OldValue) y el valor actual en la base de datos (propiedad CurrValue). El idioma original de este formulario es inglés pero el código fuente está disponible en el directorio de la VCL por lo que podemos modificarlo para traducirlo, por ejemplo, al castellano. En todos los casos, los registros que no fueron actualizados permanecen en el registro de cambios tal y como los modificó el usuario. Esto es muy importante ya que significa que el usuario no pierde las modificaciones realizadas y que puede realizar acciones correctivas para intentar solucionar el problema que generó el error de actualización. Por ejemplo, supongamos que el usuario ha ingresado una orden con 40 ítems y la cantidad de stock disponible para el producto del último ítem no es suficiente para satisfacer el pedido. El usuario no sólo no pierde todos los datos de la orden sino que podemos informarle la cantidad disponible para que modifique el ítem número 40 para que la orden pueda ser procesada. Transacciones y actualizaciones encadenadas Los eventos del DataSetProvider nos permiten encadenar actualizaciones como si estuviéramos utilizando disparadores en una base de datos SQL. Por ejemplo, en el evento BeforeUpdateRecord generado antes de actualizar cada ítem de una orden, podemos escribir código para actualizar el stock del producto. Este código podría estar contenido en el Data Module de lógica de datos de productos y utilizar un www.danysoft.com - Página 5/7 Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de datos con Delphi ClientDataSet y un DataSetProvider para acceder a un producto en particular. No sería necesario repetir este código en el mencionado evento. Todo lo que tenemos que hacer es llamar al método apropiado en el Data Module de lógica de datos de productos. En este Data Module, el DataSetProvider de productos, al momento de actualizar el stock de un producto, no iniciará una nueva transacción porque detectará que ya existe una transacción iniciada (el DataSetProvider de órdenes la inició) por lo que sólo se limitará a actualizar la tabla de productos. Es más, como el DataSetProvider de productos no inició la transacción tampoco la finalizará. En este esquema es fundamental generar una excepción si ocurre un error al actualizar la tabla de productos. De esta forma el DataSetProvider de órdenes, al capturar la excepción, podrá finalizar la transacción actual cancelando los cambios realizados. Estas características del DataSetProvider nos permiten encapsular la lógica de productos en el Data Module de productos; la lógica de órdenes en el Data Module de órdenes y así sucesivamente. De esta forma la aplicación será más fácil de modificar y tendremos más posibilidades de reutilizar código. Por ejemplo, si por algún motivo debemos modificar el proceso de actualización del stock de productos, sólo tendremos que hacer cambios en el Data Module de lógica de datos de productos. Actualizaciones en cascada El DataSetProvider soporta el uso de actualizaciones en cascada para la eliminación y modificación de registros en relaciones maestro/detalle. Este soporte está pensado para aquellos casos en los cuales, por ejemplo, la eliminación de una orden está completamente resuelta en la base de datos. Es decir, aquellos casos en los que sólo tenemos que eliminar el registro maestro ya que en la base de datos, por medio de disparadores, los registros del detalle serán eliminados automáticamente. En estos casos, el DataSetProvider sólo deberá generar sentencias SQL de DELETE para el registro maestro. Por medio de la propiedad Options del DataSetProvider podemos indicar si dicho DataSetProvider soporta eliminaciones y actualizaciones en cascada. En el ClientDataSet, cuando el DataSetProvider soporta eliminaciones en cascada, podemos eliminar un registro del maestro aun cuando existan registros en el detalle. Esto es así porque el DataSetProvider asume que la base de datos se encargará de eliminar los registros del detalle. Hay que tener mucho cuidado con las eliminaciones y actualizaciones en cascada ya que el DataSetProvider no generará sentencias SQL para eliminar o actualizar los registros del detalle y, por lo tanto, no generará los eventos BeforeUpdateRecord y AfterUpdateRecord para los registros del detalle. Si la lógica de datos de nuestra aplicación utiliza estos eventos para los registros del detalle entonces no deberíamos habilitar eliminaciones y actualizaciones en cascada. La única limitación será que no podremos eliminar un registro del maestro si existen registros en el detalle. Esta limitación puede ser fácilmente resuelta si antes de eliminar un registro del maestro eliminamos todos los registros del detalle. Conclusiones La reconciliación de errores de actualización es un tema muy importante que pocas veces consideramos como es debido. Los errores de actualización pueden significar una experiencia frustrante para los usuarios de nuestra aplicación. El esfuerzo dedicado a que este proceso sea lo más amigable posible será muy bienvenido por ellos y nos ahorrarán muchas llamadas de soporte. www.danysoft.com - Página 6/7 Guías Técnicas Grupo Danysoft: Aplicaciones de Bases de Datos con Delphi El mecanismo ofrecido por Borland puede ser suficiente en la mayoría de los casos, pero si necesitamos modificarlos tenemos todas las herramientas para hacerlo. El formulario contenido en la unidad RecError es una fuente de aprendizaje invalorable, como la mayor parte del código fuente de la VCL. Analizando el código fuente de esta unidad podemos aprender mucho y ajustar a nuestras necesidades el mecanismo ofrecido por Borland. Otro concepto fundamente es la encapsulación. La gestión de transacciones del DataSetProvider nos permite anidar actualizaciones como si se tratara de disparadores. Esto nos da la posibilidad de encapsular la lógica de datos en distintos Data Modules y hacer que nuestras aplicaciones sean más fáciles de actualizar y mejorar las posibilidad de reutilizar código. www.danysoft.com - Página 7/7