Documente Academic
Documente Profesional
Documente Cultură
0
Diciembre de 2006
MARCAS REGISTRADAS
ARTech y GeneXus son marcas registradas de ARTech Consultores S.R.L.
Todas las otras marcas mencionadas en este documento son propiedad de sus respetivos titulares.
Contenido
Introducción teórica ........................................................................................................ 1
Objeto transacción........................................................................................................ 31
Nomenclatura GIK ............................................................................................ ...45
Tipos de datos..................................................................................................... 51
Dominios ............................................................................................................ 55
Análisis de impacto, reorganizar, especificar, generar............................................... 64
Modos en ejecución.............................................................................................. 72
Subtipos.................................................................................................................... 335
Con el objetivo de brindar a los desarrolladores GeneXus una fuente única de información potenciada con una
ingeniería de búsqueda que les permita acceder rápida y fácilmente a la información técnica (release notes,
manuales, tips, whitepapers, etc.) sobre los diferentes productos que desarrolla ARTech, se creó la GXDL
(GeneXus Developer Library), una biblioteca que centraliza toda esta información técnica.
Puede ser consultada online o puede ser utilizada en forma local mediante una sencilla instalación:
http://www.gxtechnical.com/gxdl.
Departamento de Capacitación
ARTech
INTRODUCCIÓN TEÓRICA
1
Herramientas y Metodologías
REALIDAD
BASE
DE PROGRAMAS
DATOS
GeneXus es una herramienta para el desarrollo de aplicaciones sobre bases de datos. Su objetivo es
permitir la implantación de aplicaciones en el menor tiempo y con la mejor calidad posible.
GeneXus emplea una metodología que tiene un enfoque muy diferente al de las metodologías más
comúnmente utilizadas. Por tanto, aprender a utilizar GeneXus adecuadamente va más allá de
conocer un nuevo lenguaje: lo más importante es aprender su metodología.
2
Modelado de la realidad a
partir de las visiones de
los usuarios
Satisface
MODELO DE
LA REALIDAD
Ingeniería Inversa
BASE VISIONES
DE DE
PROGRAMAS
DATOS USUARIOS
Este conocimiento se encuentra en cada una de las visiones de los usuarios. Cada usuario conoce
bien los objetos con los que trabaja cotidianamente, la información que se maneja en ellos, las
reglas que deben seguirse, los cálculos que deben realizarse.
Por lo tanto, el punto de partida de la metodología GeneXus es: describir las visiones de los
usuarios para modelar el sistema; y a partir del modelo de la realidad definido, GeneXus
construye el soporte computacional -base de datos y programas- en forma totalmente
automática.
3
Comparación
de
Metodologías
Para fijar ideas, comparemos las metodologías tradicionales más utilizadas actualmente, con la
metodología utilizada por GeneXus, conocida como metodología incremental.
Las metodologías tradicionales difieren entre sí en varios aspectos, pero tienen una característica en
común: separan el análisis de los datos del de los procesos.
A continuación veremos un esquema general que podría aplicarse a cualquiera de las metodologías
tradicionales actuales y estudiaremos sus problemas.
4
Metodología Tradicional
5
ANÁLISIS
DE
DATOS
REALIDAD
MODELO
DE
DATOS
BASE
DE
DATOS
GENERACIÓN/
INTERPRETACIÓN
ANÁLISIS
FUNCIONAL
ESPECIFICACIÓN PROGRAMAS
FUNCIONAL
PROGRAMACIÓN
Cuando esto ocurre, la complejidad suele atacarse dividiendo el sistema en módulos (“divide
and conquer”), cada uno de los cuales soluciona los problemas operativos de un área
específica de la empresa. De esta forma la tarea de modelado se simplifica notablemente,
pero como contrapartida los módulos operan sin una real integración, lo que provoca que
exista información redundante, con todos los problemas que ello acarrea.
En caso de que luego se intente realizar la integración de esos módulos, habrá que realizar
modificaciones sobre los modelos de datos, lo que a pesar de su complejidad es una tarea
realizable. Sin embargo las modificaciones que deberán efectuarse sobre los programas
asociados tienen un costo tan elevado que suelen tornar inviable la integración.
Con la división del sistema en módulos la empresa tendrá resueltos sus problemas operativos,
pero aparecerán indefectiblemente problemas de carencia de información que permita
orientar la gestión y la toma de decisiones de alto nivel. En la órbita gerencial la información
que se necesita es principalmente de naturaleza corporativa, por lo que la división del sistema
en módulos no satisface las necesidades actuales de obtención de información.
GeneXus soluciona este problema, brindando herramientas y una metodología que hacen
posible y sencilla la creación y mantenimiento de sistemas corporativos.
6
Una vez obtenido el modelo de datos, el siguiente paso de una metodología tradicional es diseñar la base de datos.
Existe una transformación trivial entre un buen modelo de datos y el diseño de la base de datos necesaria para
soportarlo.
Sin embargo, el modelo de datos no es suficiente para desarrollar los programas de aplicación, ya que el mismo
describe los datos, pero no los comportamientos. Se recurre, entonces, a una tarea llamada Análisis Funcional,
donde se estudia la empresa desde el punto de vista de las funciones existentes, y el resultado de dicha tarea es una
Especificación Funcional.
Sería deseable que la especificación funcional fuera independiente de la especificación de datos, lo que no ocurre en
las metodologías estudiadas. Las modificaciones en el diseño de la base de datos implican la necesidad de revisar las
especificaciones funcionales, siendo esta la causa fundamental de los grandes costos de mantenimiento que tienen
estos sistemas.
Una vez que se cuenta con la base de datos y la especificación funcional, ya están dadas las condiciones para crear los
programas de la aplicación.
Desde un punto de vista conceptual no hay diferencia entre la programación en lenguajes de 3ra. y de 4ta. generación.
En ambos casos el analista debe estudiar el problema, transformarlo en un algoritmo y codificarlo en el lenguaje de
programación elegido.
La principal diferencia radica en que en los lenguajes de 4ta. generación se escribe mucho menos código
(aproximadamente 10 veces menos) y, como consecuencia, la productividad es mucho mayor y el número de errores
cometidos es mucho menor.
El problema de que las descripciones de los procesos dependen de la base de datos afecta a ambos por igual, por lo
que se concluye que los lenguajes de 4ta. generación permiten aumentos de productividad muy grandes sobre los de
3ra. en la tarea de desarrollo, pero no ayudan en la etapa de mantenimiento.
Otra alternativa es la utilización de un “generador”: una herramienta que puede interpretar una especificación
funcional y producir automáticamente los programas. Aquí existe una diferencia conceptual importante con el caso
anterior. En este caso el analista describe el problema de una forma declarativa (no existe algoritmo ni codificación
procedural alguna). Por ello la especificación funcional debe ser formal y rigurosa. Existen actualmente en el mercado
varias herramientas de esta clase.
Existe asimismo otra categoría de herramientas conceptualmente idéntica: la de aquellas que simplemente
“interpretan” la especificación. La programación en ambos casos es totalmente automática, por lo que el aumento de
productividad sobre las herramientas de 3ra. y 4ta. generación es muy grande.
El problema visto -las descripciones de los procesos dependen de la base de datos- afecta también a las aplicaciones
generadas mediante estas herramientas. Como consecuencia, los Generadores e Intérpretes (como los lenguajes de
4ta. generación) no ayudan lo suficiente en la tarea de mantenimiento.
En definitiva, desde el punto de vista del mantenimiento ninguna de las herramientas ayuda mucho, dado que en
todas ellas se utilizan descripciones de procesos que pueden perder su validez cuando existen modificaciones de
archivos y que, por ello, deben ser revisadas y mantenidas manualmente. El costo de mantenimiento, claro está,
difiere enormemente entre las distintas alternativas vistas, ya que en el caso de la utilización de Generadores o
Intérpretes, una vez modificadas las especificaciones funcionales la generación de los programas es automática.
Si el siguiente postulado: “puede obtenerse una base de datos estable” fuera verdadero, los problemas anteriores
serían irrelevantes. Sin embargo, sobran contraejemplos que hablan de lo contrario.
Conclusiones:
No es posible hacer de forma abstracta un modelo de datos detallado de la empresa y con el suficiente nivel de detalle
y objetividad porque nadie la conoce como un todo. Por el contrario, es necesario recurrir a múltiples interlocutores,
cada uno de los cuales aporta su propia subjetividad. Como consecuencia, durante todo el ciclo de vida de la aplicación
se producen cambios en el modelo.
7
Aun si se considerara la situación ideal, en la cuál se conocen exactamente las necesidades y por tanto es posible
definir la base de datos óptima, el modelo, de todas formas, no podrá permanecer estático, ya que debe acompañar
la evolución de la empresa.
Todo esto sería poco importante si la especificación funcional y la base de datos fueran independientes. Sin
embargo, dado que la especificación funcional se refiere a la base de datos, las inevitables modificaciones en esta
última, implican modificaciones (manuales) en la primera.
Las principales consecuencias de lo anterior son los altos costos de mantenimiento: en la mayoría de las empresas
que trabajan de una manera convencional se admite que el 80% de los recursos que teóricamente están destinados
al desarrollo, realmente se utilizan para hacer mantenimiento de las aplicaciones ya implementadas.
Dado que es muy difícil en este contexto determinar y propagar las consecuencias de los cambios de la base de
datos sobre los procesos, es habitual que en vez de efectuarse los cambios necesarios, se opte por introducir
nuevos archivos redundantes con la consiguiente degradación de la calidad de los sistemas y el incremento de los
costos de mantenimiento.
8
Metodología GeneXus
Los fundadores de ARTech han desarrollado una amplia actividad de consultoría internacional en
diseño de grandes bases de datos, trabajando con verdaderos modelos corporativos con cientos de
tablas.
En su trayectoria, se convencieron de que no debía perderse más tiempo buscando algo que no
existe: las bases de datos estables.
9
Desarrollo con GeneXus
REALIDAD
DESCRIPCIÓN
DE OBJETOS
Utilizando GeneXus, la tarea básica del analista es la descripción de la realidad. Sólo el ser humano
puede desarrollar esta tarea ya que sólo él puede entender el problema del usuario.
El analista GeneXus trabaja en alto nivel, en vez de realizar tareas de bajo nivel como: diseñar
archivos, normalizar, diseñar programas, programar, buscar y eliminar los errores de los programas.
Para comenzar el desarrollo de una aplicación con GeneXus, el primer paso consiste en crear un
nuevo proyecto o base de conocimiento.
Una vez creada una nueva base de conocimiento (en inglés: knowledge base; abreviado: KB), el
siguiente paso es describir las visiones de los usuarios. Para ello se deben identificar los objetos de
la realidad (prestando atención a los sustantivos que los usuarios mencionan en sus descripciones,
como por ejemplo: clientes, productos, facturas) y pasar a definirlos mediante objetos GeneXus.
Con la definición de estos objetos, GeneXus puede extraer el conocimiento y diseñar la base de
datos y los programas de la aplicación en forma automática.
10
Desarrollo con GeneXus
REALIDAD
DESCRIPCIÓN
DE OBJETOS
BASE BASE DE
DE CONOCIMIENTO
DATOS
PROGRAMAS
En cada momento el analista GeneXus define el conocimiento que tiene y luego cuando pasa
a tener más conocimiento (o simplemente diferente) lo refleja en la base de conocimiento y
GeneXus se ocupará de hacer automáticamente todas las adaptaciones en la base de datos
y programas.
11
Comparación de Metodologías
ANÁLISIS
DE
DATOS
REALIDAD
DESCRIPCIÓN
DE OBJETOS
MODELO
DE
DATOS
BASE BASE DE
DE CONOCIMIENTO
DATOS
GENERACIÓN/
INTERPRETACIÓN
ANÁLISIS
FUNCIONAL ESPECIFICACIÓN PROGRAMACIÓN PROGRAMAS
FUNCIONAL
4. Desarrollo incremental.
12
Objetos GeneXus
(más importantes)
Una vez creada una base de conocimiento, el siguiente paso consiste en comenzar a describir
los objetos de la realidad mediante objetos GeneXus.
Transacciones
Permiten definir los objetos de la realidad que el usuario manipula (ej: clientes, productos,
proveedores, facturas, etc.). Son los primeros objetos en definirse, ya que a través de las
transacciones, GeneXus infiere el diseño de la base de datos.
Además de tener por objetivo la definición de la realidad y la consecuente creación de la base
de datos normalizada, cada transacción tiene asociada una pantalla para ambiente windows y
otra para ambiente Web, para permitir al usuario dar altas, bajas y modificaciones en forma
interactiva a la base de datos. El analista GeneXus decidirá si trabajar en ambiente windows,
Web, o ambos, y GeneXus generará los programas para ello.
Reportes
Permiten recuperar información de la base de datos, y desplegarla ya sea en la pantalla, en un
archivo o impresa en papel. Son los típicos listados o informes. No permiten actualizar la
información de la base de datos1.
Procedimientos
Tienen las mismas características que los reportes, pero a diferencia de éstos, permiten además
la actualización de la información de la base de datos.
Work Panels
Permiten al usuario realizar interactivamente consultas a la base de datos, a través de una
pantalla. Ejemplo: un work panel permite al usuario ingresar un rango de caracteres, y
muestra a continuación todos los clientes cuyos nombres se encuentran dentro del rango.
Son objetos muy flexibles que se utilizan exclusivamente en ambiente windows y se prestan
para múltiples usos. No permiten la actualización de la base de datos, sino solo su consulta1.
------------------------------------------------------------------------------------------------------
1 No es inherente a este tipo de objetos la modificación de la base de datos, pero como
veremos más adelante, existen unos componentes llamados “business components” que lo
harán posible, rompiendo con su naturaleza de solo consulta.
13
Web Panels
Son similares a los work panels, salvo que son usados a través de browsers en ambiente
Internet/Intranet/Extranet.
Existen algunos tipos más de objetos GeneXus, algunos de los cuales veremos en este curso, como
los Subtype Groups y los Structured Data Types, que si bien son objetos que se definen en la
base de conocimiento (KB) mediante el mismo procedimiento que los ya mencionados, tienen
algunas características que los diferencian de la mayoría de ellos, como el hecho de no generar
programas propios, sino que su conocimiento es reutilizado dentro de los otros objetos.
14
Proceso de desarrollo de una
aplicación con GeneXus
Transacciones Reportes Procedimientos Work Panels Web Panels
(Trns) (Rpts) (Procs) (Wkps) (Wbps)
Base
Basede
deConocimiento
Conocimiento
Base
de
Datos
Los primeros objetos que se definen son las transacciones, ya que es a partir de ellas que
GeneXus extrae el conocimiento necesario para diseñar el modelo de datos normalizado (en
3era. forma normal). Luego se van definiendo los demás objetos que correspondan.
15
Creación de la Base de Datos
Base
Basede
deConocimiento
Conocimiento
Base Programas
de Creación
Datos BD
GeneXus genera automáticamente los programas necesarios para crear la base de datos y
los ejecuta. De esta manera obtenemos la base de datos creada por GeneXus en forma
automática.
16
Generación de los
Programas de la aplicación
Base
Basede
deConocimiento
Conocimiento
Luego, GeneXus genera programas de aplicación para interactuar con la base de datos
previamente creada.
17
Resultado final en la Etapa de Desarrollo
Transacciones Reportes Procedimientos Work Panels Web Panels
(Trns) (Rpts) (Procs) (Wkps) (Wbps)
Base
Basede
deConocimiento
Conocimiento
Una vez creada la base de datos y generados los programas, contamos con una aplicación pronta
para ejecutar.
18
Las visiones de los usuarios cambian
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Base
Basede
deConocimiento
Conocimiento
Base Nueva
Nueva Programas de Aplicación
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos
Las modificaciones que se realicen sobre la base de conocimiento serán analizadas por GeneXus
para evaluar si es necesario efectuar cambios en la base de datos (por ejemplo:
modificación/creación de tablas/índices), o no.
En caso de detectar cambios para efectuar en la base datos, GeneXus detallará los mismos en un
reporte de análisis de impacto (IAR: Impact Analisis Report), que es un reporte que explicita
todos los cambios sobre tablas, índices, datos, etc. que habría que realizar para reflejar la nueva
realidad.
Asimismo, en el reporte de análisis de impacto se informan los eventuales problemas que los
cambios en cuestión podrían ocasionar, como inconsistencias o redundancias.
19
Análisis de Impacto Totalmente Automático
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Análisis
Base
Basede
deConocimiento
Conocimiento
de
impacto
Base Nueva
Nueva Programas de Aplicación
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos
Algunas veces la nueva base de datos coincide con la anterior. Otras veces esto no ocurre, y la base
de datos debe sufrir alguna modificación para representar la nueva realidad.
20
Generación de Programas de
Reorganización de la Base de Datos
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Programas Base
Basede
deConocimiento
Conocimiento
de
Reorganiz.
Programas de Aplicación
Base Nueva
Nueva
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos
Si el analista opta por aplicar los cambios propuestos, decimos que optó por reorganizar la base de
datos. Utilizamos este término para referirnos a la acción de aplicar cambios físicos sobre la base de
datos.
GeneXus generará los programas que implementan las modificaciones sobre las
estructuras físicas de la base de datos, y mediante su ejecución nos brindará la nueva versión
de la base de datos con los cambios efectuados.
21
Análisis automático del impacto
de los cambios sobre los programas
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Análisis
Base de Impacto
Basede
deConocimiento
Conocimiento sobre los
programas
Nuevos Programas de
Nueva
Aplicación
Base
de (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos
Ya sea que se requiera reorganizar la base de datos o no, considerando las nuevas definiciones
introducidas y a pedido del analista, GeneXus estudiará el impacto de los cambios sobre los
programas actuales.
El analista podrá seleccionar sobre cuáles objetos quiere realizar el análisis (si sobre todos, los que
hayan sufrido cambios, o algunos en particular) y para cada objeto analizado, GeneXus indicará si
es necesario realizar cambios en su programa de aplicación, o no.
22
Generación automática de nuevos programas
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Base
Basede
deConocimiento
Conocimiento
Generación
de
programas
Nuevos Programas de
Nueva Aplicación
Base
(Trns, Rpts, Procs, Wkps, Wbps, DVs)
de
Datos
23
Nueva realidad, con los cambios en la aplicación
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels
Base
Basede
deConocimiento
Conocimiento
Nueva
Base Nuevos Programas de Aplicación
de
Datos
De modo que nuevamente contaremos con una aplicación pronta para ejecutar, con los cambios
aplicados.
24
Metodología Incremental
Consiste en construir una aplicación mediante aproximaciones
sucesivas.
DEFINICION
INICIAL
25
Modelos
BASE DE CONOCIMIENTO
modelo
de
Diseño
El modelo de Diseño corresponde a la representación lógica del sistema, esto es, permite
describir la aplicación sin implementarla.
Esto significa que no existirá una base de datos física asociada a este modelo, así como
tampoco programas de aplicación ejecutables. Estos últimos existirán solo a un nivel
conceptual o lógico.
Son los objetos GeneXus que el analista haya definido dentro de este modelo los que
principalmente describen al sistema. En particular, de las transacciones especificadas1
GeneXus obtiene el diseño lógico de la base de datos, y ellas, en conjunto con el resto de
los objetos definidos, constituyen la descripción lógica de los programas.
Pero esta implementación (base de datos y programas), que es enteramente realizada por
GeneXus, ¿dónde se realiza?
Aquí es donde entran en juego los otros modelos que pueden definirse dentro de la base de
conocimiento. Cada uno de estos otros modelos, contendrán una implementación del
sistema.
--------------------------------------------------------------------------------------------------
1 En conjunción con los grupos de subtipos (objetos Subtype Group) y los índices de
usuario.
26
Modelos
BASE DE CONOCIMIENTO
modelo otro
otro otro
de modelo
modelo modelo
Diseño
A diferencia del modelo de Diseño que es creado automáticamente, estos otros modelos
son creados en forma explícita por el analista. Al hacerlo, éste debe ingresar los datos de la
plataforma o ambiente de implementación correspondiente (lenguaje de programación,
DBMS, etc.) para que GeneXus pueda implementar el sistema para ese modelo.
Los objetos GeneXus definidos en el modelo de Diseño se copian al nuevo modelo y éstos
son los que GeneXus codifica para dicho modelo de acuerdo a su plataforma.
27
Tipos de Modelo
Î Cada modelo tiene un tipo:
Î Design (Diseño):
ÎUn sólo modelo en la misma base de conocimiento.
ÎNo tiene base de datos ni programas de aplicación
asociados.
Î Prototype/Production (Prototipo/Producción):
ÎPuede haber varios modelos en la misma base de
conocimiento.
ÎCada uno de estos modelos tiene una base de datos
asociada y programas de aplicación que se generan para la
plataforma o ambiente elegido.
Por cada base de conocimiento, habrá un y solo un modelo de Diseño, cuya característica
fundamental es que representa al sistema conceptualmente.
Los otros dos tipos se parecen mucho entre sí, dado que todo modelo de Prototipo o Producción
tiene asociada una plataforma o ambiente que le permite implementar físicamente el sistema,
siendo ésta la característica fundamental que diferencia a estos modelos del de Diseño. La principal
diferencia entre ellos es conceptual (no funcional) y radica en el uso que se hace de los mismos:
-Un modelo de Producción, por el contrario, se utiliza en la etapa de puesta en producción, cuando
el prototipo fue aprobado y la aplicación o los cambios efectuados ya pueden ser llevados al cliente.
- uno de Producción, pues es necesario tener una imagen fiel de la aplicación que se lleva al
cliente, en la plataforma de producción, como veremos oportunamente.
28
Ciclos de desarrollo
Ciclo de prototipación
El bucle Diseño-Prototipo se recorre repetidamente, construyendo y probando sucesivos
prototipos.
Ciclo de producción
El bucle Diseño-Producción se recorre menos frecuentemente, ya que este ciclo corresponde
a la puesta en producción del sistema y esto se realiza solamente cuando el prototipo ha
sido aprobado o luego de haber instrumentado y probado algún cambio.
29
Ventajas de la Prototipación
Como la implementación de sistemas es habitualmente una tarea que insume bastante tiempo, y muchos
de estos problemas sólo son detectados en las pruebas finales del sistema, el costo en tiempo y dinero de
solucionarlos se torna muy grande. Sabido es que la realidad no permanece estática, por lo que no es
razonable suponer que se pueden mantener congeladas las especificaciones mientras se implementa el
sistema. Sin embargo, debido al tiempo que suele insumir la implementación, muchas veces esto se hace
y se acaba implementando una solución relativamente insatisfactoria.
Una situación bastante diferente sería la de poner a disposición del usuario para su ejecución, una
aplicación funcionalmente equivalente a la deseada hasta en los mínimos detalles. Y esto es lo que ofrece
GeneXus! Un prototipo GeneXus es una aplicación pronta funcionalmente equivalente a la aplicación de
producción.
Así es que la aplicación puede ser totalmente probada antes de ponerse en producción; y durante las
pruebas, el usuario final puede probar de una forma natural no solamente formatos de pantallas,
informes, etc., sino también fórmulas, reglas del negocio, estructuras de datos, etc., y trabajar con datos
reales.
Esto solo es posible gracias a la construcción automática que realiza GeneXus del soporte computacional
(base de datos y programas).
30
OBJETO TRANSACCIÓN
Para identificar cuáles transacciones deben crearse, se recomienda prestar atención a los
sustantivos que el usuario menciona cuando describe la realidad.
31
Elementos
• Estructura
• Form GUI-Windows
• Form Web
• Reglas
• Eventos
• Subrutinas
• Propiedades
• Documentación
• Ayuda
Algunos de estos elementos también están asociados a otros tipos de objetos GeneXus.
32
Estructura
La estructura de una transacción permite definir qué atributos la integran y cómo están
relacionados.
SupplierId*
SupplierName
SupplierAddress
SupplierPhone
Esta lista de nombres (uno de los cuales está sucedido del símbolo asterisco) corresponde a los atributos
que interesa mantener acerca de los proveedores.
Entonces, creamos una transacción de nombre “Supplier” cuya estructura se compone de los atributos
SupplierId, SupplierName, SupplierAddress y SupplierPhone.
Esto significa que cada proveedor se identificará por un código SupplierId (lo que queda determinado por
el asterisco a continuación del atributo1), tendrá un nombre SupplierName, una dirección
SupplierAddress y un teléfono SupplierPhone.
Para cada atributo definido en la estructura, deberemos indicar cosas tales como su tipo de datos,
descripción y algunos detalles más que veremos.
--------------------------------------------------------------------------------------------------------------
1 El asterisco corresponde a una notación teórica que utilizamos para indicar que el atributo es
identificador. Como veremos, nuestro asterisco en GeneXus aparece representado por un ícono de llave
y el usuario podrá configurarlo mediante un menú contextual que le ofrecerá esta posibilidad.
33
Estructura
Atributos Clave
En la página anterior hemos explicado que el asterisco a continuación del atributo SupplierId indica que se trata
del identificador de la transacción. Toda transacción debe tener un identificador, esto es, un atributo o conjunto
de atributos que definan la unicidad.
En el ejemplo no podrán existir dos proveedores con el mismo valor de SupplierId. En definitiva se trata del
concepto de clave primaria, en tanto, para hacer la elección de los atributos que la componen, se deben tener
en cuenta los requisitos del objeto de la realidad.
En los casos en los cuales no se pueda determinar un identificador, se debe optar por crear un atributo artificial
(no existente en la realidad), y que su valor se asigne internamente, por ejemplo, en forma correlativa.
Como se puede observar en el editor de transacciones de GeneXus, un ícono de llave representa el asterisco que
nosotros utilizamos como notación teórica.
Atributo “descriptor”
El ícono con una lupa representa al atributo que mejor describe o representa a la transacción. En otras palabras
sería el atributo que tiene mayor carga semántica en la transacción.
Por defecto el primer atributo en la estructura de la transacción que sea de tipo de datos character, se definirá
como “atributo descriptor”. Es posible definir a otro atributo como descriptor utilizando el menú popup
correspondiente, así como no definir ninguno.
34
Estructura
El hecho de definir un segundo nivel significa que existen varias instancias del mismo, para cada
instancia del nivel anterior. En el ejemplo, un cabezal de factura tiene varios productos.
Cada nivel de una transacción define un grupo de atributos que deben operar en conjunto, es decir, se
ingresan, se eliminan o se modifican conjuntamente en la base de datos.
Llamaremos transacción plana a una transacción de un solo nivel. Así, la transacción “Supplier” es una
transacción plana.
En cambio, la transacción “Invoice” tiene dos niveles. Es común hablar de “cabezal” para referirnos al
primer nivel y de “líneas” para referirnos al segundo.
Para cada nivel de la transacción, se debe indicar cuáles de sus atributos actúan como identificador. El
identificador de cada nivel puede estar compuesto de un solo atributo, como es el caso de las
transacciones que hemos visto hasta ahora, o puede estar conformado por varios atributos.
Una transacción puede contener varios niveles paralelos, así como anidados.
--------------------------------------------------------------------------------------------------------------
1 Al igual que el asterisco es una notación teórica que representa que el atributo que lo antecede es
identificador en la transacción, el juego de paréntesis también es utilizado como notación teórica, para
representar que los atributos contenidos forman parte de un nivel anidado, y que tiene una
representación visual en GeneXus distinta, pero que indica lo mismo.
35
Estructura
InvoiceId* InvoiceId*
CustomerId CustomerId
CustomerName CustomerName
InvoiceDate InvoiceDate
InvoiceTotal InvoiceTotal
(ProductId* (ProductId*
ProductDescription ProductDescription
ProductPrice ProductPrice
InvoiceLineQuantity InvoiceLineQuantity
InvoiceLineAmount) InvoiceLineAmount
(InvoicePaymentDate* Fecha de pago (InvoicePaymentDate*
InvoicePaymentAmount) Importe pagado InvoicePaymentAmount))
Con la estructura de la izquierda se define que una factura tiene muchos productos y muchos pagos, pero
no hay una relación directa entre los productos y los pagos (a no ser el hecho de pertenecer a la misma
factura). En la estructura de la derecha se registran los pagos por producto llevado.
Es sencillo comprender que el segundo y tercer nivel de la transacción de la izquierda, son paralelos.
Ambos se encuentran anidados al primer nivel, pero entre ellos, son paralelos. En la estructura de la
derecha, son todos niveles anidados.
--------------------------------------------------------------------------------------------------------------
1 Como veremos luego, el tipo que se define para un nivel de una transacción, será utilizado para trabajar
con business components, concepto relacionado a las transacciones.
36
Definición del modelo de datos a partir de las
estructuras de las transacciones
Tabla
Transacción Transacción INVOICE
“Supplier” “Invoice”
InvoiceId*
SupplierId* InvoiceId* CustomerId
SupplierName CustomerId CustomerName
SupplierAddress CustomerName InvoiceDate
SupplierPhone InvoiceDate InvoiceTotal
InvoiceTotal
( ProductId* Tabla
ProductDescription INVOICELINE
Tabla ProductPrice
SUPPLIER InvoiceLineQuantity InvoiceId*
SupplierId* InvoiceLineAmount ) ProductId*
SupplierName ProductDescription
SupplierAddress ProductPrice
SupplierPhone InvoiceLineQuantity
InvoiceLineAmount
GeneXus utiliza la estructura de las transacciones para capturar el conocimiento necesario para definir
automáticamente cuál es el modelo de datos que debe crear.
Para poder realizar la normalización de la base de datos llevándola a 3era. forma normal, GeneXus
debe extraer las dependencias funcionales existentes entre los atributos definidos en la base de
conocimiento.
Dadas estas dependencias funcionales, GeneXus determina que debe crear una tabla que tendrá por
defecto el mismo nombre que la transacción (SUPPLIER)1, y que estará conformada ni más ni menos
que por los cuatro atributos anteriores, siendo SupplierId la clave primaria de la misma:
Diremos que la transacción “Supplier” tiene asociada la tabla SUPPLIER en el entendido de que cuando
se ingresen, modifiquen o eliminen datos por medio de la transacción, éstos se estarán almacenando,
modificando o eliminando físicamente en la tabla asociada.
------------------------------------------------------------------------------------------------------------
1 En la documentación, para distinguir el nombre de una tabla del nombre de una transacción
escribiremos el nombre de la tabla todo en mayúscula.
37
A partir de la estructura de la transacción “Invoice”, GeneXus determina que debe crear dos tablas:
InvoiceLineQuantity InvoiceLineAmount
Clave primaria: {InvoiceId, ProductId}
Clave foránea: InvoiceId
Observemos que la clave primaria de la tabla INOVICELINE es la concatenación del identificador del
primer nivel, InvoiceId, con el identificador del segundo nivel, ProductId. El caso es general: la clave
primaria de la tabla correspondiente a un nivel n de una transacción se obtiene de concatenar los
identificadores de los n-1 niveles anteriores anidados, con el identificador de ese nivel.
GeneXus asigna un nombre predeterminado a las tablas que crea. A la tabla asociada al primer nivel de
una transacción le asigna el mismo nombre que el de la transacción; y a las tablas de niveles
subordinados les asigna la concatenación de los nombres de los niveles. Por esto es que la tabla
asociada al segundo nivel de la transacción “Invoice” recibe el nombre INVOICELINE, dado que el
nombre del primer nivel es el de la transacción, INVOICE, y el del segundo nivel es LINE. Los nombres
de las tablas pueden ser modificados por el analista GeneXus cuando así lo desee.
38
Al definir las nuevas transacciones:
Transacción “Customer”
CustomerId*
CustomerName
CustomerAddress
CustomerGender Sexo del cliente
Transacción “Product”
ProductId*
ProductDescription
ProductPrice
ProductStock
Luego de haber modelado la transacción “Invoice”, nos damos cuenta que hay información de clientes y de
productos que nos interesa mantener independientemente de las facturas. Es decir, los clientes y los
productos son dos objetos de la realidad independientes de las facturas, por lo tanto creamos las dos
nuevas transacciones “Customer” y “Product” detalladas arriba.
Dependencias funcionales
Con estas nuevas transacciones definidas, aparecen nuevas dependencias funcionales:
CustomerId Æ {CustomerName, CustomerAddress, CustomerGender}
ProductId Æ {ProductDescription, ProductPrice, ProductStock}
y:
39
Normalización: cambios en las tablas
Tabla
INVOICE
Tabla CUSTOMER
InvoiceId* CustomerId*
CustomerId CustomerName Tabla
CustomerName CustomerAddress
SUPPLIER
InvoiceDate CustomerGender
SupplierId*
InvoiceTotal
SupplierName
Tabla SupplierAddress
INVOICELINE Tabla PRODUCT SupplierPhone
InvoiceId* ProductId*
ProductId* ProductDescription
ProductDescription ProductPrice
ProductPrice ProductStock
InvoiceLineQuantity
InvoiceLineAmount
El conjunto total de dependencias funcionales existentes requiere que deban realizarse algunas
modificaciones en las tablas INVOICE e INVOICELINE diseñadas previamente para que el diseño de la base
de datos permanezca en 3era. forma normal1.
Si representamos sobre las tablas CUSTOMER e INVOICE las dependencias funcionales encontradas para sus
atributos:
podemos ver claramente que INVOICE viola la 3era. forma normal (existe una dependencia funcional
transitiva):
InvoiceId Æ CustomerId
CustomerId Æ CustomerName
InvoiceId Æ CustomerName
por lo tanto GeneXus normaliza, quitando el atributo CustomerName de la tabla INVOICE:
-------------------------------------------------------------------------------------------------------------------
1Por más información sobre las formas normales (3era. forma normal, etc.) le recomendamos la lectura del
documento “Fundamentos de bases de datos relacionales”, el cual está incluido en el capítulo de “Anexos”
del curso GeneXus no presencial. De lo contrario, puede pedírselo al docente.
40
Ahora veamos las dependencias funcionales encontradas en las tablas PRODUCT e INVOICELINE:
podemos percibir claramente que la tabla INVOICELINE está violando la 3era. forma normal (existen atributos que
están dependiendo en forma parcial de la clave primaria):
Por lo tanto GeneXus normaliza, quitando los atributos ProductDescription y ProductPrice de la tabla INOVICELINE:
41
GeneXus establece las relaciones
por los nombres de atributos
Conceptos iguales deben tener el mismo nombre y conceptos diferentes deben ser nombrados
diferentes.
GeneXus establece las relaciones a través de los nombres de los atributos, de modo que cuando
encuentra atributos de igual nombre en distintas transacciones, entiende que se trata del mismo
concepto, y mediante dicho conocimiento es que puede normalizar.
En el ejemplo que venimos viendo, cuando GeneXus encuentra el atributo de nombre CustomerId
tanto en la transacción “Customer” como en la transacción “Invoice”, analiza que: el atributo se llama
igual en ambas transacciones, así que se refiere al mismo concepto. En la transacción “Customer”,
CustomerId está marcado como identificador, lo que significa que será clave primaria en la tabla
física CUSTOMER; entonces en la tabla física INVOICE será clave foránea.
InvoiceId Æ CustomerId
CustomerId Æ CustomerName
así que GeneXus incluirá CustomerName en la tabla física CUSTOMER (y no en la tabla física
INVOICE).
42
Atributos almacenados e inferidos
Al definir las transacciones “Customer” y “Product”, hemos incluido en ellas ciertos atributos que no
hemos eliminado de la transacción “Invoice”.
Todos estos atributos han quedado en más de una transacción: se han dejado en la transacción
“Invoice” tal como se habían definido en un principio, y se han incluido en las transacciones
“Customer” y “Product” respectivamente, porque nos hemos percatado de la necesidad de crear estos
objetos.
Probablemente usted no comprenda la razón por la cual los atributos secundarios CustomerName,
ProductDescription y ProductPrice se han dejado en la estructura de la transacción “Invoice”.
Ahora, ¿con qué finalidad hemos dejado los atributos secundarios CustomerName, ProductDescription
y ProductPrice en la estructura de la transacción “Invoice”? Los hemos dejado para poder incluirlos en
alguno de los forms (GUI-Windows y/o Web) asociados a la transacción “Invoice” y así poder
visualizar los valores de dichos atributos en tiempo de ejecución.
43
Es conveniente usar padrones
para los nombres de atributos
44
Nombrado de atributos:
Nomenclatura GIK
…y en inglés:
Componente de Entidad (u Objeto):Una Entidad es un actor (ej: Customer), objeto o evento (ej:
Vendor Invoice, factura de venta) que interviene en una aplicación dada, representado por una
transacción Genexus2. Un Componente de Entidad, representa a cualquier nivel subordinado o paralelo
que defina la entidad.
Ejemplo: la factura tiene los siguientes componentes, Invoice (cabezal), InvoiceLine (líneas de la
solicitud).
Se sugiere un largo de un entorno de 10 caracteres para representar el componente de la Entidad.
Categoría: Es la categoría semántica del atributo, es decir, define el rol que el mismo asume dentro del
objeto y dentro del entorno de la aplicación. Se sugiere que no supere los 10 caracteres.
Ejemplos: Id (identificador), Code (código), Name (nombre), Date (fecha), Description (descripción),
Price (precio), Stock.
--------------------------------------------------------------------------------------------------------------
1 Para países que utilicen lenguas en las que los adjetivos suelan colocarse después del sustantivo. En el
45
Calificador: Es cualquier adjetivo o adverbio, en el entorno de 10 caracteres, que agregue
diferenciación conceptual al nombre del atributo para hacerlo único.
En general refiere al texto que califica la categoría: Fecha de vencimiento,
Ejemplos: Start (inicial), End (final), Due (vencimiento)
Complemento: Texto libre hasta completar la cantidad de caracteres significativos (30) para el nombre.
Nota 1: Las letras mayúsculas permiten establecer fronteras entre los componentes que forman a los
nombres de atributos.
Nota 2: Para cada componente se pueden utilizar la cantidad de caracteres que se deseen, aunque se
sugiere utilizar 10 y que el nombre completo del atributo no supere los 30.
46
Demo
ÎCreación de base de conocimiento
ÎCreación de transacciones
Para crear una base de conocimiento, se debe seleccionar en la barra de menú de GeneXus, el ítem
File / New Knowledge Base. A continuación aparecerá un diálogo que solicitará la ubicación y
nombre de la base de conocimiento a crear.
Una vez creada la base de conocimiento, la misma quedará abierta en el modelo de Diseño, para
que se empiecen a crear las transacciones.
Antes que nada debemos posicionarnos (haciendo clic simplemente) en la carpeta “Objects” del
árbol que se encuentra en la división izquierda de la pantalla, ya que los objetos que vayamos
creando irán quedando dentro de dicha carpeta.
La creación de objetos, se realiza mediante el ítem Object / New Object de la barra de menú de
GeneXus.
Al seleccionar el ítem Object / New Object se desplegará un diálogo en el cual se deberá elegir el
tipo de objeto que se desea crear (en este caso el tipo de objeto: transacción), dar un nombre al
objeto que se está creando (por ejemplo: “Customer”), una descripción larga, y se podrán indicar
algunas cosas más que iremos viendo.
Una vez creada la transacción, la misma quedará abierta para que se defina su estructura.
47
Definición de atributos
Para definir un atributo, simplemente se debe digitar en el primer campo de una línea (o rama)
de la estructura, el nombre del atributo que se desea crear. Mediante la tecla de tabulación se
puede pasar a los siguientes campos para indicar el tipo de datos del atributo, así como su
descripción, si admitirá valores nulos de la base de datos, y en el caso que el mismo vaya a ser
una fórmula (concepto que veremos en breve), cómo calcularla. Con la tecla Enter se puede
pasar a la siguiente línea, para definir otro atributo.
Una vez definidos los atributos en la estructura de la transacción, solamente restará guardar /
salvar los cambios.
Para indicar que uno o más atributos son identificadores en la transacción, se los debe
seleccionar y presionar CTRL + K; en consecuencia aparecerán con un símbolo de llave.
Para definir que comienza otro nivel en la transacción, se debe digitar CTRL + L, y
automáticamente se producirá una indentación y el usuario deberá darle nombre a ese nivel,
así como definir el nombre de su tipo de datos1 y una descripción para el nivel.
Para indicar que un atributo de uno de los niveles de la transacción será el atributo
“descriptor”, se lo debe seleccionar y presionar CTRL+D.
GeneXus cuenta con menús pop up2, que son menús que se abren cuando el usuario está
posicionado en determinado contexto y da clic con el botón derecho del mouse. Por ejemplo, al
hacer clic con el botón derecho del mouse sobre un atributo de la estructura, se abre un
menú pop up sobre el atributo seleccionado, que ofrece varias utilidades, como por ejemplo
indicar que el atributo es clave (opción “Toggle key”), quitarlo de la estructura, pasarlo a un
siguiente nivel de anidación, etc.
Cada atributo tiene propiedades. Algunas de ellas (las fundamentales y obligatorias) son las
que se ofrecen directamente en la estructura para su ingreso “inline”. Para acceder al diálogo
que permite configurar todas las propiedades de un atributo, se debe seleccionar el ítem
Properties del menú contextual.
----------------------------------------------------------------------------------------------------
1 Veremos su utilidad cuando estudiemos los “business components”.
2 También llamados “contextuales” dado que varían según el contexto.
48
La posibilidad de modificar las propiedades de un atributo solo estará disponible en el modelo de Diseño, ya que
es en este modelo de la base de conocimiento en el que se diseñan y editan las transacciones y atributos.
El diálogo de configuración de las propiedades de un atributo tiene 4 solapas: General, Control Info, Help y
Documentation.
Description: La “Descripción” o más propiamente “Nombre largo” es una descripción ampliada del atributo. Debe
identificar bien al atributo, con independencia del contexto, y principalmente debe ser entendible por el usuario final.
Debe ser un identificador global del atributo, es decir, no se le debe asignar a dos atributos en la base de conocimiento
la misma descripción, ya que será a través de esta descripción que el usuario final podrá seleccionar atributos para
definir sus propias consultas a la base de datos, con GeneXus Query, ejecutando “reportes dinámicos” (tema bastante
simple, pero que no se abordará en este curso).
Relacionadas a esta propiedad, están las propiedades Title y Column Title, que por defecto toman el mismo valor que
se especifica en Description, pudiéndose modificar. Estas propiedades se describen a continuación:
Title: La descripción que se ingrese aquí será colocada por defecto al lado del atributo cada vez que se utilice en
salidas “planas” como en el primer nivel de los forms de las transacciones, o en reportes generados con el Report
Wizard. El valor de esta propiedad inicialmente se hereda de la propiedad Description del atributo, pudiendo ser
modificada por el programador.
Column Title: La descripción que se ingrese aquí será colocada por defecto como título del atributo cada vez que se lo
incluya en la columna de un grid (grilla). El valor de esta propiedad inicialmente se hereda de la propiedad
Description del atributo, pudiendo ser modificada por el programador.
Domain: Permite asociarle un dominio1 al atributo. Al hacerlo, ciertas propiedades del atributo se deshabilitarán (como
Data Type y Length) tomando los valores del dominio. En caso de que el atributo no pertenezca a un dominio, el
programador dejará el valor [none] en esta propiedad, y las correspondientes al tipo de datos del atributo estarán
habilitadas para ser ingresadas.
Data Type: Permite indicar el tipo de datos del atributo. Aquí se podrá elegir uno de los tipos de datos soportados por
GeneXus. Dependiendo del tipo de datos que se seleccione habrá ciertas propiedades, u otras, para configurar.
Length: Permite indicar el largo del atributo. Si en la propiedad Data Type se indica que el atributo es numérico,
entonces se deberá tener en cuenta que el largo incluya las posiciones decimales, el punto decimal y el signo. Esta
propiedad estará deshabilitada cuando el tipo de datos elegido no requiera establecer un largo (por ejemplo, si se trata
del tipo de datos Date).
Decimals: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad, para que
se especifique la cantidad de decimales. El valor 0 en este campo, indicará que se trata de un entero.
Signed: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad para que se
indique si manejará signo o no. El valor por defecto es “False”, lo que indica que no se representarán valores
negativos.
Value Range: Permite indicar un rango de valores válidos para el atributo. Por ejemplo:
• 1:20 30: - significa que los valores válidos son entre 1 y 20; y 30 o mayor.
• 1 2 3 4 - significa que los valores válidos son 1, 2, 3 o 4.
• 'S' 'N' - significa que los valores válidos son 'S' o 'N'.
Picture: Permite indicar el formato de edición para la entrada y salida del atributo. Dependiendo del tipo de datos del
atributo, aparecerán determinadas propiedades bajo esta categoría.
GeneXus provee más propiedades para los atributos que las recién mencionadas. Dependiendo del valor que se elija
para determinada propiedad, se ofrecerán ciertas propiedades relacionadas, u otras. Recomendamos para la lectura de
otras propiedades, acceder al Help de GeneXus.
-------------------------------------------------------------------------------------------------------------------------------1 Los
dominios se abordarán más adelante en el curso, pero a modo de resumen, el objetivo de los dominios es realizar
definiciones de datos genéricas. Por ejemplo: se puede definir un dominio de nombre Precio y tipo de datos
Numeric(10,2) y luego asociarle este dominio a todos los atributos que almacenan precios. Esto tiene la ventaja de que
si después se desea modificarlo a por ejemplo Numeric(12,2), hay que modificar solamente la definición del dominio y
automáticamente todos los atributos basados en ese dominio heredan el cambio.
49
2) Control Info
A un atributo se le puede asociar un tipo de control. Los tipos de controles posibles son:
- Edit
- Radio Button
- Check Box
- Combo Box
- List Box
- Dynamic Combo Box
- Dynamic List Box
La asociación de cierto tipo de control a un atributo, sirve para especificar el tipo de control por defecto que se utilizará
para el atributo cada vez que se lo incluya en un form.
Cuando se define un atributo el tipo de control que tiene asociado es Edit, pudiendo el programador cambiarlo a
cualquiera de los otros tipos.
En la solapa Control Info del diálogo de edición de las propiedades de un atributo es donde el programador podrá
cambiar el tipo de control asociado al atributo y dependiendo del tipo de control seleccionado, se solicitará distinta
información complementaria.
Luego, cada vez que se agregue el atributo en un form se presentará automáticamente con el tipo de control que
tenga asociado aquí.
Nota
En caso de asociar al atributo el tipo Edit, se podrá especificar que “disfrace” sus valores, mostrando los de otro
atributo (propiedad InputType), e incluso que sugiera los valores posibles al usuario, a medida que éste vaya
digitando (propiedad Suggest), entre otras, como veremos luego, cuando estudiemos “Descripciones en lugar de
códigos”.
También se puede elegir el color de la fuente de letra que se desea asociar por defecto al atributo, así como el color de
fondo, de modo que cada vez que se agregue el atributo en un form, se presente automáticamente con los colores que
se le hayan asociado.
3) Help
Esta solapa permite que el programador ingrese un texto de ayuda asociado al atributo, para que el usuario final pueda
consultarlo en tiempo de ejecución. Si el usuario final solicita ayuda (presionando F1), estando posicionado en el
atributo, se le desplegará el texto que se haya ingresado aquí.
Más adelante, veremos otros temas relacionados al help de una aplicación GeneXus.
4) Documentación
Esta solapa permite que el programador ingrese documentación técnica del atributo, útil para los desarrolladores.
50
Tipos de Datos
VarChar
- Equivalente a Character, salvo en la forma en que se almacena en la BD.
- Propiedades Maximum Length y Avarage Length asociadas.
Long Varchar
- Permite almacenar textos largos, comentarios, etc. (memo).
DateTime
- Permite almacenar una combinación de fecha y hora.
Blob
- Permite almacenar cualquier tipo de información: texto, imágenes,
videos, planillas, etc., en la base de datos.
- Win / Web ofrecen manipulación distinta de este tipo de datos
Numeric: Permite almacenar datos numéricos. Cuando se selecciona este tipo de datos se debe indicar
la cantidad total de dígitos del número, la cantidad de posiciones decimales, y si permite signo o no.
Ejemplo:
Si definimos que el tipo de datos del atributo InvoiceTotal es numérico de largo 10, con decimales 2, y
sin signo, estamos especificando que representará números con hasta 7 dígitos en la parte entera y 2
decimales (7 dígitos en la parte entera + punto + 2 dígitos para los decimales = 10 dígitos).
Character: Permite almacenar cualquier tipo de texto (caracteres y dígitos). Para este tipo de datos,
se debe indicar el largo.
Ejemplo: El atributo CustomerName que utilizamos para almacenar el nombre de un cliente, es de tipo
Character y si sabemos que nunca un nombre tendrá más de 20 caracteres, podemos fijar el largo: 20.
51
• VarChar: Permite almacenar texto de largo variable. Su función, en contraposición al Character, es optimizar el
almacenamiento en la base de datos.
Cuando se selecciona el tipo de datos VarChar en el diálogo de definición del atributo se agregan las 2 siguientes
propiedades: Maximum Length y Average Length.
La primera es para indicar el largo máximo de caracteres que se podrán almacenar, mientras que la segunda es
para indicar el largo promedio de caracteres que se suele almacenar por lo general.
Ejemplo: Cuando se define un atributo de tipo Character y largo 60, si se le ingresa un dato que ocupa 25
caracteres, la capacidad restante de almacenamiento del atributo (35 caracteres) se rellena con blancos. El tipo
de datos Varchar, en cambio, optimiza el almacenamiento de la siguiente forma: se le define Average Length
(por ejemplo: 25), y Maximum Length (por ejemplo: 60); entonces, si el dato tiene largo menor o igual que 25,
se lo almacena en el campo (rellenado con blancos) mientras que en los casos que el dato sea de largo mayor que
25, se almacenan los primeros 25 caracteres en el campo, y el resto en un área de overflow.
Como contrapartida a la ventaja de la optimización del almacenamiento, para los casos en que se utilice el área de
overflow, será necesario realizar un acceso adicional tanto para la lectura como para la escritura del dato.
El tipo de datos Varchar es equivalente al tipo Character en todos los sentidos, salvo en la forma en que se
almacena en la base de datos. Se le pueden aplicar todas las funciones y operadores existentes para el tipo de
datos Character. Si el DBMS no soporta este tipo de datos, se creará el atributo de tipo Character.
Long Varchar: Permite definir un atributo memo; es decir, se utiliza normalmente para almacenar textos
largos, comentarios, etc. Al seleccionar este tipo de datos, se debe indicar un largo máximo.
Existen dos funciones para manipular este tipo de datos: GXMLines y GXGetMLi.
GXMLines retorna la cantidad de líneas que tiene un atributo Long Varchar, teniendo como parámetros el nombre
del atributo, y la cantidad de caracteres que se desea considerar por línea.
GXGetMLi por su parte, extrae una línea del atributo Long Varchar (para luego imprimirla, por ejemplo); teniendo
como parámetros el nombre del atributo, el número de línea deseado, y la cantidad de caracteres que se desea
considerar por línea.
Generalmente se usan estas 2 funciones en combinación, ya que primero se suele consultar la cantidad de líneas
que tiene cierto atributo Long Varchar, indicando la cantidad deseada de caracteres por línea, y luego se prosigue
extrayendo el contenido de las líneas, utilizando un bucle hasta llegar a la última línea, y de esta forma se
imprimen, por ejemplo.
DateTime: Permite almacenar una combinación de fecha y hora.
La propiedad Picture de este tipo de datos, permite elegir qué se desea mostrar de la fecha, y qué se desea
mostrar de la hora.
Acerca de la fecha se puede elegir: no manejarla, manejarla y presentar el año con 2 dígitos, o manejarla y
presentar el año con 4 dígitos. Acerca de la hora se puede elegir: manejar solo 2 dígitos para la hora (no
manejando minutos ni segundos), manejar 2 dígitos para la hora y 2 dígitos para los minutos (no manejando
segundos), o manejar 2 dígitos para la hora, 2 dígitos para los minutos y 2 dígitos para los segundos.
Los valores anteriores no afectan la forma de almacenar el tipo de datos sino específicamente la forma de
aceptar o mostrar su contenido.
Nota: En caso de no manejar la fecha, sino solo la hora, el valor de fecha que quedará en el campo será el
mínimo soportado por el DBMS, y será reconocido por GeneXus como fecha vacía o nula. En lo que respecta a la
hora, los valores no aceptados (minutos y/o segundos) serán almacenados con valor cero.
Blob: Ante el creciente manejo de imágenes digitalizadas, videos, planillas así como documentos de todo tipo,
las aplicaciones requieren cada vez más mantener y trabajar con este tipo de información.
El tipo de datos Blob permite almacenar esta diversidad de información en la propia base de datos, aprovechando
así los diferentes mecanismos de integridad y control que proveen los DBMSs.
Este tipo de datos solamente se puede utilizar cuando se cuenta con un DBMS.
Dependiendo de si se implementa la aplicación en ambiente Win o Web, será la forma de manipulación de un
atributo de este tipo de datos. El ambiente Web ofrece más funcionalidades para trabajar con atributos de tipo
Blob de forma muy amigable.
Para profundizar en el conocimiento de este tipo de datos puede dirigirse al Help de GeneXus.
52
Definición de variables
Para definir variables en determinado objeto, estando dentro del mismo, se debe presionar el botón
de la barra de herramientas “Fast Access” de GeneXus, y se abrirá el diálogo de definición de
variables, mostrado en la transparencia.
Este diálogo muestra variables definidas por defecto (como por ejemplo la variable Today que contiene la
fecha del sistema) para el objeto, y permite definir variables nuevas mediante los botones “Add” y “Add
Based On”.
Botón “Add”
Al seleccionar el botón “Add”, se abre el siguiente diálogo para la definición de una variable:
Este diálogo solicita el nombre de la variable, su descripción, tipo de datos y largo, o dominio, de forma
análoga a cuando se define un atributo.
La propiedad Dimensions permite indicar si la variable será escalar o si se tratará de un vector (1
dimensión) o matriz (2 dimensiones). El valor que ofrece por defecto esta propiedad es escalar.
53
Botón “Add Based On”
El botón “Add Based On” ofrece una forma rápida de definir una variable. Cuando se selecciona, se despliega un
diálogo que muestra todos los atributos definidos en la base de conocimiento; en dicho diálogo, simplemente se debe
seleccionar un atributo, y a continuación se definirá automáticamente una variable con el mismo nombre y la misma
definición que el atributo. Vale aclarar que se pueden seleccionar varios atributos, creándose en tal caso una variable
por cada atributo seleccionado, con sus mismas características.
El botón “Copy” ofrece la posibilidad de copiar en el portapapeles la definición completa de las variables que se hayan
seleccionado previamente, para luego poder pegar dichas definiciones de variables en otro objeto de la base de
conocimiento, con el botón “Paste” de este mismo diálogo; es decir, en el objeto destino, se deberá entrar a este
mismo diálogo, y presionar el botón “Paste”.
Por otra parte, es posible definir variables dentro del editor de código de cada objeto (source, eventos, etc.), haciendo
uso del menú contextual (botón derecho) luego de escribir el nombre de la variable. Esto es, al escribir
&nombreDeVariable (ej: &x) y presionar botón derecho del mouse, se abrirá el siguiente menú contextual:
También es posible editar variables luego de definidas, de esta misma forma. Si al escribir &nombreDeVariable (ej:
&x), la misma ya existe definida para el objeto, el menú contextual mostrará:
Al seleccionar la opción “Define” se abrirá el mismo diálogo que al presionar el botón “Add”. Análogamente, al
seleccionar la opción “Edit” de este menú, se abrirá el mismo diálogo que al presionar el botón “Edit” del diálogo de
definición de variables mostrado en la transparencia.
54
Dominios
Ejemplo:
Es común tener en una base de conocimiento atributos que comparten definiciones similares pero
que no tienen relación entre sí. Por ejemplo, es común definir todos los nombres como atributos de
tipo character y largo 20; o todos los importes, como atributos de tipo numérico y largo 10.2.
Así como podemos asociarle a un atributo un dominio, también lo podemos hacer para una variable.
Un mismo dominio puede asignarse tanto a atributos como a variables, ya que su objetivo es
exactamente el mismo.
Estos accesos para trabajar con dominios solo se encuentran habilitados en el modelo de Diseño de
la base de conocimiento, al igual que todas las funcionalidades que puedan implicar modificaciones
en la base de datos física.
55
Forms
Para cada transacción, GeneXus crea un form GUI-windows y un form Web, los cuales serán la interfaz
con el usuario, en ambiente windows y Web respectivamente.
Ambos forms son creados por defecto por GeneXus al momento de grabar la estructura de la
transacción, y contienen todos los atributos incluidos en la misma, con sus respectivas descripciones,
además de algunos botones.
Si bien son creados por defecto, es posible modificarlos para dejarlos más vistosos, cambiar por ejemplo
controles de tipo edit a otros tipos de controles, agregar y/o quitar botones, etc.
Para editar el form GUI-windows de una transacción, se debe seleccionar la solapa Form que se
encuentra en la barra de edición del objeto, mientras que para editar el form Web, se debe seleccionar la
solapa Web Form en la misma barra.
56
Form GUI-Windows
de la transacción “Invoice”
GRID
A través del form de la transacción (GUI-Windows o Web según el ambiente de trabajo) el usuario podrá
ingresar, modificar y eliminar registros en tiempo de ejecución.
57
Form Web
de la transacción “Invoice”
botón “Get”
GRID
En el ejemplo se muestra el form Web correspondiente a la transacción “Invoice”. A través de este form el
usuario final podrá ingresar, modificar y eliminar facturas en la aplicación Web.
Pueden verse algunas diferencias en el diseño gráfico del form Web respecto al form Win, pero podemos ver
que todos los controles que están en el form Win, también lo están en el Web. El recíproco no se cumple: el
botón “Get” al lado de la clave primaria aparece en el form Web y no en el Win que hemos visto1. El control
“Error Viewer” aparece también solamente en el form Web. Y el grid correspondiente a las líneas de la factura,
contiene en el form Web una primera columna con un check box, que el grid del form Win no la incluye.
Comentaremos a continuación el control Error Viewer. Sobre el botón “Get” entenderemos el por qué de esta
diferenciación un poco más adelante, cuando estudiemos el diálogo con validación a nivel del cliente. Con
respecto al check box también veremos este tema más adelante, cuando estudiemos la ejecución.
En una aplicación con interfaz Win, los mensajes que deban mostrarse al usuario en tiempo de ejecución se
desplegarán en una ventana de Windows independiente, que no programamos nosotros. En Web, en cambio,
los mensajes2 deben mostrarse dentro de la página HTML que contiene el form de la transacción. Es por este
motivo que existe el control Error Viewer, para poder ubicar y programar el lugar donde queremos que los
mensajes generales le sean desplegados al usuario final de una transacción Web.
Podremos especificar entre otras cosas el lugar donde queremos que este control se ubique dentro del form, su
tipo de letra, color, etc.
Existen algunas diferencias más entre ambos forms, pero son menores dado que representan formas distintas
de realizar lo mismo: por ejemplo, mientras que el botón para confirmar las acciones y comenzar la grabación
de los registros de la transacción en el form Win tiene como texto “Confirm”, en el Web su texto es “Apply
Changes”. Análogamente, mientras el botón para eliminar todo (cabezal y líneas) tiene como texto solo
“Delete” en el form Win, en el Web dice “Delete All”. La funcionalidad es la misma.
Por último vale mencionar que en el form Web se puede percibir el botón Check, que permite chequear que
todo lo que ha ingresado el usuario hasta el momento sea correcto.
Más adelante, cuando estudiemos cómo se trabaja en ejecución con las transacciones, arrojaremos más luz
sobre estos asuntos.
-----------------------------------------------------------------------------------------------------------------------
1 Más adelante cuando estudiemos el diálogo con validación a nivel del cliente, veremos que en ambiente Win
se podrá optar por trabajar con dicho diálogo o no, y dependiendo de ello, el botón “Get” estará presente en el
form o no.
2 Cuando estudiemos el diálogo con validación a nivel del cliente en Web, veremos que muchos de los mensajes
se desplegarán en cajas de texto sobre la pantalla, específicamente sobre los campos que el usuario haya
ingresado y en los que se deba informar de algo al usuario. Es decir, funcionarán de manera combinada el
control Error Viewer junto con los mensajes interactivos en cajas de texto.
58
Paletas de herramientas para
diseño de forms Win y Web
Insertar controles:
Podemos definir un control como un área de la interfaz con el usuario, que tiene una forma y un
comportamiento determinado.
Existen distintos controles:
La mayoría de los controles anteriores (salvo línea y recuadro) están disponibles tanto para ser utilizados en
interfaz GUI-Windows como Web. A su vez el “tab control” es un control que solo está disponible para ser
utilizado en interfaz GUI-Windows, mientras que otros controles solo pueden utilizarse en interfaz Web (text
blocks, tablas, grids freestyle, Error Viewer, etc.).
Tab Control: tiene una o varias solapas, en las cuales se pueden distribuir controles. Cada solapa tiene un
título y un área útil para que se le incluyan controles. El uso de este control es útil para los casos en los
cuales se quiere distribuir los datos en distintos grupos, para presentarlos de forma amigable para el usuario.
Por ejemplo, si queremos dividir la información de la transacción “Customer” en 2 solapas: una para ingresar
los datos generales del cliente, y otra para ingresar los datos comerciales, podemos insertar en el form un
Tab Control con dos solapas, y distribuir los controles de texto, atributo, etc. en cada una de ellas.
Paleta de herramientas para insertar controles: Cuando se está editando un form, se encuentra
disponible una paleta de herramientas que ofrece los controles posibles de insertar en el mismo.
Simplemente se deberá seleccionar en la paleta de herramientas el control que se desee insertar en el form,
para lo cual se deberá hacer clic en el ícono que lo represente; seguidamente se deberá hacer clic en el form,
en el lugar que se desee ubicar el control, y se insertará el control elegido en el lugar del form que se haya
indicado; en caso de ser necesario, se abrirá el diálogo correspondiente a las propiedades del control, para
que se indiquen aquellas que sean de carácter obligatorio.
59
Controles en form Web
• Cada control del form Web podrá tener una clase asociada, de
un objeto theme (tema) determinado, asociado al objeto.
Una de las ventajas de las aplicaciones con interfaz Web frente a las mismas aplicaciones con interfaz GUI-
Windows, refiere a los aspectos de diseño. Las aplicaciones Web, programadas en HTML, pueden llegar a
ser verdaderamente vistosas.
Para lograr separar los aspectos de diseño gráfico de un sitio web, de los aspectos de programación
propiamente dichos, existe un tipo de objeto llamado Theme (Tema, en español), y un utilitario
independiente para editar objetos de este tipo, el Editor de Temas. Este utilitario puede también invocarse
desde GeneXus mediante el ítem Tools/Theme Editor estando en el modelo de diseño.
Un objeto “tema” contendrá un conjunto de clases, para cada tipo de control de los que pueden aparecer
en el form Web de un objeto GeneXus. Por ejemplo, tendrá varias clases para el control botón, algunas
para el control atributo, una clase para el control Error Viewer, etc.
Cuando se crea una base de conocimiento, automáticamente aparecerá dentro de la carpeta “Objects”
(que contendrá todos los objetos GeneXus que se vayan creando) un objeto de tipo Theme cuyo nombre
es “Default”. Este tema contiene un conjunto de clases asociadas a los distintos controles existentes2.
Los objetos con form Web que se vayan creando tendrán por defecto asociado este Theme, y cada
control que aparezca en sus forms tendrá asociado una clase de ese tema, para este control.
-------------------------------------------------------------------------------------------------------------
1 Al final de este capítulo se mencionará que al igual que cada atributo tiene un conjunto de propiedades
configurables por el programador, así cada modelo y cada objeto. Por ejemplo, la propiedad Theme se
encontrará para configurar en los diálogos de propiedades asociados a cada modelo Web y a cada objeto
que tenga form Web (transacciones y Web Panels).
2 No solo existen clases para los controles en un tema, pero de esto no nos ocuparemos en este curso.
60
Controles en form Web
Ejemplo: transacción “Customer” con tema “Default”
De este modo, cuando el analista crea la transacción “Customer” e ingresa su estructura, al grabar
podrá apreciar que el form Web tendrá automáticamente el aspecto que se muestra en la pantalla de
arriba a la izquierda. ¿Quién dio formato a todos los controles si no lo hizo el analista explícitamente?
Pues bien, todas las transacciones tendrán asociado por defecto el theme “Default” y todos los controles
que se coloquen en el form, tendrán clases de este tema asociadas.
Si sobre el botón “Apply Changes”, con el botón derecho del mouse se editan sus propiedades, se abrirá
un diálogo (que se presenta abajo a la izquierda) y en el mismo se puede observar la propiedad Class,
que tiene configurada la clase BtnEnter que es una clase de botón. Esta propiedad puede ser cambiada
por el analista (podemos ver que se trata de un combo box). All cliquear el combo box podremos ver
una lista de clases posibles para ese botón (son las que figuran en el tema asociado), pero también
aparece la opción “(none)” para no asociarle clase alguna. Esto es lo que hemos seleccionado en la
pantalla de la derecha (el valor “none” para la propiedad Class del botón) y podemos ver su repercusión
inmediata en el botón en el form.
Si se comparan los dos diálogos de propiedades del control “Apply Changes”, donde lo único que ha sido
modificado explícitamente por el analista ha sido la clase, podemos ver que implícitamente se han
modificado algunas propiedades (el BackColor, ForeColor, BorderWith, etc.). Esto se debe a que estas
propiedades se heredan de la clase una vez definida, pero si el analista así lo desea, puede modificarlas
para ese control botón, independizándolo de seguir el comportamiento de su clase.
Sobre este tema podrá profundizar si así lo desea en el curso “Desarrollo de Aplicaciones para Internet”,
tanto presencial como no presencial.
Aquí solamente pretendemos tener un barniz del uso de los temas y las clases de los controles.
61
Demo
WIZARD MANUAL
Para definir un nuevo modelo -a excepción del de Diseño, que se crea automáticamente-, debe
seleccionarse la opción File / New Model.
GeneXus provee de un wizard para guiar al usuario en los pasos de creación del nuevo modelo.
Si el usuario prefiere no utilizar el wizard e ingresar los distintos datos sin esta guía, deberá
presionar el botón Manual Creation de la primera pantalla del wizard, y éste se cerrará y se abrirá
el diálogo Model Properties.
Information
Language: Idioma en el que saldrán impresos los textos de los botones, mensajes, etc., que son
generados automáticamente por GeneXus en los programas de la aplicación. Los lenguajes
soportados son: Español, Inglés, Italiano, Portugués y Chino. El valor del lenguaje solo puede
modificarse en el modelo de Diseño de la base de conocimiento; los demás modelos heredarán
este valor y no podrán modificarlo.
Se debe suministrar información del ambiente o plataforma para el modelo que se está definiendo:
Environment
Language: Este es el lenguaje en el cuál serán generados los programas usados para la creación
y reorganización de la base de datos, y también es el lenguaje predeterminado en el que serán
generados todos los objetos. Pueden definirse ambientes secundarios en la solapa Environments
del diálogo, que permitirán generar algunos de los objetos en otros lenguajes. Los lenguajes
soportados por GeneXus son: .NET, .NET Mobile, C/SQL, Cobol para iSeries, Java, RPG para
iSeries, Visual Basic, Visual FoxPro.
62
User Interface: Una vez que el lenguaje ha sido elegido, se puede seleccionar el tipo de interfaz que se quiere usar
para el ambiente principal: Win o Web Form. Existen lenguajes que solo soportan un tipo de interfaz.
DBMS: Aquí se debe seleccionar el DBMS (Database Manager System) sobre el cuál operarán los programas en
ejecución. La lista solo incluirá aquellos soportados por el lenguaje e interfaz previamente seleccionados.
Target Path: Directorio donde estarán ubicados los programas generados. Este directorio será creado bajo el
directorio de la base de conocimiento. El valor predeterminado es DATAnnn, donde nnn representa el número de
modelo (GeneXus numera secuencialmente los modelos a medida que se van creando).
A continuación se muestra una tabla con los valores de DBMSs posibles para cada lenguaje:
Properties
Haciendo clic en el botón Properties se pueden configurar las propiedades para el lenguaje seleccionado. Solemos
llamar a estas propiedades: propiedades a nivel del modelo o más reducido: propiedades del modelo.
DBMS Options
Haciendo clic en el botón DBMS Options se puede configurar la información requerida para el acceso a la Base de
Datos (Data Source, etc.) para el lenguaje y DBMS seleccionados.
Execution
Haciendo clic en este botón, se pueden especificar configuraciones de ejecución para el lenguaje seleccionado.
Save/Load
Estos botones permiten almacenar la información de las propiedades del modelo en un archivo (y luego recuperarlo).
La extensión predeterminada de este archivo es GMP (GeneXus Model Properties).
From Model
Permite copiar la información en forma directa, desde otro modelo de Prototipo / Producción dentro de la base de
conocimiento.
Una vez creado un modelo, es posible modificar sus propiedades. Para ello, se debe seleccionar la opción File / Edit
Model, y se presentará el diálogo Model Properties.
Suele ser usual acceder al diálogo Model Properties, en especial para utilizar los botones Properties, DBMS Options
y Execution para configurar sus propiedades con los valores particulares que se requiera para el modelo. Sin
embargo no es usual cambiar en el diálogo Model Properties lo configurado en la sección Enviroment (ya que si se
desea probar la aplicación para otra plataforma / ambiente, la forma correcta de proceder es crear otro modelo para la
plataforma deseada).
63
¿Qué son los conceptos...?
ÎAnálisis de Impacto
ÎReorganizar
ÎEspecificar
ÎGenerar
Esta comparación que hace GeneXus entre las definiciones del modelo de Diseño de una base de
conocimiento y las definiciones de cierto modelo de Prototipo o Producción al cual el programador
pasa, se llama análisis de impacto. Este nombre es autodescriptivo: GeneXus analiza el impacto
causado por las definiciones del modelo de Diseño sobre un modelo de Prototipo o Producción. El
resultado del análisis de impacto es un reporte de análisis de impacto (IAR: Impact Analisis
Report) que informa al programador qué cambios físicos o estructurales habría que realizar en la
base de datos asociada al modelo de Prototipo o Producción en cuestión.
Cuando se decide efectuar una reorganización GeneXus genera programas (en el lenguaje elegido
en la definición de la plataforma del modelo) que implementan las modificaciones a realizar en la
base de datos. La ejecución de estos programas tiene por resultado la obtención de una nueva
versión de la base de datos con los cambios efectuados.
64
Inmediatamente después de reorganizar la base de datos, se copiarán automáticamente las nuevas definiciones
del modelo de Diseño al modelo de Prototipo o Producción en el cual se esté trabajando (destino); esto se llama
Copy Model y significa que el modelo de Prototipo o Producción quedará con exactamente las mismas definiciones
que el modelo de Diseño.
El siguiente paso será obtener los programas de aplicación asociados a los objetos GeneXus. Para ello el
programador GeneXus deberá especificar y generar los programas de aplicación.
Especificar un objeto significa que GeneXus analizará todo lo definido en cada uno de los elementos que lo
componen: estructura, forms, u otros elementos según corresponda. GeneXus verificará la sintaxis de las
definiciones, la validez de lo definido, y como resultado de la especificación mostrará al usuario un listado de
navegación, en el cual informará la lógica que ha interpretado, y si hay alguna advertencia o error.
Otro ejemplo
Como hemos explicado, cuando se pasa a un modelo de Prototipo o Producción GeneXus realiza una comparación
de las definiciones del modelo de Diseño con respecto a las definiciones que existan en el modelo al cual se está
pasando. Esto es, un análisis de impacto.
Si se pasa a un modelo de Prototipo o Producción al cual ya se había pasado anteriormente alguna vez,
seguramente dicho modelo ya tenga algunas definiciones1 , a diferencia del ejemplo anterior en el cual se trataba
de un modelo nuevo y vacío.
Si por ejemplo el modelo de Prototipo o Producción al cual se está pasando tiene definida una transacción
“Customer” con la estructura:
Customer
CustomerId* - Numeric(6)
CustomerName - Character(20)
CustomerAddress - Character(30)
CustomerGender - Character(1)
y en el modelo de Diseño se encuentran definidas las transacciones “Customer” y “Product” con las siguientes
estructuras:
Customer Product
CustomerId* - Numeric(4.0) ProducId* - Numeric(4.0)
CustomerName - Character(30) ProductDescription - Character(20)
CustomerAddress - Character(30) ProductPrice - Numeric(10.2)
CustomerGender - Character(1) ProductStock - Numeric(4.0)
CustomerEMail - Character(30)
el análisis de impacto determinará que en el modelo de Prototipo al cual se está ingresando, se deberá:
Estos cambios se informarán en un reporte de análisis de impacto (IAR: Impact Analisis Report), y el
programador deberá estudiarlo para decidir si efectuar la reorganización o no.
-------------------------------------------------------------------------------------------------------------------------
1 Cabe la posibilidad de que en una base de conocimiento haya algún modelo de Prototipo o Producción sin objetos.
Esto puede ocurrir por el simple motivo de que se haya creado un modelo, pero luego no se haya ejecutado una
reorganización en el mismo, ni se hayan copiado las definiciones del modelo de Diseño (Copy Model) tampoco. Sin
embargo, el ejemplo trata de un modelo Prototipo en el cual ya se ha ejecutado una reorganización y seguidamente
se han copiado las definiciones del modelo de Diseño (Copy Model) al mismo.
65
En el caso de decidir reorganizar la base de datos, seguidamente se copiarán automáticamente las nuevas
definiciones del modelo de Diseño al modelo de Prototipo, quedando éste con exactamente las mismas definiciones
que el modelo de Diseño. (Copy Model).
Por último sólo restará que el programador GeneXus especifique y genere los programas correspondientes a los
objetos que hayan sufrido cambios.
Concluyendo, hemos explicado varios conceptos que son muy importantes, viendo una primera aplicación de ellos,
y luego una segunda aplicación de los mismos, con el objetivo de entender qué realiza cada una de estas
operaciones y en qué orden se ejecutan.
66
Ítem “Build” de la barra de menú de GeneXus
El ítem Specify se habilitará cuando se esté en un objeto abierto; el objetivo de este ítem es especificar el
objeto activo.
El ítem Specify..., abrirá un diálogo de selección de objetos para seleccionar cuáles objetos se desean
especificar.
Y el ítem Build All..., permitirá especificar todos los objetos del modelo.
Vale aclarar que será exactamente lo mismo seleccionar el ítem Specify... y elegir todos los objetos del
modelo para ser especificados, que seleccionar directamente el ítem Build All....
Luego de especificar uno o varios objetos -mediante cualquiera de los ítems anteriores- GeneXus presentará
un listado de navegación por cada objeto especificado; cada listado de navegación informará cuál fue la lógica
interpretada para el objeto, y si será necesario generar el programa de aplicación asociado al mismo, o no1. La
ventana que contiene los listados de navegación incluirá un botón de nombre Generate, que podrá ser
seleccionado por el programador para continuar con la generación de los programas de aplicación que sean
necesarios.
Además del botón Generate, se ofrecerá también un botón Cancel. Es importante tener claro que en caso de
seleccionarlo, quedará pendiente la generación de los programas de aplicación asociados a los objetos que
fueron especificados; esto significa que en la siguiente oportunidad que el programador seleccione el botón
Generate –así haya especificado en ese momento un solo objeto o varios- se generarán además de los
programas que correspondan ser generados en esa ocasión, todos los programas pendientes de generación,
por haberse especificado anteriormente sin continuar con la generación2.
--------------------------------------------------------------------------------------------------------------------
1 El motivo por el cual un listado de navegación podrá informar que no será necesario generar el programa
asociado a un objeto, es que el objeto no haya sufrido cambios con respecto a la última vez que se generó, y
por ende el programa generado antes seguirá estando vigente ahora.
2 Por esto solemos decir que podemos elegir qué objetos especificar, pero no cuáles generar.
67
¿En qué modelos se especifican y generan los objetos? ¿Diseño? ¿Prototipo? ¿Producción?
En los tres tipos de modelos es posible especificar los objetos, pero solamente en los modelos de Prototipo y
Producción se podrán generar los programas de aplicación asociados a ellos.
Recordemos que todo modelo de Prototipo o Producción tiene asociada una plataforma (base de datos,
lenguaje de programación, interfaz GUI-Windows / Web). En cambio, el modelo de Diseño no tiene asociada
una plataforma y por tanto no se generará para el mismo base de datos ni programas. Es por esto que en el
modelo de Diseño el programador podrá especificar objetos con el único objetivo de estudiar los listados de
navegación resultantes y con ello poder analizar la lógica interpretada por GeneXus para los mismos. Sin
embargo, luego de estudiar listados de navegación en el modelo de Diseño, no será posible generar (no se
ofrecerá el botón Generate).
Es en los modelos de Prototipo y Producción en los que a continuación de la especificación de los objetos se
ofrecerá el botón Generate en la ventana que contiene los listados de navegación, para que el programador
pueda continuar con la generación de los programas de aplicación, en la plataforma definida para el modelo.
Solamente es necesario especificar los objetos que hayan sufrido cambios; esto es, objetos nuevos que se
hayan definido, u objetos existentes que se hayan modificado.
Surge la necesidad en este momento de explicar un punto de suma importancia: en qué modelo de una base
de conocimiento se deben realizar las distintas definiciones y/o modificaciones de objetos.
Para ser más exactos diremos que todas las operaciones que puedan provocar cambios estructurales
en las tablas de la base de datos podrán realizarse solamente en el modelo de Diseño. En modelos de
Prototipo y Producción estarán deshabilitadas las operaciones de este tipo, y para realizarlas el programador
deberá pasar al modelo de Diseño de la base de conocimiento.
En modelos de Prototipo / Producción se podrán realizar definiciones que no provoquen cambios estructurales
en las tablas de la base de datos (por ejemplo, crear o modificar objetos reportes, procedimientos, work
panels, etc.; modificar reglas o forms de las transacciones, etc.1) y automática e instantáneamente se
estarán realizando las mismas definiciones en el modelo de Diseño.
¿Cuál es la forma de trabajo entonces? Inicialmente se comienza a trabajar en el modelo de Diseño de una
base de conocimiento. Se definen las primeras transacciones y demás objetos y definiciones que se consideren
oportunas. Luego el analista deberá crear un modelo de Prototipo para probar la ejecución de las definiciones
realizadas; habrá un análisis de impacto, reorganización, actualización del modelo de Prototipo (Copy Model), y
el analista especificará y generará los programas de aplicación.
Se podrá ejecutar la aplicación definida hasta el momento, y luego de ello se continuará trabajando en el
modelo de Prototipo.
Todas las definiciones y/o modificaciones de objetos que se efectúen en el modelo de Prototipo, se efectuarán
automáticamente en el modelo de Diseño también; de modo que ambos modelos irán quedando
instantáneamente con exactamente las mismas definiciones (sincronizados). Por este motivo es que
aconsejamos trabajar en el modelo de Prototipo.
Cuando surja la necesidad de realizar definiciones que puedan afectar el diseño de la base de datos (por
ejemplo, crear una nueva transacción, modificar la estructura de una transacción existente, modificar un
dominio, etc.), habrá que pasar necesariamente al modelo de Diseño; pero si no, recomendamos trabajar
en el modelo de Prototipo, ya que automáticamente el modelo de Diseño se estará actualizando
también.
--------------------------------------------------------------------------------------------------------------------
1 Más adelante en el curso se irán introduciendo estos temas.
68
¿Qué pasa si trabajamos en el modelo de Diseño? Si en el modelo de Diseño realizamos al menos una
definición que implique modificar el diseño de la base de datos, cuando el programador pase al modelo de
Prototipo, GeneXus detectará que habrá cambios físicos para hacer. Habrá un análisis de impacto,
reorganización y actualización del modelo de Prototipo (Copy Model), y lo único que le restará hacer al analista
será especificar y generar los objetos nuevos (que nunca hayan sido especificados y generados), y los
objetos existentes que hayan sido modificados luego de su última especificación.
Sin embargo si se trabaja en el modelo de Diseño solamente definiendo objetos que no implicarán modificar
estructuralmente la base de datos, cuando el programador pase al modelo de Prototipo, GeneXus no detectará
cambios físicos para hacer, y no efectuará ninguna operación, ni siquiera la actualización del modelo de
Prototipo (Copy Model). De modo que las definiciones y modificaciones que se hayan hecho en el modelo de
Diseño, no se copiarán automáticamente al modelo de Prototipo, y será responsabilidad del programador
hacerlo, seleccionando uno de los ítems posibles para ello1.
Si se pasa a un modelo de Prototipo/Producción y éste no se actualiza con las definiciones del modelo de
Diseño (Copy Model), los objetos del modelo de Prototipo/Producción al cual se pasó estarán desactualizados.
Si se especifican y generan estos objetos, deberá comprenderse que se tratará de definiciones viejas; y si se
ejecuta la aplicación generada, no se verán las nuevas definiciones realizadas, ya que las mismas quedaron
hechas en el modelo de Diseño, pero faltó que fueran copiadas al modelo de Prototipo, y recién luego de ello,
se tendrían que haber especificado y generado.
Además, si el analista llegara a modificar objetos desactualizados en el modelo de Prototipo, la próxima vez
que realice una actualización del modelo de Prototipo (Copy Model), se perderán las modificaciones realizadas
en versiones viejas de los objetos, ya que se traerán las versiones de los objetos del modelo de Diseño.
Concluyendo, hemos recomendado en qué modelo definir qué objetos, cuándo habrá que pasar de un modelo a
otro, y cuáles pasos seguir.
Como dijimos anteriormente, si bien es posible hacerlo en Diseño, la especificación de los objetos suele
realizarse en el modelo de Prototipo. En el modelo de Diseño no se generan programas a continuación de las
especificaciones, y el único motivo por el cual se podría necesitar especificar algún objeto, sería porque se vaya
a seguir trabajando en el modelo de Diseño realizando definiciones, y se necesite corroborar la lógica
interpretada por GeneXus acerca de uno o varios objetos que se hayan definido. De modo que no es necesario,
ni mucho menos obligatorio, estar especificando los objetos en el modelo de Diseño.
En los modelos de Prototipo y Producción, en cambio, lógicamente será necesario especificar y generar los
objetos que hayan sufrido cambios, previamente a la ejecución de la aplicación.
Todas las opciones que se describen a continuación se encuentran disponibles únicamente para modelos de
Prototipo y Producción.
Es fundamental tener bien claras todas las acciones posibles que pueden desencadenarse al efectuar un
análisis de impacto:
-------------------------------------------------------------------------------------------------------------------
1 Existen algunas opciones del menú que permiten hacer esto, con algunas variaciones y son las que se
presentan al final de esta página, bajo el título “Otras opciones del ítem Build”.
69
Sin embargo, puede ocurrir que no se desee ejecutar un análisis de impacto, ni reorganización consecuente, ni
actualización total del modelo actual, sino que solo se deseen copiar algunos de los objetos del modelo de Diseño
al modelo actual.
Importante: Deberá tenerse en cuenta que aunque la lista muestre todos los objetos que tengan diferencias en
el modelo de Diseño con respecto al modelo actual, sólo podrán ser copiados aquellos objetos que usen la misma
estructura de base de datos en ambos modelos. Por ejemplo, una transacción no podrá copiarse al modelo actual
si se le ha agregado un nuevo atributo (en el modelo de Diseño) y aún no se ha efectuado la reorganización
correspondiente. Resulta sencillo de entender que no sea posible solamente copiar una transacción al modelo
actual, si la misma tiene implicada una reorganización pendiente para hacer.
Para cada objeto de la lista que se haya seleccionado e intentado copiar al modelo actual, se informará si fue
posible realizar la copia o no (y en caso negativo, el motivo).
70
Ejecución de las aplicaciones
ÎBuild / Run:
Bajo el ítem Build de la barra de menú de GeneXus, se encuentra la opción Run para ejecutar las aplicaciones
generadas.
Al seleccionar Build/Run o F5, se desplegará el diálogo de ejecución (Execution Dialog) mostrado arriba.
El diálogo de ejecución ofrece todos los programas posibles de ser compilados y ejecutados.
Estos son:
2) Objetos que se hayan definido main: Todo objeto puede ser definido main, lo cual significa que será el
principal de un ejecutable.
GeneXus generará un ejecutable que contendrá al objeto mismo y a todos los que éste llame. Esto permitirá
ejecutarlo independientemente (en lugar de ejecutarlo mediante el Developer Menu).
71
Modos de las transacciones
en tiempo de ejecución
72
INTEGRIDAD
REFERENCIAL
73
Diagrama de Bachman
COUNTRY CountryId*
CountryName
1
CUSTOMER CustomerId*
N CustomerName
………
CountryId
El concepto de integridad referencial es un concepto que tiene que ver con las bases de datos
relacionales.
Se refiere a que debe haber consistencia entre los datos de las distintas tablas de una base de datos
relacional.
Las tablas de una base de datos relacional se encuentran relacionadas por atributos que tienen en
común. Estas relaciones implican que los datos de las tablas no son independientes, sino que al
insertar, modificar y eliminar registros en una tabla, se deben tener en cuenta los datos de las otras
tablas para que siempre se conserve la consistencia de la información en la base de datos.
El hecho de que el atributo CountryId en la tabla CUSTOMER sea una clave foránea con respecto a la
tabla COUNTRY, establece una relación entre ambas tablas. La relación entre ellas puede verse en el
diagrama que mostramos arriba (Diagrama de Bachman).
74
Diagrama de Bachman
1
En transacción “Customer”:
CUSTOMER • si se inserta nuevo registro, o
N • si se modifica el CountryId de un registro
En la terminología GeneXus, decimos que existe una relación de subordinación entre ambas tablas.
Decimos que:
Debido a esta relación entre las tablas, la información contenida en ellas no es independiente, y es
necesario realizar controles para que los datos sean consistentes. A estos controles se les llama de
Integridad Referencial y básicamente son los siguientes:
GeneXus genera los programas asociados a las transacciones, incluyendo en el código generado
estos controles de Integridad Referencial. Por esta razón, si el usuario final inserta (o modifica) un
cliente a través de la transacción "Customer", se validará automáticamente que el valor ingresado en
el código de país CountryId, exista como clave primaria de un registro en la tabla COUNTRY. En caso
de fallar este control de integridad referencial, un mensaje se le desplegará al usuario indicándole
que no se encontró ese país.
75
Índices
GeneXus crea automáticamente algunos de ellos, y los otros deberán ser creados por el
programador cuando éste así lo determine, basándose en criterios de optimización.
Existen cuatro tipos de índices en GeneXus:
• Primarios
• Foráneos
• De usuario
• Temporales
De todos ellos, los únicos que no son creados automáticamente por GeneXus son los “de usuario”.
En cuanto a los tipos de índices que son creados por GeneXus, la diferencia que hay entre ellos es
el momento en que son creados y el tiempo durante el cual se mantienen.
- Los índices primarios y foráneos son creados al momento de crear o reorganizar las
tablas que componen la base de datos, y de ahí en más son mantenidos
automáticamente por GeneXus.
- Los índices temporales, en cambio, son creados al ejecutar las aplicaciones, para
acceder a tablas ordenadas por algún atributo o conjunto de atributos para el/los que no
existe un índice de usuario creado; éstos se crean en tiempo de ejecución de las
aplicaciones, se utilizan, y luego se eliminan.
GeneXus crea para cada tabla un índice por su clave primaria (índice primario), y un índice por
cada clave foránea que la tabla tenga (índices foráneos). ¿Por qué crear índices primarios y
foráneos para las tablas desde el comienzo en forma automática, siendo que luego deben ser
mantenidos?
Sean las tablas COUNTRY y CUSTOMER, que vimos un par de páginas atrás, creadas en nuestro
modelo de datos a partir de las estructuras de las transacciones "Country" y "Customer”. Existe
entre estas tablas una relación 1-N, que viene dada por el hecho de que el atributo CountryId en
la tabla CUSTOMER es una clave foránea con respecto a la tabla COUNTRY.
76
Índices primarios y foráneos
PK PK
CountryId* ICountry CustomerId* ICustomer
CountryName CustomerName
...
FK ICustomer1
CountryId
Por existir esta relación, GeneXus incluye en los programas asociados a las transacciones
"Country" y "Customer", los controles de integridad referencial pertinentes. Estos controles son:
• Si el usuario final inserta o modifica un cliente a través de la transacción "Customer", se
validará automáticamente que el valor ingresado en la clave foránea CountryId exista como clave
primaria de un registro en la tabla COUNTRY. En caso de fallar este control de integridad
referencial, se le indicará al usuario que no se encontró ese país.
Para controlar esto, se debe buscar en la tabla COUNTRY la existencia de un registro que tenga
ese valor de CountryId como clave primaria; dado que se debe consultar la tabla COUNTRY,
buscando por la clave primaria, resulta evidente que la búsqueda puede optimizarse si existe un
índice por la clave primaria en dicha tabla.
• Si el usuario final intenta dar de baja un país a través de la transacción “Country”, se validará
automáticamente que no existan clientes con dicho país asociado, como clave foránea; en caso de
encontrar un registro en la tabla CUSTOMER, cuyo valor de clave foránea CountryId sea el que se
desea eliminar, se le indicará al usuario que no es posible eliminar el país (ya que de lo contrario
quedarían datos inconsistentes en la base de datos).
Para controlar esto se debe consultar la tabla CUSTOMER, buscando por la clave foránea
CountryId. Esta búsqueda será óptima si existe un índice por CountryId en la misma.
77
Índices de usuario
• Los crea el analista sobre una tabla. Deberá
categorizarlos según si aceptará valores repetidos
(duplicate) o no (unique).
ÍNDICES DE USUARIO
Estos índices deben ser definidos explícitamente por el analista. No son definidos
automáticamente. Se dividen en duplicate y unique.
Un índice de usuario duplicate es aquel que se define para atributos de una tabla para los que
pueden existir varios registros con el mismo valor en los mismos (es decir, se define para
atributos que no son una clave candidata).
Este tipo de índices se define fundamentalmente para acceder a los datos ordenados por
determinados atributos de forma eficiente.
A modo de ejemplo, suponiendo que el nombre de cliente no es clave en la tabla CUSTOMER (sus
valores se pueden repetir) podremos definir un índice de usuario duplicate por el atributo
CustomerName, siendo muy útil para realizar consultas y listados que se necesite salgan
ordenados por nombre.
Un índice de usuario unique se utiliza para especificar que un conjunto de atributos es clave
candidata en una tabla (diferente de la clave primaria).
Esta es la forma de representar claves candidatas en el modelo de datos. Con ello logramos que
GeneXus incorpore automáticamente el control de unicidad correspondiente en las transacciones
asociadas.
A modo de ejemplo, si el nombre de cliente no se puede repetir, la forma de representarlo y lograr
que GeneXus lo controle automáticamente es definiendo en la tabla CUSTOMER un índice de
usuario unique por el atributo CustomerName.
78
Índices de usuario
• Si un mismo nombre no puede repetirse (es por tanto un
atributo clave de la tabla)
En una tabla de la base de datos pueden existir varios conjuntos de atributos cuyos valores sean
únicos en la realidad. Se dice que cada uno de esos conjuntos es una clave de la tabla. Luego, el
analista elige una de las claves como la clave primaria.
GeneXus identifica la clave primaria de la tabla de acuerdo a los atributos que fueron calificados
por el analista con el símbolo de llave.
Supongamos que en la realidad además de poder identificar a un cliente por su código, también se
lo puede identificar por su cédula de identidad. En este caso tanto el atributo CustomerId como el
atributo CustomerSSN (donde se almacena la cédula de identidad) serían claves de la tabla
CUSTOMER. Al indicar que CustomerId es el identificador de la transacción, GeneXus creará
automáticamente un índice primario por dicho atributo y controlará la unicidad de los valores
ingresados para el mismo.
¿Y qué sucederá con la cédula de identidad del cliente? Al ser este atributo clave, quisiéramos que
GeneXus obrara de igual manera, no permitiendo que se ingrese un registro, si es que ya existe
otro con el mismo valor de cédula de identidad (CustomerSSN). Para poder controlar esto de
forma eficiente, GeneXus debería contar con un índice por cada atributo clave.
A partir de allí, GeneXus incluirá en la lógica de la transacción ese control de unicidad utilizando
ese índice definido por el usuario.
79
Índices temporales
• Son creados automáticamente, bajo ciertas condiciones,
cuando son necesarios, y se eliminan cuando termina la
ejecución del objeto que los creó.
ÍNDICES TEMPORALES
Si se desea acceder a los datos ordenados por determinados atributos, pero no se desea crear un
índice permanente para ello (por ejemplo, porque se trata de una consulta que se realiza con muy
poca frecuencia), entonces, dependiendo de la plataforma, se creará un índice temporal.
80
Manejo de nulos
La permisión o no del valor <NULL> en la base de datos es muy importante en el modelo relacional.
Permitir el “valor” null para un atributo dado, significa que puede, bajo ciertas circunstancias, ser
“ignorado” dado que es “valor no especificado”. Por otro lado, si un atributo no permite valor null,
un valor válido siempre deberá asignarse a él.
81
Manejo de nulos
No: significa que el atributo no permitirá el valor null en la tabla asociada (valor por defecto)
Yes: significa que el atributo sí admitirá el valor null en la tabla asociada
La definición de nulls es utilizada por GeneXus al momento de crear / reorganizar las tablas de la
base de datos, ya que el soporte o no soporte de nulabilidad de los atributos en su tabla,
se define a nivel de la base de datos.
O sea que modificar el valor de la propiedad Nulls para un atributo implicará ejecutar una
reorganización (para redefinir a nivel de la base de datos el soporte de nulabilidad del atributo en su
tabla).
82
Manejo de nulos
La definición de nulls para atributos que conforman una clave foránea le dice a GeneXus cuán
fuerte es la referencia a la otra tabla.
Si ninguno de los atributos que componen una clave foránea permiten valores nulos (caso 1),
se tratará de una referencia fuerte (también conocida como “not null reference”), ya que
establece que la FK deberá siempre apuntar a un registro existente de la tabla referenciada.
Por el contrario, una clave foránea que tenga al menos un atributo que soporte nulos (caso 2),
establece una referencia débil (también conocida como “null reference”), ya que si alguno de
los atributos que conforman la clave foránea son nulls, entonces la referencia no será
chequeada.
Cuando una clave foránea es compuesta y están permitidos los nulos para algunos de sus
atributos, pueden aparecer nuevas referencias (no chequeadas en caso de tratarse de
referencias fuertes) si los atributos que restan componen también una clave foránea. Un
ejemplo es el que presentamos arriba, donde en caso de que el usuario deje nulo el valor de
CityId para un cliente, se realizará el chequeo de IR contra COUNTRY, para asegurarse que el
país ingresado sea correcto.
83
Manejo de nulos
Ejemplos:
84
TABLA BASE
Y
TABLA EXTENDIDA
85
Tabla Base y Tabla Extendida
Definición
Los criterios de normalización del diseño de la base de datos apuntan a minimizar la posibilidad de
inconsistencia en los datos. Una base de datos diseñada de esta manera tiene una serie de ventajas
importantes (tal es así que actualmente la normalización de datos es un estándar de diseño), pero
se deben tener en cuenta también algunos inconvenientes.
El inconveniente más notorio es que los datos se encuentran dispersos en muchas tablas, y por lo
tanto cuando se quieren hacer consultas más o menos complejas a la base de datos, se debe
consultar una cantidad importante de tablas.
Así, por ejemplo, si el siguiente Diagrama de Bachman representa nuestro modelo de datos:
para listar las facturas sería necesario consultar las tablas: INVOICE e INVOICELINE (líneas de
Facturas), CUSTOMER, COUNTRY y PRODUCT.
Para simplificar esta tarea GeneXus utiliza el concepto de tabla extendida.
Llamamos tabla base a cualquier tabla de la base de datos en la cual estemos posicionados en
determinado momento; y dada cierta tabla base, su tabla extendida comprenderá a todos los
atributos de la propia tabla base, más todos los atributos de las tablas que tengan información
relacionada unívocamente con la tabla base (relación N-1 desde la tabla base, directa e
indirectamente).
86
Tabla Base y Tabla Extendida
INVOICELINE PRODUCT
InvoiceId* ProductId*
ProductId* ProductDescription
InvoiceLineQuantity ProductPrice
InvoiceLineAmount ProductStock
87
Tabla Base y Tabla Extendida
Para cada tabla de nuestro modelo de datos, describimos cuál es su tabla extendida.
88
Descripciones en vez de Códigos
Country Transaction
CountryID*
CountryName
Customer Transaction
CustomerID*
CustomerName
CountryID
CountryName
Este atributo no es CountryName, sino
CountryId disfrazado de CountryName.
Se muestra (y acepta) en ejecución el valor de
CountryName, mientras que lo que se almacena
es CountryId.
Hasta ahora hemos visto que el usuario final debe manipular los códigos con los que el sistema identifica
a sus entidades. Por ejemplo, a la hora de ingresar un nuevo cliente a un sistema, vimos que el usuario
final ingresa el código del país de ese cliente y a continuación se le muestra en forma inferida, el nombre
correspondiente a ese país.
Para ello debe memorizar un código que no tiene demasiada carga semántica (ninguna cuando éstos son
numéricos) o apelar a prompts para poder buscar por descripción o nombre y recuperar así el código a ser
colocado en el campo.
Sin embargo no es necesario condenar al usuario a que manipule códigos. Puede manipular las
descripciones, que son las que contienen la semántica y el programa se encargará del resto. Es posible
(tanto en Win como Web, Java y .Net) que el usuario trabaje en pantalla con la descripción,
CountryName, y que automáticamente se resuelva la manipulación del código, CountryId.
El usuario verá en pantalla el CountryName, pero el atributo que muestra esos valores ¡no es
CountryName! Por el contrario, es el atributo: CountryId “disfrazado” de CountryName.
Mientras el usuario escribe “Uruguay” internamente se realiza una búsqueda en la tabla COUNTRY para
recuperar el código correspondiente a ese nombre. Ese valor, en nuestro ejemplo, 1, es el que realmente
queda almacenado allí. Pero para el usuario esto es absolutamente transparente.
Para que esto pueda lograrse, deberá haber una relación biunívoca entre el código (o identificador) y la
descripción. Es decir, para cada descripción solo existirá un código asociado. En el ejemplo, no deberá
haber dos o más países con el mismo nombre y distinto identificador, porque en ese caso no es posible
realizar el matcheo.
Dicho en otras palabras: el atributo descriptivo deberá ser una clave candidata de la tabla, es decir,
deberá existir un índice unique para ese atributo.
Spc0107 Candidate Key CountryName for CountryId may have duplicated values.
y en ejecución, si el usuario selecciona un valor duplicado (para el que existen varios CountryId) dará un
error “Country is ambiguous”, dado que no sabrá con cuál quedarse.
89
Descripciones en vez de Códigos
Propiedad InputType del atributo identificador
Valores posibles:
• Values
atributo real: lo que
muestra es su contenido
• Descriptions
atributo disfrazado: toma
las descripciones de
otro atributo, aunque su
contenido sigue siendo el
propio.
Como podemos ver, en la solapa Control Info del diálogo de definición del atributo, aparece la
propiedad InputType. Esta propiedad solo se ofrece cuando el atributo está asociando a un
control de tipo Edit (no Radio Button, Combo, etc.).
• Descriptions: aquí es donde le indicamos que queremos que “disfrace” sus valores en ejecución,
mostrándole al usuario no sus valores reales, sino los asociados y definidos en la propiedad que se
habilita: Descriptions from.
En la propiedad Descriptions from se debe indicar de qué atributo se tomarán las descripciones.
Evidentemente este atributo no puede ser cualquiera: debe tener una relación biunívoca con el que
estamos definiendo, y además, debe ser un atributo de descripción, es decir, un atributo cuyo
tipo de datos sea Character o Varchar.
90
Como en casi todos los casos, cuando una propiedad tiene que ver con la forma en que un atributo
funciona a nivel de pantalla (control), existe la posibilidad de configurar su comportamiento:
1- En forma centralizada: Es decir, a nivel de la definición del atributo en sí (en la solapa Control Info
asociada a la definición del atributo), para que aplique por defecto en todos los forms en donde el atributo
esté presente como control
2- En forma particular: Es decir, para un control atributo en concreto de un form determinado (en la
solapa Control Info asociada a la definición del control).
En el caso de configurar la propiedad IntputType a nivel de atributo, en toda transacción en la que este
atributo sea clave foránea (FK), GeneXus colocará en el form de la transacción, el valor de la propiedad
Title, del atributo elegido como descriptor (en este caso el title de CountryName) y a su lado al
atributo identificador (en este caso la FK CountryId), que mostrará los valores de CountryName, pero
almacenará los de CountryId.
Como resulta evidente, ya no es necesario que en el form se coloque el atributo real CountryName, porque
el atributo CountryId “se disfrazó” de él. El usuario no sabrá que mientras él está seleccionando o
escribiendo un nombre de país, en realidad lo que se está grabando en ese atributo es el código o
identificador correspondiente.
GeneXus quita automáticamente del form el atributo descriptor al momento en que la FK es definida con
InputType= Description.
Sin embargo, el atributo descriptor debe estar presente en la estructura de la transacción, pues de lo
contrario dará el siguiente error al especificar:
Spc0109 CountryName is (part of) a candidate key. It must be referenced in the transaction’s
structure.
91
Descripciones con autocomplete
Propiedad Suggest
La propiedad Suggest posibilita brindarle una ayuda útil al usuario final, para que elija en tiempo de
ejcución para un control edit, entre un conjunto de valores posibles y no tenga que recordar
“exactamente” el valor.
Al igual que la propiedad InputType, aplica a controles edit y puede ser usada tanto en transacciones,
como en Web y Work panels, objetos de los que hablaremos más adelante.
Habilitando la propiedad Suggest en un control edit, se conseguirá que una lista de posibles valores
para ese control sea desplegada al usuario, debajo del control.
La lista de sugerencias puede ser calculada de un modo incremental (la lista será actualizada con las
entradas del usuario) o haciendo pedidos explícitos (el usuario manualmente tendrá que hacer un
pedido para que se calculen las sugerencias y se le muestren). La actualización de la lista es
asincrónica y los tiempos de cálculo dependen de la calidad de la conexión.
92
Descripciones con autocomplete
Propiedad Suggest
Valores posibles:
• Incremental
• No
• OnRequest
Si miramos un par de páginas atrás, cuando vimos la propiedad InputType, teníamos la misma
pantalla de propiedades para el atributo CountryId, solo que el valor que teníamos especificado en la
propiedad Suggest era el valor por defecto: “No”. En ese caso el usuario tenía que digitar el nombre
del país sin recibir ayuda alguna. Si se cambia el valor de esta propiedad a “Incremental” u
“OnRequest” se brindará ayuda al usuario para que elija en tiempo de ejecución entre los valores
posibles y no tenga que recordar “exactamente” el valor.
•Incremental: Se le irán sugiriendo al usuario los valores que correspondan con lo que haya
digitado hasta el momento. Es incremental, puesto que va reduciendo el rango de lo mostrado
interactivamente a medida que el usuario va agregando letras a lo que lleva digitado hasta el
momento.
•OnRequest: La única diferencia que tiene el valor Incremental es que en este caso no es
interactivo. El usuario ingresará el substring que recuerde de la palabra y luego pedirá explícitamente
las sugerencias de las palabras que empiecen por o contengan esa cadena. Por ejemplo, si el usuario
final sabe que un cliente que está buscando tiene apellido Pérez, pero no recuerda el nombre, puede
escribir “Pérez” y pedir las sugerencias.
Si tuviera configurado el valor “Incremental” en esta propiedad, entonces al digitar “P” de “Pérez” ya
se le traerían todos los nombres de clientes que empiezan con “P” y se iría afinando la búsqueda a
medida que siga escribiendo letras.
•No: no sugiere nada. Este es el valor por defecto, y en este caso el usuario no recibirá ningún tipo
de ayuda para ingresar el valor en el campo.
Para lograr que el usuario trabaje con descripciones en lugar de códigos, otra posibilidad es
configurar que el atributo CountryId utilice el control: combo box dinámico (indicándose también en
este caso, que se muestren los nombres de los países existentes en la tabla COUNTRY
(CountryName), mientras que al momento de seleccionar uno en el combo, quedará el código
correspondiente en el atributo CountryId.
93
Reglas
• Se utilizan para definir el comportamiento de las transacciones.
• Algunas reglas:
Default
Asignación
Msg
Error
Noaccept
Add
Subtract
Serial
Update
Las reglas, en el objeto transacción, cumplen un rol muy importante ya que permiten programar su
comportamiento (por ejemplo: asignar valores por defecto, definir controles sobre los datos, etc.).
Se escriben de forma declarativa, es decir que el orden en el que se escriben no significa que sea el
orden en el que se ejecutarán.
Son solo válidas dentro de la transacción en la que están definidas, es decir, son locales.
--------------------------------------------------------------------------------------------------------
1 Todas las reglas de transacciones pueden involucrar atributos de las tablas base asociadas a la
transacción; y la mayor parte de ellas puede también involucrar atributos de las tablas extendidas de
dichas tablas base. Para poder referenciar un atributo en una regla, el mismo deberá estar incluido en
la estructura de la transacción (ya sea que pertenezca a alguna de las tablas base asociadas a la
transacción, o a sus tablas extendidas).
94
Algunas reglas válidas para transacciones son:
Default
OBJETIVO: Permite asignar un valor por defecto a un atributo o variable; el valor por defecto inicializará al atributo o
variable si se está realizando una inserción por medio de la transacción (modo Insert), pero el usuario final podrá
cambiarlo si ese valor no es el que desea.
DONDE:
• att: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción.
• var: es el nombre de una variable.
• exp: es una expresión que puede involucrar constantes, funciones, variables u otros atributos.
FUNCIONALIDAD: Esta regla asigna el valor de la expresión exp como valor por defecto del atributo att o variable var,
cuando la transacción se ejecuta en modo Insert.
Esta regla no es válida para atributos que forman parte de la clave primaria de alguno de los niveles de la transacción,
porque es disparada luego de que la clave es ingresada (ya que solo en ese momento el modo es conocido y esta
regla se dispara solo en modo Insert).
EJEMPLOS:
Default( InvoiceDate, &today ); /*Regla definida en la transacción “Invoice”*/
Cuando se está insertando una factura nueva, se sugiere como valor de InvoiceDate el valor contenido en la variable
del sistema today.
Análoga a la anterior, solo que en lugar de utilizar la variable del sistema today, se utiliza la función Today que
devuelve la fecha correspondiente al día.
Cuando se está insertando una línea de factura, se sugiere que el atributo InvoiceLineAmount (importe de la línea)
tome el valor resultante de evaluar la expresión ProductPrice*InvoiceLineQuantity (precio del producto por cantidad
llevada del mismo).
Nota: El tipo de datos de la expresión debe coincidir con el tipo de datos del atributo o variable
Regla de asignación
DONDE:
• att: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(debe estar declarado en la estructura).
• var: es el nombre de una variable.
• exp: es una expresión que puede involucrar constantes, funciones, variables u otros atributos, y debe ser del mismo
tipo que att o var.
• cond: es una expresión booleana (puede contener los operadores lógicos and, or, not)
• evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de
transacciones, que permiten definir el momento específico de ejecución de una regla.
FUNCIONALIDAD: Una regla de este tipo permite asignar al atributo o variable de la izquierda, el valor resultante de
evaluar la expresión. Como puede verse, la condición es opcional; de no ponerla, la asignación se realiza siempre.
La asignación a un atributo, implica su actualización en el registro que corresponda. Se pueden definir reglas de
asignación a atributos de alguna de las tablas bases asociadas a la transacción, e incluso de sus tablas extendidas.
Esto significa que pueden actualizarse atributos inferidos en una transacción (siendo necesario declararlos en la
estructura).
EJEMPLO:
Si el importe de cada línea de una factura se calcula siempre multiplicando el precio del producto por la cantidad
llevada del mismo, podemos utilizar esta regla de asignación para liberar al usuario de realizar el cálculo.
95
Error
OBJETIVO: Permite desplegar un mensaje de error si la condición se satisface. Sirve para definir los controles que deben
cumplir los datos.
SINTAXIS: Error( ‘msg’ | &var | character expresion, msgId ) if cond [on evento/momento de disparo];
DONDE:
• msg: es un string con un mensaje de error a desplegar.
• var: es el nombre de una variable de tipo character, que contiene un string con un mensaje de error a desplegar.
• character expression: es una expresión cuyo tipo resultante es character y que será desplegada.
• msgId: es un string (sin espacios en blanco ni comillas) que será utilizado solo si la transacción es definida también
como business component1.
• cond: es una expresión booleana (que puede contener los operadores lógicos and, or, not)
• evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de transacciones,
que permiten definir el momento específico de ejecución de una regla.
FUNCIONALIDAD: Esta regla despliega el mensaje del parámetro msg, var o character expresion, si la condición cond que
se evalúa resulta verdadera. El mensaje de error se despliega en una ventana popup cuando el ambiente de trabajo es
Windows y en el control Error Viewer y/o un cuadro de texto cuando el ambiente de trabajo es Web, deteniendo
cualquier actualización a la base de datos. Cuando la transacción se ejecuta como business component1, de
dispararse el error, generará una entrada en el SDT messages, con identificador msgId.
EJEMPLOS:
Se evalúa la condición CustomerName.isEmpty(), y si ésta se satisface, se despliega el mensaje ‘No se permiten clientes
sin nombre’ en pantalla. No se permite continuar hasta que el usuario ingrese un nombre en el campo CustomerName o
abandone la transacción (en cuyo caso no se hará en la base de datos actualización alguna).
Se necesita prohibir la eliminación de facturas. Con esta regla, si el usuario intenta realizar una eliminación, la condición
dará True y se disparará la regla, evitando la eliminación.
Msg
SINTAXIS: Msg( ‘msg’ | &var | character expresion, msgId) if cond [on evento/momento de disparo];
DONDE:
msg, var, character expresion, msgId, cond, evento/momento de disparo: son los mismos que para la regla error.
Observar que la sintaxis es exactamente la misma.
FUNCIONALIDAD: Esta regla se utiliza para presentar mensajes de advertencia al usuario. Despliega el mensaje del
primer parámetro, si la condición se satisface, análogamente a la regla Error; pero a diferencia de esta última, permite
continuar con la ejecución si la condición sigue satisfaciéndose. Del mismo modo, si la transacción es business
component1, de dispararse la regla genera entrada en el SDT messages.
Los mensajes de advertencia se despliegan en una ventana popup en ambiente Windows y en el Error Viewer o cuadro de
texto en ambiente Web.
EJEMPLO:
Se evalúa la condición CustomerName.isEmpty(), y si ésta se satisface se despliega el mensaje ‘No se permiten clientes
sin nombre’. A diferencia de lo que ocurre con la regla Error, aquí sí se permite continuar la ejecución, pues no se trata de
un error sino de una advertencia.
--------------------------------------------------------------------------------------------------------------------------------
1 Estudiaremos este tema más adelante en el curso.
96
Noaccept
OBJETIVO: Permite indicar que los atributos no aceptarán valores por parte del usuario (serán solo de salida).
DONDE:
atti: es un atributo perteneciente a alguna de las tablas bases asociadas a la transacción.
cond: es una expresión booleana (puede contener los operadores lógicos and, or, not).
evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de transacciones,
que permiten definir el momento específico de ejecución de una regla.
FUNCIONALIDAD: En una transacción, todos los atributos que pertenecen a las tablas base asociadas a la transacción, por
defecto son aceptados. Si queremos que algunos atributos con estas características no sean aceptados, entonces
contamos con la regla Noaccept.
EJEMPLO:
Noaccept( IvoiceDate) if Update; /* Regla definida en la transacción “Invoice” */
Si se está modificando una factura (modo Update), no se permite que se modifique su fecha.
Subtract
OBJETIVO: Sustrae el valor de un atributo al valor de otro atributo, si se satisface la condición especificada.
DONDE:
att1, att2: son atributos pertenecientes a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(y deben estar declarados en la estructura)
cond: es una expresión booleana (que puede contener los operadores lógicos and, or, not).
FUNCIONALIDAD: La sustracción se realiza teniendo en cuenta el modo en el que se esté trabajando en la transacción
(Insert, Update o Delete).
En modo:
- Insert: se le sustrae al valor del atributo att2, el valor del atributo att1
- Delete: se le suma al valor de att2, el valor del atributo att1
- Update: se le sustrae al valor del atributo att2, la diferencia entre el valor nuevo y el viejo de att1
EJEMPLO:
En la transacción “Invoice”, cada vez que se ingresa una línea con un producto que se está comprando, se debe disminuir
el stock del mismo, según la cantidad llevada.
Si prestamos atención, sin embargo, vemos que esta regla no nos sirve, pues no está condicionada a un modo particular,
razón por la cuál se disparará tanto cuando se está insertando una nueva línea en la factura, como cuando se está
eliminando o modificando una ya existente. Y en estos últimos dos casos es incorrecto disparar la regla.
De hecho, cuando se esté eliminado una línea existente, debe realizarse la operación contraria, es decir, se debe
“devolver” al stock lo que se había quitado cuando se insertó la línea.
Lo correcto entonces, teniendo en cuenta todos los casos posibles sería tener 3 reglas:
Aquí estamos utilizando la función “old”, que devuelve el valor almacenado del atributo (es el valor antes de modificarlo).
Para evitar tener que hacer todo esto, GeneXus provee la regla subtract que se encarga de hacer la asignación correcta
de acuerdo al modo. Entonces podemos sustituir las 3 asignaciones anteriores, por:
Esta regla tiene la inteligencia para, dependiendo del modo, restar o sumar.
97
Add
OBJETIVO: Suma el valor de un atributo al valor de otro atributo, si se satisface la condición especificada
SINTAXIS: add( att1, att2) [if cond];
DONDE:
att1, att2: son atributos pertenecientes a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(y deben estar declarados en la estructura).
cond: es una expresión booleana.
FUNCIONALIDAD: La adición se realiza teniendo en cuenta el modo en el que se esté trabajando en la transacción (Insert,
Update o Delete).
En modo:
- Insert: se le suma al valor del atributo att2, el valor del atributo att1
- Delete: se le sustrae al valor de att2, el valor del atributo att1
- Update: se le suma al valor del atributo att2, la diferencia entre el valor nuevo y el
viejo de att1
EJEMPLO:
Definimos en la transacción “Customer”, un atributo de nombre CustomerTotalPurchases, para registrar el importe total
de compras efectuadas por el mismo. El comportamiento que se desea es que cada vez que se cree una factura para un
cliente dado, se le sume el total de la factura (InvoiceTotal) al total de compras efectuadas por el cliente
(CustomerTotalPurchases).
Al igual que vimos en la regla subtract, no debemos olvidar que en la transacción “Invoice” podemos también eliminar y
modificar facturas, y no solo crearlas; por lo tanto es importante tener en cuenta el modo de trabajo en la transacción
(Insert, Update, Delete). GeneXus nos libera de tener que considerar nosotros a los modos, teniendo que escribir las
siguientes tres reglas de asignación en la transacción “Invoice”:
y en su lugar nos provee de la regla add, que se encarga de sumar o restar, dependiendo del modo.
Así es que si en la transacción “Customer” agregamos el atributo CustomerTotalPurchases (total de compras del cliente):
CustomerId*
CustomerName
CustomerAddress
CustomerGender
...
CustomerTotalPurchases
• si se inserta una factura (Insert): se le suma al valor del atributo CustomerTotalPurchases el valor del atributo
InvoiceTotal
• si se elimina una factura (Delete): se le sustrae al valor del atributo CustomerTotalPurchases el valor del atributo
InvoiceTotal
• si se modifica una factura (Update): se le suma al valor del atributo CustomerTotalPurchases, la diferencia entre el valor
nuevo y el viejo de InvoiceTotal
98
Serial
OBJETIVO: Permite numerar serialmente atributos numéricos.
DONDE:
• att1: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción (es decir, no
inferido), que desea autonumerarse (debiendo estar declarado en la estructura).
• att2: Tiene que pertenecer a una tabla directamente superordinada a la del atributo att1.
• step: es el paso o incremento de la serialización.
FUNCIONALIDAD: El propósito de esta regla es asignar un número correlativo a att1 cada vez que se inserta un
registro en la tabla a la que pertenece att1. Se toma el valor de att2 (att2 contiene el último número utilizado en
la autonumeración), se le suma el valor del parámetro step, y el valor resultante se asigna tanto al atributo att1
del nuevo registro, como al atributo att2 para conservar el último número asignado.
Es decir, cuando se está insertando un registro por medio de una transacción en la cual se ha
definido la regla: Serial(att1, att2, step);, se accede al att2 (habrá un solo valor de este atributo relacionado,
pues pertenece a una tabla directamente superordinada1), se le suma el valor step, y se asigna el valor obtenido
tanto a att1 del registro que va a ser insertado, como a att2 perteneciente a una tabla directamente
superordinada con respecto a la tabla que contiene a att1.
InvoiceId*
CustomerId
CustomerName
InvoiceDate
InvoiceLastLineId
(InvoiceLineId*
ProductId
ProductDescription
……)
En este diseño el atributo ProductId no es identificador único del nivel, sino clave foránea únicamente.
Cada línea tiene un número de línea que la identifica en forma única, y es posible ingresar el mismo producto en
distintas líneas.
Podría ser útil asignar por medio del sistema, números correlativos al campo InvoiceLineId, definiendo la
regla:
El primer parámetro de la regla serial define cuál es el atributo a numerar automáticamente, en el segundo
parámetro debe indicarse un atributo cuya función es guardar el último valor asignado hasta el momento, y por
último el tercer parámetro es para indicar el incremento (en este caso se incrementa de uno en uno).
El segundo parámetro (en el ejemplo InvoiceLastLineId) debe pertenecer a una tabla directamente
superordinada a la tabla que contiene el atributo que se desea numerar automáticamente (InvoiceLineId). La
regla serial lo requiere así. En el ejemplo, se puede observar que InvoiceLastLineId se encuentra en la tabla de
clave InvoiceId*, la cual es directamente superordinada respecto a la tabla que contiene el atributo a numerar
(InvoiceLineId).
Es decir, cada factura tendrá en el cabezal un atributo que almacenará el último número de línea asignado hasta
el momento (InvoiceLastLineId). La regla serial está implementada de forma tal que
99
necesita este atributo (para fijarse el último número utilizado, sumarle el incremento, y asignar el próximo número a la
nueva línea).
Consideración:
La regla serial es útil a la hora de autonumerar líneas, no así cabezales (por ejemplo identificadores de facturas, de
clientes, etc.). El motivo es el siguiente: para utilizar la regla serial, se requiere definir un atributo en una tabla
directamente superordinada; esto resulta bien sencillo si se desean autonumerar líneas ya que alcanza con incluir este
atributo en el nivel de la estructura inmediatamente superior al del atributo a autonumerar.
Sin embargo, si lo que se quiere numerar automáticamente es un atributo del primer nivel (cabezal), allí no existe un
nivel inmediatamente superior en la estructura; esto no es un problema sin solución ya que para definir una relación
de superordinación/subordinación entre tablas, no solo está la posibilidad de diseñarlo en una misma estructura de
transacción con más de un nivel; también transacciones distintas con atributos en común, provocan la creación de
tablas relacionadas, y por ende, con relaciones de superordinación / subordinación entre ellas.
El siguiente diseño incluye a una tabla de nombre NUMBER superordinada a las tablas INVOICE y CUSTOMER:
NumberCode*
NUMBER
NumberLast
InvoiceId* CustomerId*
InvoiceDate INVOICE CUSTOMER CustomerName
NumberCode NumberCode
CustomerId …
Como se puede
… observar, la tabla NUMBER tiene solamente 2 atributos: NumberCode que sería un código para
almacenar el tipo de número que se brinda (número de factura, número de cliente, etc.) y NumberLast para almacenar
el último número dado para cada NumberCode.
La estructura de la tabla NUMBER sería:
NumberCode* NumberLast
CUSTOMER 20 último identificador de cliente asignado
INVOICE 10 último identificador de factura asignado
Para obtener este diseño, deberíamos definir una transacción “Number” con estructura:
NumberCode*
NumberLast
InvoiceId* CustomerId*
InvoiceDate CustomerName
CustomerId …
CustomerName Inferido: está en la tabla NumberCode
NumberCode NUMBER directamente NumberLast
NumberLast subordinada
(InvoiceLineId*
ProductId
ProductDescription
…)
100
Finalmente, agregando en la transacción “Invoice” las siguientes reglas, lograríamos autonumerar las facturas:
La regla equal asigna un valor (‘INVOICE’) a un atributo (NumberCode) cuando se ejecuta la transacción en modo insert,
y filtra los registros que tengan dicho valor en el atributo, cuando se ejecuta la transacción en modo update y delete.
Y en la transacción “Customer” la situación es análoga; con las reglas siguientes podemos autonumerar clientes:
Sin embargo, contamos con una solución mucho más simple para autonumerar cabezales: cuando una tabla tiene una
clave simple (es decir formada por un solo atributo) y el tipo de datos es numérico, puede numerarse automáticamente
utilizando la funcionalidad que brindan los manejadores de base de datos para esto. La forma de indicarlo en GeneXus es
configurando la propiedad Autonumber del atributo clave:
Si en la propiedad Autonumber de un atributo numérico clave, se selecciona el valor True, significa que se realizará la
numeración automática del mismo. Se agregarán las siguientes propiedades en el diálogo:
Start: Mediante esta propiedad se configura a partir de qué número comienza la numeración automática.
Step: Mediante esta propiedad es posible configurar el incremento del campo (entre dos registros).
For replication: Esta propiedad es sólo válida para el motor de base de datos SQL Server; el valor Yes le indica a éste
que no debe aplicar la propiedad en caso de que la tabla sea receptora de replicación (sino que debe mantener los
números que le vienen por replicación).
101
Update
OBJETIVO: Posibilita actualizar en el form de una transacción (win/web) atributos de la tabla extendida (inferidos).
DONDE:
atti: es un atributo perteneciente a la tabla extendida de alguna de las tablas bases asociadas a la transacción.
FUNCIONALIDAD: En una transacción, todos los atributos que pertenecen a las tablas base asociadas a la transacción, por
defecto son aceptados y los que perteneciendo a la tabla extendida, no pertenecen a la base, son inferidos, y por tanto no
aceptados.
Pero si queremos que algunos de estos atributos inferidos sean aceptados, para que el usuario pueda modificar desde el
form su valor, entonces contamos con la regla Update.
EJEMPLO:
InvoiceId* CustomerId*
InvoiceDate CustomerName
CustomerId …
CustomerName
…
update(CustomerName);
102
Conceptos importantes sobre reglas de
transacciones
¿En qué nivel de una transacción se ejecutarán las reglas definidas en la misma?
La mayor parte de las veces no es necesario agregar explícitamente en la definición de las reglas el nivel
de la transacción en el cual se desea que se disparen, ya que los atributos involucrados en las reglas le
dan la pauta a GeneXus del nivel en el cual corresponde ejecutarlas.
Por ejemplo, si una regla referencia únicamente a atributos del primer nivel de la transacción en la cual
se encuentra definida (ya sea en la propia regla o en la condición de disparo), GeneXus entenderá que la
misma estará asociada al primer nivel de la transacción.
Análogamente, si una regla referencia solamente a atributos del segundo nivel de la transacción en la
cual se encuentra definida (ya sea en la propia regla o en la condición de disparo), GeneXus entenderá
que la misma estará asociada al segundo nivel de la transacción.
En el caso que una regla referencie atributos de varios niveles, GeneXus entenderá que la regla estará
asociada al último de los niveles de los atributos involucrados, ya que será en el último nivel en el que
contará con los valores de todos los atributos implicados.
como el único atributo que se menciona en la regla es InvoiceDate, y es un atributo del primer nivel de
la transacción, GeneXus determinará que se tratará de una regla asociada al primer nivel.
como los dos atributos que se mencionan en la misma se encuentran en el segundo nivel de la
transacción, GeneXus determinará que se tratará de una regla asociada al segundo nivel.
103
3) Si se define la siguiente regla en la transacción “Invoice”:
InvoiceLineDiscount= InvoiceLineAmount * CustomerDiscountPercentage/100;
siendo InvoiceLineDiscount (descuento correspondiente a una línea) un atributo perteneciente al segundo nivel de
la transacción “Invoice” y CustomerDiscountPercentage (porcentaje de descuento otorgado a un cliente) un
atributo declarado en el primer nivel de la transacción “Invoice”, GeneXus determinará que se tratará de una regla
asociada al segundo nivel de la transacción.
Cuando nos referimos a que una regla está asociada a determinado nivel, significa que la misma se ejecutará para
cada instancia con la cual se trabaje a través de ese nivel (si se cumple la condición de disparo de la regla, claro
está).
En el caso del primer ejemplo visto, la regla Default(InvoiceDate, &today) es una regla asociada al primer nivel
de la transacción “Invoice”. Esto significa que se ejecutará para todo cabezal de factura que se inserte a través del
primer nivel de la transacción “Invoice” (la regla Default tiene la particularidad de dispararse únicamente cuando
el modo de ejecución es Insert).
En el caso del segundo ejemplo visto, la regla subtract(InvoiceLineQuantity, ProductStock) es una regla asociada
al segundo nivel de la transacción “Invoice”. Esto significa que se ejecutará para toda línea de factura que se
inserte, actualice o elimine a través del segundo nivel de la transacción “Invoice”.
Concluyendo, tal como se desprende de todo lo explicado, para cada factura con la cual se trabaje a través de la
transacción “Invoice”:
- para el cabezal: se ejecutarán las reglas asociadas al primer nivel
- para cada una de las líneas: se ejecutarán las reglas asociadas al segundo nivel
Es importante saber que como norma general GeneXus siempre determina que una regla se dispare en el
primer momento en que sea posible, es decir, en aquel momento en el que cuente con todos los datos
necesarios. Y solamente en algunos casos que así lo requieran, una misma regla se volverá a disparar más
adelante.
¿Qué nivel de disparo por defecto se le asociará a una regla que no referencia atributos?
Cuando no hay atributos involucrados en una regla, el nivel asociado por defecto a la regla será el primero.
Existe una cláusula opcional de nombre Level que permite modificar el nivel por defecto de disparo de una regla,
cambiándolo por un nivel posterior.
Es decir, si por ejemplo una regla se ejecuta por defecto para el primer nivel de una transacción y se desea que se
ejecute para el segundo, se deberá agregar a la regla el componente Level seguido de un atributo o conjunto de
atributos del segundo nivel. Esto hará que la regla se ejecute para cada una de las instancias correspondientes a
las líneas, y no para la instancia correspondiente al cabezal como era el comportamiento por defecto.
104
Agregar la cláusula Level a una regla solamente tiene sentido si a continuación de la misma se mencionan
atributos que son de algún nivel posterior a los niveles de los atributos implicados en la definición de la regla en
sí.
En el ejemplo que sigue, el hecho de haber agregado la cláusula Level a la regla no aporta más información de la
ya aportada por el atributo implicado en la definición de la regla en sí:
msg(‘La fecha de la factura es mayor a la fecha actual’) if InvoiceDate > &Today Level CustomerId;
Es fácil comprender que el atributo InvoiceDate ya le da la pauta a GeneXus de que se trata de una regla asociada
al primer nivel, así que lo especificado por la cláusula Level en el ejemplo no aporta más información y por lo
tanto su agregado es innecesario.
si bien se incluyó la cláusula Level en la definición de la regla, como el atributo que sigue a la cláusula es de un
nivel superior al nivel de los atributos referenciados en la regla, la cláusula Level definida no aportará información
útil en este caso tampoco. Es decir, no es posible que habiendo involucrados atributos de un segundo nivel en una
regla, la misma se ejecute en el primer nivel, ya que en el primer nivel no se tiene la información del o de los
niveles inferiores (además de que hay N instancias para el o los niveles inferiores). De modo que la regla seguirá
estando asociada al segundo nivel de la transacción “Invoice”, no habiendo aportado información útil la cláusula
Level en este ejemplo.
Concluyendo, la cláusula Level solamente tiene sentido que sea agregada para modificar el nivel por defecto de
disparo de una regla, a un nivel posterior.
GeneXus provee las siguientes funciones booleanas para poder incluirlas en la condición de disparo de las
reglas con el objetivo de limitarlas a que se ejecuten puntualmente en algunos de los modos posibles:
• Insert
• Update
• Delete
Si se está modificando una factura (modo Update), no se permite que se modifique su fecha. Si se definiera la
regla sin la condición de disparo que hemos explicitado, el atributo InvoiceDate se deshabilitaría en todos los
modos de ejecución.
Si se intenta eliminar una factura, se disparará el error deteniendo la eliminación. Como no hay atributos
involucrados en la regla, por defecto el nivel asociado a la regla será el primero.
Si se intenta eliminar una línea de una factura, se disparará el error deteniendo la eliminación. Observar que se ha
explicitado en la regla la cláusula Level seguida de un atributo del segundo nivel de la transacción “Invoice”, para
indicar que se desea que la misma esté asociada al segundo nivel de la transacción.
105
Tipos de diálogo
GeneXus genera distintos tipos de diálogos para las transacciones. El diálogo utilizado para un modelo en
particular, dependerá de su ambiente o plataforma (en particular de su interfaz, Win o Web).
• campo a campo
• a pantalla completa (full screen)
Luego se ha creado un híbrido entre ambos, que funciona en algunos aspectos como el diálogo a pantalla
completa y en otros como el campo a campo. Le llamaremos: diálogo con validación a nivel del cliente
(Client Side Validation).
En este tipo de diálogo, cada vez que se digita un valor en un campo y se abandona, se controla
inmediatamente su validez. Las reglas y fórmulas1 se van disparando a medida que se va pasando por los
campos, y los datos de la tabla extendida se van infiriendo ni bien se van ingresando los valores de las
claves foráneas.
Generalmente la grabación de los datos en este tipo de diálogo es por registro, vale decir:
• Ni bien se terminan de ingresar los datos del cabezal y se pasa a las líneas, el registro correspondiente al
cabezal es grabado.
• Ni bien se terminan de ingresar los datos de una línea y se pasa a la siguiente, el registro correspondiente
a la línea de la cual se salió, es grabado.
Los generadores Visual Basic y Visual FoxPro trabajan con este tipo de diálogo.
------------------------------------------------------------------------------------------------------------------
1 Estudiaremos las fórmulas un poco más adelante.
106
Tipos de diálogo
En este tipo de diálogo no se realizan validaciones de los datos, controles de integridad referencial (IR),
inferencias de la tabla extendida, ni disparos de reglas y fórmulas a medida que los datos van siendo
ingresados por el usuario en la pantalla.
Está en la naturaleza de los generadores Java y .Net trabajar con este tipo de diálogo. Sin embargo existe la
posibilidad de modificar este comportamiento, para que el usuario final pueda tener mayor interacción con la
aplicación de manera tal que no deba esperar a confirmar la transacción para ver los atributos inferidos, así
como las validaciones y disparo de reglas y fórmulas. En lo que sigue explicaremos el diálogo a pantalla
completa con Validación a nivel del Cliente.
Diálogo a pantalla completa con Validación a nivel del Cliente (Client Side Validation)
La base de este diálogo es a pantalla completa, pero el usuario tendrá mayor interacción con la aplicación.
Tanto las validaciones de datos, como los controles de IR y disparo de algunas reglas y fórmulas, además de
realizarse cuando se confirma la transacción (como en el diálogo full screen clásico), también se realizarán
antes, en forma interactiva a medida que el usuario vaya ingresando y abandonando los campos de la pantalla.
De modo que se trata de un híbrido entre los diálogos campo a campo y a pantalla completa.
Nota: en el caso de tratarse de ambiente Web, la pantalla que se ejecuta en el cliente es la ejecutada en el
browser, y el servidor en este caso es el Servidor Web que ejecuta la aplicación. Si bien la naturaleza de
internet no posibilitaba la validación a nivel del cliente (es decir, enviar partes de la lógica de las transacciones
al browser), con el advenimiento de Ajax1, un conjunto de tecnologías existentes operando juntas, esto ahora
es posible.
------------------------------------------------------------------------------------------------------------------
1 Puede encontrar un artículo interesante “Ajax: A New Approach to Web Applications” en
http://www.adaptivepath.com/publications/essays/archives/000385.php
107
Diálogo con Validación a nivel del cliente
(Client Side Validation)
Mientras que para aplicaciones .Net y Java con interfaz Web siempre se trabajará con diálogo con validación
a nivel del cliente (WCSV: Web Client Side Validation) posible solo gracias a la irrupción del conjunto de
tecnologías conocido como Ajax en el mundo de Internet, para el caso de las mismas aplicaciones para
interfaz Win existe la posibilidad de configurar una propiedad de nombre ‘Client Side Validation’, para poder,
si así se desea, trabajar con el diálogo base, exclusivamente a pantalla completa.
Interfaz Win
Cuando se trabaja con generadores .Net o Java y ambiente Windows, la validación a nivel del cliente
ofrece una alternativa al diálogo a pantalla completa para proveer mayor interacción con el usuario final.
- Yes: Si se selecciona este valor, en la medida que el usuario final vaya pasando por los campos de la
pantalla y/o ingresando información en ellos, se irán validando los datos, infiriendo los atributos de la tabla
extendida, y disparándose las reglas y fórmulas definidas. Todas estas operaciones se ejecutarán en forma
interactiva para que el usuario final pueda ir viendo resultados. De todas formas, las características del
diálogo a pantalla completa se mantendrán, en el sentido de que cuando el usuario final confirme los datos,
se grabarán los de la pantalla completa (disparándose previamente todas las validaciones y acciones, esta
vez en el servidor). Este es el valor por defecto.
-No: Indica que el diálogo a pantalla completa será puro, es decir, sin el agregado de las validaciones en
forma interactiva.
La propiedad Client Side Validation se encuentra disponible tanto a nivel de modelo como a nivel de
objeto. Esto significa que el valor que se configure en la propiedad a nivel del modelo determinará el
comportamiento de todas las transacciones, a excepción de aquellas que tengan configurado el valor
contrario en su propiedad a nivel del objeto.
Cuando se crea un modelo de prototipo con interfaz Win y generador Java o .NET, uno de los pasos del
Wizard de creación de dicho modelo contiene un combo titulado “Client Side Validation” que ofrece los dos
valores posibles: ‘Yes’ o ‘No’. De esta manera el analista ya puede determinar si desea que su aplicación
tenga un diálogo a pantalla completa (propiedad CSV = No) o si por el contrario, desea incrementar la
interactividad de las transacciones, con validación a nivel del cliente (propiedad CSV = Yes). También se
puede configurar esta propiedad a nivel del modelo más tarde (editando las propiedades del modelo), así
como a nivel de objeto si se desea que ciertas transacciones tengan el comportamiento contrario al
configurado para el modelo.
108
Diálogo con Validación a nivel del cliente
(Client Side Validation)
WIN: WEB:
CSV=No Æ Hay botón get / no se infiere el modo Hay botón get / no se infiere el modo
CSV=Yes Æ No hay botón get / se infiere el modo
Resumen sobre diálogo a pantalla completa con validación a nivel del cliente:
Mientras el usuario va trabajando con la pantalla, ingresando datos en los distintos campos y
abandonándolos, se irán realizando validaciones de los mismos, controles de IR e inferencias de la tabla
extendida, así como disparo de reglas y fórmulas que involucren a esos atributos que se abandonan.
Esto le brindará al usuario la idea de una alta interacción.
Luego, cuando el usuario termina de trabajar con la instancia de cabezal y líneas, confirma presionando
el botón de confirmación, y allí se realiza el procesamiento de datos a “pantalla completa”. Aquí vuelven
a validarse los datos, a dispararse los controles de IR e inferencias, junto con las reglas y fórmulas,
además de realizarse las grabaciones de todos los registros correspondientes (cabezal y líneas). Todo
esto se realiza en el servidor, como si nada hubiese ocurrido en el cliente (tiene que ver con aspectos
de seguridad, dado que si no vuelven a realizarse las validaciones luego en el servidor, podría haberse
“colado” algo en el camino al servidor que burle la seguridad).
109
Diálogo a pantalla completa
Propiedad: Confirmation
WIN: cambia texto del botón, WEB: no cambia texto, “confirm” se muestra
“confirm” en el 2do. paso en el 2do. paso en el Error Viewer
La propiedad Confirmation se encuentra disponible tanto a nivel de modelo como a nivel de objeto. Esto
significa que el valor configurado en la propiedad a nivel de modelo determinará el comportamiento de todos
los objetos del modelo, a excepción de aquellos que tengan configurado otro valor en forma particular en su
propiedad a nivel de objeto.
Esta propiedad permite configurar:
• para transacciones: si se desea o no, que antes de que se graben directamente los datos, se le solicite
confirmación al usuario para hacerlo.
• para reportes y procedimientos: si se desea o no, que antes de que se comience a ejecutar el proceso, se le
solicite al usuario confirmación para hacerlo.
Los valores que admite esta propiedad son:
• Always prompt: Si se selecciona este valor, se le solicitará confirmación al usuario.
• Never prompt: Si se selecciona este valor, no se le solicitará confirmación al usuario.
• Do not prompt on first level: Este valor se ofrece únicamente para los generadores Visual Basic y Visual
Fox Pro, y aplica solamente a transacciones. Si se selecciona el mismo, se le solicitará confirmación al usuario
antes de que se graben los datos en cada nivel de la transacción, a excepción del primero.
El trabajo con confirmación reviste relevancia fundamentalmente en el caso en que se trabaje con diálogo a
pantalla completa (sin validaciones del lado del cliente), pues como dijimos, es en ese tipo de diálogo en el
que el usuario no tiene una respuesta interactiva por parte del sistema respecto a los datos que acaba de
ingresar y requiere de una primera confirmación para obtener alguna respuesta. Por ejemplo, puede haber
ingresado un valor que no era el que deseaba para una clave foránea que corresponde al país del cliente
(suponiendo que no disfraza los valores del atributo y trabaja directamente con los códigos1), y en ese caso
como las inferencias no se realizarán interactivamente, puede querer realizar una confirmación en dos pasos,
de modo tal que en el primero se le muestren los datos inferidos y así ya sabrá que el país que digitó era el
deseado. En caso que no lo fuera, podrá modificarlo antes de que la información sea efectivamente grabada
en la base de datos (cosa que se logrará recién en el 2do. paso).
Para interfaz Web (que trabaja con CSV) así como para Win con validación a nivel del cliente, no resulta
necesario este tipo de comportamiento, ya que las inferencias y validaciones se van disparando
interactivamente a medida que el usuario va trabajando con la interfaz. Si igualmente se utiliza
Confirmación, las validaciones, controles de IR e inferencias, las reglas que no dependen de las grabaciones
de los registros, y disparo de fórmulas se ejecutarán tres veces (una en el primer paso de la confirmación (en
el cliente), segunda vez en el 2do. paso de la confirmación (en el cliente) y una última vez en el servidor,
junto con el resto de las reglas, cuando los registros pasan a grabarse en la base de datos.
------------------------------------------------------------------------------------------------------------
1 “Descripciones en vez de códigos” dentro del tema Integridad Referencial en este manual.
110
Transacciones en tiempo
de ejecución
WIN
eliminación
de líneas
(tecla Supr)
Update
En caso de existir registro con ese identificador, se edita en los campos del form, así como se
recuperan también las líneas de la tabla INVOICELINE asociadas y se editan en el grid.
En este caso el usuario podrá tanto modificar los valores editados (tanto de cabezal como de
líneas), para los atributos para los que esto esté permitido (los que no sean read only), insertar
nuevas líneas en el grid, así como eliminar alguna línea existente (posicionándose en la línea y
presionando la tecla DEL-Suprimir del teclado).
Luego de efectuadas todas las modificaciones (de datos preexistentes, inserción de nuevas líneas o
marcado para la eliminación de líneas mediante la tecla DEL), se presiona el botón de “Confirm”1
para aplicar los cambios.
En cambio, si lo que se desea es eliminar la factura entera, en ese y solo ese caso se presiona el
botón Delete.
Insert
En caso de no existir registro en la tabla INVOICE con el valor ingresado por el usuario en la PK,
entonces los campos del form estarán vacíos esperando ser ingresados por el usuario. El botón de
confirmación dirá “Insert”. El grid no contendrá líneas ingresadas. El usuario ingresará por tanto los
datos para el cabezal y las líneas, pudiendo arrepentirse de una línea que ingresó previamente y
borrarla utilizando el mismo procedimiento indicado antes (tecla DEL-Suprimir del teclado sobre la
línea).
Obsérvese que en este caso no tendrá sentido presionar el botón “Delete”, dado que los registros
aun no existen en la base de datos, por lo que no hay nada que borrar. Simplemente cerrando la
transacción, o pasando a editar otro registro, lo digitado por el usuario en la pantalla se perderá sin
grabarse.
Luego de ingresados sus datos, el usuario confirmará la pantalla mediante el botón “Insert”
(teniendo luego que reconfirmar si se trabaja con confirmación; botón “Confirm”).
---------------------------------------------------------------------------------------------------------
1 Si se trabaja con confirmación el botón dirá “Update” en primera instancia y luego “Confirm”. Si se
111
Transacciones en tiempo
de ejecución
WEB
eliminación de
líneas
(&GxRemove)
Existen algunas pequeñas diferencias, no obstante. Mientras que en ambiente Win contamos con el
control Grid que muestra la cantidad de líneas por pantalla que determine el analista cuando diseña
el form de la transacción, permite sin embargo el desplazamiento vertical (barra de scroll),
permitiendo entonces ingresar tantas líneas como se desee, con tan solo hacer scroll cuando se han
ingresado todas las líneas de la pantalla.
En Web, sin embargo, al no contar con esta facilidad, siempre se mostrará un número de líneas
vacías fijas para ser ingresadas por el usuario (valor configurable por el analista a nivel del grid, en
su propiedad “Rows”). Por defecto se muestran 5 líneas vacías.
Si el usuario ingresó las 5 líneas y necesita ingresar más, le bastará con presionar “Apply Changes”
y 5 líneas vacías más se adicionarán al formulario (si se trabaja con confirmación, esto dará la
oportunidad de agregar las líneas sin ingresar todo lo anterior en la base de datos).
Otra diferencia respecto al tratamiento de las líneas con respecto a Win es que en el grid del form
Web se incorpora automáticamente una primera columna conformada una variable del sistema de
nombre &GxRemove, en la forma de check box. Como lo indica su nombre, se utiliza para permitir
al usuario en ejecución marcar varias líneas para ser eliminadas.
Lo que en Win se realizaba mediante la tecla DEL, aquí se realiza marcando el check box de la línea
que quiere eliminarse.
Cuando se ingresa en la transacción en modo Insert, el check box no aparecerá en el grid, dado que
no existe ninguna línea aún ingresada, que pueda ser por tanto eliminada.
Al aplicar los cambios (Apply Changes) esta primera columna aparecerá para las líneas ingresadas
en el form.
Por lo demás, el modo de operación (cómo insertar, modificar, o eliminar registros) es análogo al de
Win.
112
Algunos elementos más
de las transacciones
Propiedades
• Propiedades
• Documentación
• Ayuda Ayuda
Documentación
Propiedades
Las propiedades asociadas a un objeto –en este caso nos referimos a una transacción, pero vale
en general, independientemente de cuál objeto se trate- permiten definir ciertos detalles
referentes al comportamiento general del objeto.
Sin tener el objeto abierto, es posible editar sus propiedades haciendo botón derecho sobre el
nombre del objeto, y seleccionando el ítem Properties del menú contextual.
Mediante cualquiera de los caminos anteriores, se abre un diálogo que ofrece las propiedades
asociadas al objeto activo, y donde el analista podrá especificar sus valores, definiendo así detalles
referentes al comportamiento del objeto.
Por ejemplo, la propiedad Confirmation de la que hablamos antes, así como la propiedad Client
Side Validation aparecen en este diálogo en caso de estar trabajando en un modelo .Net o Java
Win, dado que como dijimos oportunamente, pueden configurarse a nivel de objeto.
La propiedad Confirmation también aparecerá en este diálogo si el modelo es .Net o Java Web; no
así la otra. Asimismo dentro de la sección Web Transaction properties del diálogo de configuración
de propiedades, se encontrará la propiedad Theme para asociarle un objeto tema a la transacción.
Cada modelo, dependiendo de su configuración (lenguaje, interfaz, etc.), tendrá algunas
propiedades u otras para configurar.
A lo largo del curso iremos viendo algunas de estas propiedades. Para profundizar en el manejo de
alguna propiedad en particular, recomendamos acceder al Help de GeneXus.
113
Documentación
Los objetos GeneXus ofrecen una sección para que el analista pueda incluir documentación técnica acerca del
objeto.
Para ingresar a la sección de documentación de un objeto, teniendo al objeto abierto, se deberá seleccionar la
solapa Documentation, o el ítem Object / Documentation de la barra de menú de GeneXus.
También el botón de la barra de herramientas “Fast Access” que señalizamos en la transparencia, conducirá a la
sección de documentación del objeto activo.
Se abrirá un editor html para que el analista ingrese la documentación técnica correspondiente.
Ayuda
Los objetos GeneXus tienen una sección específica para poder escribir la Ayuda para el usuario final en el uso del
objeto.
Para ingresar a la sección de ayuda de un objeto, teniendo al objeto abierto, se deberá seleccionar la solapa Help,
o el ítem Object / Help de la barra de menú de GeneXus.
Se abrirá un editor Html para que el programador ingrese el texto de ayuda que se le desplegará al usuario cuando
ejecutando el objeto, solicite ayuda.
Si se ingresa texto de ayuda para objetos y/o atributos y/o variables de la base de conocimiento, luego de
especificar y generar las aplicaciones, se deberá generar el Help. Para ello se deberá seleccionar el ítem Build /
Generate Help.
Al seleccionar el ítem Build / Generate Help, se desplegará una pantalla para elegir entre dos formatos: CHM y
WEB. También se deberá seleccionar el lenguaje en el que se generará el Help1.
CHM: Es el formato que se deberá elegir para aplicaciones GUI-Windows. Permite empaquetar toda la ayuda
generada en un único archivo de formato CHM, brindando la posibilidad de buscar en la ayuda generada, se definirá
un índice, etc.
WEB: Es el formato que se deberá elegir para aplicaciones Web. En tiempo de ejecución se abrirá un browser con el
texto de la ayuda en formato HTML.
En el caso de elegir el formato CHM, se solicitará el path del compilador de Help (este compilador viene con Visual
Studio, pero no se instala automáticamente, sino que hay que instalarlo por separado).
Cualquiera sea el formato de ayuda elegido, se generará un directorio HELP bajo el directorio del modelo, en el que
se creará un archivo con extensión HTML por cada objeto/atributo/variable que tenga ayuda.
Si el formato elegido es CHM, se creará además un archivo de nombre APPHLP.CHM en el directorio del modelo, y
un archivo de definición de proyecto (de extensión HHP) que será utilizado por el compilador de Help, para la
generación del CHM.
-------------------------------------------------------------------------------------------------------------------------
1 Esto está directamente vinculado a la posibilidad de tener un texto de help por cada uno de los objetos Language,
utilizados para poder traducir la aplicación. Es decir, la posibilidad de tener un texto de Help para cada idioma
posible. Así deberá seleccionarse aquí cuál de esos textos de help (el correspondiente a qué lenguaje) deberá
tomarse para generar el help.
114
ATRIBUTOS FÓRMULA
115
Características
Cuando el valor de un atributo puede calcularse a partir del valor de otros atributos, constantes y/o
funciones, el mismo puede ser definido como fórmula.
Para definir que un atributo es una fórmula y especificar cómo calcularla, existen tres posibilidades,
todas ellas disponibles únicamente en el modelo de Diseño:
1) En la estructura de una transacción, así como se define para cada atributo su nombre, tipo de
datos y descripción, es posible definir que el atributo es una fórmula e indicar cuál es el cálculo que
debe hacerse. Esta vía directa en la estructura de una transacción que permite definir que un
atributo es una fórmula, se habilita tanto para un atributo ya definido y salvado previamente, como
para un atributo que se está definiendo en el momento.
2) En la estructura de una transacción, es posible pulsar el botón derecho del mouse sobre un
atributo que haya sido definido y salvado previamente; se abrirá el menú pop up correspondiente,
se deberá seleccionar el ítem Formula del mismo, y a continuación se presentará una pantalla de
edición de fórmula para que se indique cómo calcularla.
La definición de un atributo como fórmula -indicando cuál es el cálculo que debe hacerse para
obtener su valor- es una definición global. Dicho de otro modo, se trata de una definición a nivel
de la base de conocimiento y no a nivel de un objeto específico, independientemente de por cual de
las vías se realice su definición. Siempre que se referencie/utilice un atributo fórmula en cualquier
objeto de la base de conocimiento en el cual sea válido, GeneXus tendrá el conocimiento de qué
cálculo habrá que hacer para obtener su valor; así, en los programas generados asociados a los
objetos que utilicen fórmulas, incluirá el código necesario para que en tiempo de ejecución se
dispare el cálculo y se muestre el valor resultante.
116
Los atributos definidos como fórmula, a menos que se especifique lo contrario, no estarán almacenados en
la base de datos. Dado que sus valores se pueden calcular a partir de los valores de otros atributos,
constantes y/o funciones, resulta innecesario y sería redundante almacenarlos (motivo por el cual decimos
que son atributos virtuales).
A pesar de que los atributos definidos como fórmula no se crearán como campos físicos en ninguna tabla de
la base de datos, cada uno de ellos estará “asociado” a una tabla (siguiendo los mismos criterios de
normalización que se utilizan para determinar en qué tabla corresponde almacenar un atributo común).
Expresiones del estilo: “la tabla donde está el atributo fórmula x” no son correctas; nos referimos en cambio
a la tabla “asociada” o tabla “base” de un atributo fórmula.
117
Fórmulas vs. Asignaciones
atributo = expresión
• Diferencias:
fórmula Æ global
regla Æ local
Cuando el valor de un atributo puede obtenerse mediante un cálculo, existen dos opciones para reflejar este
conocimiento:
La sintaxis de definición en ambos casos es similar, sin embargo, existen diferencias importantes entre
ambas alternativas:
1. Un atributo definido como fórmula no se creará como campo físico en una tabla de la base de datos (a no
ser que se especifique lo contrario, definiéndolo como redundante, como se verá más adelante). En cambio,
si se especifica su cálculo por medio de una regla de asignación dentro de la transacción, el atributo no
presentará ninguna diferencia con respecto al resto de los atributos que no son fórmula y al igual que los
otros, se almacenará en la tabla que corresponda.
2. La asignación de un cálculo a un atributo mediante la definición del atributo como fórmula es una
definición global. Esto significa que el valor del atributo en tiempo de ejecución siempre se mostrará
actualizado al ejecutar todo programa que lo utilice, ya que su valor se calculará en el momento. Por el
contrario, la asignación de un cálculo a un atributo mediante la definición de una regla de asignación en una
transacción, es una definición local. Esto significa que solamente si se ejecuta la transacción que tiene
definida esa regla1, el cálculo se efectuará y se almacenará el valor resultante en el campo físico
correspondiente. De existir otro objeto definido en la base de conocimiento que actualice la tabla que
contiene al atributo cuyo valor se debe calcular, habrá que definir explícitamente en el mismo la asignación
del cálculo al atributo.
--------------------------------------------------------------------------------------------------------------
1 La transacción podrá ejecutarse como tal o como business components, como veremos más adelante. En
118
Clasificación
• Fórmulas Verticales
• SUM
• COUNT
Son incondicionales
• Fórmulas Aggregate/Select
• Aggregate: Sum, Count
• Select: Max, Min, Find
Son condicionales
• Fórmulas Compuestas
Varias expresiones Horizontales y/o Aggregate/Select condicionales
• Fórmulas Verticales:
Las fórmulas verticales son dos: SUM y COUNT.
SUM permite sumar y COUNT permite contar todas las ocurrencias del atributo referenciado, en la tabla en la
que se encuentre.
El atributo referenciado en la fórmula deberá pertenecer a una tabla directamente subordinada a la tabla
asociada (tabla base) del atributo fórmula, y no podrá ser un atributo primario.
Son incondicionales, significando esto que no permiten que se les incluya condición de disparo ni condiciones
sobre los registros a ser sumados/contados.
• Fórmulas Compuestas:
Una fórmula de este tipo está compuesta por varias fórmulas Aggregate/Select condicionales, pudiendo también
contener expresiones horizontales.
Como se puede percibir, dependiendo de la clase de fórmula que se defina, se podrán involucrar en la misma
atributos de tablas que tengan determinada relación en particular con respecto a la tabla asociada al atributo
fórmula.
En el presente curso no ahondaremos en las últimas dos clases de fórmulas. Para un estudio de las mismas
recomendamos dirigirse al curso no presencial de GeneXus, donde encontrará un tratamiento profundo de estos
temas.
119
Fórmulas Horizontales
Donde:
• expi : cualquier expresión válida (atributos, constantes,
funciones, operadores).
Los atributos involucrados deben ser de la tabla extendida.
expi : puede ser cualquier expresión válida, pudiendo contener atributos pertenecientes a la tabla
extendida de la tabla asociada al atributo que se está definiendo como fórmula y/o constantes y/o
funciones y/o operadores aritméticos (+,-,*,/,^) y/o operadores de strings (+) y/o operadores de fechas (+,-
). Otra posibilidad es que contenga una invocación con udp (comando que veremos en breve) a un
procedimiento GeneXus o a un programa externo que retorne un valor. El resultado -ya sea de las
expresiones o el retornado por el programa invocado- deberá ser del mismo tipo de datos que el del atributo
que se está definiendo como fórmula.
condi : es cualquier expresión lógica válida, pudiendo contener atributos pertenecientes a la tabla
extendida de la tabla asociada al atributo que se está definiendo como fórmula y/o constantes y/o
funciones y/o operadores lógicos (and, or, not) y/o operadores de comparación (>, >=, <, <=, =, like). La
primera condición que al ser evaluada dé True, provocará que el resultado de la fórmula sea el de la
expresión de la izquierda de esa condición (las demás no se seguirán evaluando).
otherwise: cuando ninguna de las condiciones evaluadas dan True, si existe una expi con cláusula
otherwise, el resultado de la fórmula será el de la expresión expi que anteceda a esta cláusula.
120
Fórmulas Horizontales: Ejemplos
CustomerBalance = CustomerTotalPurchases
- CustomerTotalPayments
En la transacción "Customer" del ejemplo, como se puede observar, además de los atributos
correspondientes a los datos personales del cliente, existen los atributos CustomerTotalPurchases
(total de compras del cliente) y CustomerTotalPayments (total de pagos del cliente), y se necesita
saber en todo momento cuál es el saldo del cliente, por lo qué también aparece el atributo
CustomerBalance.
Este saldo del cliente se calcula siempre igual: como la diferencia entre su total de compras
CustomerTotalPurchases y su total de pagos CustomerTotalPayments.
Como el cálculo que debe realizarse es siempre ese, tenemos dos alternativas para definirlo:
Analizando las diferencias entre ambas alternativas decidimos optar por la segunda. Para ello, lo
único que tendremos que hacer estando en el modelo de Diseño, es abrir la transacción "Customer"
y agregar en su estructura al atributo CustomerBalance. Se deberá ingresar el nombre, descripción
y tipo de datos del atributo, y en el campo fórmula lo siguiente: CustomerTotalPurchases –
CustomerTotalPayments.
Con esta definición GeneXus tendrá el conocimiento de que el atributo CustomerBalance será un
atributo virtual y sabrá de qué forma calcular su valor. Cuando el analista pase a un modelo de
Prototipo o Producción y haya un análisis de impacto, la definición del atributo CustomerBalance no
causará la creación del campo físico en la tabla CUSTOMER.
Este es un ejemplo de fórmula horizontal, debido a que se trata de un cálculo aritmético que
involucra atributos de la tabla extendida de la tabla asociada al atributo definido como fórmula
(en particular, en este caso los atributos involucrados pertenecen a la propia tabla base del atributo
fórmula).
121
Fórmulas Horizontales: Ejemplos
InvoiceId*
CustomerId CustomerId*
InvoiceDate CustomerName CountryId*
InvoiceTotal CountryId CountryName
InvoiceLineAmount =
ProductPrice*InvoiceLineQuantity if InvoiceLineQuantity<=100;
ProductPrice*InvoiceLineQuantity*0.9 otherwise;
Supongamos que queremos definir al atributo InvoiceLineAmount (importe de línea de factura) como la
siguiente fórmula: InvoiceLineQuantity * ProductPrice (incondicional).
Al hacerlo, si bien el atributo estará “asociado” a la tabla INVOICELINE, no se almacenará en ella (diremos que
INVOICELINE será su tabla base).
La definición de esta fórmula se compondrá de una expresión aritmética sin condiciones de disparo, porque su
valor siempre se calcula de la misma forma. En la expresión aritmética intervienen los atributos
InvoiceLineQuantity y ProductPrice, ambos pertenecientes a las tablas base y extendida del atributo fórmula
respectivamente.
Si, en cambio, se necesita definir que cuando la cantidad de producto llevada por el cliente supere las 100
unidades, se haga un 10% de descuento al importe de línea correspondiente, la fórmula se transformará en la
que aparece arriba en la transparencia, en la que hay dos expresiones condicionales.
Si bien elegimos condicionar la segunda expresión con la cláusula otherwise, hubiese sido equivalente definir:
ya que las dos condiciones de disparo son disjuntas y no dejan fuera ninguna posibilidad.
122
Fórmulas Verticales
Características:
• Suman o cuentan todas las ocurrencias del atributo att en
una tabla.
• La tabla asociada a att debe ser directamente
subordinada de la tabla base del atributo fórmula.
• att no puede formar parte de ninguna clave del modelo
(no puede ser atributo primario).
• Son incondicionales.
SUM permite sumar los valores contenidos en el atributo att para todos los registros de la tabla asociada a
él, que necesariamente deberá ser directamente subordinada a la del atributo que estamos definiendo
como fórmula.
COUNT permite contar todas las ocurrencias del atributo referenciado en la fórmula, que también debe
pertenecer a una tabla directamente subordinada a la tabla base del atributo que se está definiendo
como fórmula.
Las fórmulas verticales son incondicionales, es decir, no permiten que se les incluya condiciones sobre los
registros de la tabla subordinada a ser sumados/contados.
donde:
att: Puede ser un atributo almacenado en una tabla directamente subordinada a la tabla base del
atributo que se está definiendo como fórmula, o un atributo fórmula horizontal o vertical asociado también
a una tabla directamente subordinada a la tabla base del atributo fórmula. En cambio, no podrá ser un
atributo fórmula Aggregate/Select, ni involucrar directa o indirectamente a un atributo fórmula
Aggregate/Select.
No puede formar parte de ninguna clave del modelo.
Para el caso de fórmula SUM, debe ser de tipo numérico.
El tipo de datos del atributo fórmula debe ser numérico.
123
Fórmulas Verticales
Características (continuación):
Tabla Tabla
Base Navegada
Registros sumados/contados:
Si bien no pueden establecerse condiciones sobre los registros de la tabla subordinada para que éstos
sean utilizados en la suma/cuenta (incondicionalidad de las fórmulas verticales), en la resolución de
la fórmula GeneXus no considera todos los registros de la tabla subordinada.
Por el contrario, solo se suman/cuentan aquellos que estén “relacionados” con el registro de la tabla
base para el cuál la fórmula se está evaluando.
Quedará más claro este punto con los ejemplos que veremos a continuación.
124
Fórmulas Verticales: Ejemplos
Transacción "Invoice"
InvoiceId*
CustomerId INVOICE InvoiceId*
CustomerId
CustomerName 1
InvoiceDate
InvoiceDate
InvoiceTotal = SUM(InvoiceLineAmount)
N
(ProductId*
ProductDescription INVOICELINE
ProductPrice
InvoiceId*
InvoiceLineQuantity
ProductId*
InvoiceLineAmount = ProductPrice*InvoiceLineQuantity
InvoiceLineQuantity
)
Queremos definir en la transacción "Invoice" que el importe total (InvoiceTotal) se calcule automáticamente
como la suma de los importes de las líneas (InvoiceLineAmount).
El atributo InvoiceTotal se encuentra en el primer nivel de la transacción "Invoice" (siendo su tabla asociada:
INVOICE) y el atributo InvoiceLineAmount se encuentra en el segundo nivel de la transacción "Invoice"
(siendo su tabla asociada: INVOICELINE).
Las fórmulas verticales no permiten que se les incluya condiciones de filtro. Por ejemplo, no podremos definir
con una fórmula vertical “contar las líneas de la factura que cumplan que la cantidad de producto llevada
supere las 100 unidades”. Solamente podremos “contar las líneas de la factura” (todas).
Sin embargo, como resulta evidente en el ejemplo que venimos viendo, los registros de INVOICELINE que
intervendrán en el cálculo serán sólo los relacionados con el registro de INVOICE para el que se esté
calculando InvoiceTotal. Es decir, se contemplarán todos aquellos registros de INVOICELINE que cumplan,
para el registro actual de INVOICE:
INVOICELINE.InvoiceId = INVOICE.InvoiceId.
De modo que si bien las fórmulas verticales no permiten que se les incluya condiciones sobre los registros de
la tabla directamente subordinada a ser sumados/contados, en la resolución de la fórmula GeneXus no
considera todos los registros de la tabla subordinada, sino solamente aquellos que estén relacionados con el
registro de la tabla base para el cuál se esté evaluando la fórmula.
125
Fórmulas Verticales: Ejemplos
Transacción "Invoice"
InvoiceId*
CustomerId INVOICE InvoiceId*
CustomerId
CustomerName 1
InvoiceDate
InvoiceDate
InvoiceLines = COUNT(InvoiceLineQuantity)
N
(ProductId*
ProductDescription INVOICELINE
ProductPrice
InvoiceId*
InvoiceLineQuantity
ProductId*
InvoiceLineAmount = ProductPrice*InvoiceLineQuantity
InvoiceLineQuantity
)
Si necesitamos contar la cantidad de líneas que tiene una factura, podemos agregar un atributo en el primer
nivel de la estructura de la transacción "Invoice" (de nombre InvoiceLines), y definirlo como la fórmula:
COUNT(InvoiceLineQuantity)
o:
COUNT(InvoiceLineAmount)
Es exactamente lo mismo optar por referenciar a cualquiera de los atributos anteriores en la fórmula COUNT,
ya que el primero es un atributo almacenado en la tabla INVOICELINE y el segundo es un atributo fórmula
asociado a la tabla INVOICELINE, la cual es directamente subordinada a la tabla INVOICE (asociada al
atributo InvoiceLines que se está definiendo como fórmula). En cambio a los atributos InvoiceLineId y
ProductId no es posible referenciarlos en la definición de la fórmula COUNT porque si bien se encuentran en
la tabla INVOICELINE, forman parte de alguna clave.
126
Fórmulas Verticales: Ejemplos
Transacción “Customer"
CustomerId* CUSTOMER
CustomerName
CustomerTotalPurchases= SUM(InvoiceTotal)
1 CustomerId*
CustomerName
CustomerTotalPayments
CustomerTotalPayments
CustomerBalance N
InvoiceId* INVOICE
CustomerId 1
InvoiceDate N
INVOICELINE
InvoiceTotal =
SUM( InvoiceLineAmount) InvoiceId*
ProductId*
InvoiceLineQuantity
CUSTOMER e INVOICE:
Relación 1-N Æ hay subordinación InvoiceLineAmount =
directa Æ vale la fórmula ProductPrice*InvoiceLineAmount
GeneXus la interpretará como una fórmula vertical, ya que la tabla base es CUSTOMER e InvoiceTotal está
asociado a INVOICE, tabla directamente subordinada a CUSTOMER.
Como restricción, no se pueden utilizar más de dos fórmulas SUM anidadas. Es decir, es posible definir una
fórmula SUM que involucre otra fórmula SUM, pero ésta última no puede involucrar otra fórmula SUM.
127
Fórmulas Verticales
Características (continuación):
El hecho de que una fórmula no esté almacenada puede ocasionar en algunos casos problemas de
performance debido al tiempo que puede demorar el cálculo. Esto dependerá de la cantidad de registros
que se sumen o cuenten.
Supongamos, por ejemplo, que tenemos miles de facturas, cada una de las cuales puede tener miles de
líneas. Si frecuentemente se requiere sacar listados de las facturas mostrando sus totales, y los totales se
calculan mediante la definición de un atributo fórmula SUM, por cada una de las miles de facturas, se
deberán navegar miles de registros correspondientes a sus líneas para realizar el cálculo del total. Esto
representa un costo elevado de performance.
Para evitar este inconveniente, GeneXus provee la posibilidad de definir a un atributo fórmula como
redundante. Al hacerlo, el atributo deja de ser virtual y pasa a estar almacenado en la tabla asociada.
En tiempo de ejecución, al utilizar una transacción que contiene un atributo fórmula redundante, el
comportamiento es el siguiente: la fórmula se dispara efectuándose el cálculo y el resultado se almacena
en el campo físico de la base de datos.
En los programas generados correspondientes a transacciones que tienen involucrados atributos fórmulas
redundantes, GeneXus incorpora rutinas que se encargan de almacenar los datos redundantes, siempre
que se recalculen.
Al consultar un atributo fórmula redundante, no se dispara la fórmula para obtener el cálculo sino que se
toma el valor almacenado del campo de la base de datos.
128
Equivalencia entre definir un atributo fórmula SUM redundante y utilizar regla add
Definir un atributo como fórmula SUM redundante es equivalente a que el atributo sea común (no fórmula) y
definir una regla add en las transacciones en las cuales se encuentre, para efectuar el mismo cálculo. Ambas
cosas son equivalentes, con la salvedad de que la definición de la fórmula es global (está asociada al
atributo), mientras que la definición de la regla es local (es decir, la regla add se va a ejecutar solamente
cuando se hagan ABM1 a través de la transacción en la cual está definida la regla).
− Cada vez que se inserte una línea, la regla add sumará el valor de InvoiceLineAmount al valor de
InvoiceTotal.
− Cada vez que se elimine una línea, la regla add restará el valor de InvoiceLineAmount al valor de
InvoiceTotal.
− Cada vez que cambie el valor de InvoiceLineAmount de una línea (porque se haya modificado el valor de
alguno de los atributos que participan en la definición de su fórmula), la regla add sumará a InvoiceTotal, la
diferencia entre el valor nuevo y el viejo de InvoiceLineAmount.
Una de las diferencias a tener en cuenta a la hora de decidir si calcular el valor de un atributo mediante la
definición de una fórmula o regla, es que con la primera alternativa, el atributo no se almacenará y con la
segunda, si. Sin embargo, como nos estamos refiriendo a definir al atributo InvoiceTotal como la fórmula:
SUM(InvoiceLineAmount) redundante, el atributo InvoiceTotal se almacenará en su tabla asociada (INVOICE),
de igual forma que si se define al atributo InvoiceTotal común (no fórmula) y se incluye la regla:
add(InvoiceLineAmount, InvoiceTotal) en la transacción "Invoice" para efectuar el cálculo.
Concluyendo, estas dos alternativas son equivalentes, ya que en tiempo de ejecución en la medida que se
vayan insertando, modificando o eliminando líneas por medio de la transacción "Invoice", el valor del atributo
InvoiceTotal se irá calculando y su valor quedará almacenado en un atributo físico de la tabla INVOICE.
-------------------------------------------------------------------------------------------------------------------1 ABM
= Altas, Bajas y Modificaciones.
129
COMUNICACIÓN
ENTRE OBJETOS
130
Comunicación entre objetos
Procedimiento
Reporte PDF
Los objetos GeneXus pueden comunicarse entre ellos o con otros programas externos.
Un objeto GeneXus puede llamar o ser llamado por otro objeto, intercambiando información a
través de parámetros1.
Veremos a continuación cómo invocar desde un objeto a otro, y cómo especificar los parámetros
(en el objeto llamador y en el llamado) para el intercambio de la información.
El esquema presentado arriba ilustra las posibles interacciones entre objetos GeneXus para una
aplicación Web. Obsérvese que la flecha simple entre Web Panel y Reporte PDF (así como entre
Transacción y Reporte PDF) indica que un Web Panel podrá invocar a un Reporte2 pero un Reporte
no podrá invocar a un Web Panel (o transacción Web).
El esquema de interacción entre los objetos GeneXus Win es más flexible pues todos los objetos
pueden interactuar con todos. En ese caso habrá que sustituir al objeto Web Panel por el objeto
Work Panel, y los reportes tendrán también más flexibilidad, pues podrán invocarse en cualquier
circunstancia, y podrán emitirse en formatos varios (y no solo PDF).
----------------------------------------------------------------------------------------------------------
1 Para aplicaciones Web también se utilizan cookies para el intercambio de información.
2 En Web solamente podrá invocarse a reportes PDF, como mencionaremos en el capítulo que
131
Comunicación entre objetos
Win - Web
2 posibilidades:
CALL - Permite invocar a un objeto GeneXus o a un programa externo, tanto sin pasarle
parámetros, como pasándole.
UDP (User Defined Procedure) - Permite invocar a un objeto GeneXus o programa externo
tanto sin pasarle parámetros como pasándole, y con la particularidad de que el programa
llamado retornará necesariamente al menos un valor al programa que lo invocó. En Win la
utilización de UDP es más amplia que en Web, pues en Win todo objeto al finalizar su ejecución
devuelve el control al objeto llamador; sin embargo en Web esto no sucede en todos los objetos,
por lo que UDP se utiliza únicamente para invocar a procedimientos (debido a que estos cumplen la
condición de ejecutar y devolver el control al llamador).
Una invocación (ya sea con CALL o UDP) podrá escribirse en distintas partes del objeto llamador,
dependiendo de si el mismo es una transacción, web panel, procedimiento, etc.:
A su vez UDP puede utilizarse también en la definición de un atributo fórmula. Es decir, se define
que cierto atributo es una fórmula y que la definición de la misma consiste en la invocación a un
procedimiento utilizando UDP.
PARM – Cuando un objeto es invocado desde otro con parámetros, debe tener declarada la lista de
parámetros que recibe. Esta declaración se realiza mediante la regla: PARM.
A continuación daremos más detalles acerca del uso de CALL, UDP y PARM.
132
Comunicación entre objetos
Win - Web
2 posibilidades – Ejemplos:
1) Ej: RListInvoice.Call(InvoiceId)
NOTAR PREFIJO
En el reporte ListInvoice Æ Parm(InvoiceId)
Aquí mostramos un ejemplo de uso de CALL para realizar una invocación y otro ejemplo de uso de
UDP.
Dependiendo de qué objeto llamador se trate, estas invocaciones podrán escribirse en una sección u
otra del mismo, pero independientemente de eso, aquí apuntamos a mostrar que CALL permite
invocar a un objeto con estilo de invocación a un programa, mientras que UDP invoca a un objeto
con estilo de invocación a una función.
En el primer ejemplo se está utilizando CALL para invocar a un reporte (objeto ListInvoice)
pasándole 1 parámetro (InvoiceId). En el reporte invocado se ha declarado el parámetro que recibe
(en su sección de reglas, mediante la regla parm).
Podemos ver entonces que cuando se utiliza CALL para invocar a un objeto enviándole N
parámetros, se deben declarar los N parámetros (posicionales y del mismo tipo de datos que los
enviados) en el objeto invocado mediante la regla parm.
133
Convención de nombrado de los objetos
GeneXus invocados
Tanto si se utiliza, CALL como UDP para invocar a un objeto GeneXus, el nombre del objeto que se
invoca debe estar formado por: un prefijo (que identifica al tipo de objeto) + el nombre del objeto.
Por ejemplo, si se desea invocar desde algún objeto GeneXus a la transacción “Invoice” el nombre
que se deberá escribir en la invocación es TInvoice. A su vez, si se desea invocar desde algún objeto
GeneXus a un reporte de nombre “CustomersReport”, el nombre que se deberá escribir en la
invocación es RCustomersReport.
Recomendamos utilizar el ítem: Insert/Object de la barra de menú de GeneXus para seleccionar los
objetos que se deseen invocar, ya que automáticamente se insertarán con el prefijo que corresponda
y el analista no tendrá que recordarlo de memoria.
Nota: Si el objeto invocado es un programa externo, el nombre del mismo deberá incluirse entre
comillas en la invocación.
134
¿Qué declarar en la regla parm:
variable o atributo?
Al definir una invocación a un objeto (ya sea utilizando CALL o UDP), si se tienen que enviar datos
por parámetro al objeto invocado, resulta evidente determinar si enviar atributos y/o variables: si
un dato a ser enviado por parámetro, se encuentra en el objeto invocador, en un atributo, habrá
que enviar al mismo; y si se encuentra en una variable, habrá que enviar a la variable.
¿Cuál es la diferencia entre declarar un parámetro como variable o como atributo en la regla parm
del objeto invocado? Si se declara una variable, se la podrá utilizar libremente en la lógica del
objeto invocado: se la podrá utilizar como condición de filtro por igualdad, por mayor, mayor o
igual, menor, menor o igual, LIKE, etc.; se la podrá utilizar para alguna operación aritmética, como
bandera, o lo que se necesite. Si en cambio se declara un atributo, automáticamente el mismo
actuará como filtro por igualdad en el objeto, no siendo posible modificar el valor recibido.
Cuando lleguemos a la etapa del curso en la cual podamos invocar a reportes pasándoles
parámetros así como a otros objetos, podremos terminar de comprender esto ya que la práctica
nos permitirá ver este tema.
135
Definición de parámetros de entrada (in),
salida (out),entrada-salida (inout) en Parm
• Ventajas:
• Mejor especificación de la semántica de las interfaces.
• Independencia del lenguaje de generación.
• Optimizar el pasaje de parámetros de las aplicaciones de acuerdo a la
arquitectura en la que éstas se implementan (ventaja contrapuesta a la
anterior).
Para cada parámetro que se declara en la regla parm, es posible definir si se desea que el mismo opere: de
entrada (in), de salida (out), o de entrada-salida (inout).
Como se puede percibir claramente en la sintaxis del ejemplo, el primer parámetro definido es de salida, el
segundo parámetro es de entrada, y el cuarto parámetro es de entrada-salida. Cuando no se especifica
nada, como es el caso del tercer parámetro de la sintaxis, dependerá de lo siguiente:
Declarar explícitamente cómo se desea que cada parámetro opere, tiene las siguientes ventajas:
1. Mejor especificación de la semántica de las interfaces; es decir, tanto para GeneXus como para el
programador cuando trabaje con un objeto, será claro tener definido en la regla parm el objetivo de cada
parámetro, es decir:
- si el mismo vendrá con valor y luego de la ejecución del objeto invocado, se
devolverá al objeto invocador el valor con que haya quedado (inout).
- si el mismo vendrá con valor y luego de la ejecución del objeto invocado, no se
devolverá al objeto invocador el valor con que haya quedado (in).
- si el mismo no vendrá con valor y luego de la ejecución del objeto invocado, se
devolverá al objeto invocador el valor que tenga (out).
2. Independencia del lenguaje de generación; es decir, si se define explícitamente cómo se desea que cada
parámetro opere, al generar las aplicaciones utilizando diferentes lenguajes de generación no estará
cambiando el comportamiento de los parámetros en base al comportamiento por defecto del lenguaje de
generación correspondiente.
3. Optimizar el pasaje de parámetros de acuerdo a la arquitectura en la que éstas se generen (siendo una
ventaja contrapuesta a la anterior); esto se refiere a lo siguiente: para la mayoría de los lenguajes es más
eficiente pasar los parámetros por referencia (inout) que por valor (in / out); pero en Java, por ejemplo, los
parámetros solo se pueden pasar por valor, por lo que para lograr la funcionalidad de pasarlos por referencia
es necesario hacer conversiones de parámetros, lo cual puede redundar en un overhead importante; por otro
lado, cuando se trata de aplicaciones distribuidas (por ejemplo Java con RMI o HTTP), la utilización de
parámetros de tipo out tiene la ventaja de que no es necesario enviar al parámetro en la invocación, a
diferencia de si los parámetros se definen de inout (que implica que haya que pasar todos los parámetros);
esto tiene como consecuencia que se envíen más bytes de los necesarios, lo cual es inconveniente
especialmente en entornos tales como Internet.
136
Comunicación entre objetos
Web
2) control.Link = Link(URL)
La función Link se asocia a la propiedad link de un control dentro de cualquier evento de una
transacción o web panel1, teniendo como resultado que al hacer clic sobre dicho control se realizará la
llamada al objeto o URL referenciada en el link.
PgmName (el objeto invocado) podrá ser un web panel, transacción, o reporte PDF 2.
--------------------------------------------------------------------------------------------------------
1 a excepción del evento Load de los web panels (que veremos más adelante)
2 también un procedimiento HTTP, pero no profundizaremos sobre este concepto en este curso
137
Comunicación entre objetos
Web
1) PgmName.Link([,par1 …, parN])
Ej: TCustomer.Link(CustomerId)
2) Link(URL)
Ej: Link(‘http://www.google.com’)
El comando Link puede ser utilizado dentro de cualquier evento de una transacción o web panel1.
--------------------------------------------------------------------------------------------------------
1 a excepción del evento Load de los web panels (que veremos más adelante)
2 también un procedimiento HTTP, pero no profundizaremos sobre este concepto en este curso
138
ORDEN DE EJECUCIÓN DE REGLAS Y
FÓRMULAS
139
Orden de ejecución de
reglas y fórmulas
Transacción "Invoice"
InvoiceId* “Customer”
InvoiceDate CustomerId*
CustomerId CustomerName
CategoryId
CustomerTotalPurchases
CustomerTotalPurchases
CategoryDiscount
InvoiceDiscount = InvoiceSubTotal *CategoryDiscount “Category”
InvoiceShippingCharge = Max( ShippingDate, ShippingDate CategoryId*
<=InvoiceDate,,ShippingCharge) CategoryDiscount
InvoiceSubTotal = SUM( InvoiceLineAmount )
InvoiceTotal = InvoiceSubTotal – InvoiceDiscount “Shipping”
+ InvoiceShippingCharge ShippingDate*
(ProductId* ShippingCharge
ProductPrice
ProductStock “Product”
InvoiceLineQuantity ProductId*
ProductPrice
InvoiceLineAmount) = InvoiceLineQuantity *
ProducStock
ProductPrice
Reglas:
Add( InvoiceTotal, CustomerTotalPurchases);
Error( ‘Insufficient Stock’ ) if ProductStock<0;
Subtract( InvoiceLineQuantity, ProductStock);
Supongamos que estamos definiendo una aplicación para una empresa que vende determinados
productos, y que cuenta con un servicio de entrega a domicilio que lleva la mercadería a sus
clientes. Y definimos entre otras, las siguientes 5 transacciones:
"Customer" (para registrar los clientes de la empresa)
“Category” (a las que pertenece cada cliente)
“Shipping” (envíos: guarda un histórico de costos de envío)
"Invoice" (facturas que se emiten a los clientes)
"Product" (productos vendidos por la empresa)
140
Árbol de evaluación
R. Add(InvoiceTotal, CustomerTotalPurchases);
F. InvoiceTotal = InvoiceSubTotal - InvoiceDiscount +
InvoiceShippingCharge
F. InvoiceDiscount = InvoiceSubTotal*CategoryDiscount CustomerTotalPurchases
F. InvoiceShippingCharge = MAX( ShippingDate, ShippingDate <=
InvoiceDate,,ShippingCharge)
F. InvoiceSubTotal = SUM( InvoiceLineAmount )
F. InvoiceLineAmount = InvoiceLineQuantity *ProductPrice InvoiceTotal
R. Subtract(InvoiceLineQuantity, ProductStock) ;
R. Error( ‘Insuffcient Stock’) if ProductStock < 0 ;
InvoiceDiscount
InvoiceShippingCharge
error (‘Insufficient Stock ’)
InvoiceSubTotal ShippingDate InvoiceDate
ShippingCharge
ProductStock InvoiceLineAmount CategoryDiscount
InvoiceLineQuantity ProductPrice
Podemos imaginar que el árbol se ejecuta de abajo hacia arriba, es decir que cada vez que
cambia el valor de un atributo, se ejecutan todas las reglas y fórmulas que dependen de ese
atributo (y que en el árbol se encuentran hacia arriba).
Por ejemplo, si cambia la cantidad de una línea de una factura (InvoiceLineQuantity), como este
atributo interviene en la fórmula que calcula el importe de la línea (InvoiceLineAmount), dicha
fórmula se redisparará. Por cambiar el importe de una línea, deberá redispararse la fórmula
correspondiente al subtotal de la factura (InvoiceSubTotal) y en consecuencia, también deberá
recalcularse la fórmula correspondiente al descuento (InvoiceDiscount), ya que depende del
subtotal. Deberá redispararse también la fórmula correspondiente al total de la factura
(InvoiceTotal) ya que depende tanto del valor de InvoiceSubTotal como del valor de
InvoiceDiscount. Por último, por cambiar el total también se tendrá que disparar la regla
Add(InvoiceTotal, CustomerTotalPurchases);.
Además de dispararse todas las fórmulas y reglas involucradas en la rama derecha del árbol desde
el atributo InvoiceLineQuantity, también se dispararán las fórmulas y reglas involucradas en la rama
izquierda. Es decir, que al cambiar el valor del atributo InvoiceLineQuantity, se redisparará también
la regla Subtract(InvoiceLineQuantity, ProductStock); y en consecuencia, por modificar esta regla
el valor del atributo ProductStock, se evaluará si habrá que disparar la regla Error(‘Stock
Insuficiente’) if ProductStock < 0;
Concluyendo, las reglas y fórmulas que se definen en una transacción suelen estar interrelacionadas
y GeneXus determina las dependencias entre ellas así como su orden de evaluación.
141
Estas reglas están interrelacionadas porque las dos involucran al atributo ProductStock. Ahora, mientras la
segunda solamente consulta su valor, la primera lo actualiza. Entonces, la regla que actualiza al atributo será la
que se disparará primero, y luego se disparará la que lo consulta.
Toda regla que actualice el valor de un atributo se disparará antes que una regla que lo consulte (esto se puede
observar claramente en el árbol). Por este motivo es que la regla Error consulta si el atributo ProductStock quedó
con valor negativo; porque como dijimos la sustracción se realizará primero.
En la programación clásica se suele consultar primero si alcanza el stock, y en caso de que sea suficiente recién se
hace la sustracción. Por eso quienes están aprendiendo GeneXus pueden intuitivamente escribir la regla:
Error(‘Insufficient Stock') if InvoiceLineQuantity > ProductStock. Esta sintaxis es correcta, sin embargo no es
correcta su lógica ya que como venimos explicando, en el árbol de evaluación determinado por GeneXus primero
se disparará la regla Subtract y luego la regla Error; por lo tanto tendremos que especificar que se dispare el
mensaje de error si es que quedó el stock con valor negativo, dado que ya se habrá ejecutado la sustracción al
momento de consultar el valor de ProductStock.
Y no:
Cuando se dispara una regla Error, se detiene cualquier actualización a la base de datos y se desarma el
árbol de evaluación, quedando todo en el estado anterior a producirse el error. Siguiendo el ejemplo que
veníamos viendo, si al dispararse la regla Subtract el stock quedara negativo, se dispararía la regla Error. Como
consecuencia de dispararse la regla Error, se desharía el Subtract que se había ejecutado, así como todas las
demás reglas y fórmulas que se hayan ejecutado (recálculo de los atributos InvoiceLineAmount, InvoiceSubTotal,
...., CustomerTotalPurchases).
Al especificar una o varias transacciones, seleccionando la opción Detailed Navigation, se detallará en el listado
de navegación resultante el orden de ejecución de todas las reglas y fórmulas definidas en la transacción.
142
Alteraciones del orden de disparo
de las reglas
Error
Total Total
SupplierId* Calculado Ingresado
InvoiceId*
...
InvoiceEntTotal Entered Total
InvoiceLineAmount
( ProductId*
InvoiceLineQuantity
InvoiceLinePrice
InvoiceLineAmount = InvoiceLinePrice * InvoiceLineQuantity)
...
InvoiceCalcTotal = SUM(InvoiceLineAmount) Calculated Total
En la mayoría de los casos el orden de ejecución de las reglas definido por GeneXus a partir de
nuestras especificaciones es el deseado. Pero en algunos casos podemos querer cambiar el momento
de disparo de una regla.
Ejemplo:
Definimos una transacción para registrar las facturas que nos entregan nuestros proveedores.
El identificador del primer nivel es compuesto por el identificador de proveedor y el identificador de
factura, ya que el número de factura no nos sirve como identificador único, porque proveedores
distintos pueden repetir el mismo número de factura.
Para cada factura de un proveedor que se ingrese, nos interesa controlar que el total que venga
escrito en la factura (y que se ingresará en el atributo InvoiceEntTotal) sea correcto. Para hacer este
control, definimos al atributo InvoiceCalcTotal como fórmula vertical SUM(InvoiceLineAmount), y
agregamos una regla Error que se disparará si no coinciden los valores de los atributos
InvoiceEntTotal y InvoiceCalcTotal:
Error('El total ingresado no coincide con el total calculado') if InvoiceCalcTotal <> InvoiceEntTotal;
Si construimos el árbol de evaluación correspondiente a las fórmulas y regla que hemos definido en
esta transacción:
143
vemos que las dependencias indican que cada vez que se agreguen, modifiquen o eliminen valores de los atributos
InvoiceLinePrice e InvoiceLineQuantity en las líneas, se recalculará el valor del atributo InvoiceLineAmount
correspondiente; en consecuencia, se recalculará el valor del atributo fórmula InvoiceCalcTotal que hemos definido
para tener el total calculado de la factura; y como este atributo está involucrado en la condición de disparo de la
regla Error, si se cumple dicha condición de disparo, se disparará la regla Error(‘El total ingresado no coincide con
el total calculado’) if InvoiceCalcTotal <> InvoiceEntTotal.
Ahora, prestemos atención a que la condición de disparo “InvoiceCalcTotal <> InvoiceEntTotal” se va a cumplir
repetidamente en la medida que el operador vaya ingresando líneas, porque para cada línea que se ingrese se
calculará el valor del atributo fórmula InvoiceLineAmount de la línea, y en consecuencia se recalculará el valor del
atributo fórmula InvoiceCalcTotal. Pero el valor calculado de este atributo no coincidirá con el valor ingresado en el
atributo InvoiceEntTotal hasta que no se hayan ingresado todas las líneas de la factura; entonces, se disparará la
regla Error(‘The calculated total doesn’t match with the entered total’) if InvoiceCalcTotal <> InvoiceEntTotal;.
Concluimos entonces que en este caso no nos sirve lo que determina el árbol de evaluación, ya que no queremos
que se evalúe la condición de disparo de la regla Error cada vez que el operador ingrese, modifique o elimine líneas,
sino que recién necesitamos que se evalúe cuando el usuario haya terminado de trabajar con todas las líneas de la
factura.
GeneXus ofrece eventos o momentos de disparo en las transacciones, que ocurren antes o después de
determinada acción, como la grabación del cabezal, o de una línea. Las reglas de las transacciones pueden
condicionarse de manera tal de dispararse en el preciso instante en que ocurre alguno de esos eventos de disparo.
Siguiendo el ejemplo que veníamos viendo, existe un evento de disparo que ocurre luego de iterar en un nivel y
salir del mismo. La sintaxis de este evento de disparo es: AfterLevel Level Atributo, debiendo ser Atributo un
atributo perteneciente al nivel que se ha iterado y se abandona.
De modo que a la regla Error de nuestro ejemplo, le agregaríamos este evento de disparo, y quedaría definida de la
siguiente forma:
Error(‘The calculated total doesn’t match with the entered total’) if InvoiceCalcTotal<>InvoiceEntTotal On
AfterLevel Level InvoiceLinePrice;
Con este evento de disparo que hemos agregado a la regla logramos controlar lo que deseábamos en el momento
adecuado.
144
Eventos de disparo
•BeforeValidate
Î Eventos de disparo:
•AfterValidate
•BeforeInsert, BeforeUpdate, BeforeDelete
•AfterInsert, AfterUpdate, AfterDelete
•AfterLevel
•BeforeComplete
•AfterComplete
Al momento de la confirmación de la transacción, ocurre una serie de acciones que es necesario conocer para poder
programar correctamente el comportamiento de las reglas.
Para una transacción de dos niveles, podríamos enumerarlas como sigue:
• validación de los datos del cabezal
• grabación física del cabezal (ya sea inserción, modificación o eliminación)
• validación de los datos de la primera línea
• grabación física de los datos de la primera línea
• validación de los datos de la segunda línea
• grabación física de los datos de la segunda línea
•…
• validación de los datos de la n-ésima línea
• grabación física de los datos de la n-ésima línea
• commit
La acción de “validación de los datos del cabezal” ocurre cuando se han validado todos y cada uno de los campos
ingresados en el cabezal. Observar que en este punto ya se han disparado todas las reglas que correspondían a
atributos del cabezal y que no tenían evento de disparo asociado (ejemplo: Default(InvoiceDate, &today)).
Inmediatamente después se grabará el registro correspondiente al cabezal.
Análogo es el caso de las líneas: “la validación de los datos de una línea” ocurre cuando ya se han validado todos y
cada uno de los datos de la línea, y también se han disparado todas las reglas correspondientes según el árbol de
evaluación (ejemplo: subtract( InvoiceLineQuantity, ProductStock)). Inmediatamente después de esta acción de
validación, se grabará físicamente el registro correspondiente a la línea.
Cada transacción, al terminar de trabajar con un cabezal y sus líneas, realiza un commit (es automático; será
colocado en el código generado por GeneXus, a menos que el analista especifique lo contrario, como veremos más
adelante). Es decir, si se van a ingresar los datos de dos facturas distintas utilizando la transacción “Invoice”, luego
de ingresados los datos de la primera, se commitearán sus registros, y luego se ingresará la segunda, al cabo de lo
cuál se commitearán sus registros.
Los eventos de disparo de reglas permiten definir que se ejecuten antes o después de alguna de las acciones que
acabamos de enumerar. Veremos cuándo ocurre cada evento de disparo.
145
Evento de disparo: BeforeValidate
Este evento de disparo ocurre un instante de tiempo antes de que la información de la instancia con la que se está
trabajando (cabezal o línea x) sea validada (o confirmada). Es decir, ocurrirá un instante de tiempo antes de la
acción de “validación del cabezal” o “validación de la línea”, según corresponda. Observar que aquí también se
habrán disparado todas las reglas según el árbol de evaluación que no estén condicionadas a evento de disparo
alguno.
El evento de disparo AfterValidate permite especificar que una regla se ejecute inmediatamente antes de que se
grabe físicamente cada instancia del nivel al cual está asociada la regla, en la tabla física correspondiente, y después
de que se hayan validado los datos de esa instancia.
En otras palabras, si se le agrega el evento de disparo AfterValidate a una regla, la misma se ejecutará para cada
instancia del nivel al cual esté asociada, inmediatamente antes de que la instancia se grabe físicamente (ya
sea que se inserte, modifique o elimine) como registro en la tabla física asociada al nivel.
EJEMPLOS
1. Hay veces en las que no contamos con la posibilidad de utilizar la propiedad Autonumber para numerar de forma
automática y correlativa los atributos que son clave primaria simple. Tal funcionalidad es provista por los
manejadores de base de datos (DBMSs) y GeneXus la aprovecha y permite usarla; sin embargo en los casos en los
que no se trabaja con un manejador de base de datos, no contamos con la posibilidad de utilizar esta facilidad.
En esos casos en los que necesitamos numerar de forma automática y correlativa ciertos atributos, y no podemos
utilizar la propiedad Autonumber, debemos resolverlo programándolo. Para ello solemos definir una transacción
conteniendo dos atributos, uno para almacenar un literal y otro para almacenar el último número asignado
automáticamente al atributo descripto por el literal1; la transacción conlleva la creación de una tabla, y definimos un
procedimiento que consulta esa tabla, obtiene el último número asignado para el atributo a ser numerado, le suma
uno y devuelve el próximo número, además de actualizarlo en la tabla.
Para invocar al procedimiento de numeración automática se debe definir en las transacciones que lo requieran una
regla del siguiente estilo:
Del mismo modo, si queremos autonumerar el identificador de facturas, escribiríamos en la transacción “Inovice” la
siguiente regla:
De esta forma definimos que se efectúen numeraciones automáticas en las transacciones únicamente cuando se
realicen inserciones (por la condición de disparo: if Insert) e inmediatamente antes de que se grabe
físicamente cada instancia a ser insertada (por el evento de disparo: on AfterValidate) a través del primer nivel
de la transacción (porque en las dos reglas de invocación mostradas, hay solamente un atributo involucrado que
pertenece al primer nivel de las transacciones "Customer" e "Invoice" respectivamente).
El motivo por el cual agregamos el evento de disparo on AfterValidate a estas reglas es para invocar al
procedimiento de numeración automática inmediatamente antes de que se inserte el registro en la base de datos y
luego de la validación, intentando de esta forma tener el mayor grado de seguridad posible de que el número
asignado será utilizado (y no perder números). Piense unos instantes el lector cuándo se dispararía la regla anterior
de no estar condicionada a evento de disparo alguno, y qué podría pasar en el caso de que fallara la validación de
alguno de los datos del cabezal. La respuesta es simple: se perdería un número. Es decir, si el número de factura
anterior fuera 5 y el usuario quisiera ingresar la siguiente factura, la regla de asignación con udp que invoca la
procedimiento de numeración se dispararía ni bien se ingresara a la transacción estando en modo insert, ya que
involucra al primer atributo del cabezal. El procedimiento devolvería el número 6, y si validando los datos del
cabezal se encuentra algún error que no permite continuar con el proceso, y se abandonara la transacción, por
ejemplo, ese número 6 se habrá perdido y la próxima factura, que correlativamente debería tener el número 6 no lo
tendrá, tendrá el 7.
----------------------------------------------------------------------------------------------------------------------------
1 Cuando vimos la regla serial introdujimos este tema de numeración de cabezales, con la transacción “Number” que
era la que tenía el literal: NumberCode y el último número asignado a la transacción correspondiente a ese literal:
NumberLast
146
Existen tres eventos de disparo que ocurren en el mismo momento que el AfterValidate, pero que ya contienen
intrínseco el modo. Ellos son: BeforeInsert, BeforeUpdate y BeforeValidate.
Es equivalente escribir la regla presentada antes como lo hicimos, a escribirla:
Observar que aquí es redundante condicionar la regla a “If Insert”. Por tanto, valen las siguientes equivalencias:
Si hacemos un esquema de las acciones que rodean al evento de disparo, quedarán claros los dos sinónimos
elegidos para este evento (AfterValidate y BeforeInsert para modo insert)
Así como existe un evento de disparo que permite definir que determinadas reglas se ejecuten inmediatamente
antes de que se produzca la grabación física de cada instancia de un nivel (AfterValidate, BeforeInsert,
BeforeUpdate, BeforeDelete), también existen eventos de disparo para definir que ciertas reglas se ejecuten
inmediantamente después de que se inserten, modifiquen o eliminen físicamente instancias de un nivel.
Estos eventos son AfterInsert, AfterUpdate y AfterDelete.
El evento de disparo AfterInsert permite definir que una regla se ejecute inmediatamente después de que se inserte
físicamente cada instancia del nivel al cual está asociada la regla; el AfterUdate luego de que se actualice
físicamente la instancia, y el AfterDelete luego de que se elimine.
EJEMPLOS
Supongamos que en la transacción "Customer" queremos invocar a un reporte que realice la impresión de los datos
de cada cliente con el cual se trabaje por medio de la transacción.
No es adecuado agregarle este evento de disparo a la regla de invocación al reporte, porque éste se invocaría
inmediatamente antes de la grabación física de cada cliente. En consecuencia, el reporte no encontraría al
cliente con sus datos en la tabla CUSTOMER (si se estaba insertando un cliente por medio de la transacción), o lo
encontraría con sus datos desactualizados (si se estaba modificando un cliente por medio de la transacción). Si en
cambio se estaba eliminando un cliente por medio de la transacción, el reporte encontraría los datos del cliente en la
tabla CUSTOMER y los listaría justamente antes de la actualización física (eliminación).
Si se desea esto, es decir, emitir un listado con los datos de cada cliente que se elimine, sería adecuado definir la
siguiente regla:
--------------------------------------------------------------------------------------------------------------------------
1 Existe otra forma de provocar que una regla que contiene atributos de un nivel determinado, se dispare en el nivel
siguiente, mediante la cláusula Level que mencionamos cuando vimos conceptos importantes sobre reglas de
transacciones.
147
para restringir el disparo de la regla únicamente a cuando se esté eliminando un cliente, porque es el único caso en
el sería correcto utilizar el evento de disparo AfterValidate (ya que justamente necesitamos emitir el reporte antes
de la eliminación).
El evento de disparo AfterInsert ocurre inmediatamente después de que se inserte físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").
Como lo indica claramente su nombre, el evento de disparo AfterInsert sólo ocurre al insertar una nueva instancia
(precisamente luego de ser insertada como registro físico). Es por ello que cuando se agrega el evento de disparo on
AfterInsert a una regla, no es necesario agregarle la condición de disparo if insert.
Es correcto agregarle este evento de disparo a la regla de invocación al reporte, ya que el reporte se invocaría
inmediatamente después de que se inserte físicamente cada cliente. Así que el reporte encontraría al cliente
con sus datos en la tabla CUSTOMER y los imprimiría.
Lo que debe quedar claro es que con esta definición el reporte se invocará únicamente luego de realizar inserciones.
El evento de disparo AfterUpdate ocurre inmediatamente después de que se actualice físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").
Es adecuado agregarle este evento de disparo a la regla de invocación al reporte, ya que el reporte se invocaría
inmediatamente después de que se actualice físicamente un cliente. Así que el reporte encontraría al cliente
con sus datos actualizados en la tabla CLIENTES y los imprimiría.
El evento de disparo AfterDelete ocurre inmediatamente después de que se elimine físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").
No es adecuado agregarle este evento de disparo a la regla de invocación al reporte, porque el reporte se invocaría
inmediatamente después de la eliminación física de cada cliente. En consecuencia, el reporte no encontraría al
cliente con sus datos en la tabla CUSTOMER.
Para finalizar, estas dos reglas son las adecuadas para invocar a un reporte en la transacción "Customer", con el
objetivo de imprimir los datos de cada cliente con el cual se trabaje, abarcando los tres modos de trabajo.
Como se puede observar en la primera regla es posible incluir varios eventos de disparo separados por coma,
cuando los mismos aplican a una misma regla.
148
Caso 6: Si definimos una regla a la cual le incluimos el evento de disparo on AfterInsert, pero a diferencia de los
ejemplos recién vistos, se referencia en la regla al menos un atributo del segundo nivel de la transacción en la cual se
está definiendo la regla, la misma estará asociada al segundo nivel. Por lo tanto, la regla se ejecutará
inmediatamente después de que se inserte físicamente cada instancia correspondiente al segundo nivel de la
transacción.
Ampliamos el esquema que habíamos efectuado antes, de las acciones que rodean a los eventos de disparo vistos
hasta ahora:
Este esquema se repite para cada instancia del nivel. Por ejemplo, pensemos en el ingreso de las líneas de una
factura. Para cada línea ocurrirá este esquema, por lo que podemos pensar en un loop que se repite hasta que se
termina de grabar la última línea.
La acción que sucede a la grabación de la última línea sería el abandonar ese nivel (en este caso el de las líneas de
factura). Y luego de esa acción, a menos que venga otro nivel con el que se volvería a ingresar en el esquema
anterior, ocurrirá la última acción en la ejecución, que es el commit.
Entre la acción de abandonar el nivel, y el commit tendremos un evento (que admite dos nombres distintos) y otro
para luego del commit. Son los que veremos a continuación pero que ya mostramos en esquema:
ABANDONAR NIVEL 2
AfterLevel - BeforeComplete
COMMIT
AfterComplete
149
Eventos de disparo: AfterLevel, BeforeComplete
El evento de disparo AfterLevel permite definir que una regla se ejecute inmediatamente después de terminar
de iterar determinado nivel.
DONDE:
regla: es una regla de las permitidas en transacciones
condición de disparo: es una expresión booleana que permite involucrar atributos, variables, constantes y funciones,
así como los operadores Or, And, Not.
atributo: es un atributo perteneciente al nivel para el cual se desea que luego de ser iterado, se ejecute la regla.
FUNCIONALIDAD:
Si el atributo que se especifica a continuación del evento de disparo AfterLevel pertenece al segundo nivel de la
transacción, la regla se ejecutará cuando se hayan terminado de iterar todas las líneas del segundo nivel.
Y si el atributo que se especifica a continuación del evento de disparo AfterLevel pertenece al primer nivel -siguiendo
el mismo concepto- la regla se ejecutará cuando se haya terminado de iterar por todos los cabezales. Observar que
esto se da al final de todo, es decir, una vez que se hayan ingresado todos los cabezales y sus líneas y se cierre la
transacción (en ese momento se habrán iterado todos los cabezales). Por lo tanto, si el atributo especificado
pertenece al primer nivel, la regla se disparará una vez sola antes del Evento Exit (es un evento que se ejecuta una
sóla vez cuando se cierra una transacción en tiempo de ejecución, como veremos).
Ejemplo: Rever el ejemplo presentado antes, donde teníamos una transacción para representar las facturas que nos
entregan nuestros proveedores, y donde queríamos controlar que el total calculado de cada factura coincidiera con el
total ingresado. Allí teníamos la regla:
que necesitáramos se disparara luego de ingresadas todas las líneas de la factura. Por tanto, el evento de disparo
apropiado será AfterLevel Level att, donde att sea cualquier atributo de las líneas.
El evento de nombre BeforeComplete, en este caso, coincide con el AfterLevel. Si observamos el esquema
presentado en la página anterior, podemos ver que el instante de tiempo que hay entre que se abandona el último
nivel y se realiza el commit es el instante en que ocurren estos eventos. Son dos nombres para referirnos a lo mismo.
Cuidado que esto es así siempre y cuando el nivel abandonado sea el último. Supóngase por ejemplo una transacción
con dos niveles paralelos. Por ejemplo, si agregamos al cliente sus direcciones de mail y sus números telefónicos
(puede tener varios):
CustomerId*
CustomerName
…
(CustomerPhone*
…)
(CustomerEMail*
…)
El momento en que deberá dispararse una regla condicionada a: On AfterLevel Level CustomerPhone NO
COINCIDIRA con el de una regla condicionada a on BeforeComplete.
Mientras que la primera se disparará cuando se abandona el nivel de los teléfonos, y antes de entrar a validar todos
los emails, la segunda se disparará después de abandonar este último nivel.
En este caso el evento BeforeComplete coincidirá con el AfterLevel Level CustomerEMail.
Este evento corresponde al instante de tiempo que sucede al commit. Hablaremos más de este evento unas páginas
adelante, cuando estudiemos la integridad transaccional.
Si se abre la transacción de facturas, se ingresan 3 facturas (cabezal y sus respectivas líneas) y se cierra la
transacción, ocurrirán 3 commits (uno al final de cada ingreso de cabezal + líneas) y 3 eventos AfterComplete.
150
Ejemplo en transacción de 2 niveles
Interactivamente (Web o Win con Client Side Validation) y antes de confirmar:
REGLAS STAND-ALONE
EVALUACION DE REGLAS Y
FORMULAS SEGÚN ARBOL
PARA
EVALUACION DE REGLAS Y
FORMULAS SEGÚN ARBOL CADA
LINEA
El siguiente ejemplo pretende mostrar visualmente en qué momentos se irán disparando las reglas y
fórmulas definidas en una transacción1.
Esto dependerá en parte del tipo de diálogo, aunque el orden será el mismo para cualquiera de ellos.
La diferencia radicará en el momento en que se realizará la grabación de los registros y el disparo de
reglas que estén condicionadas a eventos.
Si tenemos un diálogo con validación a nivel del cliente (.Net o Java, ya sea interfaz Win con la
propiedad Client Side Validation en Yes, o interfaz Web) entonces las reglas que no posean evento
de disparo, así como las fórmulas, se ejecutarán en forma interactiva a medida que el usuario vaya
pasando por los campos (ejecución en el cliente).
El disparo de reglas y fórmulas se irá haciendo de acuerdo al árbol de evaluación, siguiendo el orden
que éste determina.
Una vez que el usuario confirme la pantalla (esto es, en Win: presione el botón “Confirm”, cuyo
título será: Add, Update, Delete, Confirm, según el caso; en Web: presione el botón “Apply
Changes”), los datos se enviarán al servidor y allí se realizarán las validaciones y disparo de reglas y
fórmulas mencionadas antes, nuevamente, además de dispararse las reglas que estén condicionadas
a eventos de disparo, por primera vez, junto con las grabaciones de los registros correspondientes,
en el orden que se muestra en la siguiente hoja. Esto se realizará en el servidor.
Con respecto al diálogo campo a campo, si bien el orden será el mismo que el de la siguiente hoja,
la diferencia se encuentra en el momento en que esto se realiza: en este tipo de aplicaciones, será
absolutamente interactivo, dado que cuando el usuario pase en la pantalla del cabezal a las líneas, o
de una línea a la siguiente, ya en ese momento se realizará la grabación.
151
Ejemplo en transacción de 2 niveles
Al confirmar los datos, se ejecutan en el siguiente orden:
REGLAS STAND-ALONE
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Update / Delete
GRABACION DEL CABEZAL
AfterInsert / Update / Delete
EVALUACION DE REGLAS Y
PARA CADA
FORMULAS SEGÚN ARBOL
LINEA
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Udpate / Delete
GRABACION DE LA LINEA
AfterInsert/Update/Delete
ABANDONAR NIVEL 2
AfterLevel Level attNivel2 - BeforeComplete
COMMIT
AfterComplete
Ejemplos de reglas stand alone (por poder ejecutarse con la información provista por los parámetros):
• &A = parámetro2;
• Msg( ‘...’ ) if parámetro1 = 7;
Luego de la ejecución de las reglas stand alone, se ejecutan las reglas asociadas al primer nivel de la
transacción, que no tengan evento de disparo definido, siguiendo el orden de dependencias determinado
por GeneXus (así como las fórmulas asociadas al primer nivel). A modo de ejemplo, se disparará la regla:
“Default( InvoiceDate, &Today);”
Después de ejecutadas las reglas mencionadas para el cabezal, se ejecutarán todas las reglas que tengan
como evento de disparo BeforeValidate, ya que inmediatamente después ocurrirá la acción de
validación (o confirmación) de la información de ese primer nivel.
Inmediatamente después de la validación del primer nivel se ejecutan las reglas asociadas al primer nivel
de la transacción que incluyan en su definición el evento de disparo AfterValidate, o los BeforeInsert,
BeforeUpdate, BeforeDelete, dependiendo del modo en el que se esté.
Por ejemplo: Si no podemos autonumerar las facturas con la propiedad Autonumber por no ser soportada
por el DBMS elegido:
InvoiceId = PGetNumber.udp(‘INVOICE’) on BeforeInsert;
152
Seguidamente a la ejecución de las reglas asociadas al primer nivel con alguno de estos eventos de disparo se
ejecuta la acción de grabación; es decir, se grabará físicamente la instancia correspondiente al primer nivel
de la transacción como registro físico en la tabla correspondiente (en este ejemplo, en la tabla: INVOICE).
Inmediatamente después de haberse grabado esa instancia:
• si la grabación correspondió a una inserción: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterInsert.
• si la grabación correspondió a una actualización: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterUpdate.
• si la grabación correspondió a una eliminación: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterDelete.
Si se trata de una transacción de dos niveles, como en este caso, a continuación se ejecutará para cada una
de las líneas:
En primer lugar, las reglas asociadas al segundo nivel de la transacción que no tengan evento de disparo
definido, siguiendo el orden de dependencias determinado por GeneXus (así como las fórmulas asociadas al
segundo nivel). Ejemplos de ello son la regla
Subtract( InvoiceLineQuantity, ProductStock );
la fórmula
InvoiceLineAmount = InvoiceLineQuantity*ProductPrice.
Después de ejecutadas las reglas mencionadas para una línea, se ejecutarán todas las reglas que tengan
como evento de disparo BeforeValidate, dado que inmediatamente después ocurre la validación de la
línea; esto es una acción que ocurre a continuación de haber terminado de trabajar con la línea.
Inmediatamente después de la validación de la línea, se ejecutarán las reglas asociadas al segundo nivel
de la transacción que incluyan en su definición alguno de los eventos de disparo: AfterValidate,
BeforeInsert, BeforeUpdate, BeforeDelete.
Seguidamente a la ejecución de las reglas asociadas al segundo nivel con alguno de estos eventos de
disparo se ejecutará la acción de grabación; es decir, se grabará físicamente la instancia correspondiente a
la línea como registro físico en la tabla correspondiente (en este ejemplo, en la tabla: INVOICELINE).
Inmediatamente después de haberse grabado la instancia correspondiente a la línea como registro físico en
la tabla correspondiente:
• si la grabación correspondió a una inserción: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterInsert.
• si la grabación correspondió a una actualización: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterUpdate.
• si la grabación correspondió a una eliminación: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterDelete.
Todas estas operaciones sombreadas de gris claro, se ejecutarán en el orden descrito, para cada una de las
líneas.
Luego de la iteración de todas las líneas, podemos suponer la existencia de una acción que podríamos
llamar abandono del segundo nivel. Luego de la misma se ejecutarán las reglas definidas con evento de
disparo AfterLevel Level Atributo del 2do nivel. Si no existe otro nivel, como es el caso del ejemplo,
Aclaración importante:
entonces coincidirá Todas las
con el evento operaciones
de disparo sombreadas, tanto en gris claro como en gris oscuro, se
BeforeComplete.
ejecutan únicamente si se trata de una transacción de dos niveles; de modo que cuando se trata de una
transacción de un nivel, tales operaciones no se ejecutarán. El motivo de los dos sombreados distintos, es
para diferenciar el conjunto de operaciones que se ejecuta para cada una de las líneas (sombreado gris claro)
de las operaciones que se ejecutan solamente una vez finalizada la iteración en las líneas (sombreado gris
más oscuro). A continuación seguimos explicando en orden, el resto de las operaciones que se ejecutan, así
sea que se trate de una transacción de un nivel o dos.
Luego de haberse ejecutado todas las operaciones explicadas hasta el momento, se efectuará un commit,
siempre y cuando el ambiente o plataforma de trabajo sea Cliente/Servidor.
153
A continuación se ejecutarán las reglas con evento de disparo AfterComplete.
Es de fundamental importancia que quede claro que todas las operaciones explicadas se ejecutarán en el orden
en el que se han descrito, para cada factura con la cual se trabaje por medio de la transacción "Invoice" (ya sea
que se ingrese, modifique o elimine).
Puede ser útil tener en cuenta que se han resaltado en negrita las acciones cada vez que se las ha mencionado.
Las mismas son: validación, grabación , abandono del segundo nivel y commit.
Es indispensable asimilar el orden en el que se ejecutan las reglas en una transacción, cuáles son los eventos de
disparo disponibles para asignarles, cuándo se disparan exactamente, y qué acciones ocurren antes y después de
cada evento de disparo, ya que solamente conociéndolos bien se podrá programar el comportamiento de las
transacciones adecuadamente. Es sencillo comprender que si necesitamos programar determinados controles o
acciones en las transacciones, tendremos que saber bien si hacerlo antes de que se grabe el cabezal, después de
que se haya grabado el mismo, para cada una de las líneas después de que se hayan grabado, o antes, después
del commit o antes, por lo tanto es fundamental tener claro todo este tema.
Nos ha faltado mencionar cuándo ocurrirá una regla condicionada al evento de disparo: AfterLevel Level Atributo
1er nivel.
Mientras que una aplicación Win mantiene la conexión con el servidor de manera tal que éste pueda mantener el
estado de una transacción que se esté ejecutando: controlar cuándo se abrió, cuántas instancias se han
ingresado y cuándo se ha cerrado la misma, en Web esto no es posible (no mantiene estado).
Por tanto una regla condicionada al evento de disparo AfterLevel Level atributo del 1er. nivel solo se disparará
en una transacción Win (para una Web no tiene sentido) y lo hará una sola vez, cuando se cierra la
transacción. Recordemos que una regla con evento de disparo AfterLevel Level Atributo 1er nivel se ejecuta
luego de que se haya iterado por todos los cabezales, y esto se da al final de todo, es decir, una vez que se haya
trabajado con todos los cabezales y sus líneas y se cierre la transacción (en ese momento se habrá iterado por
todos los cabezales).
No podemos dejar de mencionar algo que también para Win se ejecutará una única vez en la ejecución de una
transacción: el Evento Start y el Evento Exit.
Como mencionaremos luego, en una transacción Web estos eventos se ejecutarán una vez por cada instancia de
transacción con la que se trabaje.
154
Ejemplos
155
Ejemplos
156
Reglas con el mismo evento de disparo
• Ejemplo 1
‘xxx’.call() On AfterComplete;
‘yyy’.call() On AfterComplete;
• Ejemplo 2
‘pgmname’.call( CustomerId, &flag) On AfterComplete;
error(' ') if &flag = 'N’ On AfterComplete;
Ejemplos:
1) Se definen las siguientes reglas en una transacción:
‘xxx’.Call() on AfterComplete;
‘yyy’.Call() on AfterComplete;
Como las dos reglas definidas están condicionadas con el mismo evento de disparo, y no existe
ninguna dependencia entre ellas, las mismas se ejecutarán en el mismo orden en el cual se han
escrito.
157
En la primera alternativa, se ha definido una regla call y una regla error. Ambas reglas tienen el mismo
evento de disparo, y aparentemente existiría dependencia entre ellas, ya que la regla de error está
condicionada al valor de la variable &flag, y la variable &flag se pasa por parámetro en la regla call.
Sin embargo, si bien la dependencia nos puede parecer evidente porque en el procedimiento
programaremos a la variable &flag, de salida, en la sección de reglas de la transacción -que es donde se
encuentran las reglas que estamos viendo-, el especificador de GeneXus no puede saber si los parámetros
pasados en un call son de entrada, de salida, o de entrada-salida; en consecuencia el especificador no
encontrará interdependencia entre las reglas call y error, ya que la variable &flag podría ser pasada como
variable de entrada al procedimiento, y en ese caso por ejemplo, no habría una dependencia por la cual
primero se deba ejecutar la regla call y luego la regla error.
Así que concluyendo, no se detectan dependencias entre las reglas call y error de la alternativa 2.1), por lo
que las mismas se dispararán entonces en el orden en el que estén escritas. Es importante ver que si las
reglas call y error estuvieran escritas en orden inverso (es decir, primero la regla error y después la regla
call), el comportamiento no será el esperado en muchos casos.
Con respecto a la segunda alternativa, observemos que la misma consiste en una regla con udp y una regla
error. Ambas reglas tienen el mismo evento de disparo, y en este caso sí existe dependencia entre ellas, ya
que la regla error está condicionada al valor de la variable &flag, y como la invocación al procedimiento se
realiza con udp, para el especificador de GeneXus queda claro que la variable &flag vuelve modificada del
procedimiento; por lo tanto el especificador de GeneXus entiende que primero se debe disparar la
invocación al procedimiento con udp y luego la regla error, porque la variable &flag se carga mediante la
invocación al procedimiento con udp, y luego de que dicha variable tenga valor, es que habrá que evaluar si
disparar la regla error, o no.
En el caso 2.2) entonces, independientemente del orden de definición de ambas reglas, la invocación al
procedimiento con udp se disparará primero, y luego de ello, se disparará la regla error (en caso de que se
cumpla la condición de disparo, claro está).
Por esta razón se recomienda que siempre que se quieran definir validaciones de este tipo, se utilice udp en
lugar de call.
158
Consideración importante acerca del
disparo de reglas
Æ Para que no se dispare más de una vez, habrá que asignarle evento de disparo
específico a la regla, (o bien, para Win, dejar el diálogo a pantalla completa para esta
transacción), o estudiar bien la lógica del procedimiento y tener en cuenta la doble o
triple ejecución del mismo.
• Esto no sucederá con reglas de GeneXus (como subtract, add) que actualizan
la BD porque GX tiene la inteligencia para realizar el update solo al confirmar
(lo mostrado en forma interactiva se calcula en memoria).
Es importante tener en cuenta que las reglas que no tienen evento de disparo asociado, se ejecutarán una
vez o dos o tres, dependiendo de lo que se haya configurado en la propiedad del modelo Confirmation y
de tratarse de una aplicación .Net o Java Win, de la propiedad Client Side Validation (recordemos que
en Web siempre se trabaja con este tipo de diálogo)
Por ejemplo, si se trabaja en Web o en Win (con la propiedad: Client Side Validation=Yes) y propiedad
Confirmation=No, valor por defecto, las reglas que no tengan evento de disparo asociado se dispararán:
primero en forma interactiva en la medida que el usuario final vaya trabajando en el form, y luego
nuevamente cuando el usuario final efectúe la confirmación.
Si trabajando en Win, en lugar de configurar la propiedad: Client Side Validation= Yes, se la dejara con
valor No y se configurara la propiedad Confirmation= Yes, estaríamos en la misma situación de doble
disparo de las reglas que no tienen evento de disparo. En este caso no se dispararían en forma interactiva
las reglas (ya que la propiedad Client Side Validation=No) pero se dispararían en la primera confirmación
del usuario (en la cual se efectúan las validaciones pero no se graba) y en la reconfirmación del usuario
(en la cual se efectúan las validaciones y se graba).
Por último, si se configurara Confirmation= Yes (y en Win Client Side Validation= Yes), las reglas sin
evento de disparo asociado tendrían un triple disparo.
159
EVENTOS EN TRANSACCIONES
Los eventos son acciones reconocidas por un objeto que pueden suceder o no. A cada evento se le
puede asociar código, que se ejecutará solamente si el evento se produce.
El código que se le puede asociar a un evento se escribe siguiendo el estilo procedural; y cuando el
evento se produce, el código asociado al mismo se ejecutará secuencialmente.
160
Eventos en Transacciones
• Evento Start
• Evento Exit
Como en Web no se mantiene un estado en el servidor que permita saber qué es lo que se ejecutó
en el cliente, no es posible saber si se está ingresando la primera instancia de una factura, o si es la
n-ésima. Por esta razón, mientras que el evento Start se ejecutará en una aplicación Win una sola
vez cuando se abre la transacción, y luego con cada iteración no vuelve a ocurrir, en Web esto no
es posible, por lo que se disparará el evento cada vez que se envíe al servidor la información de la
instancia con la que se esté trabajando.
Análogas consideraciones podemos hacer para el caso del evento Exit: en una aplicación Win es
posible saber que se está abandonando la transacción, por lo que puede capturarse ese evento. En
cambio en una aplicación Web esto no será posible, razón por la cuál el evento se ejecutará por
cada iteración, al final de la misma.
161
Evento Start
• Se ejecuta:
El evento Start es un evento del sistema, por lo tanto ocurre automáticamente. ¿En qué momento se
ejecuta? La respuesta dependerá de si se trata de una transacción Win o Web.
Win: Cuando comienza la ejecución del programa asociado a una transacción, es decir, ni bien se abre
una transacción en tiempo de ejecución. Entonces, en el evento Start de una transacción se puede incluir
código que se desee se ejecute una única vez, cuando comience la ejecución de la transacción.
Generalmente el código que se incluye en este evento, es para inicialización (ejemplo, asignar valores a
variables para inicializarlas una única vez en la ejecución del programa).
EJEMPLO:
En una transacción nos interesa capturar la fecha y hora de entrada a la misma. Para ello en el evento
Start le asignamos a una variable de nombre &entrada y tipo de datos DateTime, el resultado de la
función Now() que devuelve la fecha y hora actual:
Event Start
&entrada = Now()
EndEvent
Web: Se ejecutará cada vez que se someta el form de la transacción, es decir cuando se presione
cualquier botón del form (“Get”, “Apply Changes”, botones de navegación, botón Select o cualquier
botón con un evento de usuario asociado).
Notas generales:
En el evento Start fundamentalmente se trabaja con variables. En cuanto a utilizar atributos en este
evento, ya sea para evaluarlos y/o usarlos de algún modo menos para actualizarlos, se debe tener en
cuenta que los únicos atributos que se tienen disponibles son los que se reciben por parámetro
en la regla parm. Ningún otro atributo tendrá valor en este evento, pues todavía no se ha editado
ninguna instancia de la transacción.
162
Evento Exit
• Se ejecuta:
El evento Exit es un evento del sistema (por lo tanto ocurre automáticamente) y es lo último en
ejecutarse. ¿En qué momento se ejecuta? Otra vez, esto dependerá de la interfaz:
• En Win se ejecuta una sola vez cuando se cierra una transacción en tiempo de ejecución.
• En Web, ocurre una única vez, al final de c/iteración (es lo último que se ejecuta).
Al igual que en el evento Start, en el evento Exit fundamentalmente se trabaja con variables.
En cuanto a utilizar atributos en este evento, ya sea para evaluarlos y/o usarlos de algún modo salvo
actualizarlos, se debe tener en cuenta que los únicos atributos que se tienen disponibles son
los que se reciben por parámetro en la regla parm. Ningún otro atributo tendrá valor en este
evento.
WIN
EJEMPLO: Cada vez que se cierre cierta transacción en tiempo de ejecución, invocaremos a un
procedimiento que grabe la fecha y hora de entrada a la transacción (que se capturó en el evento Start,
en una variable &in) y la fecha y hora de salida de la misma, para cada usuario en una tabla de control:
Event Exit
&user = userid()
&out = now()
PControlStore.call(&in, &out, &user)
Endevent
¿Cuál es la diferencia en Win entre definir código en el evento Exit o en cambio definir una
regla con evento de disparo on AfterLevel Level atributo 1er nivel, siendo que ambas cosas se
dispararían una única vez al cerrar la transacción, una a continuación de la otra?
Si se define una regla con el evento de disparo AfterLevel Level atributo 1er nivel, la misma se
disparará una sola vez cuando el usuario cierre la transacción, existiendo la posibilidad de retorno. Es
decir, si fuera necesario, se podría definir una regla Error para mantener la pantalla abierta. El Evento
Exit en cambio, si bien ocurre también una vez sola al cerrar la transacción, ya no brinda la posibilidad
de retorno. Cuando ocurre el Evento Exit, la pantalla ya se cerró.
WEB
Como se dispara cada vez que se dibuja la pantalla, al final, no hace las veces de un verdadero exit, por
lo que no suele utilizarse en este ambiente.
163
Eventos de Usuario
• Además de los eventos ofrecidos por GeneXus, el analista puede
definir eventos creados por él, llamados eventos de usuario.
Como se puede observar en la sintaxis, se le debe dar un nombre a un evento de usuario, debiéndose
declarar a continuación de la palabra Event, encerrado entre comillas simples.
EJEMPLO:
Se desea que en la transacción "Invoice", el usuario tenga la posibilidad de imprimir la factura con la cual
esté trabajando, presionando F7 (esto solo es válido para Win):
Event ‘Print Invoice’ 7 //evento definido en la transacción "Invoice"
RPrintInvoice.Call( InvoiceId )
EndEvent
Nota: se pueden ejecutar eventos asociados a botones con Alt+<letra>. Se logra colocando un ‘&’ en el
Caption del botón, antes de la letra con la que se desea acceder al evento. (Tanto Win como Web)
Ejemplo: si se desea que el código del evento de usuario asociado a un botón de caption “MyEvent” se
pueda ejecutar también con Alt+E, entonces en el Caption del botón, tendremos que escribir “My&Event” y
se verá en el form web (en diseño) con un infraguión antes de la letra correspondiente, mientras que en
ejecución no se percibirá nada:
164
Evento After Trn
• Sintaxis:
Event After Trn
código
Endevent
El evento After Trn de las transacciones ocurre inmediatamente después de la ejecución de las reglas
con evento de disparo AfterComplete. Por consiguiente, el código que se incluya en este evento se
ejecutará luego de culminada cada iteración completa por medio de la transacción (es decir,
luego de haberse grabado cada cabezal con sus correspondientes líneas como registros
físicos en las tablas que corresponda y de haberse efectuado COMMIT).
Existen las siguientes alternativas para programar comportamientos que se deseen ejecutar luego de
cada iteración completa por medio de una transacción:
1. Definir reglas individuales con evento de disparo AfterComplete y dejar el evento After Trn sin código
2. Definir todas las sentencias en el evento After Trn con estilo procedural, y no definir reglas con
evento de disparo AfterComplete
3. Definir ambas cosas: algunas reglas con evento de disparo AfterComplete y código en el evento After
Trn
Como venimos explicando, primero se ejecutan las reglas definidas con evento de disparo
AfterComplete, e inmediatamente después de las mismas se ejecuta el código definido en el evento
After Trn.
Un concepto que es muy importante tener claro es que tanto en reglas con evento de disparo
AfterComplete como en el evento After Trn, se conocen los valores de los atributos del primer nivel de
la transacción.
Es decir, si bien ya se grabaron físicamente los registros correspondientes al cabezal y las líneas de
cierta iteración completa, e incluso se efectuó COMMIT, aún se tienen disponibles los valores de los
atributos del primer nivel, pudiendo estos utilizarse para pasarlos por parámetro en una invocación, o
evaluar su valor, o usarlos de algún modo salvo actualizarlos 1.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 Hay dos motivos por los cuales no es posible actualizar atributos en reglas con evento de disparo
AfterComplete ni en el evento After Trn. El primer motivo es que ya se han hecho las grabaciones
correspondientes e incluso se ha efectuado COMMIT, de modo que ya es tarde para asignar valores a
atributos. Y además, en lo que respecta al evento After Trn, en los eventos no se permite realizar
asignaciones a atributos.
165
Un detalle a tener en cuenta es que en el evento After Trn -como en todo evento- es posible incluir comandos, a
diferencia de en la sección de reglas de una transacción, en la que no es posible.
EJEMPLOS:
Si en el evento After Trn de una transacción se incluye el comando return, al terminar de ejecutarse la primera
iteración completa, se ejecutará el evento After Trn y se cerrará el programa correspondiente a la transacción,
volviendo al objeto llamador.
3. Se necesita controlar la cantidad de iteraciones completas realizadas por medio de una transacción Win durante una
sesión. Para ello definiremos código en varios eventos, no sólo en el evento After Trn.
Event Start
&veces = 0
EndEvent
Event Exit
Msg( ‘Se han realizado la siguiente cantidad de iteraciones completas: ’ + str( &veces ) )
EndEvent
Con este ejemplo logramos dejar bien en claro que el evento Start se ejecuta una sola vez en ambiente Win, ni bien se
abre una transacción en tiempo de ejecución, el evento Exit se ejecuta una sola vez cuando se cierra una transacción en
tiempo de ejecución (lo veremos a continuación), y el evento After Trn por su parte, se ejecuta una vez por cada
iteración completa culminada.
Si la misma transacción se generara en ambiente Web, el resultado sería absolutamente distinto: el mensaje siempre
mostraría 1, ya que el start y el exit se ejecutan en este caso por cada iteración.
166
Ejemplo en transacción de 2 niveles
Resumiendo, al confirmar los datos, se ejecutan en orden todo lo siguiente:
START (lo primero en ejecutarse)
REGLAS STAND-ALONE
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Update / Delete
GRABACION DEL CABEZAL
AfterInsert / Update / Delete
EVALUACION DE REGLAS Y
PARA CADA
FORMULAS SEGÚN ARBOL
LINEA
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Udpate / Delete
GRABACION DE LA LINEA
AfterInsert/Update/Delete
ABANDONAR NIVEL 2
AfterLevel Level attNivel2 - BeforeComplete
COMMIT
AfterComplete After TRN
EXIT (lo último en ejecutarse)
Para completar el diagrama visto anteriormente, agregamos al comienzo la ejecución automática del
código asociado al evento START y al final la ejecución automática del código asociado al evento EXIT.
Además, incluimos la ejecución del evento After TRN en el lugar que corresponde a su ejecución.
167
Consideraciones
Solemos decir que los eventos Start y Exit son sin tabla base. Con esta expresión nos referimos a que en
los eventos Start y Exit no hay consulta activa a la base de datos (ya que en el evento Start aún no se ha
hecho la consulta y en el evento Exit en Win ya se está cerrando el programa asociado a la transacción y en
Web se está cerrando la instancia y ya no disponemos de la consulta). Por este motivo es que no se conocen
valores de atributos en los eventos Start y Exit, salvo los recibidos por parámetro.
Por el contrario solemos decir que los eventos After Trn y de usuario son con tabla base, ya que cuando
los mismos se ejecutan, sí hay una consulta en edición. Entonces, en particular en el evento After Trn, se
conocen los valores de los atributos del primer nivel (el segundo nivel ya se ha iterado a esa altura y no hay
posibilidad de posicionamiento en alguna línea en particular); y en lo que respecta a los eventos de usuario
se disponen los atributos de todos los niveles 1.
Es fundamental comprender que así se disponga de los valores de ciertos atributos u otros dependiendo del
evento, los mismos podrán utilizarse para ser evaluados y/o pasados por parámetro a objetos que se
invoquen, y/o para alguna otra operación cualquiera que no sea asignarles valor.
Concluyendo, en ningún evento (no sólo de transacciones, sino de ningún objeto GeneXus) se permite realizar
asignaciones a atributos.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 Si en un evento de usuario se referencian atributos de un segundo nivel u otro nivel subordinado, cuando
el evento de usuario se ejecute se tendrán en cuenta los atributos de aquella línea en la que se esté
posicionado; al momento de ejecutarse el evento de usuario se considerarán los valores de los atributos de
dicha línea. Y si el usuario no se había posicionado explícitamente en determinada línea, por defecto la línea
que estará seleccionada será la primera, así que se considerarán los valores de los atributos de la misma.
168
INTEGRIDAD TRANSACCIONAL
169
¿Qué es el concepto: integridad transaccional?
Muchos manejadores de bases de datos (DBMSs) cuentan con sistemas de recuperación ante
fallos, que permiten dejar la base de datos en estado consistente cuando ocurren imprevistos
tales como apagones o caídas del sistema.
170
¿Qué es el concepto: unidad de trabajo lógica
(UTL)?
Los manejadores de bases de datos (DBMSs) que ofrecen integridad transaccional permiten
establecer unidades de trabajo lógicas (UTLs), que corresponden ni más ni menos que al
concepto de transacciones de base de datos.
171
¿Qué es efectuar COMMIT?
• De modo que efectuar COMMIT en una base de datos, significa que se da por
finalizada una unidad de trabajo lógica (UTL).
Podemos ver que una unidad de trabajo lógica (UTL) queda definida por el conjunto de
operaciones entre un par de Commits.
172
¿Qué es efectuar ROLLBACK?
173
Unidades de trabajo lógicas (UTLs)
por defecto en GeneXus
(*) una excepción la brindan los Business Components, pero no realizan commit automáticamente.
Es importante aclarar que GeneXus incluye la sentencia COMMIT en los programas generados asociados a
transacciones y procedimientos, sólo en ambientes de trabajo Cliente/Servidor (incluyendo, por tanto, los
ambientes Web). El motivo de esto es que en ambientes Cliente/Servidor existe un DBMS que asegura la
integridad transaccional, por lo tanto GeneXus efectúa la tarea de definir las unidades de trabajo lógicas
(UTLs). En cambio, en ambientes de trabajo que no son Cliente/Servidor, GeneXus no incluye sentencias
COMMIT pues no hay un DBMS por detrás que maneje la integridad transaccional.
En cada transacción: inmediatamente antes de las reglas con evento de disparo AfterComplete. Es decir,
que por cada iteración completa que se efectúe en tiempo de ejecución por medio de la transacción, habrá
un COMMIT, justo antes de las reglas con evento de disparo AfterComplete.
Nota: El nuevo tipo de datos Business Component que veremos más adelante permite actualizar la base
de datos desde cualquier objeto GeneXus, pero también como veremos, no realiza automáticamente un
COMMIT.
174
Personalización de unidades de trabajo lógicas
(UTLs) por defecto en GeneXus
Valores:
• Yes (Default): Se ejecuta COMMIT
• No: No se ejecuta COMMIT
GeneXus ofrece una propiedad a nivel de cada objeto transacción y procedimiento, para definir
si se desea que su programa generado efectúe COMMIT, o no. El nombre de la propiedad es
Commit on Exit y su valor por defecto es Yes (por eso, toda transacción y procedimiento por
defecto efectúa COMMIT).
175
Personalización de unidades de trabajo lógicas
(UTLs) por defecto en GeneXus
call
Trn. “X” Proc. “Y”
La transacción “X” invoca al procedimiento “Y”, y se desea que ambos objetos conformen una única
UTL. La transacción actualiza ciertos registros, y el procedimiento otros, y se desea que ese conjunto
total de operaciones conforme una única UTL (para asegurarnos de que si ocurre una falla, quede
efectuado el conjunto completo de actualizaciones a la base de datos, o nada).
Para lograrlo podemos eliminar el COMMIT del procedimiento y dejar que se realice en la transacción
(al retornar del procedimiento a la transacción, para que se ejecute al final de todas las
operaciones); de modo que configuraríamos la propiedad Commit on Exit del procedimiento con
valor: No y dejaríamos la propiedad Commit on Exit de la transacción con el valor por defecto:
Yes. Pero además de esto, es fundamental que la invocación al procedimiento se realice antes de
que se ejecute el COMMIT en la transacción (ya que la idea es que ambos objetos conformen
una única UTL, y para ello el COMMIT debe efectuarse en la transacción al retornar del
procedimiento); así que la invocación al procedimiento deberá definirse en la transacción, con un
evento de disparo que ocurra antes de la ejecución del COMMIT (dependiendo de si la transacción es
de un nivel o más, y de los requerimientos, podría servir AfterInsert por ejemplo, AfterUpdate, o
AfterLevel Level Atributo del 2do nivel, o BeforeComplete, pero no AfterComplete).
No existe una única solución para personalizar una UTL. Lo fundamental es analizar cuál objeto
puede hacer COMMIT (pudiendo haber más de una posibilidad) y una vez que se decida cuál objeto
efectuará COMMIT, las invocaciones que se requieran hacer, deberán efectuarse en momentos
adecuados, considerando si ya se efectuó el COMMIT o no.
-----------------------------------------------------------------------------------------------------------
1 En ambiente Web existe una importante restricción a este respecto: si desde una transacción se
invoca a otra, el Commit que realice una no aplica sobre los registros
ingresados/modificados/eliminados por la otra. Es decir, el Commit de cada transacción solo tiene
“visibilidad” sobre los registros operados por esa transacción, y no por la otra, por lo que dos
transacciones distintas no pueden quedar incluidas en una misma UTL. No puede realizarse
personalización en este caso, al contrario de lo que sucede con los procedimientos, donde el
comportamiento es idéntico al de ambiente Win.
176
Por ejemplo, para que la transacción y procedimiento vistos conformen una única UTL, podríamos haber optado
también por la alternativa de que no efectúe COMMIT la transacción (Commit on Exit = No), sino que lo haga
el procedimiento al final de todo; y de hacerlo así, no sería un error –como sí lo sería en la solución anterior-
invocar al procedimiento utilizando el evento de disparo AfterCompete, porque la transacción no hará COMMIT,
sino que lo hará el procedimiento.
Concluyendo, es cuestión de decidir cuál objeto hará COMMIT y que las invocaciones que se deban hacer, se
hagan en momentos adecuados, para que la UTL personalizada quede bien definida.
Otro ejemplo:
Sea la transacción “Invoice” estudiada hasta el momento, en un modelo de Prototipo cliente/servidor.
Supongamos que no modificamos el valor predeterminado de la propiedad Commit on Exit.
Supongamos ahora que el usuario ejecuta la transacción, ingresando la factura 1 con todas sus líneas. Luego
pasa a ingresar la factura 2 y cuando está ingresando la 3era. línea de la misma, ocurre un apagón. Al
recuperarse la energía y reiniciarse la ejecución, ¿qué registros habrán quedado grabados en las tablas y
cuáles se habrán perdido?
La factura 1 íntegra estará grabada (cabezal y sus líneas). ¿Por qué? Pues porque al terminar de ingresarla y
pasar a ingresar la factura 2, se efectuó un Commit. La factura 2 con los registros que se habían grabado hasta
el momento de la falla de energía, se habrá perdido. ¿Por qué? Pues porque la transacción realiza el rollback de
todo lo que se hubiere efectuado luego del último Commit. El cabezal de la factura 2 y las 2 líneas que se
habían ingresado no estaban “commiteadas” aún.
Observar entonces que el Commit no es por transacción entera (es decir, todas las iteraciones del cabezal y
sus líneas) sino por cada instancia de cabezal + líneas.
Si el Commit se realizara una única vez antes de cerrar la transacción, entonces si se hubieran ingresado 29
facturas y a la trigésima se cayera el sistema, se perderían las 29 facturas anteriores (se desharía todo, ya que
aún no se habría alcanzado el Commit). Esto no es así, y si ocurriera una caída del sistema a la trigésima
factura ingresada, las 29 anteriores quedarán grabadas (no así la trigésima).
177
Personalización de UTLs
en ambiente Web
• No puede definirse una UTL compuesta por varias transacciones Web.
call
Trn.“X” Trn.”Y”
UTL 1 UTL 2
• Una transacción Web solo puede Commitear los registros insertados por ella
misma, o por procedimientos en una cadena de invocaciones, pero no puede
Commitear los registros insertados por otra transacción.
call call
UTL
Trn.“X” (luego del
Trn.”Y” (antes del
Proc.”Z”
Commit) Commit)
En ambiente Web los registros “visibles” para ser commiteados por una transacción son los
actualizados por la propia transacción, y por los procedimientos que ésta invoque antes de su
Commit, pero no los de otra transacción.
Es por ello que en el primer ejemplo presentado arriba, donde la transacción “X” llama a la
transacción “Y” luego de haber insertado un registro, aunque la transacción “Y” realice un Commit al
final de que cabezal y líneas sean ingresados, este Commit no valdrá sobre el registro que había
sido ingresado previamente por la transacción “X”. Este registro quedará “perdido”, sin Commit.
Por la forma de trabajo en Internet, las transacciones Web “viven” solamente el tiempo entre que el
usuario de un navegador selecciona el link o presiona un botón y la nueva página es mostrada.
Toda modificación a la base de datos que se haga durante la “vida” de la transacción debe ser
confirmada o eliminada antes de que la Transacción Web termine su ejecución y retorne la página
resultante.
Como consecuencia, una Transacción Web inicia una UTL (unidad de trabajo lógica) al comenzar a
ejecutar y la cierra (ya sea por COMMIT o ROLLBACK) antes de terminar. No puede formar parte de
otra UTL. Si un programa llama a una Transacción Web, ésta iniciará otra (nueva) UTL.
En cambio no sucede lo mismo con los procedimientos. En el segundo ejemplo mostrado arriba,
vemos que podemos formar una UTL que engloba a la transacción “Y” y al procedimiento “Z”… sin
embargo no podemos incluir a la transacción “X” en la misma UTL.
178
Personalización de (UTLs)
Trn.“X” Trn.”Y”
Si se necesita que las operaciones de dos o más transacciones (con o sin procedimientos incluidos)
conformen una misma UTL, se pueden emular las transacciones con Web panels y Business
Components y utilizar el comando Commit.
Dejamos aquí anotado simplemente el tema, para volver a él luego de estudiados los Business
Components, donde nos será posible comprender esta solución.
179
Comandos COMMIT y ROLLBACK de GeneXus
180
OBJETOS REPORTE
y PROCEDIMIENTO
181
Reportes y Procedimientos
Reportes
Procedimientos
Reportes:
Definen procesos no interactivos de consulta a la base de datos. Los reportes son listados que
pueden (dependiendo de la interfaz win o web) emitirse por impresora, visualizarse por pantalla, o
grabarse en un archivo.
Procedimientos:
Definen procesos no interactivos de actualización de la base de datos. Los procedimientos pueden
hacer todo lo que los reportes hacen y además actualizar la base de datos1. Por este motivo, todo lo
que se trate en la presente exposición referido al objeto reporte, será también válido para el objeto
procedimiento.
----------------------------------------------------------------------------------------------------------
1 Como veremos más adelante, existe un tipo de datos especial, que no es estrictamente un tipo de
datos, sino algo un poco más complejo, el business component, por medio del cuál se podrán
realizar actualizaciones a la base de datos en cualquier objeto GeneXus. Por tanto, utilizando
variables de tipo de datos business component, podrán realizarse actualizaciones incluso en los
objetos que por naturaleza no ofrecen esta posibilidad, como los reportes.
182
Características
• Definición procedural
Definición procedural
A diferencia de las reglas de las transacciones donde las especificaciones se realizan en forma
declarativa y GeneXus determina en el momento de generar el programa la secuencia de ejecución,
en los reportes y procedimientos las especificaciones se realizan en forma procedural. De esta forma,
la secuencia de ejecución es determinada por el analista, utilizando para ello un lenguaje bastante
simple que contiene comandos de control, de impresión, de acceso a la base de datos, etc.
Por ejemplo, si deseamos desplegar el resultado de una fórmula alcanza con nombrar al atributo
fórmula en el lugar adecuado y GeneXus disparará su cálculo desplegando el resultado, sin necesidad
de que el analista tenga que brindar ninguna otra información. La información de cómo se calcula un
atributo fórmula está contenida en la base de conocimiento.
También podremos utilizar el concepto de tabla extendida, ya que GeneXus conoce las relaciones
entre las tablas de la base de datos, por lo que el analista no necesita explicitar estas relaciones a la
hora de recuperar datos.
De esta manera logramos una real independencia de la base de datos, ya que cualquier cambio en las
tablas será manejado automáticamente por GeneXus y de esta forma, para actualizar los programas
alcanzará, gran parte de las veces, con regenerar los objetos sin tener que modificar nada de lo
programado en ellos.
183
Elementos
• Layout
• Source
• Reglas y Propiedades
• Condiciones
• Ayuda
• Documentación
• Layout: Así como las transacciones tienen una pantalla (form), los reportes tienen un “layout” de
la salida. En esta sección se define la presentación del reporte: los datos que se quieren listar y el
formato de la salida.
• Source: Aquí se escribe el código correspondiente a la lógica del reporte. También pueden
definirse al final del código subrutinas1 que podrán ser invocadas desde el propio código mediante el
comando adecuado.
• Reglas-Propiedades: Definen aspectos generales del reporte, como su nombre, descripción, tipo
de salida (impresora, archivo, pantalla), parámetros que recibe el objeto, etc.
• Condiciones: Condiciones que deben cumplir los datos para ser recuperados (filtros).
• Ayuda: Permite la inclusión de texto de ayuda, para ser consultado por los usuarios en tiempo de
ejecución, para el uso del reporte. Se puede redactar una ayuda para cada lenguaje.
• Documentación: Permite la inclusión de texto técnico, para ser utilizado como documentación del
sistema.
---------------------------------------------------------------------------------------------------
1 No se tratarán en el presente curso. Véase Curso No Presencial de GeneXus.
184
Ejemplo
Por ejemplo, supongamos que queremos implementar un reporte para imprimir el identificador,
nombre y país de todos nuestros clientes y queremos que el listado luzca como se muestra en la
figura.
Para ello, debemos identificar en la salida del listado las distintas áreas que lo componen. A cada
una de ellas la representaremos con un print block.
Los primeros dos print blocks lucirán en GeneXus tal cuál las primeras dos áreas señaladas pues
éstas contienen únicamente textos, líneas, recuadros. También podríamos haber fusionado estas dos
áreas convirtiéndolas en una y utilizando por tanto un único print block.
El tercer print block será el correspondiente al área de datos variables de la figura anterior, que
representa información que debe ser extraída de la base de datos.
Lo que queremos mostrar en este caso es el identificador y nombre de cada cliente, junto con el
nombre del país al que pertenece. Esta información es la representada por los atributos CustomerId,
CustomerName y CountryName de la base de conocimiento de la aplicación, por lo que el tercer
print block contendrá los tres controles atributo CustomerId, CustomerName y CountryName.
Transformando las áreas en print blocks, el Layout del reporte nos quedará como el que figura en la
página siguiente.
185
Layout: ejemplo
Nombre de cada print block print block
Layout:
•Sucesión de print blocks
•No importa el orden de definición
•Cada print block debe tener un nombre único
•Solo se declaran, son invocados desde el Source con el
comando “print” (Ej.: print header)
El Layout de un reporte será una sucesión de print blocks que no tienen por qué seguir el orden en
el que se desea que aparezcan en la salida.
En el ejemplo anterior, el mismo reporte habría sido impreso si se hubieran especificado los print
blocks en el orden inverso (o en cualquier orden).
Por esta razón, cada print block deberá tener un nombre único para poder ser referenciado luego
desde el Source.
En el ejemplo, para listar todos los clientes, el print block de nombre “customer” deberá ser invocado
dentro de una estructura repetitiva en el Source. Esta estructura repetitiva es el comando For each
que estudiaremos luego.
186
Layout: print block
• Texto
• Línea
• Recuadro
• Atributo/Variable
• Bitmap
El print block es un tipo de control válido solamente en reportes y procedimientos, que es insertado
y eliminado del Layout por el analista, y que contendrá otros controles -atributos, textos, recuadros,
líneas, etc.-, siendo estos últimos los que efectivamente especifican qué es lo que se quiere
desplegar en la salida.
Para insertar los controles en el Form de una transacción contábamos con una toolbar. La misma
toolbar se utiliza para insertar los controles en el Layout. De hecho esta toolbar está disponible para
todos los objetos GeneXus que se creen, y en cada caso tendrá habilitados los controles disponibles
según el tipo de objeto y la interfaz (Win o Web).
Para los reportes (y procedimientos) se habilita el ícono correspondiente al print block para poder
insertar este tipo de control en el Layout.
Como todo control, el print block tiene propiedades que pueden ser configuradas por el usuario. En
particular, tiene la propiedad “Name”, muy importante dado que es el identificador del print block.
Con este identificador es que el print block puede ser invocado desde el Source para ser impreso.
187
Source
El lenguaje que se utiliza para programar el código fuente de los reportes y procedimientos es muy
simple, y consta de algunos comandos que iremos viendo.
El estilo de programación es procedural –imperativo– por lo que el Source será una sucesión de
comandos para los que el orden es fundamental: el orden en el que estén especificados
corresponderá, salvo excepciones, al orden en el que serán ejecutados.
Existen, como en todo lenguaje imperativo, comandos de control para la ejecución condicional (if,
do case), la repetitiva (do while, for), para invocar a otro objeto (call), para cortar las iteraciones
dentro de un bucle (exit) o abandonar el programa (return), así como también comandos
específicos de este lenguaje: para imprimir un print block del Layout (print), para acceder a la base
de datos (for each), para insertar nuevos registros en una tabla (new: solo en procedimientos),
para invocar a una subrutina (do), etc.
Al final de la sucesión de comandos que constituye el código general o principal del reporte, pueden
definirse subrutinas que podrán ser invocadas (mediante el comando do) desde el código general.
No pueden ser invocadas desde otro objeto (son locales).
188
Comando for each
La definición del acceso a la base de datos para recuperación de información se realiza con un único
comando: el comando for each1.
Usando el for each se define la información a la que se va a acceder. La forma de hacerlo se basa en
nombrar los atributos a utilizar.
Así, con este comando se definen qué atributos se necesitan y en qué orden se van a recuperar, y
GeneXus se encarga de encontrar cómo hacerlo. No se especifica de qué tablas se deben obtener, ni
qué índices se deben utilizar para acceder a esas tablas: eso lo inferirá GeneXus. Evidentemente esto
no siempre es posible, y en tales casos GeneXus da una serie de mensajes de error indicando por qué
no se pueden relacionar los atributos involucrados.
La razón por la cuál no se hace referencia al modelo físico de datos es porque de esta manera la
especificación del reporte es del más alto nivel posible, de tal forma que ante cambios en la
estructura de la base de datos la especificación del mismo se mantenga válida la mayor parte de las
veces.
Cuando aparece un for each se está indicando que se quiere recuperar información de la base de
datos. Concretamente GeneXus sabe que con un for each se quiere recorrer (o navegar) una
tabla. Para cada registro de esa tabla, se quiere hacer algo con la información asociada (ej:
imprimirla).
Por lo tanto, todo comando for each tendrá una tabla física asociada: la tabla que será recorrida o
navegada. A esta tabla la llamaremos tabla base del for each.
------------------------------------------------------------------------------------------------------------
1 Cuando estudiemos los business components veremos que utilizando su método Load también se
189
Comando for each
CUSTOMER COUNTRY
• Layout:
• Source:
for each
print customer
endfor
Intuitivamente resulta claro que con este comando estamos queriendo listar identificador, nombre y
país de cada uno de los clientes de la base de datos. Es decir, queremos que se recorra la tabla
CUSTOMER, y para cada cliente se recupere de la tabla COUNTRY el nombre del país al que
pertenece, imprimiendo esta información, junto con el identificador y nombre del cliente. (Observar
que la tabla COUNTRY pertenece a la extendida de CUSTOMER)
¿Cómo infiere esto GeneXus si todo lo que hicimos en el for each del ejemplo fue nombrar los
atributos que nos interesaba mostrar?
190
Comando for each
CUSTOMER COUNTRY
INTERPRETACIÓN
For each record in table CUSTOMER
Find the corresponding CountryName in table COUNTRY
print customer
endfor
Dentro de todo for each se navega -recorre o itera- la tabla base, pero puede accederse a las
tablas que constituyen su tabla extendida para recuperar información, que por pertenecer a la
extendida estará unívocamente relacionada con cada registro de la tabla base con el que se esté
trabajando en cada iteración (el concepto de tabla extendida es muy importante en este comando y
sugerimos repasar su definición).
Es por ello que en el for each del ejemplo, la tabla base será CUSTOMER, y además se accederá
“para cada” cliente, no solo a los datos de su registro, sino a los del registro asociado en la tabla
COUNTRY (que está en la extendida de CUSTOMER). Decimos entonces que se recorre CUSTOMER
y se accede además a COUNTRY para buscar el resto de la información requerida.
La tabla base de esa extendida es la que elige como tabla base del for each.
191
Comando for each: determinación tabla base
A la tabla base correspondiente a esa tabla extendida la llamaremos tabla base del for each
y será recorrida en forma secuencial, ejecutando para cada registro lo que se indique en los
comandos internos al for each.
192
Comando for each: determinación tabla base
{CustomerId, CustomerName,
CountryName} ⊂ ext(CUSTOMER)
{CustomerId, CustomerName,
CountryName} ⊂ ext(INVOICE)
Pero:
ext(CUSTOMER) < ext(INVOICE)
Para el ejemplo presentado en el que se quieren listar de cada uno de los clientes su identificador,
nombre y nombre de país, si observamos los atributos utilizados dentro del for each, vemos que
ellos son los contenidos en el print block de nombre “customer”: CustomerId, CustomerName y
CountryName.
GeneXus conoce las relaciones entre las tablas. Podemos ver el diagrama de Bachman
correspondiente a las tablas en las cuales aparecen los atributos del for each (Tools/Diagrams).
Aquí puede verse claramente el por qué del requerimiento de que la tabla extendida sea la mínima
(entendiendo por mínima aquella que involucra menor cantidad de tablas). La tabla extendida de
INVOICE también contiene a todos los atributos del for each, pero no es mínima, pues la de
CUSTOMER también los contiene.
Por lo tanto, se va a recorrer secuencialmente la tabla CUSTOMER, y para cada registro de esa
tabla, se va a acceder a la tabla COUNTRY, para recuperar el registro de la misma que cumpla:
COUNTRY.CountryId = CUSTOMER.CountryId y para el mismo se va a recuperar el valor del atributo
CountryName, para poder imprimirlo, junto con el código y nombre del cliente.
193
Listado de navegación
tabla base
Listado de navegación
GeneXus ofrece para todos sus objetos un listado conocido como listado de navegación, que es el
resultado de la especificación del objeto. Este listado es muy útil para los reportes, ya que indica
cuáles son las tablas que se están accediendo en cada for each del Source, si existe un índice para
recuperar los datos de la tabla base, y en caso de que así sea cuál es ese índice (su nombre), si se
aplican filtros sobre los datos o se van a listar todos, etc.
De esta manera, el analista no tiene que ejecutar el objeto para verificar que la lógica sea la
esperada. Con estudiar el listado de navegación ya tiene la información necesaria para saber si se
está recorriendo la tabla esperada, si se están aplicando correctamente los filtros deseados, etc.
Como puede verse en el listado correspondiente al reporte del ejemplo, muestra para el comando
for each del Source, cuál es su tabla base, por qué orden se va a resolver esa consulta (será el
orden en el que se imprimirán los resultados), si existe un índice que satisfaga ese orden cuál es su
nombre, y además aparecen dos elementos más: los filtros de navegación y el diagrama de tablas.
Los filtros de la navegación indican qué rango de la tabla base se va a a recorrer. En el ejemplo
se va a recorrer toda la tabla base del for each: empezando por el primer registro de CUSTOMER, y
hasta que se alcance el fin de tabla (utilizando el índice ICUSTOMER).
El diagramita de tablas muestra la tabla base del for each con su clave primaria, e indentadas
todas las tablas de la extendida que deban accederse para recuperar información asociada al
registro de la tabla base con el que se esté trabajando en cada iteración del for each.
En el comando for each del ejemplo no aparece explícitamente ninguna información respecto al
orden en el que queremos que se imprima la información. En este caso GeneXus elige como orden
la clave primaria de la tabla base del for each. Es por esta razón que para el for each del
ejemplo GeneXus determinó que el orden será el correspondiente al atributo CustomerId, clave
primaria de la tabla CUSTOMER.
194
For each: cláusulas Where
for each
where CustomerName >= &Start when not &Start.IsEmpty()
where CustomerName <= &End when not &End.IsEmpty()
print customer
endfor
• Solo para los registros que cumplan las condiciones booleanas de las
cláusulas where deben ejecutarse los comandos internos al for each.
Para restringir los datos que se quieren listar en un for each se utilizan las cláusulas where del
comando.
Si en el listado de clientes no queremos listar todos los clientes, sino solo aquellos cuyo nombre
esté dentro de un rango ingresado por el usuario, entonces debemos agregar al for each que
habíamos visto una clálusula where, para especificar los filtros deseados sobre los datos:
for each
where (CustomerName >= &Start) and (CustomerName <= &End)
print customer
endfor
donde las variables &Start y &End deben definirse en el reporte con el mismo tipo de datos que
CustomerName, y cargarse con valores fijos ó recibidos por parámetro1.
Con la cláusula where definida le estamos diciendo a GeneXus que no queremos quedarnos con
todos los registros de la tabla base, sino solo con aquellos para los que se satisfaga la condición
booleana de la cláusula.
En el ejemplo escribimos una sola cláusula where con una condición compuesta, pero podríamos
haber programado lo mismo con dos cláusulas where, como se muestra arriba en la transparencia.
Es decir, cuando aparecen varios “where” la condición de filtro que se va a aplicar sobre los datos es
la conjunción booleana de todas las condiciones de los “where” que aparezcan.
Observemos que en la transparencia las cláusulas where están a su vez condicionadas con
claúsulas when. Esto se lee de la siguiente forma: se aplicará el filtro establecido por el where
solo cuando se satisfaga la condición del when.
En el ejemplo, solo se aplicará el primer filtro: “CustomerName >= &Start” si la variable &Start no
está vacía. Si está vacía, este filtro no se aplicará. Análogo es el caso de la segunda cláusula when.
Observar que si &Start y &End están vacíos, no aplicará ninguna de las cláusulas where, y por tanto
se listarán todos los clientes (como si las cláusulas where no hubiesen sido escritas).
----------------------------------------------------------------------------------------------------------
1 A través de un objeto que los pide al usuario, como un Work Panel para ambiente Win y un Web
Panel para ambiente Web. En el caso de ambiente Win, también podrá utilizarse la función Ask para
pedir datos al usuario en ejecución.
195
Cada condición booleana de un “where” puede estar compuesta de varias expresiones booleanas
concatenadas con los operadores lógicos and, or y not.
La cláusula when para condicionar la aplicación de una cláusula where solo puede utilizarse en arquitecturas
cliente/servidor. Si no es el caso, y deseamos que si el usuario no ingresa valores en las variables (las deja
vacías), no se realicen los filtros sobre los datos, podemos hacer:
For each
where CustomerName >= &Start or &Start.IsEmpty()
where CustomerName <= &End or &End.IsEmpty())
print customer
Endfor
Un cliente será impreso si cumple con las condiciones de ambos “where”. Si la variable &Start es vacía
entonces todo registro cumplirá la condición del primer where, ya que es una condición compuesta por “or”,
donde una de las subexpresiones da True, por lo que la expresión completa también dará True.
Por lo tanto, si ambas variables están vacías, todos los registros cumplirán con las condiciones de los
“where”, y por lo tanto no se aplicará filtro alguno sobre los datos. Se listarán todos los clientes.
Interfaz Win: GeneXus cuenta con la función estándar Ask, que se ejecuta antes de entrar al objeto y pide
al usuario el ingreso de un valor.
Utilizaremos esta función para pedir al usuario los valores que cargaremos en las variables &Start y &End
por medio del comando de asignación. En el Source del reporte escribimos:
Notas
• No importa el lugar del Source donde se especifiquen estos comandos, dado que la función Ask se
ejecutará siempre al principio.
• Lo mismo puede programarse utilizando la regla de asignación, en lugar del comando de asignación, en
las Rules del reporte, en lugar de hacerlo en el Source.
Interfaz Web: El reporte no tendrá ningún tipo de interacción con el usuario, por lo que la función Ask no
tendrá efecto alguno. Por tanto si el reporte va a ejecutarse en el marco de una aplicación Web, deberán
pedirse estos valores al usuario en uno de los objetos diseñados específicamente para tal fin: esto es, el
Web Panel y luego invocar al reporte enviándole estos datos por parámetro. También podrá utilizar esta
solución en un ambiente Win, utilizando el objeto análogo: el Work Panel1.
-----------------------------------------------------------------------------------------------------------
1 Para ver cómo realizar esto, puede dirigirse al capítulo del objeto correspondiente (Web Panel o Work
Panel según corresponda), y estudiar el primer panel presentado, clasificado como panel de entrada.
196
Listado de navegación
Listado de navegación
Aparece un nuevo elemento en este listado que no estaba presente antes, cuando no teníamos
cláusulas where: las constraints (restricciones).
•pero que para cada cliente evaluará si cumple con las restricciones que aparecen enumeradas
(CustomerName>= &Start si &Start no está vacía y CustomerName<=&End si &End no está vacía)
y solo en caso de que las cumpla, ejecutará para ese cliente los comandos que aparecen dentro del
for each. En este caso, imprimirá los valores de los atributos del print block “customer”:
CustomerId, CustomerName, CountryName.
• que debe acceder a la tabla COUNTRY cuya clave primaria es CountryId para obtener algún dato
(CountryName)
197
For each: cláusulas Where
• Ejemplo:
for each
where CountryName = ‘Uruguay’
print customer
endfor
Los atributos utilizados en las condiciones de filtro pueden ser cualesquiera de la tabla extendida
del for each.
En el ejemplo, si bien la tabla base del for each es CUSTOMER, estamos filtrando los datos a
recuperar utilizando el atributo CountryName, que es de la tabla COUNTRY, perteneciente a la
extendida de CUSTOMER.
En este ejemplo tampoco se explicita nada con respecto al orden, por lo cuál los datos aparecerán
ordenados por la clave primaria de la tabla base, es decir, por identificador de cliente, CustomerId.
198
For each: cláusula Order
• Permite establecer el orden en el que se quieren
recuperar los datos.
• Ejemplos:
for each order CustomerName
print customer
endfor
for each
order CustomerName when not (&Start.IsEmpty() and
&End.IsEmpty())
print customer
endfor
• Para determinar orden descendente se deben colocar
paréntesis rodeando a los atributos del orden.
Ejemplo: order (CustomerName)
Si queremos realizar un listado de todos los clientes pero ordenado por nombre del cliente en lugar
de por código, lo único que tenemos que hacer es modificar el comando for each agregando esta
información del orden.
Esto se logra utilizando la cláusula order del for each, como se muestra en el primer ejemplo.
Como no existe un índice definido en la tabla CUSTOMER por el atributo CustomerName, GeneXus
indicará en el listado de navegación mediante una advertencia (“warning”) que no existe un
índice para satisfacer el orden, lo que podría ocasionar problemas de performance, dependiendo
de la plataforma de implementación elegida, de la cantidad de registros que deben ser leídos de la
tabla, etc.
En algunas plataformas, como VFP o iSeries (AS/400), si no existe índice para satisfacer el orden
especificado se crea uno temporal cada vez que se ejecuta el reporte y se elimina al finalizar la
ejecución. Este tipo de índice es el que llamamos índice temporal.
En otras plataformas, como VB con Access o en ambientes cliente/servidor, si no existe índice para
satisfacer el orden, el for each se traduce en una consulta SQL (“select”) que es resuelta por el
motor del DBMS de la plataforma.
Al igual que en el caso de las cláusulas where, en plataformas cliente/servidor la cláusula order
puede condicionarse, como se muestra en el segundo ejemplo.
En caso de no cumplirse la condición del when, no aplicará ese orden y de no existir orden
incondicional (sin cláusula when) como en el ejemplo, el orden a utilizar será indefinido,
significando esto que el orden resultante podrá variar de DBMS a DBMS e incluso entre ejecuciones
sucesivas.
Pueden especificarse varias cláusulas order condicionadas (con cláusula when) consecutivas en
casos de arquitecturas cliente/servidor y una sin condición (la última de la lista). La primera
cláusula order cuya condición del when se satisfaga, será la elegida y su orden el utilizado.
Cláusula Order None: cláusula que evita que GeneXus elija por defecto el orden de los atributos
de la clave primaria de la tabla base y utilice un orden de navegación indefinido.
La cláusula order none admite condición para aplicarse (when).
199
Listado de navegación
Listado de navegación
Cuando no existe índice que satisfaga el orden de un for each, como es el caso del ejemplo, el
analista GeneXus puede resolver crearlo (índice de usuario). En la mayoría de los casos el reporte
será bastante más eficiente de esta manera, pero se debe mantener un índice más (lo que implica
mayor almacenamiento y mayor procesamiento para mantener actualizado el índice)
No es posible recomendar a priori cuál de las dos soluciones es la mejor (no índice vs índice de
usuario), por lo tanto se debe estudiar, caso por caso, la solución particular teniendo en cuenta la
plataforma de implementación y siendo fundamental la frecuencia con que se ejecutará el reporte.
De cualquier manera, si al comienzo no se definió el índice de usuario y posteriormente se decide
definirlo, alcanza con regenerar el reporte (sin modificar nada de lo programado en el mismo) y
éste pasará a utilizarlo.
200
For each: cláusula Order
• Atributos permitidos:
De tratarse de una plataforma centralizada (Visual Basic / Access o Visual Fox Pro / DBFs), al
especificar un reporte que contenga:
En cambio, si se está generando en una plataforma cliente/servidor, no habrá ningún problema con
un reporte que contenga el comando anterior.
201
OPTIMIZACIÓN: Orden compatible
con los filtros
for each
where CustomerName >= &Start Se recorre
where CustomerName <= &End toda
print customer la tabla base
endfor
Si nos interesa filtrar los clientes de forma tal que sus nombres pertenezcan a un rango, en el primer
ejemplo, como no especificamos cláusula order GeneXus ordena por clave primaria, es decir, por
CustomerId.
En este caso, el listado de navegación nos va a informar que se debe recorrer toda la tabla
CUSTOMER, y para cada registro de la misma se debe evaluar si el registro cumple o no con las
condiciones (restricciones o “constraints”). En caso afirmativo, se imprimen para el mismo los datos
correspondientes.
Si en lugar de ordenar los datos por CustomerId pedimos que se ordenen por CustomerName, como
se presenta en el segundo ejemplo, la tabla base se recorre ordenada por CustomerName y como en
los filtros establecemos que queremos quedarnos solo con aquellos clientes cuyo nombre,
CustomerName, esté en el rango determinado, entonces ¡ya no será necesario recorrer toda la tabla
base para obtener los datos que cumplen con las condiciones!
Diremos que esta segunda consulta está optimizada en ese sentido. Tener en cuenta, sin embargo,
que GeneXus no tiene creado un índice en forma automática por CustomerName, y aquí habrá que
evaluar si conviene crear un índice de usuario (que debe ser mantenido por Genexus luego) o no
crear índice y que se cree un índice temporal en ejecución para resolver la consulta si el DBMS no
puede resolverlo de otra forma.
202
Definición de la estrategia de
acceso a las tablas
Begin of table Begin of table
READ READ
READ READ
READ READ Start point
READ READ
READ READ
READ READ End point
READ READ
READ READ
Normalmente los programas procesan registros agrupando aquellos que tengan ciertas
características comunes, por ejemplo, todas las facturas de un determinado cliente, o las ventas de
una clase de artículos.
Estas características comunes se establecen a través de condiciones que determinan un filtro sobre
los registros. Tales condiciones se especifican en GeneXus de diversas formas, una de las cuales es
el caso de las cláusulas where en un For each.
En el ejemplo que estamos viendo: de todos los clientes, quiero imprimir los datos de aquellos cuyo
nombre esté en un rango determinado.
El tiempo necesario para realizar el proceso con los registros que cumplen las condiciones determina
el grado de "optimización" que tiene la estrategia de acceso elegida. Decimos que una estrategia de
acceso está más "optimizada" que otra cuando es necesario un menor tiempo para leer todos los
registros que cumplen las condiciones establecidas.
La optimización consiste en posicionarse lo más cerca posible del primer registro del grupo que
cumple las condiciones y desde allí leer secuencialmente hasta el último que las cumpla. Lo que se
establece, en definitiva, es un intervalo que cubre todos los registros que cumplen las condiciones,
de manera tal que los que están fuera de ese intervalo ni se consideran, pues ya se sabe que no
podrán cumplir las condiciones.
En este intervalo no necesariamente todos los registros cumplen la condición. En estos casos, la
mejor estrategia consiste en tener el menor intervalo tal que cubra a todos los registros que
cumplirán la condición. Igualmente las lecturas se realizarán para todos los registros de este
intervalo, pero solamente se considerarán aquellos que cumplan con las condiciones.
Para definir una buena estrategia de acceso, GeneXus cuenta con una inteligencia razonable.
Se determinará, estableciendo (de acuerdo al orden en que deban considerarse los registros y a las
condiciones), una posición inicial y una posición final en el archivo, leyendo en forma secuencial en
este intervalo.
203
For each:
cláusula Defined by
Puede darse el caso de que para un For each haya más de una tabla base cuya extendida contenga
a los atributos del for each, siendo mínima. Ante esta ambigüedad, GeneXus escoge la “primera” de
estas tablas extendidas mínimas.
Para resolver este tipo de ambigüedad surge la cláusula defined by, que permite nombrar atributos
de la tabla base deseada, que no se utilizarán para devolver la consulta ordenada por esos
atributos, ni para filtrar la información a recuperar, ni para ser desplegados en el listado (es decir,
no tienen funcionalidad alguna con respecto a los datos a recuperar), sino solo para aportar más
información que permita determinar la tabla base del for each.
En la cláusula Defined by se debe hacer referencia a por lo menos un atributo de la tabla base
deseada.
De la misma manera, se puede (y se suele) utilizar para modificar la que sería la tabla base en caso
de no nombrarse ningún atributo más dentro del For each. Este es el caso del ejemplo presentado,
en el que no queremos listar todos los clientes de la tabla CUSTOMER, sino por el contrario,
queremos listar todos los clientes de las facturas. De no nombrarse dentro del For each algún
atributo de INVOICE, la tabla base sería CUSTOMER.
En la mayoría de los casos no es necesario utilizar esta cláusula. Sin embargo, para reportes más o
menos complejos, aún cuando no exista problema de ambigüedad, se recomienda el uso del
Defined by porque mejora bastante el tiempo de especificación del reporte.
Sin embargo, no puede aconsejarse un uso indiscriminado. La contra de utilizar esta cláusula
cuando no es necesaria es que ata un poco más el código al diseño de las tablas.
Supóngase por ejemplo que no se tiene creada una tabla COUNTRY, y se tiene de cada cliente el
país al que pertenece, como atributo secundario. Si lo que queremos es listar los clientes y su país,
serían equivalentes:
donde customer es un print block con los atributos CustomerId, CustomerName y CountryName.
Si ahora decide crearse la tabla COUNTRY y tener en la transacción “Customer” a CountryId como
FK, si bien el primer for each continuará siendo válido y haciendo lo que queremos, el segundo
dejará de funcionar, ya que en el Defined By no hay ningún atributo de la tabla base.
204
For each:
cláusula Defined by
Pueden aparecer varios atributos, para el posible caso en el que no alcance uno solo para
determinar completamente la tabla base deseada, donde al menos uno de ellos deberá estar
asociado a la tabla base deseada.
Un error común es creer que cuando un for each tiene esta cláusula, la tabla base del mismo queda
determinada exclusivamente a partir de los atributos mencionados en el defined by.
La realidad es que los atributos del defined by arrojan una o más tablas base candidatas a ser la
tabla base del for each, pero luego hay que ver si tomando cada tabla base candidata, su extendida
contiene a todos los demás atributos del for each, además de los del defined by.
Si ninguna de las posibles tablas base candidatas cumplen esta condición, entonces el reporte dará
un error al ser especificado, y no podrá ser generado (recordemos que debe cumplirse que todos los
atributos del for each estén contenidos en una misma tabla extendida).
205
For each:
cláusula When none
• Ejemplo:
for each
where CustomerName >= &Start
where CustomerName <= &End
print customer
when none
print message
endfor
El print block message (que podrá contener un texto advirtiendo al usuario de que no existen
clientes que cumplan los filtros) se ejecuta sólo cuando no se entra en el For each, es decir, cuando
no hay ningún registro correspondiente a la tabla base del for each para el que se cumplan las
condiciones de filtro.
También se aplica a for each [selected] line, XFor Each y XFor First, comandos que veremos más
adelante.
La cláusula when none debe ser la última dentro del For each. Las acciones a realizar cuando no
existe ningún registro para el que se cumplan las condiciones, quedan determinadas por el bloque
de código que hay entre la cláusula when none del for each y el endfor.
Cuando un for each no tiene condiciones de filtro, los comandos del when none se ejecutarán solo
en el caso en que se cumpla que la tabla base del For each esté vacía, porque solo en ese caso no
habrá ningún registro que cumpla las condiciones de filtro.
Importante:
• Si aparecen atributos en el bloque de código correspondiente al when none, éstos no son tenidos
en cuenta para determinar la tabla base del for each.
• Si se incluyen for eachs dentro del when none no se infieren Joins ni filtros de ningún tipo con
respecto al for each que contiene el when none, ya que son considerados for eachs paralelos.
206
Comando For each - Sintaxis
for each
[{[order] order_attributes [when condo] }…|
[order none] [when condon]]
[{where condition when condw }…]
[defined by defined_attributes]
code1
[when duplicate
code2]
[when none
code3]
endfor
La sintaxis presentada generaliza el ejemplo con el que vinimos trabajando. Es la sintaxis general
que aplica a plataformas Cliente/Servidor. Para plataformas centralizadas existen algunas
limitaciones (cláusulas when y order none no aplican a este caso).
Order
order_attributes::= att1, …, attn
Es una lista de atributos, que indican el orden en el que será devuelta la consulta, siendo atti un
atributo de la base de conocimiento escrito simple, o entre paréntesis curvos. Cuando un atributo
del order aparece rodeado de paréntesis curvos se está indicando orden descendente para el
mismo.
Aquí pueden mencionarse atributos de la tabla extendida, a menos que se esté trabajando en
una plataforma centralizada, en cuyo caso solo podrán utilizarse atributos almacenados en la
tabla base.
Para plataforma centralizada, podrá especificarse a lo sumo una cláusula order, sin condición
(when). Sin embargo puede no especificarse cláusula order. En tal caso se asume como orden el
de la clave primaria de la tabla base, salvo excepciones que estudiaremos oportunamente.
Para plataforma cliente/servidor es posible definir varias cláusulas order condicionales, y una
incondicional, que debería ser la última listada. El por qué responde al hecho de que como
solamente una de esas cláusulas order tomará efecto, se van evaluando sus condiciones (las del
when) hasta la primera que de True, y con esa se queda. Si ninguna da true y existe una cláusula
incondicional (es decir, sin when), se tomará ese orden. Si no existe tal cláusula, el orden será
indefinido, queriendo esto significar que dependerá de la plataforma, e incluso podrá variar entre
ejecuciones sucesivas. La justificación para escribir cláusulas order condicionales, deriva de si
queremos aplicar cláusulas where condicionales. Es decir, por motivos de optimización de las
consultas.
Por ejemplo, si queremos filtrar por CustomerName > &Name when not &Name.IsEmpty(),
entonces para optimizar la consulta deberíamos ordenar por CustomerName, pero si no se va a
aplicar el filtro, dado que &Name está vacía, entonces será mejor dejar un orden indefinido.
Para ello especificamos la cláusula order condicional:
order CustomerName when not &Name.IsEmpty()
En lugar de todo lo anterior, también puede especificarse una cláusula order none que se
agrega para cuando no nos interesa un orden en particular y queremos que éste quede indefinido.
207
Elección del índice: GeneXus elige automáticamente el índice a utilizar para satisfacer el orden. En caso de que
no exista, informa de esta situación en el listado de navegación y dependiendo de la plataforma, crea un índice
temporal o deja en manos del DBMS la elección de la estrategia para procesar la consulta.
Los atributos del order son tenidos en cuenta a la hora de determinar la tabla base del for each. Pero ellos por
sí solos no la determinan. Deben examinarse también otras partes del for each.
Where
Condition
Condición booleana que deberán cumplir los datos para ser procesados dentro del for each, pudiendo ser una
condición compuesta, utilizando los operadores lógicos and, or y not.
Los atributos que aparezcan en la condición booleana pueden ser tanto de la tabla base del for each como de
la extendida.
Como se desprende de la sintaxis, para un mismo for each pueden especificarse n cláusulas where sucesivas, cada
una con una condición:
where cond1
where cond2
...
where condn
La ocurrencia de n cláusulas where es equivalente a la ocurrencia de una sola cláusula, con la conjunción
booleana de las condiciones:
Los datos de la tabla extendida del for each que cumplan con todas las condiciones de los “where” serán los
procesados en los comandos internos al for each (los del bloque de código code1).
Para el caso de plataformas cliente/servidor, al igual que ocurre con la cláusula order, podrán condicionarse los
filtros (con cláusulas when). De esta manera, primero se evalúa la cláusula when de cada cláusula where, y de
cumplirse su condición, aplicarán el filtro especificado en el where.
Para que una restricción condicional pueda ser generada como tal, se debe estar en una arquitectura
Cliente/Servidor y la condición del when tiene que ser "evaluable" por el DBMS que se está utilizando, es decir,
GeneXus tiene que saber cómo escribir la condición en el lenguaje propio del DBMS utilizado.
Si no se puede generar como tal (porque el generador no lo soporta o porque la condición no puede ser escrita en
el lenguaje del DBMS) se transformará en un filtro "común" sustituyendo el WHEN por un OR. Además, se
generará el mensaje de código spc0053 – ‘Unsupported conditional constraint”%1” changed to standard
constraint %2.’ - en el Diagrama de Navegación.
Nota: Existe también la cláusula Option Distinct del For Each que permite retornar los registros que cumplan
unicidad de valores de los atributos referenciados. No veremos esta cláusula. El lector interesado puede recurrir a
las distintas fuentes de documentación para estudiarla (Help, GXDL, Wiki, Release Notes, etc.)
Defined by
defined_attributes::= att1, att2,…,attp
Es un conjunto de atributos que serán utilizados a los solos efectos de determinar la tabla base del for each.
Al mencionar aquí algunos atributos de la tabla que se desea recorrer, éstos participarán en la determinación de la
tabla base del for each.
Los atributos de esta cláusula no determinan por sí solos la tabla base del for each. Podría darse el caso de que de
estos atributos surja determinada tabla como la candidata a tabla base, pero si luego el resto de los atributos
del for each no están contenidos en la extendida de esa tabla, el for each dará un error y el objeto que lo contiene
no podrá ser generado.
code1
Es una sucesión de comandos en los que pueden utilizarse atributos de la tabla extendida del for each. A este
bloque de código le llamaremos cuerpo del for each.
Los atributos que figuren en este bloque de código participarán en la determinación de la tabla base del for each.
208
Los comandos especificados se ejecutarán secuencialmente para los datos de la tabla extendida que cumplan las
condiciones de filtro, considerándose los datos en el orden especificado.
When Duplicate
Esta cláusula solo tiene sentido en procedimientos (dado que tiene que ver con la actualización) y se verá más
adelante.
Se ejecutará esta cláusula si dentro del cuerpo del For each code1, se intenta actualizar un atributo que es clave
candidata (tiene índice unique) y ya existe un registro con ese valor. GeneXus utiliza el índice unique para
asegurar la unicidad de esa clave candidata y en caso de encontrar duplicados, si el for each tiene programada
esta cláusula, ejecutará su código: code2 .
De no existir la cláusula no se ejecutará código alguno.
When none
En caso de que no existan datos que cumplan las condiciones de filtro no se ejecutarán los comandos del code1
sino que se ejecutarán los del bloque de código code3.
¾ Tanto para When Duplicate como para When none: Si se incluye dentro de alguno de los dos un comando for
each, no se infieren joins ni filtros con respecto al for each que los contiene (el del when none | when duplicate).
Son consideradas navegaciones independientes (la del code1, code2 y code3).
209
For eachs paralelos
El For each es un comando como otros, y por tanto puede aparecer varias veces dentro del Source,
tanto en forma paralela (independiente), como anidado a otro for each.
Cuando dentro del cuerpo del for each (code1) aparece otro for each, decimos que se trata de for
eachs anidados. GeneXus soporta varios niveles de anidamiento para los for eachs.
El caso en el que un for each aparece en el bloque de código code3, si bien puede verse como un
anidamiento de for eachs porque uno aparece dentro de otro, el comportamiento en cambio, es
como el del caso de for eachs paralelos.
Un for each “anidado en el when none” de otro, solo se ejecutará si no existe ningún registro de la
tabla base del for each que lo contiene que cumpla las condiciones de filtro.
En el ejemplo, “invoice” y “bill” son dos print blocks del Layout que continen atributos de las tablas
INVOICE y BILL (recibo) respectivamente.
Hemos definido dos for eachs paralelos. El primero recorrerá todas las facturas y el segundo todos
los recibos.
210
For eachs anidados
for each
...
for each Cuerpo del for each principal
...
endfor
...
when none
...
endfor
El for each es una estructura repetitiva que permite recuperar muchos registros de una tabla. Cuando
uno piensa en for eachs anidados, es evidente que lo que se busca recuperar es, por cada registro del
principal, muchos registros del anidado.
¿Qué cosas podría GeneXus detectar que se quiere hacer con este tipo de estructuras, de forma tal de
poder inferir el comportamiento automáticamente con la menor codificación posible?
• para cada registro de una tabla recuperar algunos de otra: los relacionados.
• para cada registro de una tabla recuperar todos los de otra.
• procesar información por grupos, esto es, agrupar los registros de una tabla según el valor de un
atributo o conjunto de atributos y para cada grupo, recuperar algunos registros: los correspondientes
al grupo.
Siempre la relación es uno a muchos: por cada registro de una tabla recuperar muchos de la otra
(pudiéndose tratar de la misma tabla).
Utilizando esta lógica es que GeneXus infiere las tablas base y el comportamiento de los for eachs
anidados, sabiendo que lo que se desea es implementar alguna de las tres opciones anteriores.
Por ejemplo si queremos realizar un listado de todas las facturas del sistema, donde para cada una
queremos imprimir también el detalle de la misma, debemos recorrer dos tablas: INVOICE (que
almacena los cabezales) e INVOICELINE (que almacena las líneas), y lo haremos de una forma bien
simple, sin nombrar las tablas, y sin tener que explicitar todo, como veremos.
Otro ejemplo es el de un listado de todos los clientes, donde para cada uno se quieren imprimir,
además de sus datos personales, los datos de todas sus facturas. Este comportamiento se logra con un
par de for eachs anidados, donde el primero recorrerá la tabla CUSTOMER y el segundo recorrerá la
tabla INVOICE, recuperando solo las facturas de ese cliente, como veremos en breve.
211
For eachs anidados
for each
print invoice_header {InvoiceId, InvoiceDate, CustomerName}
for each
print invoice_lines {ProductDescription, ProductPrice,
InvoiceLineQuantity, InvoiceLineAmount}
endfor
print invoice_total {InvoiceTotal}
endfor
Queremos realizar un listado de todas las facturas del sistema, donde para cada una se muestre
tanto información de su cabezal como de sus líneas.
En el Source programado, claramente recorremos la tabla INVOICE, imprimiendo los atributos que
nos interesan del cabezal y para cada registro de esa tabla, recorremos la tabla INVOICELINE, para
imprimir los atributos que nos interesan de sus líneas.
¡Tan simple como eso! Otra vez, nos alcanza con nombrar los atributos que queremos utilizar y del
resto se encarga GeneXus.
Observemos que ni siquiera tuvimos que especificar condición de filtro sobre los registros de
INVOICELINE a recuperar. GeneXus se da cuenta de la relación existente entre las tablas, y aplica
automáticamente la condición de filtro sobre los datos, de manera tal de solo imprimir las líneas de
“esa” factura (recordar que algo idéntico ocurría con las fórmulas verticales, como InvoiceTotal,
donde la condición de filtro sobre los registros a ser sumados o contados quedaba implícita, y no
había que especificarla).
212
For eachs anidados
INVOICE INVOICELINE
INVOICELINE.InvoiceId = INVOICE.InvoiceId
Como para un for each simple, GeneXus debe determinar para cada for each (principal y anidado)
cuál es su tabla base. Y luego, a partir de esa determinación, inferirá la lógica correspondiente.
Más adelante veremos con exactitud cómo es que GeneXus determina cada tabla base. Aquí nos
quedaremos con la idea intuitiva de que lo hace en forma similar a como lo hacía para el caso de un
for each simple.
Como esas tablas están relacionadas de acuerdo a una relación 1-N infiere más que eso: infiere
además que debe aplicar la condición sobre los datos de INVOICELINE que se indica arriba.
213
For eachs anidados
GeneXus debe:
Cuando tenemos for eachs anidados, GeneXus debe determinar la tabla base de cada uno, y esas
serán las tablas que se navegarán.
Para cada registro de la tabla base del for each principal, se ejecutarán los comandos del cuerpo del
mismo. Entre esos comandos, se encuentra el for each interno, que se ejecutará, como cualquier
otro comando, en el lugar donde se encuentre, realizando una navegación sobre su tabla base.
Pero para la determinación de la tabla base del for each anidado, podrá influir la tabla base del for
each principal, por lo que las determinaciones no son por completo independientes, como podría
pensarse.
A partir de la determinación de las tablas base de cada for each primero y de las relaciones
que encuentre GeneXus entre las tablas involucradas luego, surgen tres posibles casos de for eachs
anidados: join, producto cartesiano y corte de control, respectivamente.
Estudiaremos en lo que sigue cada uno de estos casos con ejemplos, para luego generalizar todo lo
visto.
214
For eachs anidados:
determinación tablas base
• La determinación de la tabla base del for each principal, es análoga al caso de for each
simple (sin anidamientos). En este caso se consideran todos los atributos del for each principal,
descartando los for eachs anidados que éste contenga (y todos sus atributos). GeneXus encuentra
la mínima tabla extendida que contenga a los atributos referidos y define así la tabla base a través
de la cual llega a todas las demás.
• Para determinar la tabla base del for each anidado, GeneXus se fija si los atributos
utilizados dentro del cuerpo del mismo están incluidos o no dentro de la tabla extendida
previamente determinada (la del for each principal). En caso afirmativo, GeneXus determina que la
tabla base del for each anidado será la misma que la del for each principal.
En caso contrario, se busca la mínima tabla extendida que cumpla que contiene a todos los
atributos del for each anidado. Su tabla base será la del for each. (Observar que solo en este caso
se procede a determinar la tabla base como si se tratase de for eachs independientes).
215
For eachs anidados:
determinación tablas base
Tabla base for each externo
for each
print invoice_header {InvoiceId, InvoiceDate,
for each CustomerName}
print invoice_lines
endfor INVOICE
{InvoiceTotal}
print invoice_total
endfor
Para el ejemplo que presentamos antes, mostramos arriba cómo se determina cada tabla base.
Para la del anidado, como los atributos que figuran no están contenidos en la tabla extendida de
INVOICE (que es la tabla base del principal), entonces se pasa a determinar su tabla base como si
se tratase de un for each independiente.
Este es el caso general, pero en algunas circunstancias particulares GeneXus toma un criterio más
exhaustivo intentando encontrar relación 1-N entre los for each y en esos casos, la determinación
de la tabla base del for each anidado (cuando los atributos del mismo no están incluidos en la
extendida del principal) no es independiente. GeneXus busca otra relación, pero no entraremos en
estos casos particulares en el presente curso. De sucederle al estudiante, lo notará claramente en el
listado de navegación.
216
For eachs anidados:
lógica asociada
De la determinación de las tablas base, surgen los tres casos de for eachs anidados que se
mencionan y que estudiaremos uno a uno en lo sucesivo.
217
Caso 1: Join
Este es el caso en el que GeneXus determina que las tablas base de cada for each son distintas y hay
una especie de relación 1-N (pudiendo ser ésta indirecta, como veremos luego) entre las tablas que
se recorren.
Es decir, por cada registro de la tabla base del for each principal, GeneXus encuentra que hay N
relacionados con él, directa o indirectamente, en la tabla base del for each anidado.
Al encontrar esta relación, aplicará condiciones de filtro automáticas en el for each anidado, de forma
tal de solo quedarse con esos registros relacionados.
El ejemplo del reporte en el que se imprimen todas las facturas del sistema, con sus detalles, cae
dentro de esta categoría. En ese caso hay una relación 1-N directa entre las tablas que se recorren:
para cada cabezal de factura, se lista el mismo, junto con todas sus líneas de factura, es decir, todos
los registros de INVOICELINE que están relacionados con el registro de INVOICE en el que se está
posicionado en cada iteración.
Este es uno de los casos más comunes de for eachs anidados, donde se quiere recorrer una tabla, y
para cada registro de la misma, recorrer otra tabla, relacionada con la primera por una relación N-1.
GeneXus encuentra esta relación, y en la navegación interna solo recupera los registros asociados, y
de ahí el nombre de “join” para este caso.
En el ejemplo presentado arriba ocurre lo mismo. La tabla base del primer for each es CUSTOMER, y
la del segundo, INVOICE. Como encuentra atributo en común entre la tabla extendida del for each
principal y la tabla base del anidado1, CustomerId, determina que ese atributo actuará como
condición de filtro en la recorrida de la tabla del for each anidado.
------------------------------------------------------------------------------------------------------------
1 Esta es una forma de expresar formalmente lo que habíamos dicho en términos informales: relación
directa o indirecta 1-N entre las tablas base. La relación será directa cuando la tabla base del
principal tenga relación 1-N con la tabla base del anidado, es decir, sea superordinada de esta última.
La relación será indirecta cuando esto no exista una relación directa entre las tablas base, pero sí
entre la tabla extendida del primero y la tabla base del segundo. También será indirecta cuando la
tabla extendida del anidado incluya a la tabla base del principal. De esto veremos ejemplos luego.
218
Caso 1: Join
Veamos, por ahora intuitivamente, cómo hace GeneXus para determinar las tablas base. Los
atributos utilizados en el for each externo son CustomerId y CustomerName, por lo que la tabla
base de este for each será claramente CUSTOMER. Observemos que solo participan en la
determinación de esta tabla base los atributos del for each principal, no los del anidado.
Luego GeneXus debe encontrar la tabla base del for each anidado. Los atributos que participan son
InvoiceId, InvoiceDate y InvoiceTotal, es decir, los atributos internos a este for each. Observemos
que estos atributos no pertenecen a la tabla extendida del principal, que era CUSTOMER. Por tanto
se pasa a determinar su tabla base como la de cualquier for each simple: la tabla extendida de
INVOICE contiene a todos los atributos del for each anidado, y es la mínima tabla extendida que los
contiene. Por lo tanto, INVOICE será elegida como tabla base del segundo for each.
Observemos, nuevamente, que no explicitamos cláusula where en el for each anidado para filtrar las
facturas del cliente del for each principal. Justamente, por tratarse de tablas relacionadas por una
relación 1-N directa, esta condición es aplicada implícitamente por GeneXus y puede verse
claramente en el listado de navegación que se muestra arriba.
Hay otro hecho interesante que podemos observar en este listado: si bien en el for each anidado no
especificamos cláusula order, GeneXus no eligió el orden por clave primaria de la tabla base sino
por el atributo relación, CustomerId. De esta manera, está optimizando automáticamente la
consulta.
Acá se presenta, pues, una excepción a la regla que enunciaba que cuando no se especifica cláusula
order en un for each, GeneXus determina como orden el de la clave primaria de la tabla base de
dicho for each.
219
Caso 1: Join
print invoice
INVOICE
endfor
endfor
{InvoiceId, InvoiceDate, InvoiceTotal}
base(principal) ⊂ ext(anidado)
Si queremos realizar un listado de las facturas emitidas por país, tenemos otro caso de for eachs
anidados con distintas tablas base, donde la información que se quiere listar está relacionada.
Hilando fino, este es un caso un poco más complejo que el anterior, porque si bien la tabla
extendida del for each principal no tiene intersección con la tabla base del anidado (ext(COUNTRY)
∩ INVOICE = φ), sin embargo sí existe una relación 1-N indirecta, y GeneXus la encuentra.
En este caso, la tabla base del for each principal está incluida en la extendida del anidado
(COUNTRY ⊂ ext(INVOICE)), por lo que hay una relación 1-N indirecta.
Por este motivo, no necesitamos especificar cláusula where en el for each interno para filtrar las
facturas del país del for each principal.
Puede probar el lector este caso en GeneXus y estudiar detenidamente el listado de navegación
resultante.
220
Caso 2: Producto Cartesiano
ext(principal) ∩ base(anidado) = φ
y
base(principal) ⊄ ext(anidado)
En este caso GeneXus no logra encontrar una relación 1-N directa o indirecta entre las tablas y por
lo tanto no aplica filtros implícitos a los registros del for each anidado, vale decir, realiza un
producto cartesiano entre las tablas.
El caso se da cuando:
• ext(for each principal) ∩ base(for each anidado) = φ y
• base(for each principal) ⊄ ext(for each anidado)
Para cada registro de la tabla base del for each principal se recorre toda la tabla base del for each
anidado.
Por ejemplo, si la tabla base de un for each fuera COUNTRY y la del anidado PRODUCT,
evidentemente no existirá relación y se hará un producto cartesiano y se recorrerá para cada país,
todos los productos.
Por supuesto que el programador podrá establecer filtros sobre los datos a recuperar, pero éstos ya
no serán condiciones implícitas inferidas por GeneXus, sino especificadas explícitamente por el
programador.
221
Caso 3: Corte de Control
En el ejemplo que habíamos visto antes, del listado de clientes y sus facturas, ¿qué ocurre si un
cliente no tiene facturas?
Como la tabla base del for each principal es CUSTOMER, el cliente sale impreso antes de saberse si
tiene o no facturas.
Si no deseamos que esto ocurra, es decir, que salgan listados clientes que no tengan facturas,
entonces la solución es acceder únicamente a las facturas, pues si un cliente está en esta tabla,
¡es porque está en una factura!.
Pero para poder agrupar las facturas por cliente, de forma tal de poder desplegarlas de ese modo,
debemos recorrer la tabla INVOICE ordenada por CustomerId. De esta forma procesaremos la
información de un cliente, y luego pasaremos al siguiente, para procesar su información, y así
sucesivamente.
1. Para el registro apuntado, retener el valor del atributo de corte o agrupamiento, CustomerId.
3. Mientras el valor de CustomerId del registro apuntado coincida con el valor retenido en el paso
1 (aquí se procesan todas las facturas del cliente)
a. Imprimir InvoiceId, InvoiceDate e InvoiceTotal del registro apuntado.
(“print invoice”)
b. Avanzar el puntero al siguiente registro y volver al paso 3.
4. Volver al paso 1. (cuando se llegó a este punto, es o bien porque se llegó al fin de tabla o bien se
cambió de cliente).
222
Caso 3: Corte de Control
Ejemplo: Para cada cliente que tiene facturas, listar sus facturas.
• Source
for each order CustomerId orden determina el
defined by InvoiceDate criterio de corte
print customer
for each
print invoices Cada vez que cambia el cliente se
endfor define un nuevo grupo
endfor
Se utilizó “defined by” para que la tabla base fuera INVOICE y no
CUSTOMER y así implementar corte de control y no join.
• Layout
GeneXus brinda una forma de implementar lo anterior de una forma absolutamente sencilla.
El pseudocódigo visto en la página anterior se implementa en GeneXus con el par de for eachs
anidados que se muestran arriba.
Si se compara este código con el que vimos unas páginas atrás para el caso de join, vemos que
existen solamente dos diferencias: la cláusula order que aparece en este código, en conjunción con el
defined by. Con solo esos dos cambios al listado original, cambiamos radicalmente el
comportamiento, puesto que en este caso solamente se listarán los clientes si tienen facturas.
En este caso, ambas cláusulas (order y defined by) son indispensables para que este reporte funcione
del modo que queremos. Si agregamos solo una de ellas, pero no la otra, el resultado será otro.
La cláusula order es indispensable, porque es la que especifica por qué atributo o conjunto de
atributos se realizará el corte (o agrupamiento). Es decir, especifica esa información común al grupo,
que se procesará una sola vez (dentro el código del for each externo).
Pero también podríamos haber utilizado otra solución para modificar la tabla base del for each
principal: utilizar en vez del defined by el comando print if detail dentro del cuerpo del primer for
each (este comando le dice a GeneXus que tome como tabla base del for each, la que determine para
el anidado).
223
Caso 3: Corte de Control
Un corte de control es bien simple de implementar y puede hacerse siguiendo las consideraciones
anteriores.
En el order del for each más externo, debemos mencionar el “primer” atributo de corte, en el order
del segundo for each debemos mencionar el “segundo” atributo de corte, y así sucesivamente. No
es obligación mencionar atributo/s en el order del for each más interno (en todos los demás for
each sí lo es).
Corresponde al caso en el que nos interesa trabajar con la información de una tabla, pero agrupada
por algún atributo o conjunto de atributos.
224
Ejemplo: Corte de control doble
Supongamos que queremos como antes listar los clientes y sus facturas, pero queremos agrupar las
facturas de cada cliente por fecha. Es decir, queremos mostrar, para cada cliente, para cada fecha,
las facturas existentes.
Ejemplo:
Date: 01/01/06
Invoice Total
9 35
3 30
Como ahora queremos agrupar por cliente, y dentro de ese grupo por fecha de factura, necesitamos
tres for eachs anidados:
Como ejercicio, sigamos todos los pasos que realiza GeneXus para inferir el comportamiento del
reporte.
Como siempre, para determinar las tablas base de for eachs anidados, se empieza de afuera hacia
adentro, determinando la de cada for each, sin tomar en cuenta los atributos de los for eachs
internos al que se está considerando.
225
for each order CustomerId
defined by InvoiceDate
print customer
for each order InvoiceDate Tabla base 2do. for each
print date Intervienen los atributos de los lugares señalados en
for each negrita y como todos ellos están incluidos en la
print invoice extendida del 1er. for each, entonces se determina la
endfor misma tabla base: INVOICE
endfor
endfor
2. Determinación de la navegación
Luego de determinadas las tablas base, GeneXus determina la navegación. Como en este caso son tres for eachs
sobre la misma tabla base, se trata de un doble corte de control.
Podemos pensar que cuando hablamos de corte de control, ya sea simple, doble, triple, cuádruple, etc., tenemos un
solo puntero, que se utiliza para avanzar en los registros de la tabla base.
Recordemos que la cláusula order es fundamental para establecer el criterio de corte en cada par de for eachs.
Como en nuestro caso queremos agrupar por CustomerId y luego, para todas las facturas con ese cliente, agrupar
por InvoiceDate, entonces tendremos que ordenar el primer for each por CustomerId y el inmediatamente anidado
por InvoiceDate.
Recomendamos al lector implementar en GeneXus este reporte y observar detenidamente el listado de navegación.
Verá que GeneXus elige un único orden, para el que no tiene un índice creado: el compuesto por la concatenación
de los órdenes de cada for each con cláusula order. Esto resulta evidente si pensamos en términos de un único
puntero que se va desplazando por la tabla base.
226
Resumen: Determinación general de las tablas base
Se procede en forma ordenada, determinando cada vez la tabla base de un nivel de anidación,
yendo de afuera hacia adentro: primero se determina la tabla base del for each más externo, luego
del que está anidado a éste y así sucesivamente.
Para el for each anidado, GeneXus se fija primeramente si los atributos utilizados dentro del cuerpo
del mismo están incluidos o no dentro de la tabla extendida previamente determinada. En caso
afirmativo, GeneXus determina que la tabla base del for each anidado será la misma que la del for
each principal (y será un caso de corte de control).
En caso contrario, sí se determina como si fueran independientes: busca la mínima tabla extendida
que contenga a todos los atributos del for each anidado1.
En la siguiente página mostramos otro esquema, pero esta vez con los casos de for eachs anidados
de acuerdo a las tablas base encontradas.
----------------------------------------------------------------------------------------------------------
1 Existe alguna excepción, pero no entraremos aquí en este tema.
227
Esquema de casos
Luego de determinadas las tablas base de los for eachs anidados, el siguiente paso que realiza
GeneXus es determinar la lógica que se asociará a los mismos.
Esa lógica viene determinada de acuerdo al caso (join, producto cartesiano o corte de control) en el
que caigan los for eachs anidados bajo análisis.
Un esquema de los casos puede verse más claramente con el diagrama de flujo que se muestra a
continuación:
228
Comandos de control
if cond
do while cond do case
bloque1
bloque case cond1
[else
enddo bloque1
bloque2]
[case cond2
endif
bloque2]
......
for &var=inicio to fin [step salto] [case condn
bloque bloquen]
endfor otherwise
bloquen+1
for &var in &array endcase
bloque
endfor
Los comandos introducidos son similares a los existentes en los lenguajes de programación
imperativa conocidos, por lo que no incluimos documentación de este tema. Puede encontrarla en el
curso no presencial o en el Help de GeneXus. Los dos últimos, no obstante, incorporan algunos
elementos interesantes sobre el manejo de arrays y de colecciones1 en GeneXus, por lo que
mostraremos algunos ejemplos.
For to step:
• inicio, fin son expresiones numéricas
• salto es una constante numérica
• var es alguna variable numérica
• bloque es una sucesión de comandos válidos del lenguaje
Permite iterar una cierta cantidad de veces: desde el valor inicio que toma la variable &var cuando
se ingresa al bucle, hasta el valor fin que tomará la misma luego de cierta cantidad de iteraciones.
De iteración en iteración la variable &var se va incrementando automáticamente en una cantidad
igual a salto. El valor por defecto de salto es 1, por lo que si no se especifica la cláusula step el
incremento de la variable será de uno en uno. El valor de salto puede ser negativo y en ese caso se
irá decrementando la variable de iteración en iteración.
Ejemplo
For &i = 1 to 5
&ok = PInvoicing.udp( &month )
endfor
----------------------------------------------------------------------------------------------------------
1 Las colecciones representan listas de largo variable. Se verán cuando estudiemos el tipo de datos
estructurado (SDT).
229
For in array:
• array es un vector o matriz (variable de una o más dimensiones). También puede ser una variable SDT
collection (este tema se estudiará más adelante)
• var es una variable que debe tener el mismo tipo de datos que array o compatible (en caso de tratarse
de un SDT, deberá ser un SDT correspondiente a los ítems)
Esta estructura de programación permite recorrer con menos código una variable array de una o más
dimensiones. Se almacena en la variable &var los valores de cada posición del array.
Consideraciones:
• No es posible modificar los valores del array en la recorrida. Esto significa que cambios en el valor de
&var en el alcance de la estructura, no afectan al correspondiente valor del &array(&x) (o de &array(&x,
&y))
• No es posible obtener la posición del array durante la recorrida, para esto es necesario definir una
variable que actúe como contador
Ejemplo
For &i= 1 to 4
For &j=1 to 3
&array(&i,&j) = &i + &j
endfor
endfor
230
Comandos de impresión
• Print
• Se utiliza para imprimir en la salida un print block definido
en el Layout
• Sintaxis: Print nombrePrintBlock
• Header
• Se utiliza para definir lo que se quiere imprimir como encabezado
de cada página del listado
• Sintaxis: Header
bloque
end
• Footer
• Define las líneas de pie de página a ser impresas al final de cada
página del reporte.
• Sintaxis: Footer
bloque
end
Aquí veremos algunos comandos de impresión, que permiten diseñar la salida del reporte.
Print
donde nombrePrintBlock es el identificador de un print block del Layout. Si no existe en el Layout
un print block con ese nombre, al intentar salvar el objeto se desplegará un mensaje de error
informando sobre esta situación.
De esta forma se implementa la impresión en la salida de los print blocks del Layout.
Cuando el print block que se quiere imprimir no contiene atributos, entonces este comando se
puede utilizar en cualquier lugar del Source.
Los atributos indican acceso a la base de datos y este acceso no puede realizarse en cualquier lado,
sino únicamente dentro del comando específico para ello, esto es, el comando for each.
Por tanto no es correcto escribir el comando print fuera de un “for each” si el print block que se está
queriendo imprimir contiene atributos.
La única excepción a esta regla se produce cuando los atributos del print block están incluidos entre
los parámetros recibidos por el reporte, pues en este caso no es necesario acceder a la base de
datos para recuperar sus valores, dado que ya vienen instanciados.
Header
Aquí se define lo que se quiere imprimir como encabezado de cada página del listado.
Este encabezado es opcional. Si no se especifica, entonces las páginas del listado no tendrán
encabezado.
Ejemplo: En el reporte en el que queríamos imprimir un listado con el código, nombre y país de
cada uno de los clientes de nuestro sistema, si queremos que en cada página del listado aparezca el
encabezado: “CUSTOMERS REPORT”, entonces alcanza con escribir en el Source:
Header
Print title
End
Donde “title” es el nombre de un print block del Layout que contiene este texto.
231
También podríamos haber escrito directamente: Print title al comienzo del Source, pero en este
caso, si el listado tiene varias páginas, solo saldrá impreso este texto en la primera. En el otro caso,
saldrá en cada una de las páginas, como encabezado.
Footer
Define las líneas de pie de página a ser impresas al final de cada página del reporte.
Los comandos del bloque de código son ejecutados cuando se llega al final de una página.
Ejemplo:
Footer
print endOfPageText
end
232
Diseño de la salida
Existen algunos comandos para diseñar la salida del reporte. Presentamos aquí algunos a los
efectos de la documentación.
MT nline: nline es el número de línea en el que se quiere empezar a imprimir el listado. En caso de
no especificarse un valor se asume el valor por defecto que es 0.
MB nline: nlíneas es el número de líneas que se desea dejar como margen inferior.
En caso de no especificarse un valor se asume el valor por defecto que es 6.
PL nline: Setea el largo de página. El número de líneas que será impreso es el número especificado
menos el margen de abajo (valor por defecto es 6). Ej: PL 66
Setea el largo de página a 66 líneas, aunque sólo 60 líneas serán impresas en el form, con un
margen inferior de 6 líneas.
CP nlines: Si queda en la página actual un número de líneas mayor o igual al número especificado,
continúa imprimiendo en la misma página. De lo contrario, pasa a imprimir en la próxima página
(fuerza a un salto de página).
Lineno nline: Define el número de línea donde va a ser impresa la siguiente línea. Si el número de
línea actual es mayor al número especificado, entonces, la línea será impresa en la próxima página.
El conteo de líneas comienza en la línea 0.
Eject: Fuerza a un salto de página.
Noskip: Tiene que estar inmediatamente después de un print block. Si el comando se encuentra
entre dos líneas, este comando las imprimirá en la misma línea.
233
Reportes PDF
• Regla
• output_file( ‘xx.pdf’, ‘PDF’)
En Web los reportes solamente pueden ser PDF y se deben configurar las propiedades y regla
anteriores para que funcionen.
Al definir que un objeto es main (en este caso un reporte, pero podría ser una transacción, web
panel, etc.), GeneXus genera un programa ejecutable con la lógica del objeto mismo y la de
todos los objetos invocados directa o indirectamente por él.
La definición de un objeto como main se realiza editando las propiedades del objeto, y
configurando la propiedad Main program del mismo con valor True.
234
Condiciones
En esta sección se permiten establecer condiciones que deben cumplir los datos para ser
recuperados.
Una “condición” es equivalente a la cláusula “where” del comando for each (incluso tiene la misma
sintaxis) con una salvedad: mientras que la cláusula “where” está ligada a un for each específico:
aquel al que pertenece, las “condiciones” están ligadas a todos los for eachs del Source en los que
tenga sentido aplicarlas.
En el listado de navegación del reporte se indican los for eachs a los que se aplica cada condición de
las especificadas en la sección “Conditions”.
For each
Print customer
endfor
Entonces al especificar las condiciones que se muestran en la transparencia, estaremos filtrando los
clientes, de acuerdo a las condiciones (es equivalente a tener las condiciones como “where”).
235
Condiciones
donde:
• “customer” es un print block que contiene los atributos CustomerId, CustomerName,
CountryName
• “invoice” es un print block que contiene los atributos InvoiceId, CustomerId, CustomerName,
InvoiceDate, InvoiceTotal
• “product” es un print block que contiene los atributos ProductId, ProductDescription, ProductStock
Si el reporte anterior tiene definidas las condiciones mostradas arriba, el reporte será equivalente a
uno sin “condiciones” y con el Source que se muestra a la derecha.
Observemos que en este caso las condiciones se traducen en cláusulas where pero que se aplican
solo a los for eachs para los que tiene sentido aplicarlas. En la tabla extendida del último for each
no se trabaja con nombre de cliente, ni con identificador de factura. No tiene sentido aplicar las
condiciones en este for each ya que no existe ninguna relación.
En el primero, solo tiene sentido aplicar la que involucra a CustomerName y no la otra.
Observación
Los atributos involucrados en las “condiciones” no participarán en la determinación de las tablas
base de los for eachs del Source (a diferencia de los filtros que se especifican mediante cláusulas
where).
Es decir, las tablas base de los for eachs que aparezcan en el Source se determinan sin mirar las
“condiciones”. Una vez determinadas, recién en ese momento las condiciones son examinadas para
determinar a cuáles for eachs se aplicarán y a cuáles no.
Lo mismo ocurre con los atributos que se reciban como parámetro. Aplicarán como filtro global por
igualdad para los for eachs en los que tenga sentido, pero no participarán en la determinación de
las tablas base.
236
Filtros en la navegación
a. cláusulas where
b. Condiciones
c. parm( att, ..., att )
1. Las cláusulas where aplican exclusivamente al for each en el que se encuentran, mientras que los
filtros especificados como condiciones o los que quedan determinados por los atributos en la regla
parm son globales, es decir, aplicarán a todos los for eachs del Source en los que tenga sentido
aplicarlos.
2. Los filtros que quedan determinados al recibir en atributos en la regla parm son filtros por
igualdad, es decir, para los for eachs en los que tenga sentido aplicarlos, se instanciarán únicamente
los registros que tengan el mismo valor que el recibido por parámetro. En cambio, los filtros
especificados en las cláusulas where de un for each o en las condiciones pueden ser expresiones
booleanas cualesquiera, incluso compuestas.
3. Mientras que los atributos que aparecen en las cláusulas where participan en la determinación de la
tabla base del for each donde se encuentran, los que aparecen en las condiciones o en la regla parm no
lo hacen. Recién entran en juego LUEGO de determinadas las tablas base de los for eachs del Source.
237
PROCEDIMIENTOS
Aquí nos abocaremos a profundizar en los comandos que son específicos para este tipo de objetos,
es decir, aquellos que tienen que ver con la actualización de la base de datos.
Dijimos que los procedimientos eran un “superset” de los reportes, en el entendido de que todo lo
que se realiza con un reporte puede realizarse con un procedimiento, pero en cambio no se cumple
el recíproco, es decir, no todo lo que se realiza con un procedimiento puede realizarse con un
reporte1.
En este capítulo estudiaremos ese “plus” que es lo que diferencia a ambos tipos de objetos y que
viene dado por la posibilidad que tienen los procedimientos de actualizar la base de datos.
Estudiaremos entonces cómo se dan las altas, bajas y modificaciones en la base de datos utilizando
procedimientos.
----------------------------------------------------------------------------------------------------------
1 A menos que se utilicen business components para hacer actualizaciones a la base de datos,
238
Actualización
ProductId* Reglas:
ProductDescription Parm( &Inf );
ProductStock
(ProductDate* Source:
ProductPrice) for each
where ProductDate = &Today
ProductPrice = ProductPrice*
(1+&inf/100)
endfor
Para actualizar uno o varios atributos de una tabla se utiliza el comando for each, y dentro del
mismo el comando de asignación.
Supongamos que queremos tener un proceso batch que actualice para todos los productos
almacenados que tengan el precio fijado para el día de hoy, el atributo ProductPrice para adecuarlo
según el porcentaje de inflación.
La tabla base del for each será PRODUCTLINE y estamos asignando valor a un atributo de la misma,
para todos los registros.
Se pueden actualizar varios atributos dentro del mismo for each, pudiendo éstos pertenecer tanto a
la propia tabla base como a la tabla extendida.
239
Actualización
• Salvo:
• los que forman parte de la clave primaria de la tabla
base del for each.
• los que forman parte del índice por el que se está
accediendo a dicha tabla.
A* B*
B E
C F
D
Y en el Source de un procedimiento hacemos:
for each
C = &C
E = &E
D = &D
endfor
Aquí la tabla base del for each será claramente la de clave primaria A y dentro del for each estamos
actualizando tanto atributos de la propia tabla base como de la extendida.
Al final de cada iteración del for each se actualiza el registro de la tabla base y el/los registro/s de
la extendida que deban ser actualizados.
Es decir, la actualización no ocurre ni bien se encuentra un comando de asignación dentro del for
each, sino luego de que se encuentran todos, para cada instancia de la tabla base, es decir, cuando
se llega al endfor para cada iteración.
240
Actualización
Como vimos antes, no podemos actualizar dentro del comando for each atributos de la clave
primaria. Sin embargo podríamos querer actualizar un atributo que sin ser clave primaria, está
definido como clave candidata (mediante un índice unique).
Si el atributo es clave candidata, debe controlarse que no se dupliquen sus valores, por lo que de
encontrarse duplicado el registro en este sentido, no se permitirá hacer la actualización.
Si se desea tomar una acción en caso de que esto ocurra, el comando for each agrega la cláusula
when duplicate. Solo tiene sentido si existe alguna clave candidata para ese for each.
241
Eliminación
• Comando Delete
• Debe ir dentro de un for each
• Elimina el registro de la tabla base en el que se
esté posicionado
• Se ejecuta ni bien se encuentra el comando (y no
en el endfor)
• Ejemplo:
for each Borra todos los
defined by InvoiceDate registros de la
tabla base:
Delete
INVOICE
endfor
Para eliminar datos se utiliza el comando Delete dentro del comando for each.
El comando Delete elimina el registro en el que se está posicionado en un momento dado. Es por
ello que no puede aparecer “suelto” dentro del Source. Debe colocarse dentro de un comando for
each, cuya tabla base sea la tabla de la que se quieren eliminar registros. Solo se eliminan los
registros de la tabla base, no de la extendida.
Si deseamos eliminar todas las facturas anteriores a una fecha dada, podemos programar un
procedimiento:
for each
where InvoiceDate <=&date
for each
defined by InvoiceLineQuantity
DELETE //se eliminan las líneas
endfor
DELETE //luego de eliminar las líneas se elimina el cabezal
endfor
242
Inserción de registros
Supongamos que queremos implementar un procedimiento que haga lo siguiente: para el producto
cuyo código es recibido por parámetro, dé de alta un nuevo precio (también recibido por parámetro)
en su lista de precios, para la fecha correspondiente al día en que se ejecuta el procedimiento.
El procedimiento debe crear un nuevo registro en la tabla PRODUCTLINE, que está compuesta por
los atributos ProductId, ProductDate y ProductPrice, siendo su clave primaria una compuesta,
conformada por ProductId y ProductDate.
Para ello se utiliza el comando new que escribimos arriba. Observemos que dentro del mismo
aparecen comandos de asignación, donde se le da valor a los atributos de la tabla en la que se
quiere insertar el registro.
¿Cómo entiende GeneXus que la tabla en la que queremos insertar el registro es PRODUCTLINE, si
no la mencionamos?
Cada vez que GeneXus encuentra un new, debe determinar la tabla en la que se realizará la
inserción (tabla base del new).
Esta tabla es determinada a partir de los atributos que aparecen dentro del comando new, del
lado izquierdo en una asignación.
En el ejemplo, son tres los atributos que aparecen dentro del new del lado izquierdo de un comando
de asignación.
243
Inserción de registros
Solo se insertará uno, pues el comando new realiza el control de duplicados. Es decir, al
intentar insertar un nuevo registro, se controla previamente que ya no exista uno en la tabla con el
mismo valor en la clave primaria que el que se está intentando insertar. De existir claves candidatas
(definidas mediante índices unique) también se controlarán.
El comando new cuenta con una cláusula opcional: la cláusula when duplicate. Con ella se
programa la acción a realizar en caso de encontrarse duplicado el registro. Por ejemplo,
supongamos que en caso de que el producto ya tenga en su lista de precios una entrada
correspondiente a la fecha de hoy, entonces en tal caso queremos cambiar ese precio.
Aquí estamos actualizando el valor del atributo ProductPrice en caso que el registro se encuentre
duplicado. Es decir, si ya existe un registro en la tabla con los valores de &ProductId y &Today en
su clave primaria, entonces para ese registro se actualiza el precio.
Observemos que para realizar la actualización del atributo, la asignación debe estar dentro de un
comando for each. Si no colocamos el for each no se realizará la actualización del precio para ese
registro, es decir, es como si no se hubiera incluido cláusula when duplicate.
Como hemos visto, para actualizar la base de datos se emplea el comando for each, y por tanto
aquí simplemente se uniformiza el comportamiento, de manera tal que siempre que se pretenda
actualizar la base de datos vía comando, se hará con un for each (a excepción del uso de business
components, que veremos más adelante en el curso).
244
Inserción de registros
new
[Defined by att1,…, attN]
bloque_asignaciones1
[when duplicate
for each
bloque_asignaciones2
endfor]
endnew
La cláusula Defined By opcional, se incorpora a los mismos efectos que lo hacía para el comando
for each: ayudar a determinar la tabla base.
El comando new realiza un control de duplicados, de manera tal que no se permitirá insertar un
registro que ya exista en la tabla.
La cláusula when duplicate del comando permite programar la acción en caso de que el registro
ya exista en la tabla base (tanto por clave primaria como por clave candidata).
Normalmente, de ocurrir lo anterior, se quiere actualizar algunos de los atributos de dicho registro.
Para ello, en bloque_asignaciones2 se realizan tales asignaciones, pero como lo que se hace es
actualizar un registro (y no insertar uno nuevo), estas asignaciones aparecen rodeadas de “for each
– endfor”, pues como hemos visto, las actualizaciones solo pueden realizarse dentro de un for each.
De no especificarse cláusula when duplicate para un new, si el registro que quiere insertarse se
encuentra duplicado no se realizará acción alguna y la ejecución continuará en el comando
siguiente. Es decir, como no puede insertar el registro porque ya existe uno, no hace nada y sigue
adelante, con el próximo comando.
245
Inserción de registros
La tabla física en la que se insertará el registro del new (tabla base del new) se determina a partir
de los atributos que aparecen en bloque_asignaciones1 del lado izquierdo del comando de
asignación. Al igual que en el comando for each, se incluye la cláusula Defined by para agregar
más elementos que permitan determinar la tabla base. Es decir, la tabla base del new será aquella
en la que se encuentren físicamente almacenados los atributos att1,…, attN que figuran en la
cláusula Defined by, junto con los que aparezcan en el bloque de asignaciones, del lado izquierdo.
Observar que marcamos en negrita “físicamente almacenados” y esto introduce una diferencia
importante con respecto al comando for each: aquí, tanto los atributos del Defined by como los que
se encuentren en asignaciones del lado izquierdo, deberán pertenecer a la tabla base (no a la
extendida). Es más restrictivo, puesto que con el comando new estamos insertando un único
registro en una sola tabla de la base de datos.
GeneXus buscará una tabla física que contenga a todos estos atributos. De no existir tal tabla, al
especificar el procedimiento se desplegará un error en el listado de navegación informando de esta
situación y el objeto no será generado.
246
Inserción de registros
El ejemplo pretende dejar claro a qué nos referimos con tabla física, en contraposición a tabla
extendida.
Mientras que en el caso de la determinación de la tabla base del for each los atributos debían
pertenecer a una misma tabla extendida, en el New es más restrictivo: deben pertenecer a una
misma tabla física.
247
Inserción de registros
Una vez que GeneXus determinó la tabla del new, los atributos pertenecientes a la misma y no
asignados explícitamente dentro de este comando tomarán como valor:
• Para aquellos que estén instanciados en el contexto donde se encuentra el new, tomarán los
valores correspondientes al contexto.
• Para los que no estén instanciados en el contexto, tomarán el valor nulo (o empty, dependiendo de
la propiedad “Empty As Null” en conjunción con la Nulls).
248
Inserción de registros
• New anidado a un For each, con igual tabla base.
“MedicalDoctor” “ConsultationHour”
MDoctorId* MDoctorId*
ConsultationDate* Date
MDoctorName
MDoctorAddress ConsultationShift* Shift
MDoctorPhone ConsultationOfficeNbr M.Doctor’s office
For each
Parm(&SourceMD, Where MDoctorId = &SourceMD
&replacementMD); Defined by ConsultationDate
new
MDoctorId = &replacementMD
endnew
Delete
endfor
Queremos escribir el Source de un procedimiento que sustituya a un médico por otro para las
consultas que el primero tenía asignadas.
Es decir, debemos recorrer la tabla CONSULTATIONHOUR que es la que representa las consultas
asignadas a cada médico en una fecha y turno determinado, filtrando por el médico que debemos
sustituir, y luego reemplazar a ese médico por el sustituto para esos registros.
Haríamos una simple actualización de registro, a no ser por el hecho de que el atributo a sustituir es
parte de la clave primaria, por lo que no podemos modificarlo.
Deberemos, por tanto, crear un nuevo registro con el médico sustituto, y el resto de los atributos
tomarán los valores del registro original. Luego borraremos el registro original, así queda el nuevo
registro, con el médico sustituto.
Observemos que dado que el new está anidado al for each, en el contexto del new existirán todos los
atributos de la tabla extendida del for each. En este caso, estarán instanciados los atributos de
CONSULTATIONHOUR y los de MEDICALDOCTOR, para un registro en particular.
Es por ello que en el new solamente asignamos valor al atributo MDoctorId, pues los demás:
ConsultationDate, ConsultationShift, ConsultationOfficeNbr están instanciados y queremos que
conserven esos valores.
Sin embargo, aquí tenemos un problema. ¿Cuál elegirá GeneXus como tabla base del new?
249
Inserción de registros
• New anidado a un For each, con igual tabla base.
For each
Where MDoctorId = &SourceMD
Defined by ConsultationDate ¿Tabla base del new?
new
MDoctorId = &replacementMD MEDICALDOCTOR
endnew
Delete
endfor
For each
Where MDoctorId = &SourceMD
Defined by ConsultationDate
new
defined by ConsultationDate CONSULTATIONHOUR
MDoctorId = &replacementMD
endnew
Delete
endfor
En este ejemplo lo que pretendemos mostrar es el cuidado que hay que tener cuando queremos que
ciertos atributos de la tabla base del new tomen sus valores del contexto.
Es decir, primero tenemos que asegurarnos que por los atributos que figuren dentro del new, quede
determinada la tabla base que deseamos, para que a partir de allí, sí puedan quedar implícitos los
atributos del contexto.
250
Restricciones
Por ejemplo, al dar de alta por procedimiento una nueva factura, cuando se da de alta el cabezal no
se controla que el valor de CustomerId ingresado exista en la tabla CUSTOMER.
Un caso particular de ello son las fórmulas redundantes. Como en procedimientos no se actualizan,
hay que calcularlas y actualizarlas en forma manual.
Las fórmulas redundantes deben ser mantenidas explícitamente con los comandos de asignación.
Por ejemplo, si en la transacción "Invoice" tenemos definido el atributo InvoiceTotal como una
fórmula vertical redundante, siendo:
y por procedimiento damos de alta una o varias líneas, debemos tener en cuenta que no se
actualizará el valor almacenado de InvoiceTotal. Habrá que actualizarlo explícitamente en el
procedimiento, por cada línea dada de alta.
251
OBJETO WEB PANEL
252
Características
Los web panels son objetos GeneXus que permiten al usuario en tiempo de ejecución, realizar
interactivamente consultas a la base de datos a través de una pantalla.
El término “interactivamente” se refiere a que el usuario podrá ingresar en la pantalla de un web panel una y
otra vez distintos valores de filtros, y consultar a continuación los datos que concuerden con los mismos.
Además, sobre los datos consultados, el usuario podrá realizar distintas acciones, como veremos.
Los web panels no permiten la actualización de la base de datos, sino sólo su consulta1.
El objetivo primordial de este objeto GeneXus es la definición de consultas interactivas a la base de datos, sin
embargo se trata de un objeto muy flexible por lo que se presta para diversos usos.
--------------------------------------------------------------------------------------------------------------------
1 A menos que se utilicen en combinación con los business components (estudiados más adelante)
253
Elementos
Web Form: Cada web panel contiene un form Web, el cual debe ser diseñado por el analista agregándole
variables, atributos, así como otros controles, para que el usuario pueda interactuar con el mismo.
Reglas: Las reglas de un web panel permiten definir ciertos comportamientos puntuales de dicho objeto. Por
ejemplo, declarar qué parámetros recibe, definir qué variables no queremos que sean aceptadas en el form
sino utilizadas para desplegar información, etc.
Condiciones: Es para definir las condiciones que deben cumplir los datos a ser recuperados (filtros).
Eventos: Los web panels emplean la programación orientada a eventos. Este tipo de programación permite
definir código ocioso, que se activa en respuesta a ciertas acciones provocadas por el usuario o por el sistema.
En esta sección de un web panel es donde se define el código ocioso asociado a los eventos que pueden ocurrir
durante la ejecución del web panel.
Propiedades: Son características a ser configuradas para definir ciertos detalles referentes al comportamiento
general del web panel.
Ayuda: Permite la inclusión de texto de ayuda, que los usuarios podrán consultar en tiempo de ejecución del
web panel.
Documentación: Permite la inclusión de texto técnico como documentación para los desarrolladores.
254
Clasificación de web panels
Todo web panel tiene un form asociado, y en el mismo, contrariamente al comportamiento del form de una
transacción, los atributos que se incluyan serán de salida, y las variables que se incluyan serán de
entrada.
Es fácil de comprender que el objetivo del form de un web panel es exactamente el contrario al objetivo del
form de una transacción, ya que:
• a través del form de una transacción se ingresan los valores de los atributos en la base de datos.
• a través del form de un web panel, se consultan / recuperan los valores de los atributos de la base de datos.
Es por esto que los atributos son de entrada en las transacciones y de salida en los web panels.
Y en lo que respecta a las variables, las mismas son de salida en las transacciones y de entrada en los web
panels.
La siguiente clasificación describe los distintos usos posibles de los web panels:
· Web panel de entrada: le damos este nombre a un web panel que tiene la única función de aceptar valores
digitados por el usuario (esto significa que su form contendrá únicamente variables).
· Web panel de salida: le damos este nombre a un web panel que tiene la única función de mostrar
información (esto significa que su form contendrá únicamente atributos, pudiendo también contener variables
a las cuales se les haya cambiado el comportamiento por defecto de ser de entrada, definiéndolas de salida y
cargándoles valores explícitamente).
· Web panel mixto: le damos este nombre a un web panel que permite tanto ingresar valores como mostrar
información (en este caso su form contendrá tanto variables como atributos, o bien sólo variables, algunas con
el comportamiento por defecto de ser de entrada y otras definidas explícitamente de salida y cargándoles
valores).
Vale aclarar que esta clasificación es independiente de la herramienta; es decir, GeneXus internamente no
clasifica a los web panels.
255
Web panel de Entrada
Event Enter
Event Enter
RList.call( &InitialCustomerName, &FinalCustomerName )
endevent
Denominamos web panels de entrada a aquellos web panels cuya única función es que el usuario realice
ingresos de valores por medio de los mismos. Por lo tanto, sus forms contendrán solamente variables.
Por ejemplo, un web panel de entrada puede contener dos variables &InitialCustomerName y
&FinalCustomerName como se muestra arriba. En tiempo de ejecución, el usuario podrá ingresar valores en las
variables &InitialCustomerName y &FinalCustomerName dado que en los web panels las variables son por
defecto de entrada.
En el evento Enter del web panel (asociado al botón Confirm), se invocará a un reporte, al cual se le pasarán
por parámetro las variables &InitialCustomerName y &FinalCustomerName para que el reporte liste todos los
clientes cuyos nombres se encuentren en el rango solicitado:
Event Enter
RListCustomerRange.call( &InitialCustomerName, &FinalCustomerName )
EndEvent // Enter
De modo que la definición de este web panel de entrada es para que el usuario ingrese el rango de clientes
a listar, y al seleccionar el botón Confirm, se ejecute el reporte correspondiente.
256
Web panel de Salida
tabla base:
CUSTOMER
Regla: parm(in:CustomerId);
Denominamos web panels de salida a aquellos web panels cuya única función es exhibir datos.
Para que un web panel únicamente muestre datos, su form debe contener solamente atributos, ya que los
atributos en los forms de web panels son indefectiblemente de salida1. Otra posibilidad es incluir en el form
variables, pero habrá que cambiarles su comportamiento por defecto de ser de entrada, a ser de salida, y
cargarles valores explícitamente2.
El web panel mostrado arriba ha sido creado para exhibir los datos de un cliente. Se necesita invocarlo desde
otro objeto, pasándole por parámetro el código del cliente del cual se quiere mostrar la información.
Para resolver esto, una vez creado el web panel “View Customer Data”:
• se han agregado los atributos que se desean visualizar en su form
• se ha definido la regla: Parm(in: CustomerId); en la sección de reglas del objeto
Primeramente GeneXus observará que los atributos incluidos en el form pertenecen a las tablas CUSTOMER y
COUNTRY respectivamente. El siguiente diagrama de Bachman explicita la relación entre ambas tablas:
CUSTOMER COUNTRY
--------------------------------------------------------------------------------------------------------------------
1 Al contrario de lo que sucede con los atributos en las transacciones (salvo los inferidos o los que tienen regla
257
Teniendo en cuenta la relación entre las tablas involucradas, GeneXus descubrirá que deberá recorrer la tabla
CUSTOMER y acceder a la tabla COUNTRY por el concepto de tabla extendida. La tabla COUNTRY no podrá ser
elegida para ser recorrida porque su tabla extendida no incluye a la tabla CUSTOMER.
Así es que GeneXus determinará un for each asociado al web panel, en este caso con tabla base CUSTOMER;
nosotros no escribimos el for each, pero GeneXus lo infiere automáticamente.
A su vez, como en la regla parm definida en el web panel se recibe un atributo, el mismo actuará como filtro
por condición de igualdad. Es decir, que al ejecutarse la recorrida de la tabla CUSTOMER (accediendo a la tabla
COUNTRY para traer el nombre de país), se filtrará por el código de cliente recibido por parámetro.
Concluyendo, se recorrerá la tabla CUSTOMER, con condición de filtro por el cliente recibido en la regla parm y
se mostrarán los datos en la pantalla. El nombre del país del cliente (CountryName) se inferirá por el concepto
de tabla extendida y se mostrará también en la pantalla.
Decimos que este web panel tiene tabla base, y la misma es CUSTOMER. Esto significa que el web panel
tiene un for each implícito / automático asociado, cuya tabla base es CUSTOMER.
258
Web panel de Salida
tabla base:
Regla: parm(in:CustomerId);
INVOICE
Este web panel ha sido creado para mostrar las facturas de determinado cliente. Se necesita desde otro objeto,
invocar a éste, pasándole por parámetro el código del cliente del cual se quieren mostrar sus facturas.
Para resolver esto, una vez creado el web panel “View Customer Invoices”:
• se han agregado los atributos que deseamos visualizar en su form (utilizando el control grid para mostrar las
N facturas del cliente en cuestión)
• se ha definido la regla: Parm(in: CustomerId); en la sección de reglas del objeto
Este web panel, a diferencia del anterior no es plano, pero continúa siendo un web panel de salida, ya que lo
único que hace es mostrar datos de la base de datos, sin permitir que el usuario ingrese nada.
Cuando se incluye un grid en un form, se está indicando que se va a mostrar una cantidad indefinida de datos
(en este caso, facturas).
Dado que en este web panel hay involucrados atributos de las tablas CUSTOMER e INVOICE y que la relación
entre ambas tablas es:
INVOICE CUSTOMER
GeneXus determinará que recorrerá la tabla INVOICE y accederá a la tabla CUSTOMER por el concepto de tabla
extendida. La tabla CUSTOMER no podrá ser elegida para ser recorrida porque en su tabla extendida no se
encuentra la tabla INVOICE.
De modo que GeneXus determinará un for each implícito asociado al web panel, con tabla base INVOICE,
accediendo a la tabla CUSTOMER por el concepto de tabla extendida.
Como en la regla parm definida en el web panel, se recibe un atributo, el mismo actuará como filtro por
igualdad. Es decir, que al ejecutarse la recorrida a la tabla INVOICE accediendo a la tabla CUSTOMER, se
filtrará por el código de cliente recibido por parámetro.
Decimos que este web panel tiene tabla base, y la misma es INVOICE. Esto significa que el web panel tiene
un for each implícito/automático asociado, cuya tabla base es INVOICE.
259
Web panel Mixto: “Work With”
Las
variables
•Event Enter adquieren
el valor
•Evento Usuario digitado
luego de
presionar
algún
botón
Los web panels no tienen por qué ser sólo de entrada o sólo de salida. El web panel que se muestra arriba es de
entrada/salida (mixto), su form contiene tanto variables como atributos.
La funcionalidad de este web panel es cargar en el grid los datos de todos los clientes cuyos nombres cumplan
con la condición de filtro especificada. La idea es digitar sobre la variable &CustomerName el valor de filtro
deseado, y a continuación presionar el botón Search para que se ejecute la consulta en el servidor y el
resultado de la misma sea cargado en la página.
El evento asociado al botón Search puede ser el Evento Enter (evento del sistema) ó cualquier evento definido
por el usuario (volveremos sobre esto más adelante).
En el web panel del ejemplo tenemos un sólo grid, por lo cual ambas opciones serían equivalentes desde el
punto de vista lógico. Sin embargo es recomendable escribir las condiciones a nivel del grid ya que en un futuro
podrán agregarse más grids al web panel. Además teniendo las condiciones a nivel del grid se optimiza al
momento de la especificación (ya que en caso contrario, GeneXus deberá estudiar para cada grid si aplicar las
condiciones generales a ese grid particular o no).
Como los atributos involucrados en el web panel pertenecen algunos a la tabla CUSTOMER y otros a la tabla
COUNTRY, y en la tabla extendida de CUSTOMER está la tabla COUNTRY, GeneXus determinará que la tabla a
recorrer es CUSTOMER y que accederá por su extendida a la tabla COUNTRY para cargar el valor del atributo
CountryName. Es decir, GeneXus determinará un for each implícito asociado al web panel, con tabla base
CUSTOMER.
Las condiciones definidas antes (a nivel de grid) se incluirán en el for each implícito (como cláusulas where),
de modo tal que al ejecutarse la consulta, se recorrerá la tabla CUSTOMER, filtrando por dichas condiciones.
Es importante considerar que tanto en las condiciones globales del web panel, como en las condiciones locales a
un grid de un web panel, es posible utilizar la cláusula when al igual que cuando se definen filtros en los objetos
reportes y procedimientos.
260
Web panel ¿con tabla base?
Un web panel es con tabla base cuando de los atributos que aparecen, GeneXus puede determinar una tabla
de la base de datos a recorrer para, recuperando sus registros, mostrar la información que aparece en los
atributos del web panel.
De este modo, es como si hubiéramos escrito un for each para navegar esa tabla base y trabajar con algunos
atributos de la misma, y de la extendida.
Si en el Web Panel no aparecieran atributos, sino solo variables, evidentemente GeneXus no podrá
determinar una tabla a ser navegada. En este caso el web panel será sin tabla base.
261
Orden de los datos a recuperar
Para definir que una consulta se efectúe ordenando por ciertos atributos, y por ende que los datos extraídos de la
consulta se muestren ordenados con dicho criterio, se debe hacer clic con el botón derecho del mouse sobre el
grid, y seleccionar el ítem Order del menú pop up que se muestra arriba.
A continuación, se presentará el diálogo para que se ingresen los atributos por los que se desea ordenar.
Definir esto es equivalente a definir la cláusula order en el comando for each, y se aplica todo lo visto en dicho
tema: desde que para ordenar en forma descendente por un atributo se debe encerrar el atributo entre
paréntesis (), la creación de índices temporales cuando no exista un índice físico correspondiente a los atributos
de ordenamiento, así como la posibilidad de utilizar la cláusula when para condicionar la aplicación de ese order.
En nuestro web panel “Work With Customers” ordenamos los clientes que se listan en el grid por CustomerName.
El poder definir order para un grid permite entre otras cosas optimizar la consulta, cuando se establecen
condiciones de filtro. Así, si en las conditions generales y/o las del grid particular, siendo la tabla base CUSTOMER
establecemos los filtros, teniendo dos variables ingresadas por el usuario:
CustomerName >= &customerStartName;
CustomerName <= &customerEndName;
Entonces, de no especificar un orden por CustomerName, se deberá recorrer toda la tabla, de principio a fin,
para cargar los registros que cumplan con las condiciones. Especificando un order optimizamos la consulta.
Nota: Solamente si el form del web panel no tiene ningún grid (atributos sueltos), y se necesita definir un orden
específico para la consulta, se contará con la posibilidad de definir en la sección de reglas del web panel, la regla
de sintaxis: order(att1, att2, attN); siendo att1, att2, attN: la lista de atributos que define el orden de la
consulta.
262
Eventos en web panels
– Evento Start
– Evento Refresh
– Evento Load
– Evento Enter
– Eventos de Usuario
Dado que la programación de los Web Panels está dirigida por eventos, para poder programar adecuadamente
un objeto de este tipo es necesario conocer los eventos existentes y el momento y orden en que éstos se
disparan.
263
Evento Start
Event Start
&var.Visible = 0
&Update = LoadBitmap("images/edit.gif")
newControl.Link = Link(TCustomer)
endevent
En el ejemplo, tendremos 3 controles en el form: la variable de nombre var, la de nombre Update de tipo
Bitmap y un control de nombre newControl que puede ser, por ejemplo, un control imagen.
En el evento Start se le asigna a la propiedad Visible del control variable &var el valor 0, indicando que no
deberá verse en el form.
A su vez, a la variable de tipo bitmap, &Update, se le carga la imagen que contendrá, y al control que
suponemos imagen, newControl, se le define la propiedad Link, de manera tal que cuando el usuario haga
clic sobre el control, se invocará a la transacción Customer.
264
Evento Refresh
265
Evento Load
Cuando el web panel es con tabla base, al producirse el evento Refresh se accede a la base de datos, a esa
tabla base (la asociada al web panel), y se la recorre cargando los registros que cumplan las condiciones
(conditions del grid y generales). Ocurrirá en ese proceso un evento Load por cada registro en el que se
esté posicionado, inmediatamente antes de cargarlo. Esto nos permite realizar alguna operación que
requiera de ese registro (y de su extendida), antes de efectivamente cargarlo en el grid. Inmediatamente
luego de ejecutado el código asociado al evento Load, se cargará la línea del grid y se pasará el puntero al
siguiente registro de la tabla base, para realizar lo mismo (evento Load, carga de la línea). Este proceso se
repetirá hasta cargar todas las líneas del grid.
Si un web panel es sin tabla base, GeneXus no puede determinar automáticamente una tabla de la base de
datos a recorrer para mostrar la información que se presenta en el form. En este caso en el form solamente
aparecen variables (y no atributos) y también ocurrirán los eventos Refresh y Load, sólo que el evento Load
se ejecutará una única vez, dado que no se estará posicionado en ningún registro de ninguna tabla.
266
Evento Load
en web panel con tabla base
Luego del
evento Refresh
se ejecuta el
evento Load N
veces:
una vez por cada
registro de la
tabla base leído
para ser cargado
en la línea del
grid
Por cada registro leído en la consulta efectuada a la base de datos, se disparará el evento Load (ejecutándose
el código incluido en el mismo, y cargándose a continuación una línea en el grid con los datos asociados al
registro).
267
Evento Load
en web panel con tabla base: ejemplo
Si en el grid que muestra los clientes que cumplen con las condiciones de filtro, quisiéramos agregar una
columna al final, que marque que el cliente es moroso (deudor) si su saldo es mayor a $10.000, es decir, que
en ejecución sobresalga su condición de moroso apareciendo un literal DEBTOR en ese caso, alcanza con
agregar una variable &type al grid, de tipo Character(10) y cargarla en el evento Load del web panel como se
muestra arriba.
Para cada registro de la tabla base CUSTOMER que se vaya a cargar como línea en el grid, se ejecutará el
código del evento Load, cargándose en la columna &type el valor DEBTOR únicamente si el saldo de ese
cliente que va a listarse supera los $10.000.
Luego, si para cada cliente del grid además de mostrar su nombre, país, sexo, saldo y tipo, queremos
mostrar la cantidad de facturas que se le han emitido, alcanza con agregar una variable &quantity al grid, e
incluir en el código del evento Load, el for each para contar esas facturas.
Observar que el for each definido en el evento Load estará anidado al for each implícito (el de la tabla base),
por lo que se efectuará un join, recorriéndose solamente las facturas de ese cliente, el que se está cargando.
268
Evento Load
en web panel sin tabla base
• En un web panel sin tabla base, el evento Load se ejecutará solamente una vez.
Evento Refresh
Evento Load
Que el web panel no tenga tabla base, significa que no tiene un for each implícito asociado; por lo tanto,
cuando se ejecute el evento Refresh, no comenzará a ejecutarse ninguna consulta; se ejecutará el código
asociado al evento Refresh, y a continuación se ejecutará el código asociado al evento Load, una única vez.
Aquí es donde tendremos que cargar el grid, consultando la base de datos con un for each explícito. A
continuación vemos el código de este evento.
269
Evento Load
en web panel sin tabla base: ejemplo
El objetivo del comando LOAD dentro del evento Load es cargar efectivamente una línea en el grid.
Una vez que se hayan asignado valores a todas las variables que sean necesarias, y se desee agregar la línea al
grid, deberá ejecutarse el comando LOAD.
Solamente se puede especificar el comando LOAD dentro del evento Load del grid de un web panel y en
ningún otro lado.
Event Load
for each
&CustomerId = CustomerId
&CustomerName = CustomerName
&CustomerGender = CustomerGender
&CustomerBalance = CustomerBalance
if CustomerBalance > 10000
&type = ‘DEBTOR’
else
&type = ‘’
endif
&quantity = 0
for each
defined by InvoiceDate
&quantity += 1
endfor
Load /* LUEGO DE HABER CARGADO TODAS LAS VARIABLES CON LOS VALORES
CORRESPONDIENTES A LA LÍNEA A SER CARGADA EN EL GRID, DEBEMOS INCLUIR EL COMANDO LOAD,
EL CUAL AGREGARÁ EFECTIVAMENTE LA LÍNEA AL
GRID. */
endfor
Endevent
Si en la codificación del evento Load definimos comandos For each y asignamos valores a las variables en las
iteraciones pero no incluimos el comando LOAD, en tiempo de ejecución estaremos asignando una y otra vez
valores a las variables, pero no se estarán agregado líneas en el grid (solamente quedará una línea en el grid
con los últimos valores cargados en las variables). Por esta razón es muy importante no olvidar escribir este
comando en el lugar apropiado.
270
Evento Enter
271
Eventos de usuario
• En tiempo de ejecución, el
evento de usuario ocurrirá
luego de que el usuario haga
clic sobre el control asociado al
mismo.
Casi todos los controles que aparecen en el form brindan la posibilidad de disparar un evento cuando el
usuario hace clic con el mouse sobre ellos (aparecen como hipervínculos en ejecución); se consigue de dos
maneras distintas:
Event nombreControl.click
…
Endevent
Con esta última alternativa no tendremos que definir un evento de usuario, sino que
estaremos programando el evento click del control.
272
Web panel "Work With Customer”
Acciones sobre
el cliente
seleccionado
Para que el web panel con el que venimos trabajando sea un verdadero “trabajar con” se le deben agregar
acciones a ser efectuadas sobre los clientes: la posibilidad de insertar un nuevo registro (nuevo cliente), el
modificar uno existente, o el eliminarlo (así como también poder simplemente “visualizarlo”).
Una forma de implementar esto es agregar los cuatro botones que aparecen arriba, en el form:
. un botón que ofrezca insertar un cliente (Insert)
. un botón que ofrezca modificar un cliente (Update)
. un botón que ofrezca eliminar un cliente (Delete)
. un botón que ofrezca visualizar los datos de un cliente (View)
Además debemos permitir la selección de una línea de la grilla para aplicarle alguna de las acciones
definidas en los botones del form. Para ello, accedemos a las propiedades de la grilla con botón derecho sobre el
control grid y configuramos la propiedad AllowSelection con el valor ‘True’ como muestra la figura. Al hacerlo
se nos habilitan tres propiedades más, que permiten especificar SelectionColor: el color que tendrá la línea
cuando el usuario la seleccione (haciendo clic con el mouse sobre la misma); AllowHovering: la posibilidad de
que cambie el color de las líneas cuando el usuario se desplaza con el mouse sobre ellas, y HoveringColor: el
color que tendrá una línea cuando el mouse pasa sobre ella. Estas funcionalidades se implementan con código
javaScript que se envía al Browser al ejecutar el Web Panel.
En la sección de eventos del web panel, definiremos el código asociado a estos botones. Lo veremos en la
página siguiente.
273
Eventos de usuario en el web panel
“Work With Customer”
Event ‘Insert’
Tcustomer.call(‘INS’, 0)
Endevent En las reglas de la
transacción “Customer”:
Event ‘Update’
Parm(&Mode, &CustomerId );
Tcustomer.call(‘UPD’, CustomerId)
Endevent
Variable del Variable de
sistema usuario
Event ‘Delete’
Tcustomer.call(‘DLT’, CustomerId) CustomerId = &CustomerId if not
Endevent &CustomerId.IsEmpty();
Event ‘View’
Tcustomer.call(‘DSP’, CustomerId)
Endevent
La variable &Mode es del sistema y su tipo es Character(3). Tiene la particularidad de “entender” 4 valores:
¿Cuál es el resultado de recibir por parámetros en una transacción el modo de ejecución y la clave primaria?
El permitir insertar, modificar o eliminar puntualmente una instancia y luego retornar al objeto llamador.
Es por ello que en todos los eventos definidos en el Web Panel “Work With Customer” estamos invocando a la
transacción “Customer”, pasándole dos valores por parámetro: un literal de 3 letras, que es el modo y el código
de cliente correspondiente a la línea del grid que fue seleccionada (por ello necesitamos habilitar la selección de
líneas del grid, mediante la propiedad AllowSelection que vimos antes).
parm(&Mode, &CustomerId);
Como se puede observar, no recibimos el código de cliente directamente en el atributo CustomerId, sino en una
variable. ¿Por qué?
Si declaráramos el atributo CustomerId en vez de una variable, el valor que se recibiera en él actuaría
automáticamente como filtro por igualdad. Sin embargo cuando invocamos a la transacción "Customer" con los
parámetros ‘INS’ y 0, el modo ‘INS’ indica que queremos que la transacción se ejecute en modo insert; y como
en dicho caso no tenemos que enviar el código de cliente para instanciarlo, completamos el segundo parámetro
con valor 0 (porque la cantidad –y el tipo de datos- de los parámetros enviados, debe coincidir con la cantidad –
y el tipo de datos- de los parámetros recibidos). De modo que el valor 0 es para completar el parámetro
simplemente, no para que se filtre por él tratando de instanciar un cliente de código 0.
En los otros 3 casos en que se invoca a la transacción "Customer" (con los parámetros ‘UPD’ y CustomerId ;
‘DLT’ y CustomerId ó ‘DSP’ y CustomerId respectivamente) sí se quiere filtrar por el valor del código de cliente
recibido; pero basta que haya un caso en el cual se invoque a la transacción y que no sirva filtrar por el valor
recibido, para que no sirva recibir el parámetro en el atributo y esa es la razón por la cuál se está recibiendo en
una variable. Si la clave primaria, CustomerId es autonumerada, entonces en ese caso sí podrá recibirse en
atributo.
Recuerde que a partir de la inclusión de la regla parm en un objeto, éste desaparece del Developer Menú,
debido a que desde el mismo no es posible el envío de parámetros.
274
Web panels - Funcionamiento
Es importante entender que en Internet, cuando el usuario accede a una página del servidor Web para
visualizarla, el Browser baja la página al cliente. Por lo tanto, no existe forma de detectar lo que realiza el
usuario: el servidor Web volverá a tener el control cuando se dispare el evento ENTER o algún evento de usuario
o click. En ese momento se envía (se somete, se hace un post) el resultado al servidor para continuar con su
procesamiento. Es decir, una vez que el objeto web finaliza la ejecución en el servidor, no queda en memoria.
Como consecuencia, la forma en que programamos este tipo de aplicaciones presenta algunas diferencias con
respecto a lo acostumbrado en ambientes no web.
Es por esta razón que es importante destacar el orden en que se disparan los eventos y el momento en que las
variables adquieren el valor ingresado por el usuario.
El orden de ejecución de los eventos en web panels es diferente si se trata de la primera llamada al mismo (GET)
o si se disparó algún evento de usuario, enter o click (POST).
275
GET: Orden de disparo de eventos
• Start
• Refresh
• Load
La primera vez que se ejecuta el web panel (se conoce también como el momento en que se hace el “GET”
de la página) los eventos que se disparan son los siguientes y en el siguiente orden:
1. Start
2. Refresh
3. Load
Luego de esto, cuando el usuario haga clic sobre un control que tenga asociado el evento Enter o uno de usuario
o click se ejecutará nuevamente el web panel y el orden de disparo de los eventos será diferente, como se indica
en la siguiente página.
276
POST: Orden de disparo de eventos
• Start
• Lectura de variables
en pantalla
• Evento Enter o de
usuario (submit)
• Refresh
• Load
En el resto de las ejecuciones del web panel, que ocurren cuando se presiona un botón, o se fuerza la ejecución
del evento asociado a una imagen, text block, etc. (haciendo clic sobre el control que tiene asociado el evento de
usuario o Enter o click) momento que se conoce también como el “POST” de la página, los eventos se dispararán
en el siguiente orden:
En el ejemplo no necesitamos codificar nada en el evento asociado al botón Search. Solo lo pusimos para poder
enviar al servidor la variable con el valor que ingresó el usuario y que la página se refresque cargando en el grid
los clientes que cumplan con el filtro que el usuario estableció mediante esa variable.
277
Web panels - Variables
• Ejemplo:
Event Load
&cont+=1
Event Enter
endevent
Event Refresh if &cont<5
&cont= 0 …
endevent endevent
Relacionado con el orden de disparo de los eventos, es importante destacar el momento en que las variables
adquieren los valores ingresados por el usuario: solamente lo harán después de presionar un botón1 (que es
cuando el servidor Web tiene el control del procesamiento).
Por ejemplo, cualquier Link especificado en el evento Start a otro web panel con una variable que se ingresa en el
form no va a tener ningún valor cuando se haga clic sobre el Link.
(Ej: control.Link = HWebPanelX.Link(&var). No se debe escribir esto en el start si la &var esta en el form, porque
al momento de armarse el link no se tiene el valor de la variable)
Si en un evento se usa una variable que se carga en otro evento, entonces esa variable debe estar presente en el
form. Si no está en el form, la variable no tendrá valor cuando se disparen los eventos que la consultan (esto es
por el “orden” en que ocurren los eventos).
Además, deberá estar en el form después del control en el que se carga. Por ejemplo, si la variable se carga en el
LOAD de un grid entonces la variable tiene que estar en pantalla después del grid.
Ejemplo: web panel con grid que lista las facturas existentes, y queremos contar la cantidad de facturas que se
listan en el grid. Para ello definimos una variable &cont que debemos incrementar cada vez que se carga una
nueva línea, es decir, en el evento Load del grid.
Para que la variable se cargue correctamente, deberá incluirse luego del grid, puesto que de lo contrario ya se
habrá dibujado en la página, antes de que pueda ser cargada por el evento Load.
Gracias a tenerla en el form, cuando el usuario presione el botón Confirm que consulta por el valor de la variable,
la misma podrá tener el valor cargado antes por el Load.
Recordemos que al presionar el botón Confirm se realizará un POST al servidor, y en él se dispararán Start,
lectura de variables de pantalla (aquí se leerá el valor de &cont que había sido cargado antes por el evento Load),
luego se disparará el evento Enter asociado al Confirm y dentro del mismo se consulta por el valor de la variable,
que gracias a que fue enviada al servidor por estar en el form, tiene el valor que se había cargado antes. Luego
se dispararán Refresh y Load.
Observemos que aquí, cuando se dispare el Load, se incrementará la variable, por lo que deberemos resetearla
antes de que se empiecen a cargar las líneas, porque de lo contrario mostrará el doble de las líneas que tenía
antes. ¿Dónde resetearla?
Event Refresh
&cont = 0
endevent
-----------------------------------------------------------------------------------------------------------------------
1 O hacer clic sobre algún control del form que tenga un evento de usuario o click o Enter asociado (ya sea con la
278
Ejemplo: Supongamos que tenemos un web panel donde en un sector del form se puede ingresar usuario y
contraseña para loguearse al sistema.
En el evento donde validamos el usuario y la contraseña (asociado a algún botón o text block), guardamos en
una variable el código de usuario para poder utilizarlo en otro evento. Esto nos permitiría, por ejemplo,
llamar a un objeto que permita visualizar los datos del usuario (por ejemplo un web panel de nombre
“CustomerData”, que recibirá por parámetro el identificador de cliente).
En consecuencia, primero que nada, deberíamos programar lo siguiente en el evento donde validamos el
usuario:
Event ‘Login’
For each
Where CustomerUser = &CustomerUser
If CustomerPassword = &CustomerPassword
&CustomerId = CustomerId
Mensaje.Caption = ‘Bienvenido/a ’+trim(CustomerName)
Else
Mensaje.Caption = ‘La contraseña ingresada no es correcta’
Endif
When none
Mensaje.Caption = ‘El usuario ingresado no existe’
Endfor
Endevent
donde Mensaje es el nombre de un text block que dinámicamente (con la propiedad Caption) cambia de
texto.
Obsérvese que tenemos una tabla CUSTOMER que contiene la info de usuario y password.
Para realizar la llamada al web panel Datos del Cliente (CustomerData), existen varias alternativas, una de
las cuáles sería agregar un botón o una imagen con un evento click asociado (o definir un evento de usuario
y asociárselo al control mediante la propiedad OnClickEvent), entonces el código seria el siguiente:
1. En la primera ejecución se disparan los eventos: Start, Refresh y Load y podemos ingresar el usuario y
password en las variables respectivas.
2. Cuando presionamos el botón o text block para validar el login, se dispara el evento Start, se leen las
variables anteriores que están en pantalla, se ejecuta el código del evento Login, donde se asigna a la
variable &CustomerId el código de cliente del usuario correspondiente. Luego ocurren Refresh y Load y la
página se despliega en el Browser.
3. Ahora, ya estando logueados, cuando presionamos la imagen o botón con el evento click asociado, se
dispara el evento Start, se leen las variables que están en pantalla, se ejecuta el evento click y ahí cuando
redireccionamos al Web Panel CustomerData, la variable &CustomerId no tiene valor alguno, ya que la misma
se perdió luego de haber finalizado la ejecución del Web Panel en el punto 2.
Es por esta razón que si queremos disponer del valor de la misma, deberíamos agregar la variable
&CustomerId en el form y la ocultaríamos usando la propiedad Visible (por ejemplo en el evento Start).
Event Start
&CustomerId.Visible = 0
Endevent
Entonces en este caso, cuando el Web Panel ejecute por segunda vez, se dispararán los eventos:
1. Start
2. Se leen las variables del form (en este momento se obtiene el valor de &CustomerId)
3. Se ejecuta el evento click, y por consiguiente se llama al Web Panel con el código de cliente correcto.
Esto es porque no existe un concepto de “memoria local” para los web objects, por lo cual, si en un evento se
usa una variable que se carga en otro evento, entonces esa variable debe estar presente en el form, de
manera que, aprovechando el orden de disparo de los eventos en el POST, se obtenga el valor de la variable.
279
Definición de columnas ocultas
en el grid de un web panel
• Al hacer botón derecho sobre el grid y seleccionar Columns:
Hay veces que por motivos de presentación, no se desea incluir ciertos atributos o variables como columnas
visibles de un grid, pero se necesita tener sus valores cargados en columnas ocultas.
¿Por qué motivo se puede necesitar definir una columna oculta en el grid de un web panel?
Cuando en un Web Panel se ejecuta el evento Refresh, se comienza a ejecutar la consulta a la base de datos; a
continuación por cada registro leído que cumpla con las condiciones de filtro definidas, se ejecuta el evento Load
y se cargan los datos de dicho registro, en el archivo temporal asociado al grid.
¿Qué datos de los registros se cargan en el archivo temporal? Es decir, ¿qué columnas contendrá el archivo
temporal? Una columna por cada atributo o variable mostrado en el grid, más una columna por cada atributo o
variable declarado en el grid como columna oculta.
A modo de ejemplo, si en un grid hay 2 columnas visibles con los atributos CustomerName y CustomerBalance y
ninguna columna oculta, el archivo temporal asociado al grid contendrá 2 columnas correspondientes a los
atributos CustomerName y CustomerBalance, respectivamente. Si además de esas 2 columnas visibles, se
declara el atributo CustomerId como columna no visible en el grid, el archivo temporal asociado contendrá 3
columnas correspondientes a los atributos CustomerName, CustomerBalance y CustomerId, respectivamente.
Si en el grid sólo incluimos 2 columnas visibles con los atributos CustomerName y CustomerBalance, en el caso
en que necesitemos en un evento de usuario conocer el valor del atributo CustomerId correspondiente al cliente
de cierta línea seleccionada (para escribir alguna sentencia utilizándolo), no lo tendremos. Para conocer en un
evento de usuario el valor del atributo CustomerId correspondiente a cierta línea seleccionada, tendremos que
incluirlo en el grid ya sea visible o no visible, pero debe estar presente.
Como ejemplo, pensemos en nuestro Web Panel “Work With Customers”: necesitábamos una vez que el usuario
seleccionaba una línea, y presionaba el botón “Update” llamar a la transacción “Customer” enviándole como
parámetro el CustomerId seleccionado. En este caso necesitamos tener el CustomerId en el archivo temporal, ya
sea que esté visible en el grid o no lo esté.
280
De modo que el motivo por el cual podemos necesitar incluir un atributo o variable como columna
oculta de un grid, es porque necesitemos conocer el valor de ese atributo o variable en un evento
de usuario, pero no deseemos mostrarlo.
Así como los eventos de usuario trabajan con los datos cargados en el archivo temporal asociado al
grid, las condiciones de filtro en cambio, trabajan sobre la tabla física consultada y su tabla
extendida; por lo tanto, al definir condiciones de filtro, se podrán referenciar atributos que
pertenezcan a la tabla física que se consulta y su tabla extendida, sin la necesidad de que dichos
atributos deban estar incluidos en el grid (ni visibles ni ocultos) ni en ninguna otra sección del web
panel.
Por ejemplo, piénsese en el ejemplo que ya presentamos antes:
Event Load
if CustomerBalance > 10000
&type = 'DEBTOR'
else
&type = ''
endif
endevent
Aquí surge la pregunta: como en este evento utilizamos el atributo CustomerBalance para poder
cargar adecuadamente la variable, ¿es necesario colocarlo oculto en el grid? La respuesta es no. En
el evento Load estamos posicionados en un registro de la tabla base. Tenemos a disposición todos
los atributos de esta tabla base y de la extendida, sin necesidad de cargarlos luego en el grid.
281
Comando FOR EACH LINE
GeneXus nos provee el comando For each line para recorrer las
líneas de un grid en un web panel:
282
Comando FOR EACH LINE
Ejemplo
Event ‘Delete’
for each line
if &dlt = ‘Y’
PDelCustomers.call(CustumerId)
endif
endfor
Endevent
La operativa que pretendemos ofrecer en el web panel DelCustomers presentado arriba es la siguiente: luego de
que el usuario haya ingresado un substring para filtrar los clientes y se haya cargado el grid con los clientes que
cumplan dicho filtro, el usuario podrá marcar (con un clic del mouse) qué líneas (clientes) desea eliminar.
En el ejemplo, hemos incluido en el grid del web panel “DelCustomers", una variable de nombre &dlt (definida
como check box), además de los atributos CustomerId, CustomerName, CountryId, CountryName y
CustomerAddress. De esta forma, el usuario seleccionará el check box en los clientes que desea eliminar. A su
vez, tendríamos que tener un botón "Delete" y en el código del evento asociado a dicho botón deberíamos
recorrer el grid y para cada línea seleccionada invocar a un procedimiento que haga la eliminación física de dicho
cliente.
A continuación incluimos el código del procedimiento DelCustomers que recibe en la regla parm el código del
cliente a eliminar (CustomerId).
Reglas:
Parm(CustomerId);
Source:
for each
defined by CustomerName
Delete //se elimina el cliente recibido como parámetro
Endfor
283
Variables en un grid
1. Para indicar que el valor de una variable en un grid no puede ser modificado, debemos configurar la propiedad
Read Only de la variable con valor ‘True’. Para ello, debemos hacer clic con el botón derecho del mouse
sobre el grid y seleccionar las columnas (Columns) de la grilla. Luego, habrá que posicionarse en la variable
que se desee definir como de sólo lectura, y editar sus propiedades (Properties). Por último, se debe
configurar la propiedad Read Only de la columna con valor True.
284
Diferentes tipos de grilla/grid
Estas grillas, agregan potencia al diseño de aplicaciones web, permitiendo al desarrollador mayor libertad a la
hora del diseño.
285
Grid estándar
Propiedades:
Establece si el grid se
cargará o no por
páginas (paginado).
Indica cantidad de
filas por página.
0 Æ todas (no habrá
paginado) Permite
selección de
línea del grid
Los grids permiten trabajar con datos repetitivos en web panels y transacciones con form HTML. Las columnas de
los grids pueden ser atributos, variables (incluyendo las de tipo bitmap), y siempre tendrán una primera fila que
corresponderá a los títulos de las columnas.
ControlName: Permite indicar el nombre del control. Siempre se le asigna un nombre por defecto.
Class: Clase (del tema asociado al objeto) asociada al control. La propiedad Class solo se encuentra disponible si el
control está en el form de un objeto que tiene un Tema asociado.
Dependiendo del valor de la propiedad “BackColorStyle”, estarán disponibles otras propiedades adicionales
relacionadas con la configuración de las líneas del grid.
Rows: Esta propiedad permite al usuario indicar la cantidad de registros que va a cargar en el grid. Aplica
únicamente a los grids que tienen tabla base. Si el valor de esta propiedad es 0, se despliegan tantas líneas como
registros resulten de la consulta asociada. El valor por defecto de esta propiedad es 0.
Collapsing:
• AllowCollapsing :True: Permite colapsar el grid en ejecución
• Collapsed :True: Arranca el grid colapsado.
Selection:
• AllowSelection: True: Especifica que es posible seleccionar una línea en la grilla.
• SelectionColor: Seleccionar el color deseado al marcar la fila
• AllowHovering: True: Marca la fila cuando el mouse se posiciona sobre la misma.
• HoveringColor: Seleccionar el color deseado
286
Grid Freestyle
El grid Freestyle permite al usuario definir el formato de los datos a desplegar de una forma menos
estructurada que el grid estándar.
El grid Freestyle es básicamente una tabla a la que se le pueden insertar los atributos/variables, text blocks,
imágenes, botones, web components, embedded pages, grids freestyle y/o grids que se van a mostrar
posteriormente en la pantalla. Este tipo de grid no posee títulos para las columnas y además permite tener más
de un tipo de control, atributo/variable en una misma celda, proporcionando de esta forma mayor libertad de
diseño. Cabe destacar que el grid Freestyle posee las mismas propiedades mencionadas anteriormente para el
grid estándar.
En este caso para poder visualizar las propiedades hay que seleccionar la tabla donde se encuentran los
atributos/variables.
En el ejemplo presentado arriba queremos mostrar alguna información de los clientes. El atributo CustomerPhoto
se ha incluido en la transacción “Customer” para almacenar la foto de cada cliente (es un atributo de tipo Blob).
Pero no queremos mostrar la información como lo haríamos en un grid estándar, con cada elemento de
información en una columna distinta del grid. Aquí queremos mostrar la foto y debajo el nombre del cliente.
El comportamiento de las variables dentro de un grid Freestyle es análogo al que presentan dentro de un grid
estándar, por lo tanto también quedan de ingreso si existe un For each line o For each line in <grid> dentro de
algún evento, o si se asocia un evento a cualquier control de la fila. Nuevamente este comportamiento puede
modificarse, agregando la regla noaccept o cambiando la propiedad Read Only.
287
Grid Freestyle
Propiedades
Para visualizar las propiedades de un grid Freestyle, hay que seleccionar la tabla del grid, presionar el botón
derecho del mouse y seleccionar la opción ‘Properties’.
Nuevamente, para interiorizarse de cada una de las propiedades configurables de un grid freestyle, sugerimos
acceder al Help de GeneXus. Aquí solo mencionaremos algunas como ejemplo:
Class: Permite modificar la clase de un control, ya sea en tiempo de diseño como en ejecución.
La clase debe pertenecer al tema asociado al objeto que contiene el control. La propiedad Class solo se encuentra
disponible si el control está en el form de un objeto que tiene un Tema asociado.
BackColorStyle: Permite asignar un estilo al grid. Los estilos disponibles son los mismos que para un grid
estándar (ver grid estándar)
Rows: Esta propiedad permite al usuario indicar la cantidad de registros que va a cargar en el grid. Ídem a grid
estándar.
Columns: Esta propiedad permite al usuario indicar cuántas columnas va a tener el Freestyle grid en ejecución.
Si se ingresa un valor distinto de 1, el Freestyle grid va a mostrar los registros en tantas columnas como se haya
especificado en la propiedad. Si el valor de esta propiedad es 0, se despliegan tantas columnas como registros
resulten de la consulta asociada. El valor por defecto de esta propiedad es 1. Esta es propia de este tipo de grids.
RecordCount: La propiedad RecordCount aplica únicamente a grids que tienen tabla base y retorna un número
mayor o igual a cero representando la cantidad de registros de la tabla base del grid que cumplen las condiciones
de selección. Puede retornar -1 si no existe navegación para la tabla base del grid.
PageCount: La propiedad PageCount devuelve la cantidad de páginas del grid en base a las propiedades Rows y
Columns del mismo. Al igual que la propiedad RecordCount, devuelve –1 si el grid no tiene tabla base. Para el
caso de un grid estándar, también existe esta propiedad dinámica, pero toma en cuenta solo la propiedad Rows.
288
Paginado de grids en Web panels
• Métodos:
• Firstpage
• Nextpage
• Previouspage
• Lastpage (tabla base)
• Gotopage (tabla base)
• Propiedades:
• RecordCount (tabla base)
• PageCount (tabla base)
Descripción
El paginado del grid aplica a grids comunes y freestyle cuya propiedad ‘Rows’ tenga un valor diferente de cero.
Existen algunas diferencias relacionadas con la paginación cuando un grid tiene tabla base o no.
Podemos agregar al web panel “Work With Customer” botones de navegación para el grid (se muestran arriba) y
eventos para realizar el paginado.
Métodos
A continuación se describen los métodos disponibles:
FirstPage:
El método FirstPage lleva al usuario al primer conjunto de registros devueltos.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
NextPage
El método NextPage lleva al usuario al siguiente conjunto de registros.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
2: Ya se encuentra en la última página
PreviousPage
El método PreviousPage lleva al usuario al conjunto anterior de registros.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
2: Ya se encuentra en la primera página
Lastpage
El método LastPage lleva al usuario al último conjunto de registros. Puede ser utilizado únicamente si el grid
tiene tabla base.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
3: El grid no tiene tabla base
289
GoToPage
El método GotoPage(PageNumber) permite acceder en forma directa a un determinado conjunto de registros.
Puede ser utilizado únicamente si el grid tiene tabla base.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
Propiedades
Cada grid dispone de las siguientes propiedades que son utilizadas en la paginación:
RecordCount
La propiedad RecordCount aplica únicamente a grids que tienen tabla base y retorna un número mayor o igual
a cero representando la cantidad de registros de la tabla base del grid que cumplen las condiciones de
selección. Puede retornar -1 si no existe navegación para la tabla base del grid.
PageCount
La propiedad PageCount devuelve la cantidad de páginas del grid en base a las propiedades Rows y Columns
del mismo. Al igual que la propiedad RecordCount, devuelve –1 si el grid no tiene tabla base.
Recomendamos estudiar las consideraciones de eficiencia relacionadas con el uso de estos métodos. Se
aconseja realizar un buen filtrado de datos del grid.
290
Reglas más utilizadas en Web Panels
A diferencia del objeto transacción, en el cual se programa su comportamiento mediante la definición de reglas,
en el objeto web panel la programación es dirigida por eventos.
Son pocas las reglas para web panels, y las mismas permiten definir comportamientos puntuales (hay algunas
reglas más además de las mencionadas, que se pueden consultar en el Help de GeneXus).
Noaccept(&variable);
En los web panels las variables que están en el form fuera de un control grid, ó que están dentro de un grid pero
hay algún evento donde se utiliza el comando For each line, o se le ha asociado evento de usuario o click a algún
atributo o variable del grid, son de entrada por defecto; es decir, el comportamiento por omisión es que en las
mismas pueden ingresarse valores.
Para definir que una variable se presente deshabilitada en un web panel, es decir, no permitiendo ingresos en la
misma, una opción es definir la regla:
noaccept(&variable);
La otra opción que tenemos en GeneXus para que una variable se presente deshabilitada en un web panel es
configurando la propiedad Read Only de la variable con valor ‘True’.
Ver sección Propiedades de la grilla.
Default(&variable, valor);
291
Conceptos fundamentales
292
Web Panel “con tabla base”
GeneXus busca la mínima tabla extendida que contenga a todos estos atributos, y la tabla base de dicha mínima
tabla extendida, será la tabla base del for each implícito (es decir, la tabla que navegará el for each), se llamará
tabla base del web panel.
Observar que GeneXus no toma en cuenta para determinar la tabla base de un web panel:
293
Web panel “sin tabla base”
Los web panels de entrada generalmente son web panels “sin tabla base” por el hecho de que suelen contener
solamente variables; entonces, por no contener atributos en ninguno de los 5 lugares tenidos en cuenta por
GeneXus para determinar la tabla base, son web panels “sin tabla base”.
Además del caso de los web panels de entrada, existen otros casos que ameritan la definición de web panels “sin
tabla base”.
En la próxima página vemos la resolución de una consulta con un web panel sin tabla base.
294
Web panel “sin tabla base”
Ejemplo
Event Load
For Each CustomerId
defined by InvoiceDate
&Customer = CustomerName
&Total =0
for Each
&Total +=InvoiceTotal
endfor
Load
endfor
EndEvent
Dado que no mencionamos atributos en ninguno de los 5 lugares tenidos en cuenta por GeneXus para determinar
la tabla base, se trata de un web panel “sin tabla base”.
Por tratarse de un web panel “sin tabla base”, el evento Load se ejecuta una sóla vez; y en el mismo codificamos
explícitamente los accesos con comando for each: cargamos valores en variables y para cargar líneas en el grid
utilizamos el comando LOAD.
Nota: Observar en este caso que todas las columnas definidas en el grid (variables: &Customer y &Total) son
exclusivamente de salida, manteniendo el comportamiento por defecto definido para las variables en un grid. Esto
se debe a que no se está utilizando el comando For each line en ninguno de los eventos del web panel, ni
tampoco hay definido un evento click asociado a un control del grid (ni OnClickEvent).
295
Comando LOAD
• Una vez que se haya asignado valores a todas las variables que
sean necesarias, y se desee agregar la línea al grid, deberá
ejecutarse el comando LOAD.
Si en la codificación del evento Load definimos comandos For each y asignamos valores a las variables en las
iteraciones pero no incluimos el comando LOAD, en tiempo de ejecución estaremos asignando una y otra vez
valores a las variables, pero no se estarán agregado líneas en el grid (solamente quedará una línea en el grid con
los últimos valores cargados en las variables).
296
Web panel “con tabla base”
Ejemplo
Event Load
&Total =0
For Each
&Total +=InvoiceTotal
endfor
EndEvent
¡Corte de control!
Al especificarse este web panel, GeneXus determinará que la tabla base del mismo es INVOICE, significando esto
que el web panel tiene un for each implícito asociado, con tabla base INVOICE.
Recordemos que en los web panels con tabla base, el evento Load se ejecuta N veces y es muy importante el
siguiente concepto:
Si en un web panel con tabla base, se incluye un comando for each en el evento Load, dicho for each
se anidará al for each implícito asociado al web panel. Es decir, el for each definido en el evento Load
no será un for each independiente.
De modo que como el web panel tiene tabla base INVOICE, en su evento Load definimos un for each con tabla
base INVOICE también, y definimos un order indicando un criterio de agrupación, hemos implementado en el
web panel con tabla base, un corte de control.
En cambio, si no hubiésemos puesto el atributo InvoiceDate en el grid, el web panel seguiría teniendo tabla base,
pero esta vez sería CUSTOMER. En el evento Load se definió un for each con tabla base INVOICE (ya que dentro
del comando for each solamente se encuentra el atributo InvoiceTotal). El for each del evento Load no será
un for each independiente, sino que se anidará al for each implícito asociado al web panel y
estaremos implementando un join.
GeneXus analizará: ¿existe algún atributo relación entre las tablas “CUSTOMER” e “INVOICE”? Sí, CustomerId.
Por lo tanto, GeneXus definirá el siguiente filtro automático en el for each con tabla base “INVOICE”:
INVOICE.CustomerId = CUSTOMER.CustomerId.
297
En resumen, cuando se ejecute el evento Refresh del web panel, comenzará a ejecutarse el for each implícito
asociado al web panel. Y por cada cliente recorrido, justo antes de cargarse una línea en el grid con el mismo,
se ejecutará el evento Load. Entonces, para ese cliente que se venía recorriendo, se ejecutará el código
definido en el evento Load (es decir, se recorrerán sus facturas, sumando el total facturado del cliente). Al
finalizar la ejecución del evento Load, se cargará la línea en el grid. Obsérvese que aquí no es necesario
especificar comando Load para realizar la carga: se hace automáticamente por el hecho de tener una tabla
asociada a este grid.
En los eventos de usuario sucede algo parecido: cuando se incluye un for each en un evento de usuario, el
mismo no es un for each independiente tampoco, sino que también se anidará al for each implícito asociado al
web panel. GeneXus considerará qué línea del grid está seleccionada al momento de ejecutar el evento de
usuario (recordar Allowselection = True), y para los datos de la misma ejecutará el evento. De modo que si
estando seleccionada determinada línea con un cliente, se ejecuta un evento de usuario, y el mismo tiene un
for each con tabla base “INVOICE”, se recorrerán las facturas del cliente de la línea seleccionada.
Teniendo los conceptos relacionados al objeto web panel bien claros, el analista GeneXus podrá optar si definir
un web panel “con tabla base” o “sin tabla base”.
298
Web panels “con N grids”
Grids paralelos
A diferencia de lo que sucedía para un web panel con un solo grid, en el caso de múltiples grids los atributos de
la parte fija del web panel no participan en la determinación de la tabla base de ninguno de ellos, pero deberán
pertenecer a la tabla extendida de alguno (para que sea posible inferir sus valores).
Los atributos utilizados en los eventos del web panel tampoco participan en la determinación de la tabla base de
ninguno de los grids. Los atributos que se incluyan en los eventos fuera de comandos for each, deberán
pertenecer a la tabla extendida de alguno de los grids (al igual que los de la parte fija).
299
Web panels “con N grids”
Grids paralelos - Ejemplos
Una tabla base por grid No busca ni establece relaciones entre ellas
Ejemplo:
Dado que el web panel “View Suppliers And Customers” tiene más de un grid en su form, GeneXus no
determinará una tabla base asociada al web panel, sino que determinará una tabla base para cada grid.
En este ejemplo, no hay definidos ni Order ni Conditions para ninguno de los grids, por lo tanto GeneXus sólo
tendrá en cuenta los atributos incluidos en cada grid para determinar sus tablas bases. La tabla base del grid que
se encuentra arriba en el form será: SUPPLIER y la tabla base del grid que se encuentra abajo en el form será:
CUSTOMER.
Es importante resaltar que GeneXus determina la tabla base de cada grid pero no busca ni establece
relaciones entre las mismas.
Al igual que en el web panel “View Suppliers And Customers”, en el web panel “View Customers And Invoices”,
GeneXus determinará la tabla base del primer grid (que será CUSTOMER) y la tabla base del segundo grid (que
será INVOICE), pero no analizará si hay atributos en común entre ellas, y por ende no definirá filtros automáticos.
Es decir que los grids tendrán asociadas navegaciones independientes o paralelas.
Si bien GeneXus no establece relaciones entre las tablas bases de los grids, el analista podrá definirlas
explícitamente. Primero estudiaremos los eventos en web panels con más de un grid, y a continuación veremos
cómo definir cargas relacionadas.
300
Web panels “con N grids”
Grids paralelos – Eventos y carga
• Un Refresh global
• No se relacionan las cargas
• Refresh independiente de grids.
• Evento Load de cada grid
• Secuencia de Carga:
Refresh
<Grid Control Name1>.Refresh
<Grid Control Name1>.Load N veces si GridControlName1 tiene
<Grid Control Name1>.Load
tabla base. Sino 1.
<Grid Control Name1>.Load
<Grid Control Name2>.Refresh
<Grid Control Name2>.Load N veces si GridControlName2 tiene
<Grid Control Name2>.Load
tabla base. Sino 1.
<Grid Control Name2>.Load
En web panels con más de un grid, existe un evento Refresh global y un evento Refresh particular para
cada grid. El evento Load, no existe global sino sólo a nivel de cada grid.
Los eventos Refresh y Load a nivel de grids, deben referenciar al grid usando la siguiente nomenclatura:
Por ejemplo, si en el web panel “View Suppliers And Customers”, los nombres de los grids son SuppliersGrd y
CustomersGrd respectivamente, para codificar los eventos Refresh y Load a nivel de los grids, la sintaxis deberá
ser:
Además de estos eventos a nivel de los grids, estará el evento Refresh global:
Event Refresh
....
EndEvent
La carga de los grids en un web panel se realiza para cada grid de forma independiente. Es decir, aún si los datos
que se muestran en ambos grids están relacionados, el especificador no relaciona las cargas.
La carga asociada a los grids de un web panel incluye el evento Refresh, o sea que la secuencia de carga de un
objeto con 2 grids es como se muestra arriba. La ejecución del método Refresh de cualquier grid solo se dispara
par refrescar dicho grid.
El orden en que se cargan los grids es como aparecen en el form: de arriba hacia abajo y de izquierda a derecha.
De esa manera, cada uno de los grids se cargará con los datos correspondientes.
301
Web panels “con N grids”
Grids paralelos - Comandos
– Event Grid1.Load
....
Load
Endevent
El comando LOAD mantiene la misma sintaxis en Web Panels con más de un grid. Se debe incluir al mismo para
hacer cargas de grids sin tabla base, dentro del evento Load de un grid.
302
Web panels “con N grids”
Grids paralelos - Cargas Relacionadas
SOLUCIÓN
303
Web panels “con N grids”
Múltiples grids - Cargas Relacionadas
En la solución que se presenta, ambos grids han sido definidos “con tabla base”, así que cada uno de ellos tiene
un for each ímplicito asociado:
• GrdCountries tiene un for each implícito asociado que navega la tabla COUNTRY.
• GrdCustomers tiene un for each implícito asociado que navega la tabla CUSTOMER.
Debemos habilitar la selección de línea por parte del usuario para el grid de países (propiedad AllowSelection del
grid GrdCountries). Cuando el usuario la seleccione, presionará el botón “View customers” para que se
desplieguen en el grid inferior todos los clientes del país seleccionado.
Para ello necesitamos guardar en una variable el país seleccionado, para que luego, cuando se haga la carga del
grid de los clientes, puedan éstos filtrarse mostrando solo los que correspondan al valor almacenado en esa
variable.
Es decir, en el evento asociado al botón, asignaremos a la variable &CountryId el valor del país actual, es decir,
del de la línea seleccionada por el usuario.
Recordemos que cuando se presione este botón, se disparará el evento Start, luego se leerán las variables del
form (en este caso no hay), luego se disparará el evento asociado al botón (allí se le dará valor a la variable) y
luego se dispararán el evento refresh general y a continuación los eventos refresh y load para cargar nuevamente
el grid de países, e inmediatamente el refresh y load para cargar los clientes (y aquí se aplicará el filtro asociado).
Observar que no necesitamos colocar la variable &CustomerId en el form, puesto que es cargada y utilizada en la
misma ejecución en el servidor, por lo que no pierde su valor.
Existen otras soluciones posibles para implementar la carga relacionada en web panels con múltiples grids que
veremos a continuación.
304
Solución Nro. 2: Grid Grdcountries “con tabla base” y GrdCustomers “sin tabla base”
En esta segunda solución, el grid GrdCountries ha sido definido “con tabla base”, así que tiene un for each
ímplicito asociado al mismo, que navega la tabla COUNTRY.
Debemos habilitar la selección de línea por parte del usuario sobre este grid, a través de la propiedad
AllowSelection.
Una vez que el usuario se posiciona en una línea y presiona el botón “View customers” se debe refrescar el
grid GrdCustomers con los clientes que pertenezcan a ese país.
Al igual que antes, en el evento asociado al botón (en nuestro caso es el enter), debemos darle valor a la
variable para que luego pueda ser utilizada para filtrar.
Event Enter
&CountryId = CountryId
endevent
El grid GrdCustomers fue definido “sin tabla base” asociada, por lo cual debemos programar a mano la carga
del grid en el Evento Load.
Event GrdCustomers.Load
For each
where CountryId=&CountryId
&CustomerId=CustomerId
&CustomerName = CustomerName
Load
endfor
Endevent
Como el grid de clientes (GrdCustomers) es “sin tabla base”; cada vez que se ejecute Refresh para el grid
GrdCustomers, a continuación se ejecutará su evento Load una sola vez. Por lo tanto, programamos en el
evento GrdCustomers.Load un for each con tabla base CUSTOMER, filtrando los clientes del país que
habíamos cargado en una variable, y agregamos explícitamente los clientes en el grid (asignando a las
variables &CustomerId y &CustomerName los valores de los atributos CustomerId y &CustomerName
respectivamente y ejecutando el comando Load a continuación para efectuar la carga de cada línea).
Recordemos que cuando el usuario habiendo seleccionado un país en el grid correspondiente, presione el
botón “View customers” se realizará un post al servidor, donde se ejecutará el evento Start, luego se leerán
las variables de pantalla, se ejecutará el código asociado al botón (evento Enter), donde la variable para
filtrar es cargada, y a continuación se dispara el evento Refresh general, y luego el Refresh seguido de N
ocurrencias del evento Load del grid de países (1 por cada país a ser cargado) y finalmente el evento Refresh
del grid de clientes, seguido de un evento Load para ese grid (dentro del cuál se cargan las líneas con el
comando Load).
305
Solución Nro. 3: Ambos grids “sin tabla base”
En esta tercera solución ambos grids han sido definidos “sin tabla base”, por lo cual el evento Load de cada
grid se ejecutará una sóla vez.
Event GrdCountries.Load
For each
&CountryId=CountryId
&CountryName=CountryName
Load
Endfor
Endevent
Event GrdCustomers.Load
For each
where CountryId=&CurrentCountryId
&CustomerId=CustomerId
&CustomerName = CustomerName
Load
endfor
Endevent
En el evento Load correspondiente al grid GrdCountries, programamos explícitamente con comando for each
y comando Load, que se carguen todos los países de la tabla COUNTRY.
Event Enter
¤tCountryId = &CountryId
Endevent
306
Web panels “con N grids”
Grids paralelos - Consideraciones
307
Web panels “con N grids”
Grids Anidados
ejecución
Los grids anidados consisten en un grid Freestyle al que se puede insertar dentro de una celda otro grid estándar
u otro Freestyle.
Por ejemplo, se quiere tener un web panel que muestre los clientes, pero indentados por país, como se muestra
en la imagen en ejecución arriba a la derecha.
Para ello se define un grid freestyle con el país y dentro de éste se inserta otro grid, en este caso estándar, con
los clientes.
Puede haber grids anidados de varios niveles y puede haber también paralelos. Puede decirse que se está
definiendo un árbol en donde cada nodo es un grid.
Cada grid puede ser un freestyle o un grid estándar, aunque si es estándar no puede tener ninguno anidado. Los
grids estándar sólo pueden ser hojas del árbol de anidación.
No se ahondará en el tema en el presente curso. Podrá hacerlo en el curso de Desarrollo de aplicaciones para
Internet con GeneXus.
308
Web panels
• Tipos de Web panels
• Component
• Master Page
• Web Page
• Propiedad Type
Los objetos web pueden ser definidos con tres tipos diferentes, configurable en la propiedad Type del objeto.
Para un web panel podrá tomar uno de los valores:
•Component: (transacción ó web panel, que a partir de aquí podrá ser incluido en otro web object)
•Master Page
•Web Page (es decir, el objeto será una transacción ó web panel tal como hemos trabajado hasta el momento)
El tipo de web panel ‘Component’ se analiza en detalle en el curso Desarrollo de Aplicaciones para Internet con
GeneXus.
309
Master Pages
HEADER
CONTENT
M ENU
En el ejemplo presentado arriba, hemos capturado una pantalla del sitio de Sony. Navegando entre las distintas
pantallas, nos hemos encontrado que todas ellas tienen un header común, así como un menú a la izquierda de
cada página. Ambas cosas pueden ser visualizadas en la pantalla capturada arriba. Lo que va variando a medida
que uno va navegando por las distintas páginas es lo que aparece a la derecha, y que es el contenido específico
de cada página.
¿Cómo implementar un sitio de estas características? Con el conocimiento que tenemos hasta el momento,
tendríamos que repetir la parte del Layout común a todas las páginas del sitio, así como el comportamiento
común, en cada web panel que implementa cada una de esas páginas. Eso introduce dos problemas
fundamentales: si hubiera que realizar alguna modificación a esas partes comunes a todas las páginas, habrá
que ir página por página a realizarlo. Y relacionado con esto, la posibilidad de olvidarnos de hacerlo en alguna
página, o introducir alguna variación en la misma, degradará la consistencia del sitio.
Las Master Pages proveen una forma de centralizar el layout y el comportamiento común en un solo objeto
y reutilizarlo en todo otro objeto sin tener que programar. Esto significa que la modificación de alguna parte del
layout o del comportamiento común es tan fácil como modificarla en un único objeto y ¡listo!.
Se creará un web panel categorizado como “Master Page” con todo lo que sea el Layout y comportamiento
común a todas las páginas del sitio, y en el mismo se dejará un espacio para cargar en cada oportunidad la
página que corresponda (el contenido variable del sitio). Las páginas web que implementan el contenido
variable, se implementan como Web Panels o Web Transactions comunes y corrientes (es decir de tipo “Web
Page”, ver página anterior), y se asocian a la Master Page, de manera que cada vez que se ejecuten, se carguen
con ese “contexto”.
310
Master Pages
Master Pages
Se crea entonces un objeto Web Panel y se le configura la propiedad Type con el valor ‘Master Page’. En una
misma base de conocimiento se pueden definir tantas Master Pages como se desee.
Una vez que exista al menos una Master Page en la base de conocimiento, puede ser referenciada desde
cualquier web panel o web transaction, en la propiedad MasterPage de estos objetos (que por defecto tiene el
valor “(none)”). El efecto de hacer esto será que al ejecutar estos objetos, se ejecutarán con la master page
asociada, es decir, se cargará la master page, y en el “hueco” es donde se cargará la página individual.
Se profundiza en Master Pages, Web Components, Embedded Pages, en el curso Desarrollo de Aplicaciones para
Internet con GeneXus.
311
PATTERNS
312
Patterns
http://wiki.gxtechnical.com/wiki/tiki-index.php?page=PatternsInstallation
313
Patterns
• WorkWith
• Bill Of Materials
• OAV
Catálogo:
http://wiki.gxtechnical.com/wiki/tiki-index.php?page=Business+Patterns+Catalog
Work With: Genera a partir de una transacción (o para todas aquellas transacciones que se
deseen), todos los objetos necesarios para tener una aplicación web.
Si el patrón Work With se aplicara también a la transacción “Product” obtendríamos todas estas
funcionalidades para dicha instancia también, así como para cada una de las transacciones que se
deseen.
Bill of materials: Este patrón permite generar a partir de una transacción, otra que representa la
relación compuesto – componente.
OAV: Objeto-Atributo-Valor; Este patrón genera a partir de una transacción otras dos
transacciones que permiten extender la original, con el objetivo de permitir definir atributos en
runtime.
Además, seguimos trabajando en la definición de nuevos patterns. Sugerimos ver catálogo con
lista de patterns, algunos de los cuales ya están implementados y otros sugeridos para una futura
implementación.
314
Patterns
Ejemplo de uso:
315
Patterns
Utilización de la herramienta
(mostramos ejemplo aplicando pattern WorkWith)
Paso 1
Cuando se trabaja con la herramienta Patterns con una KB por primera vez, se presenta un
diálogo cuyo título es “Workspace Configuration”. Este diálogo permite configurar algunas
opciones relacionadas a la KB, otras opciones relacionadas al pattern a ser aplicado, y otras
opciones relacionadas a operaciones de GeneXus (impactar, especificar, etc.) que pueden
ejecutarse utilizando la herramienta Patterns. En este punto inicial puede cerrarse este
diálogo si se desea, y posteriormente es posible ingresar al mismo, mediante el ítem Build
/ Configure GX Integration (ver paso 8).
316
Patterns
Paso 2
- Se despliega información de la KB
- Dado que muchos patterns usan TRNs, se muestra una lista de las
TRNs disponibles en el tab KB Explorer:
317
Patterns
Paso 3
318
Patterns
Paso 4
• Instance files: por cada situación o instancia a la cual se quiera aplicar el patrón, habrá
que crear un instance file con la información propia de esa instancia en particular (atributos
a mostrar, etc.). Cada instance file es en definitiva un archivo XML con los datos propios de
la instancia, y tal como se explica en la transparencia, los patterns suelen proveer una
funcionalidad para crear ‘instance files’ por defecto (que luego, de considerarse necesario, se
pueden modificar fácilmente).
319
Patterns
[Paso 5]
Cada ‘instance file’ es un archivo XML con estructura jerárquica, conteniendo cada uno de sus
nodos un conjunto de propiedades.
La herramienta patterns ofrece 2 editores para editar cada ‘instance file’ en el panel derecho:
el editor XML View y el editor Tree View.
El editor XML View permite editar los instance file directamente en su formato XML. Por su
parte el editor Tree View es mucho más amigable, sencillo de usar, y con interfaz en alto
nivel que provee mayor funcionalidad para ayudar en el proceso de edición. Por todo esto el
editor Tree View es el más usado y es el recomendado para usuarios no avanzados.
320
Patterns
Paso 6
Los ‘instance files’ que no se han salvado aún se visualizan con nombre: <TRN
Name.Pattern>*. Y una vez salvados se visualizan con el nombre: TRN Name.Pattern(por
ejemplo: Country.WorkWith).
Save All – Salva todos los ‘instance files’ (si ya existen, pregunta si reemplazar).
Es importante tener en cuenta que si se generan los ‘instance files’ por defecto nuevamente a
partir de las transacciones, serán sobrescritos.
321
Patterns
Paso 7
- Opciones: Se genera en
- Build / Apply Pattern KBPath\Templates\Import, un
- Build / Apply and Consolidate archivo <TRNName>.xpz.xml
por cada ‘instance file’
Mediante botón derecho también es posible ejecutar estas acciones: es decir, estando posicionado en
el tab Folder Explorer, luego de haber seleccionado los instance files (.workwith files), se pueden
ejecutar las opciones Apply Pattern o Apply and Consolidate.
Nota: También es posible seleccionar una TRN o grupo de TRNs estando posicionado en el tab KB
Explorer, y mediante botón derecho ejecutar la opción Generate, Apply and Consolidate. Pero es
importante entender que seleccionando esta opción, se generarán los instance files por defecto
nuevamente para las TRNs seleccionadas, y luego de ello, se generarán los archivos
<TRNName>.xpz.xml correspondientes y se consolidarán en la KB. Si se está seguro que los instance
files por defecto no necesitan ser editados, es posible seleccionar esta opción directamente.
A su vez es importante saber que si se selecciona la opción Apply and Consolidate se efectuarán
también las siguientes acciones:
322
Patterns
Paso 8
Para hacer más fácil todo el proceso, desde la herramienta Patterns, es posible ejecutar las acciones que
se realizan con GeneXus: Impactar el modelo, Especificar, Generar, Compilar y Ejecutar.
Model – Muestra los modelos definidos en la KB, y permite seleccionar uno de ellos (se requiere tener
los modelos creados y la creación de sus tablas hechas).
Apply and Consolidate – Al seleccionar la opción Apply and Consolidate, es posible ejecutar más
acciones además de la generación de objetos y consolidación. Este combo ofrece las siguientes
posibilidades:
323
Build Actions- Permite seleccionar qué objetos deben especificarse y generarse al seleccionar Specify
and Generate. Las opciones disponibles son:
- Build All
- Build Pending (updated since last specification)
− Specify Consolidated Objects
− Full Specification
− Check Specification
− Force Generation
Run Command – En esta opción se debe indicar la URL que se ejecutará si se seleccionó la opción
Impact, Specify, Compile, Run.
Se debe configurar:
Nota: Vale aclarar que “home” es el nombre de un web panel generado por el patrón WorkWith; el
mismo ofrece links a todos los web panels WorkWith generados (y la letra h que antecede a su nombre
en la invocación, corresponde al prefijo que se agrega a todo objeto web panel main o ejecutable que se
invoca).
324
Patterns
Resultado en ejecución
325
Patterns
• Mediante GeneXus
Modificando los objetos GeneXus generados (Desventaja:
en caso de querer volver a generarlos con Patterns, se
regeneran los objetos, perdiendo los cambios hechos a
los mismos)
326
Patterns
Propiedades configurables
Son muchas las propiedades que se ofrecen en los archivos de instancia correspondientes al pattern
WorkWith para personalizar el comportamiento de los objetos que se generarán. A continuación
describimos algunas de ellas.
El nodo Selection ofrece las propiedades relacionadas al web panel WorkWith que se generará para
la instancia. Sus sub-nodos son:
Modes
Este nodo permite definir en cuáles modos se ofrecerá invocar a la transacción. Las posibilidades y
sus valores por defecto son:
Insert: True
Update: True
Delete: True
Display: False
Export: True (exportación a planilla excel)
Para casa modo podrá especificarse una condición. Se proveen las siguientes propiedades para ese
propósito:
Insert Condition
Update Condition
Delete Condition
Display Condition
Si se define una condición asociada a un modo, la invocación para ese modo solo se habilitará si la
evaluación de la condición es verdadera (Ejemplo: CountryId=10).
327
Attributes
Este nodo permite definir cuáles atributos se desean mostrar en el grid (y para cada atributo, se
pueden personalizar varias propiedades).
Orders
Es posible ofrecer al usuario final varios órdenes posibles para ver el resultado de la consulta (es
decir, las líneas mostrando los datos en el grid). Utilizando el botón derecho del mouse se puede
definir un nuevo orden (su nombre y composición). Cada orden puede estar compuesto por varios
atributos (pudiendo indicar para cada un de ellos si se desea orden ascendente o descendente). Se
presentará un combobox en el web panel WorkWith ofreciendo todos los órdenes posibles de
seleccionar, para que el usuario final elija uno y los datos se presenten en el grid ordenados por el
mismo.
Filters
Permiten definir condiciones de filtro para que se muestren en el grid solo los registros que cumplan
con las mismas.
El nodo View por su parte, ofrece las propiedades relacionadas al web panel View que se generará
para la instancia. El web panel View muestra toda la información de un registro, que fue
seleccionado en el grid del web panel WorkWith (la información del registro es mostrada en una
solapa de un tab control, y además hay una solapa con un grid por cada tabla directamente
subordinada, para mostrar la información relacionada).
328
Observaciones
Al crear cada ‘instance file’ por default, podemos observar que las distintas propiedades del ‘instance file’
se inicializan con valores por default. Dichos valores por default para las propiedades, se especifican en
un archivo denominado NombrePattern.config (en nuestro caso WorkWith.config).
Si deseamos tener un archivo de configuración NombrePattern.config por cada KB, debemos copiar este
archivo al directorio Templates que se crea bajo la KB al usar la herramienta Patterns; así la herramienta
Patterns utilizará dicho archivo ubicado en la KB para inicializar las propiedades de los ‘instance files’ que
se creen. Si la herramienta Patterns no encuentra el directorio Templates con este archivo bajo la KB,
utilizará el archivo NombrePattern.Config (en nuestro ejemplo WorkWith.config) ubicado en el directorio
Patterns\WorkWith.
El WorkWith.Config permite configurar algunos aspectos generales que aplicarán a todos los objetos. Por
ejemplo, las master pages a ser utilizadas por los objetos web, los web components utilizados como
header y footer, etc..
Para editar este archivo de configuración, la herramienta Patterns ofrece el ítem: Tools/Change
Pattern Configuration.
Ejemplo:
• Do not update: La trn no será modificada (web form, reglas y eventos serán mantenidos)
• Only rules and events (default value): Solo las reglas y eventos son modificados, pero no el web
form.
• Apply WW Style: la primera vez que el pattern sea aplicado, el comportamiento será el mismo que si
se hubiese seleccionado el valor Create Default. A partir de la segunda vez, el header, footer y botones
del form web serán modificados, pero no la Data Area. Los eventos y reglas también serán modificados.
• Create default: el web form, reglas y eventos serán modificados.
329
Ejemplo: Work With desde GeneXus
En el capítulo donde estudiamos el objeto web panel, implementamos manualmente un web panel “Work
With Customer” donde agregamos variables al grid para dar un mensaje de cliente moroso si su saldo
superaba los $10.000 y para contar la cantidad de facturas que se le realizaron, respectivamente. Habíamos
asimismo programado el evento Load para lograr lo anterior.
Todo esto puede ser codificado automáticamente por el Pattern “Work With”, si ud. agrega en la lista de
atributos del archivo de instancia correspondiente, las dos variables anteriores, y asociadas a las mismas en
el “LoadCode” el código asociado, como puede ver en la siguiente página.
330
…desde Patterns
Se definen las variables &type y &quantity haciendo botón derecho sobre el nodo Attributes y luego,
posicionándose en &type, en la ventana de la derecha, en la opción LoadCode, se ingresa el código que se
quiere ejecutar para esa variable, cuando se cargue la línea.
331
Ejemplo: Work With desde Genexus
Acciones sobre
el cliente
seleccionado
Asimismo, en el web panel Work With Customer que habíamos implementado manualmente antes, agregamos
botones para poder realizar las acciones típicas de un Work With (alta, baja, modificación, y vista de registro).
Asimismo, tuvimos que modificar la transacción “Customer” para recibir como parámetro el modo y clave, como
se puede recordar en la siguiente página.
332
Ejemplo: “Work With Customer”
desde GeneXus
El Pattern Work With no implementa estas acciones de la misma manera. Esta herramienta crea un dominio
enumerado para los valores que puede tomar la variable &Mode y la implementación de las acciones en el web
panel Work With Customer que realiza es como se muestra en las páginas siguientes.
Pattern modificará la transacción “Customer” para agregar exactamente las mismas reglas que nosotros
escribimos en forma manual.
333
…desde Patterns
Event Start
NewControl.Link = Link(TCustomer, TrnMode.Insert, CustomerId.SetEmpty())
&Update = LoadBitmap(!"images/edit.gif")
Endevent
Event Load
&Update.Link = Link(TCustomer, TrnMode.Update, CustomerId)
Endevent
‘UPD’
Aquí podemos ver otra forma de selección de una línea del grid. En el caso anterior utilizamos la propiedad
AllowSelection del grid para habilitar la selección de una línea en ejecución por parte del usuario.
Otra opción es la que implementa el Pattern “Work With” mediante la utilización de variables (en este caso de
tipo bitmap, &update y &delete, cargadas con la imagen correspondiente en el evento Start) a las que en el
evento Load del grid se les asigna la propiedad Link para determinar el identificador de cliente que se le
envía a la transacción Customer en cada línea cargada (en el cuadro de arriba solo se incluyó el código para
cargar la variable &update, pues para la variable &delete es análogo).
Nota: Obsérvese que delante del path relativo a la imagen aparece el símbolo “!”. No le dé importancia. Se
utiliza para la herramienta de Traducción (Application Localization), para evitar que el literal que le siga sea
traducido.
Como ya mencionamos la herramienta Patterns define el tipo enumerado TrnMode y es por ello que no
aparece ‘UPD’ directamente sino su descripción TrnMode.Update.
334
USO Y RECOMENDACIONES EN EL
USO DE SUBTIPOS
335
Definición de subtipos
336
Casos de uso de subtipos
• Algunos casos:
• Múltiples referencias
• Especialización de un nivel (relación 1-1)
• Subtipos recursivos
• Evitar controles de integridad referencial
337
A. Múltiples referencias
Realidad a representar/diseñar:
en cada reserva hay dos ciudades involucradas, las cuales cumplen roles diferentes.
El rol de una de las ciudades es el de ser la “ciudad de partida” (ciudad origen) y el rol de la otra es
el de “ciudad de arribo” (ciudad destino).
El dominio de ambas ciudades es el mismo, el de la tabla CITY.
La forma de representar que tanto el “origen” como el “destino” son ciudades de la tabla CITY, es
diseñando la transacción “Reservation” en la forma mencionada inicialmente en la transparencia.
Sin embargo, no es posible que en la estructura de una transacción figure el mismo atributo más de
una vez, pues no habría manera de identificarlos.
SOLUCIÓN: llamar a las dos ciudades de la reserva con diferentes nombres de atributos.
Cualquiera de las siguientes opciones es válida. Elegimos la 3era por mayor claridad.
Opción 1) ReservationCityFromId Å ciudad origen
CityId Å ciudad destino (mismo nombre que la PK de CITY)
Opción 2) CityId Å ciudad origen (mismo nombre que la PK de CITY)
ReservationCityToId Å ciudad destino
Opción 3) ReservationCityFromId Å ciudad origen
ReservationCityToId Å ciudad destino
El problema es que al poner por ejemplo ReservationCityFromId en lugar de CityId, GeneXus deja
de inferir que ReservationCityFromId corresponde al código de una ciudad de la tabla de CITY.
¿Cómo hacemos para relacionarlos, siendo que tienen diferente nombre de atributo? ver
respuesta en próxima hoja …
338
Para estos casos GeneXus provee los SUBTIPOS, que permiten definir que dos atributos que se
llaman diferente corresponden al mismo concepto.
Al establecer que un atributo es subtipo de otro, estamos estableciendo una dependencia funcional
entre ellos.
¾Los atributos que se encuentran en una relación subtipo-supertipo comparten la misma definición
(tipo de datos).
¾La tabla extendida que se obtiene con la definición del subtipo, es la misma que se obtendría si se
utilizara directamente el supertipo.
339
A. Múltiples referencias
IMPORTANTE:
Notar que este caso de múltiples referencias puede darse tanto:
• en la tabla base (*)
• como en la tabla extendida
(*) es el caso del ejemplo, en el que en la propia tabla (RESERVATION) hay más de una
referencia a otra tabla (CITY) y con valores diferentes.
RESUMIENDO:
siempre que desde una tabla se accede a otra que está en su tabla extendida por “más de un
camino” y con “valores diferentes”, es necesario definir SUBTIPOS, para poder llamarle
diferente a los atributos y haciéndose automáticamente todos los controles de integridad
referencial.
Una vez definidos los grupos de subtipos que sean necesarios, la forma de indicarle a GeneXus cuál
de los caminos debe tomar para acceder a la tabla destino, es mencionando los nombres de
atributos que correspondan. Ej.: mencionar ReservationCityFromName si lo que se necesita en ese
momento es el nombre de la ciudad origen, o mencionar ReservationCityToName si lo que se
necesita es el nombre de la ciudad destino.
340
A. Múltiples referencias
Nombre de c/grupo
de subtipos.
Con el grupo estamos indicando que los atributos pertenecientes al mismo grupo de subtipos, están
relacionados. Por ej., en nuestro ejemplo, GeneXus sabrá que el atributo ReservationCityToName
será inferido a través del atributo ReservationCityToId (y no a través del ReservationCityFromId).
Esto es por pertenecer ambos al mismo grupo (al de nombre ReservationCityTo).
Cuando el usuario digite un valor sobre ReservationCityToId, no solo se va a hacer
automáticamente el control de integridad referencial (que exista un ciudad con ese código en la
tabla CITY), sino que se va a inferir en ReservationCityToName el nombre correspondiente a ese
código de ciudad.
IMPORTANTE: Todo grupo de subtipos, debe contener un atributo o conjunto de atributos, cuyos
supertipos, juntos, correspondan a la clave primaria de una tabla del modelo. Estos atributos
aparecen en la ventana de edición del grupo con la categoría “class: Primary”.
Los demás atributos del grupo deberán ser de tipo “Inferred”, es decir, deberán poder inferirse a
través de esa clave.
En caso contrario estará mal definido el grupo.
341
A. Múltiples referencias en la tabla extendida
CUSTOMER
SALE COUNTRY
SELLER
Otro camino desde SALE a COUNTRY: a través del País del vendedor (CountryId)
Si quisiéramos por ejemplo listar las ventas (SALE), y de c/u de ellas mostrar los datos del cliente
(nombre, país, etc.) y del vendedor (nombre, país, etc.):
• necesitamos un for each con tabla base SALE y acceder a través de su extendida a las
tablas CUSTOMER, SELLER y COUNTRY para listar los atributos secundarios del cliente,
vendedor y país respectivamente.
Problema:
Los atributos de nombre CountryId, CountryName y todos los de la tabla
extendida de COUNTRY pertenecen a la tabla extendida de SALE por dos caminos
diferentes: 1) a través del país del cliente y 2) a través del país del vendedor.
Solución:
Debemos diferenciarlos, llamarlos con diferente nombre de atributo pero
queriendo que se sigan representando todas las relaciones y haciéndose
automáticamente todos los controles de integridad referencial.
342
A. Múltiples referencias en la tabla extendida
- Solución -
rId
sto
me CUSTOMER
al eCu
S
SALE COUNTRY
Sal
eS e
ller
Id
SELLER
Cuando queremos el país del vendedor de la venta: SaleSellerCustomerName
Una vez definidos los dos grupos de subtipos que se muestran en la figura, y haciendo el cambio
correspondiente en la estructura de la transacción Sale, queda resuelta la ambigüedad en el modelo
de datos!
Atributos almacenados
en la tabla SALE
Atributos inferidos
343
A. Múltiples referencias en la tabla extendida
- Solución -
SaleCustomerCountryId SaleSellerCountryId
Problema resuelto!
Una vez definidos los subtipos, tenemos que recordar usar el nombre de atributo que corresponda a
lo que queremos acceder. Por ejemplo, en todos aquellos objetos GeneXus en los cuales queramos
acceder al código o al nombre del país del cliente de la venta debemos usar los atributos
SaleCustomerCountryId y SaleCustomerCountryName respectivamente.
344
B. Especialización de atributos
Ej.: Sistema para una Universidad …
Sistema Sistema
Teachers Students
Generalmente es utilizada, cuando un objeto del negocio comparte todas las características de otro
objeto, pero agrega algunas más. La diferencia puede estar tanto en las propiedades, como en el
comportamiento que tendrá.
Estamos frente a un caso en el que los roles y el tratamiento de las entidades de la categorización
están claramente diferenciados.
Tanto los estudiantes como los docentes comparten información común (ambos tienen un nombre,
una dirección, etc) pero también tienen información que difiere, que es propia de c/u de ellos.
Para representar esta realidad, se crean las tres transacciones: “Person”, “Teacher” y “Student”.
En la transacción “Person” figura la información común. Para representar que tanto los estudiantes
como los docentes son personas, se utilizan los subtipos.
Cada vez que se inserte un registro en la tabla TEACHER a través de su transacción, se realizará el
chequeo de integridad referencial contra “Person”. Asimismo, cada vez que se intente eliminar un
registro de “Person”, se verificará primeramente que no exista ningún registro en la tabla TEACHER
(ni en STUDENT) con el mismo valor en la clave primaria.
345
B. Especialización de atributos
Transacciones:
“Person” “Teacher” “Student”
PersonId* TeacherId* StudentId*
PersonName TeacherName StudentName
PersonAddress TeacherAddress StudentAddress
TeacherSalary StudentAverage
La transacción “Teacher” tiene asociada una tabla que contendrá físicamente sólo dos atributos:
TeacherId y TeacherSalary.
Al ser TeacherId identificador de la transacción, será la clave primaria de la tabla asociada. Además,
al ser un subtipo de PersonId, será una clave foránea a la tabla PERSON. Por lo tanto, se harán los
chequeos de integridad referencial correspondientes.
346
C. Subtipos recursivos
• Ejemplo: Employee-Manager
Tabla EMPLOYEE
EmployeeId*
EmployeeName
EmployeeIsManagerFlag
EmployeeManagerId
FK
Error(‘Debe ingresar un gerente para el empleado’)
if EmployeeIsManagerFlag=‘N’ and EmployeeManagerId.isnull();
Este tipo de subtipos se utiliza para modelar las relaciones recursivas. Por ejemplo, la relación entre
Empleado y Gerente:
- cada empleado tiene un gerente. Un gerente, a su vez, es un empleado (aquí está la recursión).
- un gerente puede tener varios empleados a su cargo
Si además la realidad a representar es que “sólo los empleados que no son gerentes tienen un
gerente”, entonces, cuando se ingresan los datos hay que realizar los siguientes controles:
-cuando se ingresan los gerentes, hay que permitir dejar en nulo el atributo EmployeeManagerId.
Para esto, cambiamos a ‘Yes’ la columna Nulls del atributo EmployeeManagerId, el cual es FK en la
tabla EMPLOYEE.
que todo empleado que no es gerente, tenga un gerente. Este control lo hacemos con la regla error
que se muestra en la figura.
347
C. Subtipos recursivos
• Listado de navegación detallado:
348
D. Evitar controles de integridad referencial
SupplierId* SupplierId*
SupplierName SupplierName
ProductId* …
ProductDescription
PurchaseOrderHistoryQuantity
En la figura indicamos con flechas los controles que queremos que GeneXus realice
automáticamente.
349
D. Evitar controles de integridad referencial
SupplierId* SupplierId*
SupplierName SupplierName
ProductId* …
ProductDescription
PurchaseOrderHistoryQuantity
Pero los controles que en realidad realiza GeneXus en forma automática son los que se muestran en
esta figura.
350
D. Evitar controles de integridad referencial
SupplierId*
SupplierName
Solución PurchaseOrderHistoryProductId*
PurchaseOrderHistoryProductDescription
PurchaseOrderHistoryQuantity
¿Cómo evitar que GeneXus controle que el proveedor y producto de una orden de compra
existan en la tabla de histórico de compras?
Alcanza con cambiarle el nombre de atributo al código de producto (o al código de proveedor) en el
histórico de compras, pero conservando el concepto al que corresponde (es decir, definiéndolo como
subtipo de producto o de proveedor según corresponda).
351
D. Evitar controles de integridad referencial
SupplierId* SupplierId*
SupplierName SupplierName
PurchaseOrderHistoryProductId* …
PurchaseOrderHistoryProductDescription
PurchaseOrderHistoryQuantity
Como ya no existe una tabla de clave primaria compuesta por los nombres de atributos “SupplierId,
ProductId”, los controles de que el SupplierId y el ProductId digitados en la transacción “Purchase
Order” existan se hacen sobre las tablas SUPPLIER y PRODUCT respectivamente.
352
Consideraciones
353
Consideraciones
• Es posible actualizar los “subtipos inferidos”.
Ejemplo:
Primary
Inferred
Rules:
Es posible actualizar subtipos inferidos, tanto en las reglas de las transacciones como en
procedimientos.
354
Consideraciones
• En ambientes que generan SQL es posible ordenar por
subtipos inferidos.
Esta posibilidad no está disponible para los generadores que no usan SQL.
Por ejemplo: Cobol, RPG y VFP.
355
Consideraciones
En el ejemplo, se desea modelar la relación entre personas (Parejas) y sus Hijos. Para cada pareja se
desea mantener la cantidad de hijos que tiene.
Para esto se define la transacción “Person” y luego “Couple”, siendo el esposo, esposa e hijos subtipos
de personas.
356
La cantidad de hijos se calcula con el atributo CoupleChildrenQuantity definido en el primer nivel de la
transacción “Couple”, como fórmula COUNT que involucra en su definición a un subtipo inferido
(CouplePersonChildAge).
357
Consideraciones
358
TIPOS DE DATOS ESTRUCTURADOS
359
Tipos de datos estructurados
Introducción
• Los lenguajes de programación manejan tipos de datos simples y tipos
de datos complejos
• Ejemplo:
Type Customer = Record
Id: Numeric(4)
Name: Character(30)
Country: Character(20)
City: Character(20) Luego se definen variables de este tipo,
listas de elementos de este tipo, etc.
Address: Record
CompanyAddress: Character(30)
HomeAddress: Character(30)
end;
end;
360
Tipos de datos estructurados
GeneXus
Una funcionalidad interesante a tener en cuenta, es que además de definir ítem a ítem en un tipo
de datos estructurado, también está la posibilidad de definir rápidamente en la estructura de un
tipo de datos estructurado, la misma definición de cierta transacción. Para realizar esto, una vez
creado un nuevo tipo de datos estructurado y al estar editando el mismo, se debe pulsar el botón
derecho del mouse sobre su raíz, y seleccionar la opción Copy structure from… :
361
Tipos de datos estructurados
Utilización
• Se definen variables (en cualquier objeto GeneXus) cuyo tipo de datos = tipo
de datos estructurado:
362
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 1
• En un proc. se define una variable (&Customer) cuyo tipo de datos es del tipo
de datos estructurado: Customer
• El proc. recibe por parámetro un código de cliente, accede con comando For
Each a los datos del cliente recibido y carga los datos del cliente en la variable
&Customer:
Rule:
Parm(CustomerId);
Source:
For each
&Customer.Id=CustomerId Como se está cargando una variable
&Customer.Name=CustomerName escalar y no una lista o colección, no hay
&Customer.Country=CountryName necesidad de solicitar espacio de
&Customer.City=CityName memoria, ya que el espacio de memoria
&Customer.Address.CompanyAddress=… está creado para la variable como para
&Customer.Address.HomeAddress=… ser utilizada una vez.
Endfor
Dado que en este ejemplo se está cargando una variable escalar y no una lista o colección,
no hay necesidad de solicitar espacio de memoria, ya que el espacio de memoria está creado
para la variable como para ser utilizada una vez; así que simplemente se deben asignar los
valores que corresponda a los ítems de la variable.
Para cargar una colección, en cambio, sí habrá que solicitar espacio de memoria para la
variable para cada instancia a ser agregada en la lista; luego de solicitado el espacio de
memoria, habrá que asignar los valores que corresponda a los ítems de la variable, y por
último agregarla a la lista o colección, como veremos.
363
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
• Partiendo del tipo de datos estructurado: Customer, utilizamos la opción
Object / Save Structured Data Type As para crear un nuevo tipo de datos
estructurado de nombre: Customers
364
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
• Configurar la propiedad collection=True para un ítem, define que se trata de
una colección de esos ítems y no de uno solo.
Por ejemplo, de haber configurado para el ítem CompanyAddress la propiedad collection con
valor True, habríamos indicado que se trata de una colección o lista (de largo variable) de
Addresses de empresa, y no de una sola. Análogamente, dado que lo que hemos implementado
es que el ítem: Customers (es decir, la raíz del tipo de datos estructurado) tenga valor True en la
propiedad collection, lo que hemos definido es que se trata de una colección de Customers y no
de un Customer solo.
Es importante notar que al configurar la propiedad collection de un ítem con valor True,
automáticamente se define para la propiedad Item name de ese ítem un nombre por defecto
(en este caso: CustomersItem). Esto permite que podamos definir variables del tipo de datos
estructurado Customers, y a su vez variables del tipo de datos Customers.CustomersItem.
Es sencillo de comprender que definir una variable del tipo Customers significará que estaremos
definiendo una colección o lista de Customers, mientras que definir una variable del tipo
Customers.CustomersItem, significará que estaremos definiendo un solo Customer (o un ítem de
la colección de Customers, que veremos enseguida cómo hacer para agregarlo a la colección).
365
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
Rule:
Parm(&CustomerIdStart, &CustomerIdEnd);
Se definen 2 variables:
&Customers (Data Type: Customers)
&CustomersItem (Data Type: Customers.CustomersItem)
Source:
Æ
366
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
Source:
For each
where CustomerId>=&CustomerIdStart and CustomerId<=&CustomerIdEnd
&CustomersItem.Id=CustomerId
&CustomersItem.Name=CustomerName
&CustomersItem.Country=CountryName
&CustomersItem.City=CityName
&CustomersItem.Address.CompanyAddress=…
&CustomersItem.Address.HomeAddress=…
&Customers.add(&CustomersItem) /*se agrega el ítem a la lista*/
&CustomersItem = new Customers.CustomersItem() /*se solicita nuevo
espacio de memoria
para próximo ítem*/
Endfor
Como ya hemos mencionado, para una variable hay creado espacio de memoria como para ser utilizada
una vez. Si se necesita crear otra instancia para la variable, habrá que solicitar espacio de memoria para la
misma, para lo cual contamos con el operador new.
367
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
368
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 2:
369
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 3:
370
Tipos de datos estructurados
Ejemplos de utilización
Ejemplo # 3:
Source:
For each
where CustomerId>=&CustomerIdStart and CustomerId<=&CustomerIdEnd
&Customer.OneCustomer.Id=CustomerId
&Customer.OneCustomer.Name=CustomerName
&Customer.OneCustomer.Country=CountryName
&Customer.OneCustomer.City=CityName
&Customer.OneCustomer.Address.CompanyAddress=…
&Customer.OneCustomer.Address.HomeAddress=…
&CustomerList.add(&Customer) //se agrega el ítem a la lista
&Customer = new CustomerList. CustomerListItem() /* se solicita nuevo
espacio de memoria
Endfor para próximo ítem */
De modo que si bien el ejemplo #3 implementa exactamente lo mismo que el ejemplo #2,
como en este último hemos optado por otra alternativa de definición de tipos de datos
estructurados, la sintaxis en este caso queda un poquito más extensa.
371
Tipos de datos estructurados
Operadores, métodos y propiedades
New
Es un operador que retorna una nueva instancia inicializada, o sea una nueva referencia o puntero al
tipo de datos que se especifica.
Add
Es un método para aplicar a variable de tipo colección. Agrega un ítem a una colección, en la posición
relativa especificada.
Nota: Si se omite Position o se especifica 0, se agrega el Item al final de la colección. Position comienza
en 1.
Item
Es un método para aplicar a variable de tipo colección.
Sintaxis: &VbleDeTipoColeccion.Item(Position)
Retorna una referencia al elemento que se encuentra en la colección en la posición relativa Position.
No es válido asignar un valor con esta propiedad, por lo tanto no es correcto el código
&VbleDeTipoColeccion.item(&i) = Att. Para cambiar un valor se debe remover (método remove) y
agregar (método add).
372
Remove
Es un método para aplicar a variable de tipo colección. Elimina el elemento que se encuentre en la
colección en la posición relativa que se especifique, y corre un lugar todas las posiciones.
Sintaxis: &VbleDeTipoColeccion.Remove(Position)
Clear
Es un método para aplicar a variable de tipo colección. Elimina todos los elementos de la colección.
Sintaxis: &VbleDeTipoColeccion.Clear()
Sort
Sintaxis: &VbleDeTipoColeccion.Sort(“NombreCampoPorElCualOrdenar")
Es posible ordenar en forma descendente, poniendo dentro de las comillas al nombre del campo entre
corchetes rectos.
Es posible ordenar por más de un campo, poniendo dentro de las comillas los nombres de los campos
separados por coma.
Count
Es una propiedad de variable de tipo colección. Retorna la cantidad de elementos de la colección. Es read
only.
373
Tipos de datos estructurados
Comando para recorrer colecciones
Consideraciones:
• No es posible obtener la posición del vector durante la recorrida, para esto es
necesario definir un variable que actúe como contador.
• No es posible modificar los ítems de la lista en la recorrida. Esto significa que
cambios en el valor de &Var, en el alcance de la estructura, no afectan al
correspondiente valor del &Array(X) o viceversa.
• Es posible incluir comandos de “corte” de la recorrida, al igual que en for each
o do while, como exit o return.
374
Tipos de datos estructurados
Cómo mostrarlos en form
Es posible insertar en el form de Transacciones, Web y Work Panels variables de tipo de datos
estructurados. También es posible hacerlo en print blocks de reportes, siempre y cuando no sean
collection.
Desplegar una collection en un form se puede hacer de forma muy sencilla: solamente es necesario
insertar la variable en el form, y quedará asociada a un grid que será cargado automáticamente, sin
necesidad de código alguno.
375
Tipos de datos estructurados
Cómo mostrarlos en form
2 posibilidades:
1) Pallete Toolbar shortcut 2) Insert/Variable
permite selección múltiple
Hay que repetir este paso para c/atributo del 1er nivel que se
desee mostrar en el form
Notar que es una sola variable definida (NO una variable por c/atributo)
376
Tipos de datos estructurados
Cómo mostrarlos en form
En cuanto al grid:
Carga automática:
¡No se requiere código!
Como hemos visto, hay 2 posibilidades para insertar una variable de tipo SDT con sus respectivos atributos en un
form / layout:
Si utilizando la opción insert/variable se selecciona algún (o algunos) atributo(s) correspondientes a una collection,
dicho(s) atributo(s) se agregarán automáticamente en un grid (y en las “Grid Properties” del grid, se podrá observar
que automáticamente se habrá asignado en el combo “Collection”, el nombre del nivel al cual pertenecen los
atributos).
Si en cambio se utiliza el shortcut para ir agregando atributos del primer nivel del SDT en el form, y se utiliza el
shortcut para agregar un grid en el form con el fin de mostrar una collection, el analista
tendrá que seleccionar explícitamente en el combo “Collection” de las “Grid Properties”, el nivel del SDT, para luego
poder seleccionar cuáles atributos de dicho nivel desea incluir en el grid.
En tiempo de ejecución, la carga del grid se realizará en forma completamente automática, con el contenido de la
collection. Esto podrá verse en el listado de navegación:
Nota: Un detalle a tener en cuenta es que independientemente de la forma en que se seleccionen los atributos de una
collection para ser mostrados en un grid, los mismos estarán en columnas visibles del grid, y los restantes atributos
del nivel (los no seleccionados) se agregarán hidden.
377
Tipos de datos estructurados
Cómo mostrarlos en form
• Propiedad CurrentItem:
• Para collections
• Ejemplo:
Event ‘DisplayProductDescription'
msg(&invoice.line.CurrentItem.ProductDescription)
EndEvent
Si por ejemplo en el work panel visto (Invoice) deseamos agregar un botón con un evento asociado y mostrar un
mensaje para la línea del grid seleccionada, con información de ese ítem de la colección, contamos a partir de la
versión 9.0 con la propiedad CurrentItem.
378
BUSINESS COMPONENTS
379
Business Components
Objetivo
Beneficios
380
Business Components
Algunos ejemplos de uso
• Procedimientos:
Utilizar concepto de BC en vez de For Each, New, Delete cuando se deseen ejecutar las reglas
definidas en la Transacción, controles IR, mantenimiento de redundancia, sin duplicar código
• Web Services:
Un Web Service es un programa que puede ser invocado a través de Internet “para brindar
un servicio”, y utiliza el estándard XML para el formato de los datos recibidos/devueltos.
La versión 9.0 de GeneXus ofrece que desde fuera de la KB sea posible consumir un BC como
web service.
381
Business Components
¿Cómo definir un Business Component?
Veamos un ejemplo …
382
Business Components
Ejemplo: Inserción de una invoice con 2 líneas mediante proc.
En cualquier objeto será posible definir variables de estos tipos de datos… y las mismas tendrán:
• un conjunto de propiedades asociadas (los atributos de la TRN definida como BC)
• un conjunto de métodos asociados (para insertar, eliminar, recuperar, etc.)
propiedades
Una vez definida una transacción como Business Component, GeneXus creará automáticamente
un nuevo tipo de datos Business Component cuyo nombre será el de la transacción; y creará
también tantos tipos de datos como niveles posea la transacción, siendo sus nombres el
resultado de la concatenación del nombre de la transacción con el nombre dado a cada nivel
(valor de la columna “Type” del nivel).
383
Business Components
Ejemplo: Inserción de una invoice con 2 líneas mediante proc.
• Source del procedimiento:
//invoice
&Inv.InvoiceId = 1 &Inv Æ tipo de datos BC Invoice
&Inv.CustomerId = 50
&InvLine Æ tipo de datos BC Invoice.LineType
//invoiceline
&Invline.ProductId = 1
&Invline.InvoiceLineQuantity = 10
&Inv.Line.Add(&Invline)
&Invline = new Invoice. LineType() Í a partir del 2do registro a dar de alta
&Invline.ProductId = 2 en una lista es necesario el new
&Invline.InvoiceLineQuantity = 20
&Inv.Line.Add(&Invline)
&Inv.Save() // Í hay que hacer sólo &Inv.Save(), NO hay que hacer
&InvLine.Save()
&Messages= &Inv.GetMessages()
for &Message in &Messages
msg( &Message.Id) Recomendación: después de los métodos Save, Delete Load
msg( &Message.Description) y Check, manejar siempre los errores.
endfor
Los BC ignoran la property del objeto “Commit on exit”
Commit Í
Nunca hacen commit/rollback automáticamente. Recordar ponerlos explícitamente
384
Business Components
Reglas y eventos que se ejecutan
• Reglas: Todas las reglas son ejecutadas excepto (son ignoradas por el
especificador):
• las que incluyen user interface (Ej: call(Wxxx)).
• las que no aplican: Parm, Prompt, NoPrompt, Default_mode, etc.
385
Business Components
Manejo de errores
• Existe un tipo de datos estructurado (SDT) predefinido por GeneXus de nombre
Messages:
• Hay que definir una variable del tipo de datos Messages y asignarle el resultado
de aplicar el método GetMessages a la variable de tipo BC :
&Messages= &Inv.GetMessages()
• Se obtendrán:
• Los mensajes generados automáticamente por GeneXus que se hayan
disparado (Ej.: Record already exist)
• Las reglas Error y Msg definidas en la transacción que se hayan disparado
Cuando se ejecutan los métodos: Save, Check, Load, Delete se disparan y cargan los mensajes
generados automáticamente por GeneXus así como las reglas Msg y Error definidos en la
transacción. Se recomienda que siempre se recupere la lista de estos mensajes y se haga un
“manejo de errores”.
Las reglas Msg y Error a partir de la versión 9.0 de GeneXus, aceptan en su definición además del
parámetro con el mensaje a desplegar, un segundo parámetro que define el Identificador del
mensaje. El objetivo de esto, es que cuando se ejecute a la transacción como Bussiness
Component, y se obtenga la lista de mensajes ocurridos luego de ejecutar una acción sobre la base
de datos, se tenga de cada mensaje, además del mensaje en sí, su identificador, siendo posible así
evaluar el identificador del mensaje para codificar el comportamiento en consecuencia:
Msg|Error(<mensaje>, <Id del mensaje>)
Ejemplos de <Id del mensaje>: "1", "2", "Error1", "Error2" o una descripción como ser
"CustomerNameCannotBeEmpty", etc.
De no especificar un identificador para el mensaje, habrá que preguntar por el texto del mensaje.
Nota: Para los mensajes generados automáticamente por GeneXus, el Id es siempre en Inglés
(independientemente del idioma seleccionado en el modelo).
386
Business Components
Métodos asociados a las variables de tipo de datos BC
Check()
• Valida los datos pero no actualiza la base de datos
• Usado para diálogos en los que se quiere validar los datos (dándole un feedback al usuario) antes
de actualizar la BD
Save()
• Valida los datos y actualiza la base de datos
• Sólo válido para aplicar a variables de tipo BC del primer nivel de la transacción
• Ej: &Inv.Save()
commit
Delete()
• Elimina en la base de datos el registro cargado en la variable de tipo BC
• Ej: &Empleado.Load(&EmployeeId)
&Empleado.Delete()
commit
387
Business Components
Métodos asociados a las variables de tipo de datos BC
GetMessages()
• Devuelve la lista de errores que ocurrieron luego de efectuar una operación a la BD
• Ej: &Messages= &Inv.GetMessages()
Fail()
• Devuelve True si luego de haber efectuado una operación a la BD dio algún error
• Ej: &Inv.Save()
If &Bc.Fail()
&Messages = &Inv.GetMessages()
for &Message in &Messages
.....
Success()
• Devuelve True si luego de haber efectuado una operación a la BD, la operación fue exitosa
Add(&BCLine)
• Permite agregar un ítem a una colección (en este caso, a la colección de líneas correspondiente a
un nivel subordinado de la variable BC)
• Ej.: &Inv.Line.Add(&Invline)
388
Business Components
¿Cómo programar un BC en un
work panel, web panel o reporte?
Ejemplo para un web panel, con variable de tipo de datos BC Invoice…
1. Crear en web panel variable &Invoice del tipo de datos BC Invoice
2. Insertar la variable &Invoice en el form (vale lo mismo explicado para insertar variables de tipo SDT... ya
que un BC es un SDT con la estructura de su transacción).
3. Programar los eventos:
389
Business Components
¿Qué es mejor: actualización “directa” o “usando BC”?
Directa:
New
CustomerId = &CustomeriId
CustomerName = &CustomerName
Endnew
Usando BC:
&Customer.CustomerId = &CustomerId
&Customer.CustomerName = &CustomerName
&Customer.Save()
Depende.
Por lo tanto, una buena regla podría ser: “Usar Business Components a menos que la performance
sea crítica”.
De modo que para elegir una opción u otra, el analista deberá evaluar en cada caso qué necesita
efectuar, si la transacción implicada tiene muchas reglas definidas o no, fórmulas, chequeos de IR
relacionados, redundancias... y tener en cuenta los factores consistencia y performance.
390
KNOWLEDGE
MANAGER
391
Distribución
Se generará el
archivo
MyObjects.xpz
en la raíz de la
KB
Una vez seleccionados los objetos a distribuir (exportar) e indicado el nombre del archivo de
distribución (en el ejemplo: MyObjects), se crea automáticamente un archivo comprimido de
extensión XPZ. Este archivo comprimido contiene dentro un archivo XML que contiene la información
de los objetos y/o atributos distribuidos.
En Distribution Name podemos poner un path donde guardar el archivo de distribución. Si no se pone
path, como en el caso de arriba, entonces quedará almacenado en el directorio raíz de la Base de
Conocimiento.
En el ejemplo, si abrimos el archivo MyObjects.xpz con una herramienta tipo WinZip, veremos que
contiene un archivo de nombre MyObjects_1.xml
Distribute Options
Append to File: Si en el Distribution Name del diálogo de distribución se ingresa de nombre un
archivo que ya existe, al salir del campo se habilita esta opción para poder agregar la nueva
distribución al archivo existente.
Si se marca la opción Append to File, se crea un nuevo archivo XML dentro del mismo XPZ. Siguiendo
con el ejemplo anterior, se crearía un archivo MyObjects_2.xml en el archivo MyObjects.xpz. Si ya
existe un archivo con ese nombre, se incrementa el sufijo en uno y se vuelve a intentar.
Si no se marca la opción Append to File, el archivo se reemplaza con la nueva distribución.
Version 7.0 (And prior): Mediante esta opción es posible que el archivo de exportación resultante
sea creado con el formato utilizado hasta la versión 7.0 de GeneXus inclusive (extensión XPW).
392
Consolidación
La forma de importar objetos, atributos y dominios GeneXus dentro de una base de conocimiento es
mediante la opción Consolidate del Knowledge Manager. Lo que se importa son objetos
previamente distribuidos, en un archivo que puede tener tres formatos: XPZ, XML o XPW.
XPZ: Cuando se selecciona un archivo con este formato, el Knowledge Manager (KMW) recorre el
contenido del archivo XPZ y consolida cada uno de los archivos internos, siempre y cuando tengan
el formato XML apropiado.
XML: En algunos casos puede ser necesario consolidar algún XML en particular de todos los que
contiene el XPZ. Para este caso se puede descomprimir y consolidar directamente el XML.
XPW: Para poder tener compatibilidad con las versiones anteriores de GeneXus, existe un
componente encargado de realizar la conversión de un archivo XPW a un archivo XML.
393
GX OPEN
394
GX Open
www.gxopen.com
395
PUESTA EN PRODUCCIÓN
396
Puesta en Producción
397
Creación de modelo de Producción
Primera vez:
1. Una vez que el prototipo ha sido aprobado, es momento de “pasar a producción”, creando un modelo de
Producción. Este modelo tendrá como plataforma la del cliente, dado que los programas que se llevarán al cliente
son los que se obtengan de aquí. Al crear este modelo, como con todo modelo de Prototipo o de Producción, se
crearán las tablas en la base de datos asociada. Para ello ocurre exactamente lo mismo que vimos para Prototipo:
GeneXus genera un programa de creación de tablas en el lenguaje de programación asociado al modelo, (cuyo
nombre depende del generador: por ejemplo, para visual basic, es RMenu , para .Net es Reor), éste programa se
compila, creándose un ejecutable que luego es corrido y obteniendo la creación de las tablas.
Ejemplo: Creamos el modelo de Producción: “Sistema Facturación y Compras”, y elegimos la misma plataforma
(recordar que esto no tiene por qué ser así: la plataforma de prototipo y producción pueden diferir). GeneXus crea
el programa Reor en .Net. Luego, si el usuario da el ok a la reorganización, este archivo Reor se compila, y el
archivo resultante Reor.exe se coloca bajo el folder bin del modelo (DATA002) y a continuación se ejecuta. Este
programa contendrá la lógica correspondiente a la creación de las tablas INVOICE, CUSTOMER, PRODUCT y
COUNTRY, con sus respectivas restricciones referenciales e índices.
2. Como sucede en el pasaje de Diseño a cualquier modelo de Prototipo, al pasar a este modelo de Producción,
todos los objetos GeneXus son copiados del modelo de Diseño al nuevo modelo. Aquí se especifican y generan,
obteniéndose por tanto los programas ejecutables bajo el directorio del modelo. Con esto tenemos todo lo
necesario para llevar la aplicación pronta al cliente.
Ejemplo: Especificamos y generamos las transacciones “Invoice”, “Customer”, “Product” y “Country” y todos los
demás objetos que hayamos creado (reportes, procedimientos, work panels, etc.).
3. Debemos llevar la aplicación a lo del cliente. De modo que debemos llevar el programa que crea la base de datos
(Rmenu.exe o Reor.exe) y ejecutarlo en la máquina del cliente, obteniendo como resultado la creación de la base
de datos. También debemos llevar los programas de la aplicación. Dependiendo de la plataforma, será necesario
registrar dlls en la máquina destino. Para algunas plataformas que requieren modificar el registry de Windows, o
que requieren seguir algunos pasos un poco más complejos de configuración, GeneXus brinda utilitarios de manera
tal de poder obtener un setup de la aplicación para ser ejecutado en el cliente y despreocuparse de hacer tales
registraciones en forma manual. Para plataformas visuales, tales como Visual Basic y Visual FoxPro GeneXus
cuenta con el Setup Wizard. Este utilitario es un wizard que va pidiendo, al pasar por sus distintas pantallas, la
información necesaria –cuál es el modelo de Producción, etc.- para poder armar el setup que incluya las dlls para
hacer tanto las registraciones necesarias en el cliente, como incluya la aplicación completa para que quede
instalada y pronta para trabajar. El análogo del Setup Wizard para plataforma Java es el Deployment Wizard.
398
La plataforma .Net por el momento no cuenta con un utilitario como estos, debido fundamentalmente a que las dlls
necesarias no tienen que ser registradas, dado que corren bajo el Framework de .Net, por lo que simplemente
deben copiarse los archivos del folder /bin que están bajo el modelo de Producción correspondiente, al cliente y
ejecutar luego el Reor.exe para crear la base de datos.
Llegado este punto, tenemos nuestro modelo de Producción como un espejo de lo que tiene el cliente
instalado.
4. Luego de la puesta en producción, surgirán cambios a realizar en la aplicación (el usuario nos pedirá cambios,
nos brindará nuevo conocimiento, etc.), que nos llevarán nuevamente a la fase de diseño, volviendo a iterar en el
ciclo Diseño-Prototipo, hasta que los cambios implementados hayan sido suficientemente testeados y aprobados.
En este punto, volveremos a pasar a Producción, lo que conducirá, otra vez, a que GeneXus realice un análisis de
impacto, comparando la base de datos de Producción, con el diseño de la misma correspondiente al modelo de
Diseño. Si encuentra cambios, entonces generará nuevamente el programa de reorganización (Reor.exe o
RMenu.exe) de la base de datos, cuya lógica implementará la modificación de la base de datos actual de
Producción, para llevarla al nuevo diseño. Este programa deberá ser llevado y ejecutado en el cliente para
transformar la base de datos que tenía (era un espejo de la de Producción) a la nueva. También deberemos
especificar y generar en el modelo de Producción los programas que hayan cambiado y llevarlos al cliente (se
utilizará el utilitario que corresponda: Setup Wizard, Deployment Wizard o se llevará el directorio /bin para
instalar la 2da versión de la aplicación al cliente).
Terminado este punto, volveremos a tener un espejo en el modelo de Producción de lo que hay en el
cliente. Tendremos que dejar congelado el modelo de Producción y volver a iterar en el ciclo Diseño-
Prototipo. Esto es fundamental porque la versión de Producción es la versión fiel de lo que tiene el
cliente.
Nota: En el caso de hacerse dos pasajes de Diseño a Producción consecutivos sin haber ido a lo del cliente, es
imprescindible guardar cada uno de los programas de reorganización ejecutados en orden. ¿Por qué? Porque si
hacemos dos o más pasajes de Diseño a Producción sin guardar cada uno de los programas de reorganización que
se ejecutaron (Reor.exe o RMenu.exe), no tendremos cada adaptación hecha la base de datos en forma
consecutiva y ¡no podremos reorganizar la base de datos del cliente!
De acuerdo a la plataforma del cliente de su aplicación, diríjase al manual de GeneXus para dicha plataforma, a la
GXDL, o a las Release Notes –en la “sección específica para la plataforma xxx”- para estudiar los detalles
particulares de la puesta en producción de una aplicación en esa plataforma.
399
INTRODUCCIÓN A LA
METODOLOGÍA GENEXUS
De estar interesado en profundizar más en este tema, el alumno podrá inscribirse al curso
Gestión de Proyectos GeneXus si así lo desea, y/o recurrir a la documentación existente.
400
Filosofía de GeneXus
Sistema de Compras
Sistema de Ventas
Sistema de Sueldos
Es decir, el objetivo es implementar para una empresa dada, un gran sistema integrado con
base de datos corporativa, de la cual se pueda obtener información de gestión y gerencial para
la toma de decisiones.
401
Con herramientas tradicionales...
Sistema de Sistema de
Compras Ventas
PROBLEMA: no se cuenta en la
Sistema de empresa con información
Sueldos corporativa, resultando ser
información no confiable
Implementar un sistema corporativo suele resultar una tarea muy compleja cuando se utilizan
herramientas tradicionales, por lo que generalmente no se lleva a cabo.
Esto trae como resultado que se tengan aplicaciones independientes, cada una resolviendo un
problema operativo particular, sin la posibilidad de obtener información corporativa.
Así encontramos por ejemplo en una empresa comercial: una aplicación de Ventas, otra de
Compras, otra Contable, etc.
402
Sistemas Corporativos
La integración de las Aplicaciones operativas de la
Organización es la base para poder construir los sistemas para
el área de Gestión y Gerencial:
INFORMACION
GERENCIAL
APLICACIONES PARA
EL ÁREA DE GESTION
403
¿Cómo lograr Sistemas Corporativos?
3. Asegurar la integrabilidad
404
1. Dividir el problema (Modularizar)
Para:
La razón principal para modularizar es entonces, dividir el problema en partes más pequeñas y
habilitar varios frentes de desarrollo con el fin de hacer más eficiente la labor.
Realizar esta división (modularización) puede que no sea una tarea sencilla y dependerá de las
características de cada aplicación, sin embargo debemos hacerlo cuando el problema adquiere un
determinado tamaño tal que deba ser desarrollado por más de una persona.
No existen procedimientos exactos para esta tarea, pero cuando la realicemos no debemos olvidar
que el principal objetivo es obtener ambientes de desarrollo lo más independientes posibles,
reconociendo que seguramente los módulos no serán disjuntos y compartirán cierto
conocimiento. Debemos sin embargo, intentar que el conjunto de los objetos que compartan sea
lo más pequeño posible para poderlo administrarlo mejor, y realizar posteriormente una más fácil
integración en un único modelo corporativo.
405
2. Crear varios frentes de desarrollo
Luego de asignado cada módulo a cada persona o equipo de
desarrollo, surge la pregunta Æ ¿es conveniente realizar el desarrollo
en una sola KB o en KB’s independientes ?
KB Ventas
406
3. Desarrollar asegurando la
Integrabilidad de las KBs
407
Ejemplo
KB 1 Æ COMPRAS KB 2 Æ VENTAS
Objetos: Objetos:
Proveedores Clientes
FacturasCompra FacturasVta
Productos - - - - - - - - - - - - - - - - - - - -Productos
Bancos - - - - - - - - - - - - - - - - - - - - - -Bancos
Agencias - - - - - - - - - - - - - - - - - - - - -Agencias
Compras Ventas
OrdenesCompra NotasCredito
Serán desarrollados en forma independiente por dos equipos de desarrollo distintos, que
probarán sus prototipos con sus propios datos, hasta que esté todo listo para ponerlo en
producción.
Ahora bien, observemos que estos dos módulos no son disjuntos, sino que comparten
información común; entre otras cosas, ambos trabajan con productos, con bancos y con
agencias.
408
Ejemplo
Consolidación de KB1 (COMPRAS)
COMPRAS
COMPRAS
KB “CONSOLIDADO”
Base PROGRAMAS
de Datos
409
Ejemplo
Consolidación de KB2 (VENTAS)
COMPRAS
COMPRAS VENTAS
VENTAS Análisis de
Impacto
Proceso de
consolidación
KB “CONSOLIDADO”
Base PROGRAMAS
de Datos
Como los módulos no son disjuntos, surgirán alteraciones a lo consolidado anteriormente, y los
cambios a ser efectuados se mostrarán en el reporte de análisis de impacto.
Con este ejemplo pretendemos despertar la atención sobre los problemas inherentes a la
integración de KBs, sin haber coordinado previamente el factor Integrabilidad.
410
Ejemplo
Problemas que surgen en la consolidación del conocimiento
SupplierInvoiceId* CustomerInvoiceId*
SupplierId* CustomerId
SupplierName CustomerName
InvoiceDate InvoiceDate
(ProductId* (ProductNum*
......) .......)
Así GeneXus no puede reconocer que se está haciendo referencia al mismo elemento y no establece
ninguna relación para el concepto de Producto.
411
Ejemplo
Problemas que surgen en la consolidación del conocimiento
Por ejemplo:
KB 1 KB 2
Otro problema que puede ocurrir es que los desarrolladores definan visiones diferentes de la
relación entre los objetos.
Los 2 equipos de desarrollo tienen clara que la relación que existe entre Bancos y Agencias es
una relación de 1 a N : BANCO ---->> AGENCIAS
Sin embargo definen visiones diferentes, como se muestra arriba en la KB1 y KB2.
En este caso, no tenemos problema de nomenclatura, pero al integrar, solo quedará definida la
primer relación , o sea:
AgencyId*
BankId
y no:
BankId*
AgencyId*
Dado que los identificadores en las transacciones juegan un papel fundamental, esto puede
dejar inválidos objetos definidos en la segunda aplicación.
Concluimos entonces, que las relaciones entre atributos y los identificadores de las diferentes
entidades, juegan también un papel fundamental en el momento de integrar diferentes
aplicaciones.
412
¿Cómo trabajar para minimizar
los problemas de integración?
Como hemos visto, los problemas de integración se reducen a problemas de nomenclatura y de definición
de relaciones e identificadores. Veremos posibles soluciones para minimizar estos problemas:
Como sabemos GeneXus establece la relación entre los objetos y define la normalización de la Base de
Datos, basándose en los nombres de los atributos.
Es por eso que al momento de consolidar, la nomenclatura utilizada para los atributos juega un papel
primordial.
Sin embargo, no son menos importantes los nombres de los objetos (transacciones, procedimientos, etc.)
pues el Knowledge Manager reemplaza en la consolidación, los objetos con igual nombre.
Los nombres dados a las Tablas, Índices y Data Views serán también controlados en el momento de la
consolidación y deberán ser únicos en el Modelo consolidado, por lo que debemos intentar reducir
conflictos también en este sentido.
En cuanto a los nombres de las variables, a pesar de que no son relevantes para la consolidación, ya que
son definiciones locales, igual se sugiere tener una nomenclatura para las mismas con el fin de tener
uniformidad en el desarrollo y mejorar así la comprensión de las aplicaciones.
Una vez definidos los módulos y establecida la padronización para los objetos del sistema, es el momento
de diseñar una metodología para administrar el conocimiento de forma tal de mantener ambientes
independientes de desarrollo y asegurar su integración en un Modelo Corporativo que tenga asociada una
sola Base de Datos corporativa.
Hemos solucionado parcialmente los problemas de integración definiendo una nomenclatura standard
para objetos, pero queda aún potenciarla y asegurar la integración desde el punto de vista de
“relaciones”, es decir asegurar que los objetos compartidos por más de un módulo guarden la misma
relación con el resto de los objetos. Para ello, expondremos a continuación un esquema basado en tres
tipos de Modelos, que cumplen funciones diferentes en la tarea de administrar el conocimiento.
413
Metodología basada en 3 tipos
de Bases de Conocimiento
414
Metodología basada en 3 tipos
de Bases de Conocimiento
KB
KB
NUCLEO
NUCLEO
KB
KB
KB
KB
KB SUELDOS
SUELDOS
COMPRAS
COMPRAS KB
VENTAS
VENTAS
KB CORPORATIVA
BASE DE
PROGRAMAS
DATOS
415
Metodología basada en 3 tipos
de Bases de Conocimiento
KB Núcleo
416
Metodología basada en 3 tipos
de Bases de Conocimiento
Objetivo KB Núcleo
417
Metodología basada en 3 tipos
de Bases de Conocimiento
KB Núcleo = Intersección de KBs Aplicaciones
KB
COMPRAS
KB
KB VENTAS
NUCLEO
KB
SUELDOS
KB
CORPORATIVA
418
Metodología basada en 3 tipos
de Bases de Conocimiento
FOLDER
NUCLEO
KB
KB
NUCLEO
NUCLEO
KB CORPORATIVA
BASE DE PROGRAMAS
DATOS
En cada KB (Núcleo y Módulos) se deberá crear un folder, y los objetos propios de dicha KB, deberán
ubicarse dentro de ese folder.
Es decir, en la KB Núcleo habrá que crear un folder llamado Núcleo, en la KB Compras será necesario
crear un folder llamado Compras, en la KB Ventas un folder llamado Ventas, y en la KB Sueldos un folder
llamado Sueldos.
La primer KB a ser definida será la KB Núcleo. Para ello habrá que identificar los objetos comunes a todos
los módulos (por ejemplo, las transacciones Banks, Agencies, Products, Customers) y definirlos dentro del
folder Núcleo de la KB Núcleo.
Una vez definido el Núcleo, este deberá distribuirse y consolidarse en cada una de las KBs asociadas a cada
aplicación, para asegurarse el compartir todas los mismos objetos comunes, exactamente.
De modo que lo que se distribuirá será el folder Núcleo de la KB Núcleo, y se consolidará en cada una de
las KBs asociadas a una aplicación (KB Compras, KB Ventas y KB Sueldos) así como en la KB
Corporativa.
Recién luego de esto, cada desarrollador responsable de un módulo podrá comenzar a trabajar, debiendo
crear todos los objetos propios de su módulo, en el folder correspondiente a ese módulo en particular. Así,
la KB Compras tendrá 2 folders: el folder Nucleo con los objetos comunes a todos los módulos, y el
folder Compras con los objetos propios que implementen ese módulo. Análogamente, las KBs Ventas y
Sueldos tendrán el folder Nucleo y su folder propio.
1. Se estará trabajando con KBs pequeñas, asegurando la integrabilidad con el resto de las aplicaciones
2. Se estarán realizando los primeros niveles de test de funcionalidad para el módulo en forma
independiente asegurando un ciclo de prototipación dinámico
3. No se afectará al resto del desarrollo
En la medida que cada desarrollador de por finalizado su módulo, distribuirá el folder propio de su KB (por
ejemplo el folder Sueldos de la KB Sueldos) y este se consolidará en la KB Corporativa. No distribuirá el
folder Nucleo de la KB Sueldos, ya que este folder solo se distribuye de la KB Nucleo).
419
Metodología basada en 3 tipos
de Bases de Conocimiento
1. Se consolida el Núcleo
2. Se crea un folder propio, es decir que identifique a la aplicación,
y en el mismo se crean los objetos de la aplicación
3. Una vez finalizado el desarrollo y test del módulo, se distribuirá
el folder propio para consolidarlo en la KB Corporativa.
¿Cómo se procede si uno de los módulos requiere modificar un objeto del Núcleo? Es decir,
supongamos que el desarrollador de un módulo se da cuenta que le resulta necesario para su
aplicación, agregar un atributo en una transacción del Núcleo.
Esto tendrá un impacto en todas las KBs de aplicación, y por tanto deberá administrarse con
cuidado.
420
Metodología basada en 3 tipos
de Bases de Conocimiento
Características de la KB Corporativa
• Ventajas:
• Integridad total de la Base de Datos
• Minimiza la redundancia de Datos
• Base para construir la Información Corporativa de la
Organización
421