You are currently browsing the category archive for the ‘Code Generation’ category.

Siguiendo con la serie de posts sobre Cooperator Framework, esta vez voy a mostrarles cómo codificar un ABM de lo más simple.

Seguramente van a encontrar muchas formas de mejorar el ejemplo. La idea es brindar un punto de partida.

Para los que todavía no conocen Cooperator Framework les recomiendo los posts anteriores sobre el tema:

Introducción a Cooperator Framework,

Cooperator, un ejemplo sencillo para comenzar y

Cooperator, modificando el modelo.

Supongamos que tenemos una entidad en nuestro dominio denominada Occupations, con un ID autonumérico y un campo Descripcion de tipo VARCHAR.

Vamos a tener un formulario Occupations que contiene una DataGridView, de nombre dgDatos, con la lista de Occupations y un formulario Occupation con el que veremos el detalle de la Occupation al agregar o modificar un registro.

En el formulario Occupations tendremos una botonera y/o menú con las opciones de Agregar, Eliminar y Modificar.

La grilla de Occupations la completo con el siguiente código:

C#

OccupationList mOccupationList = OccupationMapper.Instance().GetAll;

OccupationListView mOccupationListView = new OccupationListView(mOccupationList);

mOccupationListView.Sort(«Description», true);

dgDatos.DataSource = mOccupationListView;

dgDatos.Columns[ColumnaGrilla.ID].Visible = false;

dgDatos.Columns[ColumnaGrilla.Description].HeaderText = «Ocupación»;

VB.NET

Dim mOccupationList As OccupationList = OccupationMapper.Instance().GetAll

Dim mOccupationListView As OccupationListView = New OccupationListView(mOccupationList)

mOccupationListView.Sort(«Description», True)

dgDatos.DataSource = mOccupationListView

dgDatos.Columns(ColumnaGrilla.ID).Visible = False

dgDatos.Columns(ColumnaGrilla.Description).HeaderText = «Ocupación»

En la primera linea declaro una lista que completo con el método GetAll del Mapper de Occupation. Luego completo una ListView a partir de la lista que complete con el GetAll. En la tercera linea le practico un ordenamiento, en la cuarta linea le paso este ListView al DataSource de la DataGridView con lo que queda poblada. En la quinta linea hago invisible la columna del ID y en la última linea de código le doy un título a la columna Descripcion.

Veamos ahora el código correspondiente a Eliminar:

C#

if ((dgDatos.SelectedRows == null))

{

MsgBox(«No se seleccionó ninguna fila de la grilla.», ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else if ((MsgBox((«¿Está a punto de eliminar a la Ocupación «

+ (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + «?»)), ((MsgBoxStyle)((MsgBoxStyle.Exclamation + (MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2))))) == MsgBoxResult.Yes))

{

OccupationMapper.Instance.Delete(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

MsgBox((«La Ocupación » + (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + » se eliminó exitosamente.»)), ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

CargarGrilla();

}

VB.NET

If dgDatos.SelectedRows Is Nothing Then

MsgBox(«No se seleccionó ninguna fila de la grilla.», CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

If MsgBox(«¿Está a punto de eliminar a la Ocupación « + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + «?», CType(MsgBoxStyle.Exclamation + MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2, MsgBoxStyle)) = MsgBoxResult.Yes Then

OccupationMapper.Instance.Delete(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

MsgBox(«La Ocupación « + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + » se eliminó exitosamente.», CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

CargarGrilla()

End If

End If

ColumnaGrilla es un Enum donde ColumnaGrilla.ID es igual a cero y ColumnaGrilla.Descripcion es igual a uno.

CargarGrilla es el primer bloque de código del post.

Ahora les muestro el código que corresponde a Agregar:

C#

Occupation miOccupation = new Occupation();

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

}

miOccupation = null;

VB.NET

Dim miOccupation As New Occupation

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

End If

miOccupation = Nothing

Y lo correspondiente a Modificar

C#

int intPivot;

if ((dgDatos.SelectedRows == null))

{

MsgBox(«No se seleccionó ninguna fila de la grilla.», ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else

{

CoopRules.Entities.Occupation mOccupation = new CoopRules.Entities.Occupation();

intPivot = ((int)(dgDatos.SelectedRows.Item[0].Index));

mOccupation = OccupationMapper.Instance.GetOne(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

Occupation miOccupation = new Occupation(mOccupation);

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

dgDatos.Rows.Item[intPivot].Selected = true;

dgDatos.CurrentCell = dgDatos.Rows[intPivot].Cells[ColumnaGrilla.Description];

MsgBox(«La modificación se registró exitosamente.», ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

}

miOccupation = null;

}

VB.NET

Dim intPivot As Integer

If dgDatos.SelectedRows Is Nothing Then

MsgBox(«No se seleccionó ninguna fila de la grilla.», CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

Dim mOccupation As New CoopRules.Entities.Occupation

intPivot = CType(dgDatos.SelectedRows.Item(0).Index, Integer)

mOccupation = OccupationMapper.Instance.GetOne(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

Dim miOccupation As New Occupation(mOccupation)

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

dgDatos.Rows.Item(intPivot).Selected = True

dgDatos.CurrentCell = dgDatos.Rows(intPivot).Cells(ColumnaGrilla.Description)

MsgBox(«La modificación se registró exitosamente.», CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

End If

miOccupation = Nothing

End If

Acá llamo la atención especialmente en la diferencia en la invocación al constructor del formulario Occupation entre Agregar y Modificar

C#

Agregar

Occupation miOccupation = new Occupation();

Modificar

Occupation miOccupation = new Occupation(mOccupation);

VB.NET

Agregar

Dim miOccupation As New Occupation

Modificar

Dim miOccupation As New Occupation(mOccupation)

Podemos observar que en el caso de Modificar al constructor le pasamos un Objeto Entity de tipo Occupation y en Agregar no.

Esto es porque el constructor del formulario Occupation tiene un parámetro opcional de tipo Occupation.

Les muestro el código del formulario Occupation:

C#

using CoopRules.Entities;

using CoopRules.Objects;

using CoopRules.Mappers;

using CoopCore.Core;

public class Occupation {

private bool mCancelo = true;

private CoopRules.Objects.OccupationObject mOccupation = new CoopRules.Objects.OccupationObject();

private CoopCore.Core.Estado mEstadoActual;

public Occupation(CoopRules.Entities.Occupation pOccupation = Nothing) {

InitializeComponent();

if ((pOccupation == null)) {

this.EstadoActual = CoopCore.Core.Estado.Agregar;

}

else {

mOccupation = pOccupation;

txtDescription.Text = pOccupation.Description;

this.Text = pOccupation.Description;

this.EstadoActual = CoopCore.Core.Estado.Modificar;

}

txtDescription.Focus();

}

public CoopCore.Core.Estado EstadoActual {

get {

return mEstadoActual;

}

set {

mEstadoActual = value;

}

}

bool Cancelo {

get {

return mCancelo;

}

set {

mCancelo = value;

}

}

private void Occupation_Load(object sender, System.EventArgs e) {

}

private void btnCancelar_Click(object sender, System.EventArgs e) {

this.Close();

}

private void btnAceptar_Click(object sender, System.EventArgs e) {

try {

Validar();

Grabar();

}

catch (Exception ex) {

if (ex.Message.Contains(«Cannot insert duplicate key row»)) {

MsgBox(«Ya existe una Ocupación con ese nombre.», MsgBoxStyle.OkOnly, NombreEmpresa);

}

else {

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa);

}

}

}

private void BlanquearControles() {

txtDescription.Text = «»;

}

private void Validar() {

mOccupation.Description = txtDescription.Text;

mOccupation.Validate();

}

private void Grabar() {

if ((this.EstadoActual == CoopCore.Core.Estado.Agregar)) {

CoopRules.Entities.Occupation eOccupation = new CoopRules.Entities.Occupation();

eOccupation.Description = mOccupation.Description;

OccupationMapper.Instance.Insert(eOccupation);

this.Cancelo = false;

MsgBox(«La Ocupación se agregó exitosamente.», MsgBoxStyle.OkOnly);

BlanquearControles();

txtDescription.Focus();

}

else {

OccupationMapper.Instance.Save(mOccupation);

this.Cancelo = false;

this.Close();

}

}

}

VB.NET

Imports CoopRules.Entities

Imports CoopRules.Objects

Imports CoopRules.Mappers

Imports CoopCore.Core

Public Class Occupation

#Region «Declaraciones Privadas»

Private mCancelo As Boolean = True

Private mOccupation As New CoopRules.Objects.OccupationObject

Private mEstadoActual As CoopCore.Core.Estado

#End Region

#Region «Eventos»

Public Sub New(Optional ByVal pOccupation As CoopRules.Entities.Occupation = Nothing)

InitializeComponent()

If pOccupation Is Nothing Then

Me.EstadoActual = CoopCore.Core.Estado.Agregar

Else

mOccupation = pOccupation

txtDescription.Text = pOccupation.Description

Me.Text = pOccupation.Description

Me.EstadoActual = CoopCore.Core.Estado.Modificar

End If

txtDescription.Focus()

End Sub

Private Sub Occupation_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

End Sub

Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelar.Click

Me.Close()

End Sub

Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click

Try

Validar()

Grabar()

Catch ex As Exception

If ex.Message.Contains(«Cannot insert duplicate key row») Then

MsgBox(«Ya existe una Ocupación con ese nombre.», MsgBoxStyle.OkOnly, NombreEmpresa)

Else

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa)

End If

End Try

End Sub

#End Region

#Region «Propiedades»

Public Property EstadoActual() As CoopCore.Core.Estado

Get

Return mEstadoActual

End Get

Set(ByVal value As CoopCore.Core.Estado)

mEstadoActual = value

End Set

End Property

Property Cancelo() As Boolean

Get

Return mCancelo

End Get

Set(ByVal value As Boolean)

mCancelo = value

End Set

End Property

#End Region

#Region «Rutinas y Funciones Privadas»

Private Sub BlanquearControles()

txtDescription.Text = «»

End Sub

Private Sub Validar()

mOccupation.Description = txtDescription.Text

mOccupation.Validate()

End Sub

Private Sub Grabar()

If Me.EstadoActual = CoopCore.Core.Estado.Agregar Then

Dim eOccupation As New CoopRules.Entities.Occupation

eOccupation.Description = mOccupation.Description

OccupationMapper.Instance.Insert(eOccupation)

Me.Cancelo = False

MsgBox(«La Ocupación se agregó exitosamente.», MsgBoxStyle.OkOnly)

BlanquearControles()

txtDescription.Focus()

Else

OccupationMapper.Instance.Save(mOccupation)

Me.Cancelo = False

Me.Close()

End If

End Sub

#End Region

End Class

Espero les sea útil. 🙂

Otra vez acá.

Basado en el ejemplo del post anterior, voy a introducir modificaciones al modelo original con la idea de ver que tan fácil es hacerlo y regenerar código.

Partimos entonces de la solución generada que pueden descargar desde acá.

Supongamos que ahora nos piden que incluyamos un campo Telefono para la tabla Sucursal.

Conviene agregar algunos registros en las tablas para facilitar la prueba.En primer lugar abrimos SQL Server Management Studio, buscamos en la base de datos Prueba y en la tabla Sucursal agregamos un campo Telefono de tipo VARCHAR(50), grabamos los cambios.

Luego abrimos nuestro modelo con el Modeler y pulsamos Refresh. Al desplegar Sucursal veremos lo siguiente:

Ahora hacemos click sobre Quick generation, al finalizar abrimos la solución con Visual Studio y ejecutamos.Podremos ver que la grilla ya contiene la columna Telefono y si cargaron algunos registros correctamente en las tablas, podrán ver los valores.

Seguimos pensando en otro tipo de modificación, por ejemplo una Asociación, supongamos que nos piden poder calificar las Sucursales por Provincia. Esto nos obliga a crear una tabla Provincia con un campo ID (PK, autonumérico e INT) y Descripcion (VARCHAR(50)). Para vincularla a la tabla Sucursal, agregamos en esta última un campo IDProvincia (FK e INT) luego establecemos la relación entre la PK de Provincia con la FK de Sucursal.

Hacemos un Refresh desde el Modeler,

Desplegamos Sucursal, chequeamos ProvinciaString y quitamos el check de ProvinciaEntity, click en Save model,

Ahora un Quick Generation y volvemos a la solución.

Veremos que se han producido una serie de erroes.

Para solucionarlo iremos primero al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ProvinciaObject.vb y en la carpeta Object/Auto la clase ProvinciaObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ProvinciaGateway.vb y en Gateways/Auto la clase ProvinciaGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ProvinciaMapper.vb y en Mappers/Auto la clase ProvinciaMapper.Auto.vb

Si ejecutamos la solución, vamos a ver la columna Provincia incorporada a la grilla.

Supongamos que nos indican que cada Sucursal está asociada a un Depósito y cada una tiene uno propio. Podemos suponer esto como una Agregación, una relación uno a uno.

Importante: Tengamos en cuenta que para que una relación quede establecida como uno a uno en una base de datos, la PK y la FK deben ser exactamente iguales, mismos campos, mismos tipos, mismos nombres.

En nuestro ejemplo la PK de Sucursal la forman los campos ID e IDCliente (podría haber sido ID solamente) entonces vamos crear una tabla Deposito con estos dos campos un ID (PK, autonumérico, INT)y un campo Descripcion (VARCHAR(50)).

Luego debemos crear una relación uno a uno entre Sucursal y Deposito.

Volvemos al Modeler y hacemos un Refresh. Desplegamos Sucursal y vemos un nuevo elemento llamado DepositoAggregation, si intentamos chequearlo vamos a ver que en pocos segundos pierde el check. Esto sucede porque todavía no definimos su dependencia. Para eso hacemos click sobre el elemento Deposito, en la propiedad GenerateAsChildOf seleccionamos Sucursal. Ahora si podemos chequear sobre el elemento DepositoAggregation de Sucursal.

Ahora vamos a mirar el elemento Cliente, lo desplegamos y seleccionamos SucursalCollection, si vemos su propiedad GenerateAsType vale ObjectList, esto significa que la entidad Cliente, trae una colección de objetos. Como antes les conté, los objetos mapean uno a uno con las tablas de la base de datos, esto quiere decir que la agregación que recién incorporamos no es accesible desde la entidad Cliente.

Para que esto suceda hay que fijar la propiedad GenerateAsType de SucursalCollection con valor EntityList en la entidad Cliente.

Volvemos a hacer un Quick generation y abrimos la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase DepositoObject.vb y en la carpeta Object/Auto la clase DepositoObject.Auto.vb.  En la carpeta Entities agregamos la clase Deposito.vb y en la carpeta Entities/Auto la clase Deposito.Auto.vb

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a DepositoGateway.vb y en Gateways/Auto la clase DepositoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase DepositoMapper.vb y en Mappers/Auto la clase DepositoMapper.Auto.vb

Si todo esto fue bien todavía nos va a quedar un error en la lista:

Value of type ‘PruebaRules.Entities.SucursalList’ cannot be converted to ‘PruebaRules.Objects.SucursalObjectList’.

Esto es en el método CargarGrillaSucursal del fromulario Form1. La linea que muestra el error es la siguiente:

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Esto es poruqe a la collección de Sucursales contenida en la entidad Cliente la cambiamos de ObjectList a EntityList. Corregida queda:

Dim mSucursalList As SucursalList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Cambiamos los tipos SucursalObjectList por tipos SucursalList.

Ahora tenemos un error en la siguiente linea:

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

Corregida queda:

Dim mSucursalListView As SucursalListView = New SucursalListView(mSucursalList)

Y esto es todo. Cuando lo ejecutemos en la grilla no vamos a ver automáticamente los campos de la agregación, pero van a notar que la agregacion ya es accesible.

Para terminar con esto incorporamos una Composición. Por ejemplo, nos cuentan que vamos a tener una lista de Contactos por cada Sucursal. Ya nos estamos imaginando una tabla Contacto con ID (PK, autonumérico, INT), IDSucursal (FK, INT), NombreApellido (VARCHAR(50)) y antes de establecer la relación entre Sucursal y Contacto, creamos un índice IsUnique sobre la columna ID

Ahora si podemos establecer la relación entre Contacto y Sucursal:

Volvemos una vez más al Modeler y hacemos Refresh.

Desplegamos Sucursal y vemos el nuevo elemento llamado ContactoCollection.

Antes de chequear ContactoCollection, debemos ir a la entidad Contacto a la propiedad GenerateAsChildOf la fijamos en el valor Sucursal. Ahora si, en la entidad Sucursal, chequemos ContactoCollection

Última vuelta al Modeler, Refresh y Quick generation, volvemos a la solución.

Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ContactoObject.vb y en la carpeta Object/Auto la clase ContactoObject.Auto.vb.

Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ContactoGateway.vb y en Gateways/Auto la clase ContactoGateway.Auto.vb.

Finalmente en la carpeta Mappers la clase ContactoMapper.vb y en Mappers/Auto la clase ContactoMapper.Auto.vb

Una vez hecho esto podemos acceder a un Contacto a través de la entidad Cliente, por ejemplo:

Dim mCliente As Cliente = ClienteMapper.Instance.GetOne(1)

mCliente.SucursalCollection.Item(2).ContactoCollection.Item(3).NombreApellido.Trim()

En la primera linea recuperamos el Cliente con ID igual a 1, en la segunda linea obtenemos el Nombre y Apellido del cuarto Contacto de la tercera Sucursal.

Repasando, con estos cuatro ejemplos que les presento vimos que las tablas pueden relacionarse de tres formas distintas en Cooperator, estas son:

  • Asociación, en el Modeler se ve como xxxxEntity, representa una relación que califica a la entidad base, en nuestro ejemplo, Sucursal Provincia.
  • Composición, en el Modeler se ve como xxxxCollection, es una relación 1 a n, hay que definir que la entidad colección sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.
  • Agregación, en el Modeler se ve como xxxxAggregate, es una relación 1 a 1 (las PK y FK deben se exactamente iguales), hay que definir que la entidad agregación sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.

Ýa en el estribo, tengamos en cuenta que, eL Mapper graba la Entidad con todas sus Composiciones y Agregaciones, no así con sus Asociaciones. Si el ID de la Asociación, no existe el motor SQL devuelve un error de integridad.

Acá les dejo el script de la base de datos resultante, el archivo Cooperator y el código generado para que jueguen.

Ojalá les sea útil   🙂

Para quien no conoce nada acerca de Cooperator Framework le recomiendo leer mi post anterior Introducción a Cooperator Framework.

Comencemos por descargar la herramienta, aquí encontramos el último release de Cooperator Modeler, al finalizar la descarga, descomprimimos el archivo y podremos ver el ejecutable de nombre CooperatorModeler.exe, con un doble click veremos lo su interfaz gráfica:

La prueba la vamos a realizar sobre SQL Server 2005, entonces vamos a abrir SQL Server Management Studio y creamos una base de datos de nombre Prueba, donde crearemos dos tablas, Cliente y Sucursal con una relación 1 a n.

La tabla Cliente tiene un campo ID (PK, INT y autonumérico) y RazonSocial (VARCHAR(50)).

La tabla Sucursal tiene un campo ID (PK, INT y autonumérico), IDCliente (FK, INT y autonumérico) y Denominacion (VARCHAR(50)).

Establecemos la relación entre la PK de Cliente y la FK de Sucursal.

Volvamos ahora al Modeler y hagamos un click sobre el botón New Model, cuando se abra el formulario hacemos click sobre el botón Get Databases, seleccionamos la nuestra y Ok:

Ahora veremos el Modelo:

Desplegamos las Entidades:

Vemos que Cliente tiene una colección de Sucursales, pero si la queremos incluir en el modelo es necesario chequearla.

Seleccionemos ahora Sucursal:

Podemos ver la propiedad GenerateAsChildOf con el valor Cliente, si esto no fuera así, antes no hubieramos podido seleccionar, en Cliente, la colección de Sucursales. También vemos que en Sucursal existe una ClienteEntity, si se chequea esto la entidad Sucursal incuirá la entidad Cliente (padre) con todas sus propiedades, incluirla o no es una desición de diseño.

El elemento ClienteString va a mostrar el campo de la tabla Cliente que nosotros determinemos, seleccionemos el elemento RazonSocial de la tabla Cliente, la propiedad GenerateAsDescriptionField vale True, esto define que RazonSocial es el campo que se mostrará cuando se invoque Sucursal.ClienteString.

Un punto muy importante que resuelve Cooperator es la aplicación del patrón LazyLoad y lo hace sin ningún esfuerzo extra de nuestra parte. Seleccionemos el elemento SucursalCollection de la entidad Cliente, vemos que la propiedad GenerateAsLazyLoad está en True, no hace falta nada más.

Supongamos que nuestro Modelo, por ahora termina acá, lo salvamos y hacemos un click sobre Code Generation

Hacemos un click sobre Generate SP, al finalizar otro click sobre Generate source code y cuando finalice seleccionamos una carpeta en la que querramos generar nuestra solución y luego un click sobre Generate Solution (para esta primera vez).

Modeler casi inmediatamente nos va a avisar que la solución se generó exitosamente y nos preguntará si queremos abrirla, le contestamos que si y vamos al Visual Studio que se está abriendo.

Abramos el formulario en modo diseño y agreguemos un ComboBox de nombre cboCliente y un DataGridView de nombre dtgSucursal.

Para hacer la prueba vamos a cargar a mano algunos registros en las tablas.

Ahora vamos a agregar código al formulario Form1, comenzando por los Imports:

Imports PruebaRules.Entities

Imports PruebaRules.Objects

Imports PruebaRules.Mappers

Imports PruebaRules.Views

 

En el evento Load del formulario disparamos CargarComboCliente

Private Sub CargarComboCliente()

Dim mClienteList As ClienteList = ClienteMapper.Instance.GetAll()

Dim mClienteListView As ClienteListView = New ClienteListView(mClienteList)

cboCliente.DisplayMember = «RazonSocial»

cboCliente.ValueMember = «ID»

Dim mCliente As New Cliente

mCliente.RazonSocial = «(Ninguno)»

mClienteListView.Add(mCliente)

mClienteListView.Sort(«RazonSocial», True)

cboCliente.DataSource = mClienteListView

cboCliente.SelectedIndex = 0

End Sub

Los Clientes se recuperan en un objeto ClienteList a través de su correspondiente Mapper,  en la segunda linea asigno este ClienteList a otro objeto ClienteListView que me permite más versatilidad. La tercer y cuarta linea de código me permiten definir que valores se van a ver en el ComboBox y cuales voy a recuperar al seleccionar un Cliente por medio de las propiedades DisplayMember y ValueMember respectivamente. En las siguientes tres lineas creo un objeto Cliente, le asigno el valor (Ninguno) y lo agrego en el ComboBox. Luego ordeno el objeto de tipo ClienteListView por medio de un Sort, le asigno dicho ListView a la propiedad DataSource del ComboBox y finalmente me posiciono en el valor (Ninguno) del ComboBox.

En el SelectedIndexChanged del ComboBox de Clientes disparo CargarGrillaSucursal:

Private Sub CargarGrillaSucursal()

Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))

Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)

dtgSucursal.DataSource = mSucursalListView

End Sub

Acá  usamos un objeto de tipo SucursalObjectList para obtener, por medio de su Mapper, la lista de Sucursales correspondiente al Cliente seleccionado en el ComboBox. Vemos que se usa un objeto de tipo SucursalObjectList y no uno de tipo SucursalList. Si vamos al Modeler y vemos el elemento SucursalCollection, tiene una propiedad de nombre GenerateAsType con valor ObjectList, si en esta propiedad seleccionamos el valor EntityList, podríamos haber usado un objeto de tipo SucursalList a cambio de SucursalObjectList.

A esta altura es necesario aclarar que un Object mapea uno a uno contra una tabla de la base de datos, mientras que en un objeto de tipo Entity no necesariamente es así.

Ahora F5 y a ver como anda.

No intenten hacer esto solos en sus casas.

Voy a subir el código de este post ya que va a ser la base de otros posteriores.

Prueba.coop es el archivo con que el Modeler persiste la información.

Script.sql, para crear la base de datos.

Prueba.zip la solución Visual Studio 2005

Espero les sirva y espero sus comentarios. 🙂

Una práctica fundamental en la Generacón Automática de Código es la Preservación del Código No Generado. En otras palabras que entre un ciclo de generación y el siguiente no perdamos el código que hayamos escrito manualmente.

Para esto AjGenesis prevee un mecanismo muy sencillo y seguro.

Este se basa en pequeñas modificaciones a los archivos .ajg y los Templates dónde exista código que se quiera preservar.

Para ejemplificar vamos a seguir con nuestro ejemplo del post anterior sobre AjGenesis, el ignoto SyP.

Comenzamos con el archivo BuildVbNet2.ajg. Como ya vimos en estos archivos se define cómo aplicar los Templates, precisamente con los comandos AjBasic TransformerManager.Transform. En los TransformerManager.Transform que generan archivos que contienen código que hayamos generado manualmente y no queremos perder, tendremos que agregar un parámetro en su invocación. Este parámetro es un string que define la palabra clave que el AjGenesis entenderá que delimita los bloques de código que se pretenden preservar, en el ejemplo este argumento será PRESERVE, pero podría ser cualquier otro String.

Nosotros vamos a suponer que en el SyP vamos a necesitar preservar código en todos sus archivos .vb

Tomo cómo ejemplo el primer TransformerManager.Transform que genera un archivo .vb que encuentro en BuildVbNet2.ajg:

TransformerManager.Transform(«Templates/VbNet2/HomeAspxVb.tpl»,»${Project.BuildDir}/Src/${Project.Name}.WebClient/Default.aspx.vb»,Environment)

y quedará:

TransformerManager.Transform(«Templates/VbNet2/HomeAspxVb.tpl»,»${Project.BuildDir}/Src/${Project.Name}.WebClient/Default.aspx.vb»,Environment,»PRESERVE»)

SI el parámetro existe y luego no se usa no es problema.

En cada archivo donde se quiera preservar código se debera definir en que lugar quedará alojado, esto se realiza ubicando dos Comentarios PRESERVE,

‘ PRESERVE

……….

‘ PRESERVE

todo el código contenido entre estos dos Comentarios se preservará a través de las sucesivas generaciones.

A continuación de la palabra clave se puede escibir lo que se necesite para hacer claro el código. En este ejemplo defino dos bloques de preservación de código, uno al comienzo y otro al final del archivo. Ambos bloques quedan definidos con los siguientes dos pares de Comentarios:

‘ PRESERVE 1

‘ PRESERVE

y

‘ PRESERVE 2

‘ PRESERVE

 El 1 y el 2 son capricosos, podría haber escrito cualquier otra cosa que los diferencie.

En el ejemplo, por una razón de claridad en el código, prefiero usarlo así.

‘ PRESERVE 1 comment start

‘ PRESERVE 1 comment end

y

‘ PRESERVE 2 comment start

‘ PRESERVE 2 comment end

Lo que trae problemas es no diferenciar los bloques, como en el siguiente ejemplo;

‘ PRESERVE

‘ PRESERVE

y

‘ PRESERVE

‘ PRESERVE

Ahora, si estás trabajando con un sólo bloque de código, no hay problema en que se use así:

‘ PRESERVE

‘ PRESERVE

pero tiene que ser un sólo bloque a preservar.

Para que esto quede establecido, tengo que agregar estas lineas en cada Template dónde quiera que se apliquen y este es todo el trabajo.

Es importante destacar que si se define un bloque PRESERVE en un archivo cualquiera, pero no se define en su correspondiente Template este bloque no se preservará y se perderá con sus Comentarios PRESERVE incluidos.

El código de ejemplo pueden descargarlo desde acá SyPP.zip, ahora a generar escribir y volvera regenerar sin caer en ataque de pánico.

Espero sus comentarios. 🙂

Cooperator es otro Generador de Código, con otras características.

El proyecto está pensado y desarrollado fundamentalmente por Daniel Calvin y Eugenio Serrano.

Si bien Cooperator está en evolución, ya hay mucha gente que está desarrollando software de alta calidad con esta herramienta y entre ellos me cuento.

Hay muy buen material de este proyecto, les recomiendo algunos links con los que se puede avanzar mucho:

Con esto hay para entretenerse un buen rato. Voy a volver sobre este proyecto ya que va a ser una de las vertientes principales de mi blog.

Un afectuoso saludo a la buena gente de Cooperator Framework. 🙂

Para quien no tenga experiencia en desarrollar software con AjGenesis, recomiendo leer Introducción a NAnt, Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte I y Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte II.

El código utilizado en esta nota puede descargarse desde acá.

Partiendo de ejemplos similares que usé en las notas anteriores, comenzamos con Natalia Requejo, estudiante avanzada de ingeniería electrónica, y avezada desarrolladora de software, a generar código.

Al ir diseñando el modelo de datos nos encontramos con la necesidad de referenciar a una misma tabla, más de una vez, desde otra única tabla. Por ejemplo, supongamos una Entidad Cliente, que se relaciona con Contacto, pero por cada Cliente podría tener varios Contactos, pero especializados, uno para Pagos y otro para Recepción. Planteamos el Modelo con los siguientes dos archivos XML:

<?xml version=»1.0″ encoding=»ISO-8859-1″ standalone=»yes»?>
<Entity>
    <Name>Cliente</Name>
    <Description>Cliente</Description>
    <SetName>Clientes</SetName>
    <Descriptor>Cliente</Descriptor>
    <SetDescriptor>Clientes</SetDescriptor>
    <SqlTable>Cliente</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>RazonSocial</Name>
            <Description>Razón Social</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>IDContactoPago</Name>
            <Description>Contacto de Pago</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
        </Property>
        <Property>
            <Name>IDContactoRecepcion</Name>
            <Description>Contacto de Recepción</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
        </Property>
    </Properties>
</Entity>

y

<?xml version=»1.0″ encoding=»ISO-8859-1″ standalone=»yes»?>
<Entity>
    <Name>Contacto</Name>
    <Description>Contacto</Description>
    <SetName>Contactos</SetName>
    <Descriptor>Contacto</Descriptor>
    <SetDescriptor>Contactos</SetDescriptor>
    <SqlTable>Contacto</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>Nombre</Name>
            <Description>Nombre</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>Apellido</Name>
            <Description>Apellido</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
    </Properties>
</Entity>

Al generar vimos que el actual esquema no contempla este caso. Hay nombres que se repiten y artefactos que se sobreescriben…

El tema de la nota es justamente cómo, modificando unas pocas línes de sólo cuatro Templates y agregando un nodo a un archivo XML, pudimos resolver muy rápidamente la situación.

Como primera medida agregamos un Nodo nuevo al archivo XML Cliente:

<?xml version=»1.0″ encoding=»ISO-8859-1″ standalone=»yes»?>
<Entity>
    <Name>Cliente</Name>
    <Description>Cliente</Description>
    <SetName>Clientes</SetName>
    <Descriptor>Cliente</Descriptor>
    <SetDescriptor>Clientes</SetDescriptor>
    <SqlTable>Cliente</SqlTable>
    <Properties>
        <Property>
            <Name>Id</Name>
            <Description>Id</Description>
            <Type>Id</Type>
            <SqlType>int</SqlType>
        </Property>
        <Property>
            <Name>RazonSocial</Name>
            <Description>Razón Social</Description>
            <Type>Text</Type>
            <SqlType>varchar(64)</SqlType>
        </Property>
        <Property>
            <Name>IDContactoPago</Name>
            <Description>Contacto de Pago</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
            <ReferenceClasification>Pago</ReferenceClasification>
         </Property>
        <Property>
            <Name>IDContactoRecepcion</Name>
            <Description>Contacto de Recepción</Description>
            <Type>IdRef</Type>
            <SqlType>int</SqlType>
            <Reference>Contacto</Reference>
            <ReferenceClasification>Recepcion</ReferenceClasification>
        </Property>
    </Properties>
</Entity>

Las modificaciones están resaltadas en verde con negrita. Se trata del Nodo ReferenceClasification, que voy a usar en los Templates, justamente como un clasificador de estas dos referencias, inicialmente iguales.

Vamos ahora a los Templates. Tomo cómo base los mismos del ejemplo Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte II.

Las Plantillas a modificar son cuatro, EntityFormWebVb.tpl, EntityCodeWebVb.tpl, EntityFormCodeWebVb.tpl y EntityWebVb.tpl

En el caso de EntityFormWebVb.tpl:

<#
message    «Generating Form for Entity ${Entity.Name}…»
include «Templates/VbNet2/VbFunctions.tpl»
include «Templates/EntityFunctions.tpl»
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
#>
<%@ Page Language=»vb» MasterPageFile=»~/MasterPages/MainMasterPage.master» AutoEventWireup=»false» CodeFile=»${Entity.Name}Update.aspx.vb» Inherits=»${WebPage.Prefix}${Entity.Name}UpdatePage» Title=»${Entity.Descriptor}»%>
<asp:Content ID=»Content1″ ContentPlaceHolderID=»MainContentPlaceHolder» Runat=»Server»>
            <br />
            <div class=»Normal»>
                <a href=»${Entity.SetName}.aspx» mce_href=»${Entity.SetName}.aspx»>${Entity.SetDescriptor}</a>
<%
    if IdEntity>0 then
%>
                <a href=»${Entity.Name}.aspx?Id=<%=IdEntity%>» mce_href=»${Entity.Name}.aspx?Id=<%=IdEntity%>»>${Entity.Descriptor}</a>      
<%
    end if
%>
            </div>
            <asp:Label id=»lblMensaje» runat=»server» CssClass=»Error» Visible=»False»></asp:Label>
            <asp:ValidationSummary id=»ValidationSummary1″ runat=»server» CssClass=»Error» DisplayMode=»List»></asp:ValidationSummary>
            <br />
            <table class=»DataTable» id=»tblDatos» cellspacing=»1″ cellpadding=»1″ width=»80%» border=»1″>
<#
for each Property in Entity.Properties where Property.Type <> «Id»
    if Property.Reference then
        RefDescriptorProperty = Property.Reference.DescriptorProperty
        RefIdProperty = IdProperty(Property.Reference)
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:DropDownList id=»ddl${Property.Reference.SetName}${Property.ReferenceClasification}» runat=»server» DataTextField=»${RefDescriptorProperty.Name}» DataValueField=»${RefIdProperty.Name}» DataSource=»<%# ${Property.Reference.SetName}${Property.ReferenceClasification} %>»>
                        </asp:DropDownList>
                    </td>
                </tr>
<#
    else
        if Property.Enumeration then
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:DropDownList id=»ddl${Property.Name}» runat=»server» DataTextField=»Description» DataValueField=»Id» DataSource=»<%# ${Property.Enumeration.Name}List %>»>
                        </asp:DropDownList>
                    </td>
                </tr>
<#
        else
#>          
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:TextBox id=»txt${Property.Name}» runat=»server» Text=»<%# Entity.${Property.Name} %>»>
                        </asp:TextBox></td>
                </tr>
<#              
        end if
    end if
end for
#>
            </table>
            <asp:Button id=»btnAccept» runat=»server» Text=»Accept»></asp:Button>
            <br />          
</asp:Content>

Nuevamente las modificaciones se encuentran en verde con negrita.

Verán que la modificación es simplemente insertar una variable que contenga el valor del nuevo Nodo ReferenceClasification del archivo XML, de esa forma logramos diferenciar las dos referencias.

Veamos que se modifica en los otros tres Templates:

EntityCodeWebVb.tpl

<#
include «Templates/VbNet2/Prologue.tpl»
#>
Imports ${Project.Name}.Services
Imports ${Project.Name}.Entities
Public Partial Class ${WebPage.Prefix}${Entity.Name}Page
    Inherits System.Web.UI.Page
    Public Entity As ${Entity.Name}
    Public Property IdEntity() As Integer
        Get
            Return DirectCast(ViewState(«IdEntity»), Integer)
        End Get
        Set(ByVal Value As Integer)
            ViewState(«IdEntity») = Value
        End Set
    End Property
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Public Entity${Property.Reference.Name}${Property.ReferenceClasification} as ${Property.Reference.Name}
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
    Public ReadOnly Property ${Property.Name}Description as String
        Get
            return Enumerations.Translate(Enumerations.${Property.Enumeration.Name}List, Entity.${Property.Name})
        End Get
    End Property
<#
    end for
#>
    Protected Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
        ‘Put user code to initialize the page here
        If Not IsPostBack Then
            IdEntity = CInt(Request(«Id»))
            Entity = ${Entity.Name}Service.GetById(IdEntity)
<#
    for each Property in Entity.Properties where Property.Reference
#>
            Entity${Property.Reference.Name}${Property.ReferenceClasification} = ${Property.Reference.Name}Service.GetById(Entity.${Property.Name})
<#
    end for
    for each Relation in Entity.Relations where Relation.RelationType=»Referenced»
#>
            ‘gvwData${Relation.Entity.SetName}.DataSource = ${Relation.Entity.Name}Service.GetBy${Entity.Name}Ex(IdEntity)
<#
    end for
#>
            DataBind()
        End If
    End Sub
End Class

Como en el Template anterior, con insertar la misma variable en dos líneas alcanza.

EntityFormCodeWebVb.tpl

<#
message    «Generating Form Code Behind for Entity ${Entity.Name}…»
include «Templates/VbNet2/VbFunctions.tpl»
include «Templates/EntityFunctions.tpl»
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
include «Templates/VbNet2/Prologue.tpl»
#>
Imports System
Imports System.Data
Imports ${Project.Name}.Services
Imports ${Project.Name}.Entities
Public Partial Class ${WebPage.Prefix}${Entity.Name}UpdatePage
    Inherits System.Web.UI.Page
    Public Entity As ${Entity.Name}
    Public Property IdEntity() As Integer
        Get
            Return DirectCast(ViewState(«IdEntity»), Integer)
        End Get
        Set(ByVal Value As Integer)
            ViewState(«IdEntity») = Value
        End Set
    End Property
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Public ${Property.Reference.SetName}${Property.ReferenceClasification} as DataView
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
    Public ReadOnly Property ${Property.Enumeration.Name}List as IList
        Get
            return Enumerations.${Property.Enumeration.Name}List
        End Get
    End Property
<#
    end for
#>
    Protected Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
        ‘Put user code to initialize the page here
        If Not IsPostBack Then
            If Request(«Id») Is Nothing then
                IdEntity = 0
                Entity = new ${Entity.Name}
            else
                IdEntity = CInt(Request(«Id»))
                Entity = ${Entity.Name}Service.GetById(IdEntity)
            End If
<#
    for each Property in Entity.Properties where Property.Reference
#>
            ${Property.Reference.SetName}${Property.ReferenceClasification} = Get${Property.Reference.SetName}${Property.ReferenceClasification}
<#
    end for
#>
                DataBind()
            if IdEntity>0 then
<#
    for each Property in Entity.Properties where Property.Reference
#>
                ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = Entity.${Property.Name}
<#
    end for
#>
<#
    for each Property in Entity.Properties where Property.Enumeration
#>
                ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = Entity.${Property.Name}
<#
    end for
#>
            else
<#
    for each Property in Entity.Properties where Property.Reference
#>
                if not Request(«${Property.Name}») is nothing then
                    ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue = CInt(Request(«${Property.Name}»))
                end if
<#
    end for
#>
            end if
        End If
    End Sub
    Private Function FormValidate() As Boolean
        Return True
    End Function
<#
    for each Property in Entity.Properties where Property.Reference
#>
    Private Function Get${Property.Reference.SetName}${Property.ReferenceClasification}() As IList
        Dim ds As DataSet
        ds = ${Property.Reference.Name}Service.GetList
        Dim dr As DataRow
        dr = ds.Tables(0).NewRow
<#
        if not Property.Required then
#>
        dr(«Id») = 0
        dr(«${Property.Reference.DescriptorProperty.Name}») = «»
        ds.Tables(0).Rows.Add(dr)
<#
        end if
#>
        Dim dw As New DataView(ds.Tables(0))
        dw.Sort = «${Property.Reference.DescriptorProperty.Name}»
        Return dw
    End Function
<#
    end for
#>
    Private Sub Update()
        if IdEntity>0 then
            Entity = ${Entity.Name}Service.GetById(IdEntity)
        else
            Entity = New ${Entity.Name}()
        end if
<#
for each Property in EntityNoIdSqlProperties
    if Property.Reference then
#>
        Entity.${Property.Name} = ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue
<#
    else
        if Property.Enumeration then
#>
        Entity.${Property.Name} = ddl${Property.Reference.SetName}${Property.ReferenceClasification}.SelectedValue
<#
        else
#>      
        Entity.${Property.Name} = txt${Property.Name}.Text
<#
        end if
    end if
end for
#>      
        If IdEntity = 0 Then
            ${Entity.Name}Service.Insert(Entity)
        Else
            ${Entity.Name}Service.Update(Entity)
        End If
    End Sub
    Private Sub btnAccept_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAccept.Click
        If Not IsValid Then
            Return
        End If
        Try
            If FormValidate() Then
                Update()
                If IdEntity = 0 Then
                    Server.Transfer(«${Entity.SetName}.aspx»)
                Else
                    Server.Transfer(«${Entity.Name}.aspx?Id=» & IdEntity)
                End If
            End If
        Catch Ex As Exception
            lblMensaje.Visible = True
            lblMensaje.Text = Ex.Message
        End Try
    End Sub
End Class

Acá tuvimos que modificar más líneas, pero en todos los casos se trata de insertar el valor del Nodo ReferenceClasification

EntityWebVb.tpl

<#
message    «Generating Page for Entity ${Entity.Name}…»
include «Templates/VbNet2/VbFunctions.tpl»
include «Templates/EntityFunctions.tpl»
EntitySqlProperties    = SqlProperties(Entity)
EntityNoIdSqlProperties    = SqlNoIdProperties(Entity)
EntityIdProperty = IdProperty(Entity)
#>
<%@ Page Language=»VB» MasterPageFile=»~/MasterPages/MainMasterPage.master» AutoEventWireup=»false» CodeFile=»${Entity.Name}.aspx.vb» Inherits=»${WebPage.Prefix}${Entity.Name}Page» Title=»${Entity.Descriptor}»%>
<%@ Register src=»~/Controls/Subtitle.ascx» mce_src=»~/Controls/Subtitle.ascx» TagName=»Subtitle» TagPrefix=»uc1″ %>
<asp:Content ID=»Content1″ ContentPlaceHolderID=»MainContentPlaceHolder» Runat=»Server»>
            <br />
            <div class=»Normal»>
                <a href=»${Entity.SetName}.aspx» mce_href=»${Entity.SetName}.aspx»>${Entity.SetDescriptor}</a>
                <a href=»${Entity.Name}Update.aspx?Id=<%=IdEntity%>» mce_href=»${Entity.Name}Update.aspx?Id=<%=IdEntity%>»>Update</a>
                <a href=»${Entity.Name}Delete.aspx?Id=<%=IdEntity%>» mce_href=»${Entity.Name}Delete.aspx?Id=<%=IdEntity%>»>Delete</a>
            </div>
            <br />
            <table class=»DataTable» id=»tblDatos» cellspacing=»1″ cellpadding=»1″ width=»80%» border=»1″>
<#          
for each Property in Entity.Properties
    if Property.Reference then
        RefDescriptorProperty = Property.Reference.DescriptorProperty
        RefIdProperty = IdProperty(Property.Reference)
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:HyperLink id=»lnk${Property.Name}» runat=»server» Text=»<%# Entity${Property.Reference.Name}${Property.ReferenceClasification}.${RefDescriptorProperty.Name} %>» NavigateUrl='<%# «${Property.Reference.Name}.aspx?Id=» & Entity${Property.Reference.Name}${Property.ReferenceClasification}.Id %>’>
                        </asp:HyperLink>
                    </td>
                </tr>
<#
    else
        if Property.Enumeration then
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:Label id=»lbl${Property.Name}» runat=»server» Text=»<%# ${Property.Name}Description %>»>
                        </asp:Label></td>
                </tr>
<#
        else
#>
                <tr>
                    <td>
                        ${Property.Description}</td>
                    <td>
                        <asp:Label id=»lbl${Property.Name}» runat=»server» Text=»<%# Entity.${Property.Name} %>»>
                        </asp:Label></td>
                </tr>
<#
        end if
    end if      
end for
#>
            </table>
</asp:Content>

En este último Template hay que insertar esta variable en una única línea.

Estas son todas las modificaciones que hay que realizar para extender el generador de forma tal que resulva estas relaciones. Con esto queda demostrado que AjGenesis presenta un modelo de generación de código fácilmente extendible.

Para quien se inicia en el mundo de estos Templates, una forma de sencilla de entenderlos, es leer paralelamente el Template y el Artefacto que genera, de esa forma se facilita la comprensión de este lenguaje de scripts que nos propone Ángel «Java» López.

Cuando tuve esto funcionando le mostré a Ángel la idea y me respondió:

……Podrias poner, en algun momento, para completar el modelo, algo como:    if Property.Reference and not Property.ReferenceClassification and Property.Name.BeginsWith(«Id») then
        Property.ReferenceClassification = Property.Name.Substring(….)
    end if……

 

 

Para el que guste, se puede seguir por allí.

Ángel suele hacer hincapié en esta interesante idea de Completar el Modelo

Si desean pueden bajarse el código de ejemplo desde acá, hacer pruebas y espero quieran dejar sus comentarios. 🙂

Sigo en la misma linea de la nota anterior.

Ahora la idea es generar una aplicación web completa en VB.NET para Visual Studio 2005, trabajando con SQL Server 2005.

Esta vez subo un conjunto de archivos somo ejemplo para que quien quiera haga sus pruebas.

Los archivos pueden bajarse desde este link SyP.zip 

Pero para esto recomiendo tener funcionando el HelloWorld del ejemplo anterior, de esta forma ya tenemos garantizado el funcionamiento de AjGenesis con NAnt correctamente.

AjGenesis, el generador de código de Ángel “Java” López, permite, entre otras cosas, definir el lenguaje en el que se quiere trabajar, Ángel tiene ejemplos publicados en VB.NET, C#, PHP, Java, en cuanto a bases de datos hay para MySQL, SQL Server 2000, y SQL Server 2005, cualquiera que quiera extender este modelo sólo debe escribir el Template y agregarlo en la Tarea que corresponda.

Como si fuera poco Ángel publica ejemplos con Modelos en Domain Driven Design, usando NHibernate, por separado y combinados también.

En esta versión de AjGenesis, Ángel, agregó un archivo .xml de configuración, en nuestro caso de nombre VbNet2.xml, en el que se define el Dialecto de Programación, el Dialecto de Base de Datos y valores de la conexión a la misma.

Los Dialectos definen el juego de Templates que se van a utilizar, en nuestro ejemplo serán los correspondientes a VB.NET para Visual Studio 2005 y SQL Server 2005.

Los Templates o Plantillas son archivos con extensión .tpl y las Tareas o Tasks son archivos .ajg, ambos archivos de texto.

Los Templates son la base del código que se autogenerará a partir de AjGenesis, los Templates “leen” el Modelo, la estructura de datos, de un conjunto de archivos .xml.

En estos archivos .xml se definen los nombres de las Entidades y campos, los tipos, longitudes y las relaciones entre Entidades.

Existe un archivo .xml que define al conjunto de las Entidades que componen al Modelo, en nuestro ejemplo será Project.xml, luego por cada Entidad tendremos un archivo .xml.

Como escribí antes, los Templates «leen» el Modelo de estos archivos .xml, los Templates se aplican al ejecutar las Tareas.

Por último para ejecutar las Tareas necesitamos NAnt, en un archivo .build se orquestan las Tareas y por línea de comando se ejecuta el archivo NAnt.El ejemplo que voy a desarrollar, como escribí antes, se continúa con el HelloWorld de la nota anterior, es decir, voy a seguir usando la misma estructura de directorios, a quién le agrade podrá usar la misma, sino el modelo permite cualquier otra, pero hay que tener particular cuidado de corregir todas las referencias necesarias.

El ejemplo que voy a tomar es el la aplicación AjSaaS que Ángel “Java” López publica en AjGenesisExamples3.

Pueden bajarse el conjunto de archivos desde acá: SyP.zip, en mi ejemplo yo lo ubico en la carpeta SyP, debajo de la misma veremos el siguiente árbol:

Carpeta Raiz

En la carpeta SyP, nuestra raiz, encontramos el archivo NAnt, en este caso, AjSaaSVbNet2.build.

En Libraries encontraremos las dlls que necesitaremos, este ejemplo usa AjFramework para acceder a datos, entonces encontraremos una carpeta AjFramework y dentro de ella AjFramework.Core.dll y AjFramework.Data.dll. Para quien quiera conocer el AjFramework, otro desarrollo de Ángel “Java” López lo puede encontrar acá, AjFramework, con código fuente disponible.

En Projects se almacenan los Modelos, un Modelo por carpeta, nosotros vamos a ver una carpeta con nombre AjSaaS, que contiene Project.xml, en el que se definen las Entidades intervinientes, veamos: 

<?xml version=»1.0″ encoding=»ISO-8859-1″ standalone=»yes»?>

<Project>

            <Name>AjSaaS</Name>

            <Description>A sample Software as a Service application</Description>

            <Company>ajlopez.com</Company>

            <CompanyName>ajlopez</CompanyName>

            <SystemName>AjSaaS</SystemName>

            <Language>En</Language>

            <Prefix>AjSaaS</Prefix>

            <OpenSource>true</OpenSource>

            <Model>

                        <Entities>

                                    <Entity Source=»Entities/Tenant.xml»/>

                                    <Entity Source=»Entities/Provider.xml»/>

                                    <Entity Source=»Entities/User.xml»/>

                                    <Entity Source=»Entities/Application.xml»/>

                                    <Entity Source=»Entities/Role.xml»/>

                                    <Entity Source=»Entities/UserRole.xml»/>

                                    <Entity Source=»Entities/TenantApplication.xml»/>

                                    <Entity Source=»Entities/Entity.xml»/>

                                    <Entity Source=»Entities/EntityProperty.xml»/>

                                    <Entity Source=»Entities/EntityApplication.xml»/>

                                    <Entity Source=»Entities/Contact.xml»/>

                                    <Entity Source=»Entities/ToDoItem.xml»/>

                        </Entities>

             </Model>

</Project>

 

 

En la carpeta Projects también encontramos otras dos carpetas más, Entities y Technologies. En Entities tenemos un archivo .xml por cada Entidad que compone al Modelo, por ejemplo Role.xml: 

 

<?xml version=»1.0″ encoding=»ISO-8859-1″ standalone=»yes»?>

<Entity>

            <Name>Role</Name>

            <Description>Role</Description>

            <SetName>Roles</SetName>

            <Descriptor>Role</Descriptor>

            <SetDescriptor>Roles</SetDescriptor>

            <SqlTable>roles</SqlTable>

            <Properties>

                        <Property>

                                    <Name>Id</Name>

                                    <Description>Id</Description>

                                    <Type>Id</Type>

                                    <SqlType>int</SqlType>

                        </Property>

                        <Property>

                                    <Name>RoleName</Name>

                                    <Description>Role Name</Description>

                                    <Type>Text</Type>

                                    <SqlType>varchar(40)</SqlType>

                                    <Required>true</Required>

                        </Property>

                        <Property>

                                    <Name>Description</Name>

                                    <Description>Description</Description>

                                    <Type>Text</Type>

                                    <SqlType>varchar(200)</SqlType>

                                    <Required>true</Required>

                        </Property>

                        <Property>

                                    <Name>IdApplication</Name>

                                    <Description>Application</Description>

                                    <Type>IdRef</Type>

                                    <SqlType>int</SqlType>

                                    <Reference>Application</Reference>

                        </Property>

            </Properties>

</Entity>

 

 

 

Los primeros nodos son respectivamente para definir Nombre, Descripción, Nombre de Conjunto, Descriptor, Descriptor de Conjunto, y nombre de tabla de base de datos, esto es definir para una determinada Entidad cómo se la conocerá y describirá en el código que se autogenere acerca de ella.

Después está Properties, una colección de nodos Property, que definen los campos de la tabla de base de datos y las propiedades de la Entidad relacionada, todas las Property tienen nodos Name, Description, Type y SqlType.

Type puede valer:

Id: Clave Primaria de la tabla de base de datos.

Int: Cualquier entero.

Date: para fechas.

Decimal: para decimales.

Text: Cualquier tipo de cadena.

IdRef: para definir relaciones con otras Entidades y Foreign Keys en las relaciones de tablas de bases de datos.

 

Si Type vale IdRef, entonces hay que agregar el nodo Reference, para referenciar a la tabla con que se quiere vincular.

El nodo SqlType define el tipo SQL Server.

Con esto tenemos bien entendido el Modelo, ahora vamos a la carpeta Technologies, en ella encontraremos el archivo VbNet2.xml: 

<Technology>

            <Programming>

                        <Dialect>VbNet2</Dialect>

            </Programming>

            <Database>

                        <Dialect>MsSql2005</Dialect>

                        <Name>AjSaaS</Name>

                        <Username>sa</Username>

                        <Prefix>AjSaaS_</Prefix>

                        <Trusted_Connection>yes</Trusted_Connection>

                        <Host>SPICA\SQLEXPRESS</Host>

            </Database>

</Technology> 

 

 

Podemos leer claramente cómo se definen los Dialectos y los valores de conexión a base de datos, como escribí más arriba.

Ya estamos en condiciones de ver el archivo NAnt, AjSaaSVbNet2.build, acá su contenido: 

 

<?xml version=»1.0″?>

 <project name=»AjSaaS VB.NET 2.0 Web Site» default=»04-Build»>

     <property name=»project.name» value=»AjSaaS»/>

     <property name=»technology» value=»VbNet2″/>

     <property name=»src.dir» value=».»/>

     <property name=»templates.dir» value=»${src.dir}/Templates»/>

     <property name=»tasks.dir» value=»${src.dir}/Tasks»/>

     <property name=»ajgenesis.dir» value=»C:.5″/>

     <property name=»nanttasks.dir» value=»${ajgenesis.dir}/bin»/>

     <property name=»website.dir» value=»${src.dir}/Build/${project.name}/${technology}/${project.name}/src/${project.name}.WebClient»/>

     <property name=»project.dir» value=»${src.dir}/Projects/${project.name}»/>

     <property name=»lib.dir» value=»${src.dir}/Libraries»/>

     <property name=»build.dir» value=»${src.dir}/Build/${project.name}/${technology}»/>

 

     <target name=»00-Clean» description=»Cleans Build Directory»>

         <delete dir=»${build.dir}» if=»${directory::exists(build.dir)}» />

     </target>

     <target name=»01-LoadTasks» description=»Loads AjGenesis Tasks» >

         <loadtasks assembly=»${nanttasks.dir}/AjGenesis.NAnt.dll» />

     </target>

     <target name=»02-Init» depends=»01-LoadTasks» description=»Init the AjGenesis model, Create build directory»>

         <mkdir dir=»${build.dir}»/>

         <loadmodel model=»${project.dir}/Project.xml»/>

         <loadmodel model=»${project.dir}/Technologies/${technology}.xml»/>

        <executetask task=»${tasks.dir}/BuildProject2.ajg»/>

        <executetask task=»${tasks.dir}/BuildTechnology.ajg»/>

    </target>

    <target name=»03-BuildSQL» depends=»02-Init»>

        <executetask task=»${tasks.dir}/BuildSql.ajg»/>

    </target>

    <target name=»04-Build» depends=»02-Init»>

        <executetask task=»${tasks.dir}/BuildProg.ajg»/>

        <copy todir=»${build.dir}/Src/Libraries»>

            <fileset basedir=»${lib.dir}/AjFramework»>

                <include name=»**/*»/>

            </fileset>

        </copy>

    </target>

    <target name=»05-DeploySQL» depends=»03-BuildSQL»>

        <exec program=»${build.dir}/Sql/CreateDatabase.bat»/>

    </target>

</project

 

 

 

Las primeras líneas del archivo ya están explicadas en la nota anterior, veremos algunas pequeñas diferencias como la definición de la carpeta de Technologies.

En la Target 02-Init podemos ver cómo se carga el Modelo y las Technologies a utilizar, luego se ejecutan las Tasks BuildProject2.ajg y BuildTechnologies.ajg.

Las Targets que más nos interesan son:

 

 

03-BuildSql, crea un script que creará todas las tablas, stored procedures, claves primarias y foráneas y se establecen las relaciones

04-Build, genera la solución con sus proyectos.

05-DeploySql, ejecuta 03-BuildSql y a continuación crea un archivo de lotes de nombre CreateDatabase.bat que ejecuta el script creado en la Targer 03-BuildSql y lo ejecuta. De esta forma se crea la base de datos automáticamente.

Las Tareas se ejecutan de la siguiente forma, abrimos una ventana de línea de comando y nos ubicamos en la carpeta que contiene al archivo NAnt AjSaaSVbNet2.build.

Si tipeamos NAnt y <Enter> se ejecutará la Target por default que es 04-Build, como se define en la segunda linea del archivo: 

 

 

<project name=»AjSaaS VB.NET 2.0 Web Site» default=»04-Build»> 

 

 

Si quisiéramos ejecutar una Target que no sea la definida como default, por ejemplo, 03-BuildSql, tipeamos lo siguiente: 

 

 

NAnt 03-BuildSql 

En nuestro caso ejecutando las Targets 04-Build y 05-DeploySql estaría todo hecho.

Para ejecutar dos Tareas con NAnt tipeamos lo siguiente: 

 

 

NAnt 04-Build 05-DeploySql 

La salida de esto es una larga lista de mensajes que al final incluye el feliz BUILD SUCCEEDED, sino algo falló. Hay que revisar las definiciones y reintentarlo. NAnt tiene que haber creado unas carpetas debajo de SyP con la siguiente estructura: 

Carpeta Build

 

En SyP\Build\AjSaaS\VbNet2\Sql están CreateDatabase.bat y Database.sql, que son respectivamente el encargado de ejecutar el script contra la base de datos y el script en si mismo.

En SyP\Build\AjSaaS\VbNet2\src está el archivo de la solución con todo lo que lo compone.

La solución se genera de tal modo que haciendo un doble click sobre el archivo .sln, para abrirlo con Visual Studio 2005 y con un simple F5 en ese ambiente, ya ejecuta sin ningún error ni ajuste adicional.

Como dice Ángel “Java” Lópezesto es ver la luz

Ahora sólo tienen que defiir un Modelo propio, crear su propio Project.xml y los archivos .xml de las Entidades y ya están en condiciones de generar una nueva aplicación.

Bienvenidos. )

Ya les conté someramente qué es NAnt, ahora…¿qué es AjGenesis?…AjGenesis es un generador de artefactos de texto de código abierto.  Para más información dirigirse a http://www.ajlopez.com/ajgenesis

Puede bajarse el generador, desde AjGenesis-0.5, ejemplos desde AjGenesisExamples.zip, más ejemplos en AjGenesisExamples3.zip, documentación en http://www.ajlopez.net/ajgenesis/documentation.php

AjGenesis se basa en un modelo soportado en XML y plantillas.Para utilizar este generador sólo hay que descargarlo y descomprimirlo en una carpeta.

AjGenesis funciona con el Framework 1.1 en adelante, nosotros vamos a necesitar la versión 2.0 que es el que corresponde a Visual Studio 2005. Tambien hay que tener instalado NAnt 0.85La idea es, mediante NAnt, ejecutar Tareas de AjGenesis en un orden que puede definirse. En algunos casos serán comandos NAnt (por ejemplo, crear o eliminar carpetas, archivos, etc.) y en otros archivos de lotes .ajg donde se define, por medio de los templates, que hacer con el modelo (los archivos XML), en sintaxis AjBasic, por ejemplo: 

TransformerManager.Transform(«Templates/VbNet2/EntityVb.tpl»,»${Project.BuildDir}/Src/${Project.Name}.Entities/${Entity.Name}.vb»,Environment) 

En esta linea se está aplicando la plantilla EntityVb.tpl a la entidad que se soportará en un archivo cuya ubicación y nombre resulte de  

 ${Project.BuildDir}/Src/${Project.Name}.Entities/${Entity.Name}.vb 

dónde;

${Project.BuildDir} es la carpeta en la que se guarda el código autogenerado, se define en NAnt, en el archivo .build,  

${Project.Name} se define en el archivo Project.xml, es el archivo raiz del modelo de datos y

${Entity.Name} se define en el archivo XML del mismo nombre, descriptor de la entidad. 

Una vez copiados NAnt y AjGenesis como se describe anteriormente, para asegurarse que todo funciona correctamente es conveniente ejecutar un ejemplo de los que Ángel Lopez nos deja.

Obviamente ejecutaremos alguna variante de Hello World

Para verificar la instalación de NAnt abramos una ventana de línea de comandos y tipeamos NAnt y Enter, como no va a encontrar ningún archivo .build mostrará lo siguiente: 

NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)

Copyright (C) 2001-2006 Gerry Shaw

http://nant.sourceforge.net  

BUILD FAILED 

Could not find a ‘*.build’ file in ‘C:\…….’ 

For more information regarding the cause of the build failure, run the build again in debug mode. 

Try ‘nant -help’ for more information 

Ahora verifiquemos que funcione AjGenesis.

Supongamos que ya bajamos el archivo AjGenesis-0.5.zip, lo descomprimimos y lo copiamos en C:\Lab\AjLopez\AjGenesis-0.5, también descomprimimos los archivos AjGenesisExamples.zip y AjGenesisExamples3.zip dentro de C:\Lab\AjLopez\AjGenesis-0.5\examples, los nombres de las carpetas que doy son arbitrarios, si los querés modificar sólo tendrías que tener en cuenta de modificar los valores en el archivo .build de NAnt, en las tareas .ajg y en los templates .tpl

Busquemos el ejemplo HelloWorldNAnt, en su carpeta encontraremos un archivo de nombre default.build, este es el archivo NAnt con el siguiente contenido: 

<?xml version=»1.0″?><project name=»Example 1″ default=»build»>

    <property name=»src.dir» value=».»/>

    <property name=»build.dir» value=»${src.dir}/Build»/>

    <property name=»templates.dir» value=»${src.dir}/Templates»/>

    <property name=»models.dir» value=»${src.dir}/Models»/>

    <property name=»tasks.dir» value=»${src.dir}/Tasks»/>

    <property name=»ajgenesis.dir» value=»${src.dir}/../..»/>

    <property name=»nanttasks.dir» value=»${ajgenesis.dir}/bin»/>

       <target name=»clean» description=»cleans build directory»>

            <delete dir=»${build.dir}» verbose=»true» if=»${directory::exists(build.dir)}» />

       </target>

       <target name=»loadtasks» description=»loads AjGenesis tasks»>

            <loadtasks assembly=»${nanttasks.dir}/AjGenesis.NAnt.dll» />

       </target>

       <target name=»init» depends=»loadtasks» description=»init the AjGenesis model, create build directory»>

            <mkdir dir=»${build.dir}»/>

            <loadmodel model=»${models.dir}/Project.xml»/>

      </target>

     <target name=»build» depends=»init»>

            <executetask task=»${tasks.dir}/Build.ajg»/>

    </target>

</project> 

Vemos un archivo XML, en los primeros tags se definen variables que contienen la rutas de algunas carpetas que se irán utilizando más adelante:

src.dir es la carpeta raiz,

build.dir es dónde se aloja el código autogenerado,

templates.dir es dónde el generador encuentra sus plantillas,

models.dir contiene los archivos XML que definen el modelo,

tasks.dir acá encontramos los archivos .ajg que definen las tareas,

ajgenesis.dir es la carpeta dónde se encuentra el generador de código, revisar bien esta definición,

nanttask.dir contiene las dll’s de AjGenesis que trabajan con NAnt.

A continuación hay cuatro nodos target, cada uno representa una tarea en NAnt, básicamente tiene un atributo name, con el nombre, que es obligatorio, description es opcional y depends, que lo encontramos en el último tag target, indica que esta tarea no se ejecutará hasta que se finalice init.

Volvamos a la ventana de linea de comandos que ya teníamos abierta, ubiquémonos en la carpeta que contiene el archivo default.build del ejemplo HelloWorldNAnt, una vez posicionados en esta carpeta tipeamos NAnt y Enter, si todo anda bien, veremos lo siguiente: 

C:\Lab\AjLopez\AjGenesis-0.5\examples\HelloWorldNAnt>NAnt

NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)

Copyright (C) 2001-2006 Gerry Shaw

http://nant.sourceforge.net

Buildfile: C:/Lab/AjLopez/AjGenesis-0.5/examples/HelloWorldNAnt/default.build

Target framework: Microsoft .NET Framework 2.0

Target(s) specified: build  

loadtasks: 

[loadtasks] Scanning assembly «AjGenesis.NAnt» for extensions. 

init:  

build: 

Generating Solution HelloWorld

Creating Directories

Creating Solution File

Creating VB.Net Project

Creating C# Project 

BUILD SUCCEEDED 

Total time: 0.4 seconds. 

Al ejecutar NAnt, este busca en la misma carpeta un archivo con extensión build, si lo encuentra lo ejecuta, si no, da el error que vimos anteriormente.Si vamos a ver en la carpeta C:\Lab\AjLopez\AjGenesis-0.5\examples\HelloWorldNAnt\Build veremos que hay una carpeta de nombre HelloWorld que contiene la solución y dos carpetas que contienen sendos proyectos uno en C# y otro en VB. 

Si funcionó, a festejar, sino escribime y vemos cómo te puedo ayudar. 🙂

Asesoramiento a Organizaciones Públicas y Privadas.

Cursos a grupos ya constituidos.

Charlas de orientación.

Metodologías Ágiles.

Startups.

Visitas

  • 146.542 hits

Mejor calificado

del.icio.us