Generando un modelo desde la base de datos usando AjGenesis

Published on Author lopezLeave a comment

AjGenesis, mi proyecto open source de generación de código, usa tareas, templates (plantillas) y un modelo de libre definición, para generar artefactos de texto, en general código fuente, pero también archivos cualesquiera, como configuraciones, definición de proyectos, etc. Muchos de los ejemplos que están en el proyecto y otros que publiqué aparte, usan modelos serializados como archivos XML o de texto. Pero el modelo inicial puede ser cualquier entrada: no sólo esos archivos, sino cualquier fuente de información que nos dé alguna base de modelo. Hasta podemos armar archivos de modelo (recuerden, el modelo lo deciden uds, dónde va, qué tiene, es todo libre) desde otro modelo. Hace unos dos años, escribí sobre generar el modelo desde la base de datos. En mi opinión, la base de datos no es el modelo más expresivo, pero es uno muy ubicuo: puede servir como punto de partida, y desde la base de datos, generar un modelo que podemos refinar posteriormente.

(sobre el tema de modelos pueden leer Modelos y metamodelos en AjGenesis: los metamodelos no son necesarios ni obligatorios, pero es un tema que estoy agregando en el proyecto, como opcional).

(un tipo de modelo a explorar Otro modelo para AjGenesis)

Hoy quiero entonces volver sobre el tema de generar un modelo desde otro modelo, esta vez usando el esquema de la base de datos que tengamos entre manos. Es sólo un ejemplo “prueba de concepto”. Pero en el proyecto ágil en que estoy participando, el equipo tomó la base que ya tenían y generó un modelo usando técnicas como las de este ejemplo, usando AjGenesis, y ahora, ya abandonaron la base inicial, y usan directamente el modelo generado alguna vez y refinado desde entonces.

Pueden bajar el ejemplo actual de mi Skydrive DatabaseExample01.zip. (el código está en el repositorio de código en Codeplex, bajo el directorio examples\DatabaseExamples). El .zip de Skydrive tiene todo lo necesario, incluso la versión en desarrollo de AjGenesis ya compilada.

Despues de bajarlo y expandirlo, tenemos estas carpetas:

Hay un solo proyecto de ejemplo. EL contenido de Projects\Northwind\Metadata.xml:

<Metadata>
  <Project>
    <Name>Northwind</Name>
  </Project>
  <Database>
    <Name>Northwind</Name>
    <ConnectionString>server=.\SQLEXPRESS;database=Northwind;Integrated Security=true</ConnectionString>
  </Database>
</Metadata>

Describe el string de conexión a usar para llegar a la base de ejemplo. Estoy usando la Northwind, en SQL Server (pueden usar el full o el express). Si no tienen esa base de datos, los scripts de creación están en el directorio Sql.

Para generar el proyecto, el modelos con sus entidades, ejecutar:

MakeModelFromDatabase.cmd

AjGenesis (compilado en el directorio Bin) comienza su trabajo:

El comando que ejecutamos contiene:

Bin\AjGenesis.Console Projects\Northwind\Metadata.xml Tasks\DatabaseProcess.ajg

Este comando carga el contenido de Metadata.xml como modelo en memoria, y ejecuta la tarea DatabaseProcess.ajg escrita en el lenguaje dinámico que uso AjBasic. Esta tarea usa las vista de Information Schema para obtener información acerca de la estructura de la base de datos (usando estas vistas, que están definidas en el estándar SQL, y presentes en varias bases de datos, nos abre la puerta para conseguir trabajar con otras marcas que no sean MS SQL Server; debería probar, por ejemplo, con Oracle).

La tarea crea el archivo Projects\Northwind\Project.xml:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<Project>
  <Name>Northwind</Name>
  <Model>
    <Entities>    
      <Entity Source="Entities/Customer.xml"/>    
      <Entity Source="Entities/Shipper.xml"/>    
      <Entity Source="Entities/Supplier.xml"/>    
      <Entity Source="Entities/Order.xml"/>    
      <Entity Source="Entities/Product.xml"/>    
      <Entity Source="Entities/OrderDetail.xml"/>    
      <Entity Source="Entities/CustomerCustomerDemo.xml"/>    
      <Entity Source="Entities/CustomerDemographic.xml"/>    
      <Entity Source="Entities/Region.xml"/>    
      <Entity Source="Entities/Territory.xml"/>    
      <Entity Source="Entities/EmployeeTerritory.xml"/>    
      <Entity Source="Entities/Employee.xml"/>    
      <Entity Source="Entities/Category.xml"/>
    </Entities>
  </Model>
</Project>

El proyecto y las entidades son parecidas a las que usé en Generando aplicaciones con AjGenesis (pero no es igual, estoy experimentando con mejoras). Parte del contenido del archivo generado Projects\Northwind\Entities\Customer.xml nos da una idea de lo que se captura de una “Entity”:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<Entity>
  <Name>Customer</Name>
  <Description>Customer</Description>
  <SetName>Customers</Name>
  <Descriptor>Customer</Descriptor>
  <SetDescriptor>Customers</SetDescriptor>
  <SqlCatalog>Northwind</SqlCatalog>
  <SqlSchema>dbo</SqlSchema>
  <SqlName>Customers</SqlName>
  <Properties>
    <Property>
      <Name>CustomerID</Name>
      <Description>CustomerID</Description>
      <SqlName>CustomerID</SqlName>
      <SqlLength>5</SqlLength>
      <IsKey>True</IsKey>
      <SqlType>nchar</SqlType>
      <SystemType>String</SystemType>
      <IsNullable>False</IsNullable>
    </Property>
....
  </Properties>
</Entity>

La lógica principal de todo este proceso de generación, reside en la tarea Tasks\DatabaseProcess.ajg, de nuevo escrita en AjBasic (I love this language! 🙂 un fragmento:

cmd = new System.Data.SqlClient.SqlCommand()
cmd.Connection = conn
cmd.CommandText = "select * from Information_Schema.Tables where Table_Type = 'BASE TABLE'"
conn.Open()
PrintLine "Reader"
dr = cmd.ExecuteReader()
Tables = CreateList()
while dr.Read()
  PrintLine "Table " & dr.Item("Table_Name") & ": " & dr.Item("Table_Type")
  Table = CreateObject()
  Table.SqlCatalog = dr.Item("Table_Catalog")
  Table.SqlSchema = dr.Item("Table_Schema")
  Table.SqlName = dr.Item("Table_Name")
  Table.Name = Table.SqlName.Replace(" ","")
  
  if IsPlural(Table.Name) then
    Table.SetName = Table.Name
    Table.Name = ToSingular(Table.Name)
  else
    Table.SetName = ToPlural(Table.Name)
  end if
  
  Table.Descriptor = Table.Name
  Table.SetDescriptor = Table.SetName
  
  Table.Description = Table.Name
  
  Tables.Add(Table)
end while
dr.Close()

Posibles mejoras

Como escribí más arriba, este ejemplo es una “prueba de concepto”; una versión anterior la usamos en un proyecto real. Pero hay varios puntos a mejorar:

– Usar el modelo generado para generar una aplicación que funcione, con scaffolding o no.

– Soportar más metadata que obtengamos via Information Schema

– Probar con otras marcas de bases de datos, y con bases de datos reales

– Tratamiento de relaciones: detectar borrados en cascadas, soportar claves primarias compuestas, otras acciones.

Otros posts relacionados (aparte de los ya mencionados):

Generando Código: Hello World con AjGenesis
AjGenesis: Modelo generado desde los assemblies
Modelo textual para generación de código con AjGenesis

Si es la primera vez que se topan con generación de código con AjGenesis, tengo varios posts escritos. Recomiendo un video, producido gracias a la comunidad ALT.NET Hispano: Resultado de la VAN ALT.NET Hispano sobre Generación de Código

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Leave a Reply

Your email address will not be published. Required fields are marked *