viernes, 2 de enero de 2009

Generación de Código

CodeDom

Hoy comentaré acerca de uno de los tópicos menos conocidos dentro del mundo de .NET, y que a mí personalmente me apasiona por el potencial de explotación que nos brinda. Y aunque existen varios posts en la red que hablan acerca de esto, aun es poca la difusión que tiene, ya que en mi opinión todo programador debería tener una mínima referencia a este tema.

CodeDom (Code Document Object Model) es un conjunto de clases agrupados en el namespace System.CodeDom, que nos proporciona la funcionalidad necesaria para representar tipos .NET a partir del mapping correspondiente con las clases de CodeDom. Este subsistema se basa en arboles binarios para crear una representación de grafo de nuestro código. Una vez que tenemos el grafo de código, podemos indicarle cualquiera de los proveedores de código para obtener la representación específica. Un proveedor de código, es aquel que se encarga de transformar ese grafo de tipos a código especifico, por ejemplo C#, VB, y cualquier otro lenguaje para el que exista un proveedor 

La generación de código es un concepto muy viejo, tanto como las diferentes soluciones que se han implementado, desde la concatenación de cadenas hasta llegar a CodeDom, pero también la orientación que se le ha dado, algunas de los usos que se le ha dado va desde la generación y compilación dinámica de código en Runtime, hasta la creación de ORM (Object Relational Mapping). Estos últimos son, para mí, la orientación más interesante y es en la que basare mi ejemplo de hoy.

El problema que intenta resolver los ORM es básicamente tratar de crear objetos que interactúen entre distintos ambientes, es decir, intermediarios. 

Diferentes tipos de datos


Aunque cada tecnología cuenta con diferentes tipos de datos y a veces con diferentes implementaciones para cada uno de ellos, un tipo de dato siempre corresponde a una unidad lógica fundamental en la estructura de representación de información en un entorno dado. Por ejemplo SQL Server, C#, XML 

Puesto que casi siempre (en la mayoría de los casos), tenemos información acerca de los tipos de datos con los que trabajamos (sobre todo en ambientes strongly-typed como .NET), independientemente de la tecnología. Por ejemplo, cuando trabajamos con SQL, conocemos información acerca de las columnas, como el tipo, longitud, si es foreign key, si es primary key, si es valor único, etc. Esto nos permite hacer una relación de tipos entre diferentes ambientes de trabajo para la generación de mappers, y así, crear una representación respectiva de una entidad 

Por ejemplo dada la siguiente tabla de SQL Server
Alumnos
clip_image002
Deberíamos poder generar automáticamente las clases que interactuaran con el acceso, persistencia y entidades contenedores de datos, ya que tenemos suficiente información para inferir el mapeo correspondiente a cada uno de estos tipos “desconocidos” para el entorno .NET.

Al final una simple clase contenedora de un row de la tabla de Alumnos podría verse así: 


namespace Blog.CodeDom.GeneratedEntities
{ 
    using System;

    public class Alumno
    { 
        private Int32 _idAlumno;
        private String _nombre;
        private String _apellidoPaterno;
        private String _apellidoMaterno;
        private String _direccion;
        private Int32 _idSalon; 
        private Int32 _idEdificio;

        public Alumno() 
        {

        }

        public Alumno(Int32 _idAlumno, String _nombre, String _apellidoPaterno, String _apellidoMaterno, String _direccion, Int32 _idSalon, Int32 _idEdificio) 
        { 
            this._idAlumno = _idAlumno; 
            this._nombre = _nombre; 
            this._apellidoPaterno = _apellidoPaterno; 
            this._apellidoMaterno = _apellidoMaterno; 
            this._direccion = _direccion; 
            this._idSalon = _idSalon; 
            this._idEdificio = _idEdificio; 
        }

        public virtual Int32 IdAlumno 
        { 
            get 
            { 
                return this._idAlumno; 
            } 
            set 
            { 
                this._idAlumno = value; 
            } 
        } 

        public virtual String Nombre 
        { 
            get 
            { 
                return this._nombre; 
            } 
            set 
            { 
                this._nombre = value; 
            } 
        }

        public virtual String ApellidoPaterno 
        { 
            get 
            { 
                return this._apellidoPaterno; 
            } 
            set 
            { 
                this._apellidoPaterno = value; 
            } 
        }

        public virtual String ApellidoMaterno 
        { 
            get 
            { 
                return this._apellidoMaterno; 
            } 
            set 
            { 
                this._apellidoMaterno = value; 
            } 
        }

        public virtual String Direccion 
        { 
            get 
            { 
                return this._direccion; 
            } 
            set 
            { 
                this._direccion = value; 
            } 
        }

        public virtual Int32 IdSalon 
        { 
            get 
            { 
                return this._idSalon; 
            } 
            set 
            { 
                this._idSalon = value; 
            } 
        }

        public virtual Int32 IdEdificio 
        { 
            get 
            { 
                return this._idEdificio; 
            } 
            set 
            { 
                this._idEdificio = value; 
            } 
        } 
    }
} 

Vamos a analizar con más detenimiento el código que genera esta salida.


Creamos un objeto CodeTypeDeclaration, que es el objeto que utilizamos para crear la definición de la clase Alumno


CodeTypeDeclaration classAlumno = new CodeTypeDeclaration("Alumno");
            classAlumno.Attributes = MemberAttributes.Public; 

Agregamos cada una de las propiedades con sus respectivos campos de la clase, para esto he definido un método de ayuda. Existen dos clases principales utilizadas en este paso, CodeMemberField, que representa un campo y CodeMemberProperty que representa una propiedad


Al final agregamos dos constructores, uno sin parámetros y el otro con el mismo número de campos de la clase.
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idAlumno", "IdAlumno"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_nombre", "Nombre"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoPaterno", "ApellidoPaterno"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoMaterno", "ApellidoMaterno"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_direccion", "Direccion"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idSalon", "IdSalon"));

            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idEdificio", "IdEdificio"));

public static CodeTypeMemberCollection CreateFieldAndProperty(string typeofProperty, string fieldName, string propertyName) 
        { 
            CodeMemberField field = new CodeMemberField(typeofProperty, fieldName); 
            field.Attributes = MemberAttributes.Private; 
             CodeMemberProperty property = new CodeMemberProperty(); 
            property.Name = propertyName; 
            property.Type = new CodeTypeReference(typeofProperty); 
            property.Attributes = MemberAttributes.Public; 
            property.GetStatements.Add(new CodeMethodReturnStatement() 
                                        { 
                                         Expression = new CodeFieldReferenceExpression() 
                                         { 
TargetObject = new CodeThisReferenceExpression(), 
                                             FieldName = field.Name 
                                         } 
                                        } 
                                        ); 
            property.SetStatements.Add(new CodeAssignStatement() 
                                        { 
                                            Left = new CodeFieldReferenceExpression() 
                                            { 
TargetObject = new CodeThisReferenceExpression(), 
FieldName = field.Name 
                                            }, 
                                             Right = new CodePropertySetValueReferenceExpression() 
                                        } 
                                        ); 
            CodeTypeMemberCollection members = new CodeTypeMemberCollection(); 
            members.Add(field); 
            members.Add(property); 
            return members; 
        }

Como habrán visto, cada uno de estos miembros de la clase son agregados a la variable classAlumno, que es el objeto contenedor, por así decirlo, de objetos como CodeMemberProperty, CodeMemberField, CodeConstructor, CodeTypeConstructor, CodeMemberMethod, etc. Y el objeto classAlumno es agregado al  objeto CodeNamespace


CodeNamespace nm = new CodeNamespace("Blog.CodeDom.GeneratedEntities");
nm.Types.Add(classAlumno); 


De tal forma que describimos un árbol de código de la forma siguiente:
clip_image003 Imagen tomada de http://www.willydev.net

Lo interesante hasta aquí es que en código que hemos escrito, aun no hemos especificado ningún lenguaje en el cual será convertido nuestro grafo de código, si bien es cierto el ejemplo está escrito en C#, esto no quiere decir que el código generado al final deba ser C#, podría ser de VB (aunque no me guste pondré como se vería la salida en VB)

Imports System 
Namespace Blog.CodeDom.GeneratedEntities 
     Public Class Alumno 
        Private _idAlumno As Int32 
        Private _nombre As [String] 
        Private _apellidoPaterno As [String] 
        Private _apellidoMaterno As [String] 
        Private _direccion As [String] 
        Private _idSalon As Int32 
        Private _idEdificio As Int32 

        Public Sub New() 
            MyBase.New() 
        End Sub 

        Public Sub New(ByVal _idAlumno As Int32, ByVal _nombre As [String], ByVal _apellidoPaterno As [String], ByVal _apellidoMaterno As [String], ByVal _direccion As [String], ByVal _idSalon As Int32, ByVal _idEdificio As Int32) 
            MyBase.New() 
            Me._idAlumno = _idAlumno 
            Me._nombre = _nombre 
            Me._apellidoPaterno = _apellidoPaterno 
            Me._apellidoMaterno = _apellidoMaterno 
            Me._direccion = _direccion 
            Me._idSalon = _idSalon 
            Me._idEdificio = _idEdificio 
        End Sub 

        Public Overridable Property IdAlumno() As Int32 
            Get 
                Return Me._idAlumno 
            End Get 
            Set(ByVal value As Int32) 
                Me._idAlumno = value 
            End Set 
        End Property

         Public Overridable Property Nombre() As [String] 
            Get 
                Return Me._nombre 
            End Get 
            Set(ByVal value As [String]) 
                Me._nombre = value 
            End Set 
        End Property 
 

        Public Overridable Property ApellidoPaterno()
As [String] 
            Get 
                Return Me._apellidoPaterno 
            End Get 
            Set(ByVal value As [String]) 
                Me._apellidoPaterno = value 
            End Set 
        End Property 
 

        Public Overridable Property ApellidoMaterno()
As [String] 
            Get 
                Return Me._apellidoMaterno 
            End Get 
            Set(ByVal value As [String]) 
                Me._apellidoMaterno = value 
            End Set 
        End Property 

         Public Overridable Property Direccion() As
[String] 
            Get 
                Return Me._direccion 
            End Get 
            Set(ByVal value As [String]) 
                Me._direccion = value 
            End Set 
        End Property 

        Public Overridable Property IdSalon() As Int32 
            Get 
                Return Me._idSalon 
            End Get 
            Set(ByVal value As Int32) 
                Me._idSalon = value 
            End Set 
        End Property 

        Public Overridable Property IdEdificio() As Int32 
            Get 
                Return Me._idEdificio 
            End Get 
            Set(ByVal value As Int32) 
                Me._idEdificio = value 
            End Set 
        End Property 

    End Class
End Namespace


Como había comentado antes, la especificación del proveedor, es el que, al final del día, nos proporcionará la transformación a los diferentes lenguajes. Y solo queda mostrar el código completo con el que se especifica el proveedor de código.

CodeNamespace nm = Blog.CodeDom.Samples.CodeDomManager.CreateClassAlumno();

            nm.Imports.Add(new CodeNamespaceImport("System"));

            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

            provider.GenerateCodeFromNamespace(nm, Console.Out, new CodeGeneratorOptions() { BracingStyle="C" });

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.CodeDom;

namespace Blog.CodeDom.Samples
{
    public partial class CodeDomManager
    {
        public static CodeNamespace CreateClassAlumno()
        {
            CodeNamespace nm = new CodeNamespace("Blog.CodeDom.GeneratedEntities");
            CodeTypeDeclaration classAlumno = new CodeTypeDeclaration("Alumno");
            classAlumno.Attributes = MemberAttributes.Public;
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idAlumno", "IdAlumno"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_nombre", "Nombre"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoPaterno", "ApellidoPaterno"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoMaterno", "ApellidoMaterno"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_direccion", "Direccion"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idSalon", "IdSalon"));
            classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idEdificio", "IdEdificio"));
            classAlumno.Members.Add(CodeDomHelper.CreateDefaultConstructor(classAlumno.Name));
            CodeDomHelper.CreateMembersConstructor(classAlumno);
            nm.Types.Add(classAlumno);
            return nm;
        }
    }
} 


Espero que este post ayude a que los desarrolladores volteen la mirada a este namespace tan olvidado y despierte en ellos la curiosidad y la inventiva para desarrollar nuevas soluciones basadas en estos temas.

Happy Coding!

1 comentarios:

Ricardo M M dijo...

Hola Gerard
He estado jugando con este código, pero a la hora de generar el código para VB, en las propiedades que genera, le falta en el Set el paréntesis que indica el tipo de la variable value. ¿Estoy haciendo algo incorrecto?.
Tomé el código directamente de la página.
Saludos y excelente Blog!

Etiquetas

SharePoint 2010 (38) Microsoft (32) Desarrollo SharePoint (31) Gerardo Reyes Ortiz (27) SharePoint (20) SharePoint 2013 (18) Errores SharePoint (12) México (10) PowerShell (9) Silverlight (8) Visio Services (7) Features (6) MVP (6) Silverlight 3 (6) WebCast (6) Workflows (6) Configuracion SharePoint 2010 (5) D.F. (5) API REST (4) Configuracion SharePoint 2010; (4) Troubleshooting (4) Visual Studio 2010 (4) Visual studio (4) WSS (4) Web parts (4) Apps (3) Comunidad SharePoint (3) Configuración SharePoint 2013 (3) ODATA (3) SharePoint Server (3) SharePoint; Instalación SharePoint; Troubleshooting; Search Service (3) Silverlight 3.0 (3) Silverlight Toolkit (3) WebParts (3) javascript (3) jquery (3) Eventos SharePoint (2) Office 2010 (2) PeoplePicker (2) REST (2) SQL Server (2) Scripting (2) Search Service Application (2) SharePoint Designer (2) UPA (2) UPS (2) Workflows SharePoint (2) host header (2) Apps Development (1) Big Bang (1) CAS (1) CSOM (1) Codeplex (1) CompartiMOSS (1) Configuracion SharePoint 2010; Errores SharePoint (1) Configuracion SharePoint 2010; SharePoint 2010 (1) Custom Actions (1) Custom Editor Parts (1) Delegate Controls (1) Deployment (1) DisableLoopbackCheck (1) Document Library (1) Entrevista (1) Examenes de Certificación (1) Extract WSP (1) FBA (1) FS4SP (1) Fakes (1) Fast Search Server 2010 For SharePoint (1) Fiddler (1) HTTP.SYS (1) HTTPS (1) JSON (1) Language Pack's (1) Latam (1) MAXDOP (1) MCSM (1) MSExpertos (1) MVC (1) Microsoft México (1) Microsoft; Codeplex; Screencast; (1) My Sites (1) SQL Server 2012 (1) SQL Server Reporting Services (1) Screencast (1) Screencast; (1) Service Applications (1) Service Pack (1) SharePoint 2007 (1) SharePoint 2010 SP 1 (1) SharePoint API (1) SharePoint Conference (1) SharePoint Emulators (1) SharePoint Farm (1) SharePoint Health Analyzer (1) SharePoint Magazine (1) SharePoint Online (1) SharePoint Search (1) SharePoint Test (1) SharePoint; Desarrollo SharePoint (1) Shims (1) Simposio (1) Simposio Latinoamericano (1) SkyDrive Pro (1) Soporte Microsoft (1) Templates (1) Tip (1) VSeWSS (1) Virtual Machine (1) Visual Studio 2012 (1) WCF (1) WSS; IIS 7 (1) Web API (1) Web Content Management (1) Web Services (1) Windows 8 (1) Windows Live ID (1) Xml (1) appcmd (1) iOS (1) jqGrid (1) onload function (1)