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. 🙂