lunes, 25 de abril de 2011

ConfORMando Nhibernate 2, Components

Vamos a continuar partiendo del ejemplo de ayer. Teníamos un dominio muy sencillo con  Company y Employee. Si nos fijamos las dos clases tienen la misma estructura para la información de contacto, así que vamos a crear una nueva clase para almacenar estos datos. El dominio queda así:

    public class Company
{
virtual public int CompanyId { get; set; }
virtual public string Name { get; set; }
virtual public ContactInfo ContactInfo { get; set; }
virtual public IList Employees { get; set; }
}
public class Employee
{
virtual public int EmployeeID { get; set; }
virtual public string Name { get; set; }
virtual public Company Company { get; set; }
virtual public ContactInfo ContactInfo { get; set; }
}
public class ContactInfo
{
virtual public string Address { get; set; }
virtual public string PostalCode { get; set; }
virtual public string State { get; set; }
virtual public string Country { get; set; }
virtual public string Phone { get; set; }
}


No nos interesa que ContactInfo tenga una tabla donde se almacene en base de datos, queremos que la información de contacto se siga almacenando en las tablas de empleado y empresa.

Veamos como queda nuestro código con ConfORM y el mapping que genera



            var orm = new ObjectRelationalMapper();          
orm.Patterns.PoidStrategies.Add(new NativePoidPattern());
var mapper = new Mapper (orm);

IEnumerable DomainEntities = typeof(Company).Assembly.GetTypes();
//A ContactInfo no queremos aplicarle una política de Persistencia por clase, asi que no la incluimos.
orm.TablePerClass(DomainEntities.Where (e=> e != typeof (ContactInfo)));
//Le indicamos al Mapper que nos genere el maepo para las TODAS clases del dominio, incluida ContactInfo
var mapping = mapper.CompileMappingFor(DomainEntities);


<?xml version="1.0" encoding="utf-8"?>
<?XML:NAMESPACE PREFIX = [default] urn:nhibernate-mapping-2.2 NS = "urn:nhibernate-mapping-2.2" />

































Con component Nhibernate nos permite mapear propiedades de una clase a la tabla de la clase que contiene la tabla. En nuestro caso, las propiedades de ContactInfo se almacenaran en las tablas donde se almacenen las clases que la usen.



En resumen, hemos añadido una clase, pero nuestro modelo de datos no se ha modificado.

domingo, 24 de abril de 2011

ConfORMando NHibernate

Voy a intentar hacer una serie de post explicando como mapear nuestros objetos de dominio a NHibernate utilizando ConfORM.

Lo primero que necesitamos es un dominio. Para este caso vamos a usar un dominio sencillo:

	
public class Company
{
virtual public int CompanyId { get; set; }
virtual public string Name { get; set; }
virtual public string Address { get; set; }
virtual public string PostalCode { get; set; }
virtual public string State { get; set; }
virtual public string Country { get; set; }
virtual public string Phone { get; set; }
virtual public IList Employees { get; set; }
}
public class Employee
{
virtual public int EmployeeID { get; set; }
virtual public string Name { get; set; }
virtual public Company Company { get; set; }
virtual public string Address { get; set; }
virtual public string PostalCode { get; set; }
virtual public string State { get; set; }
virtual public string Country { get; set; }
virtual public string Phone { get; set; }
}

Nada que comentar en el dominio. Tenemos una clase empresa que tiene empleados.

Ahora vamos a preparar nuestro ORM, en este caso NHibernate. Voy a utilizar NHibernate 3 y su nueva api de configuración


	    
Configuration nhConfig = new Configuration();
nhConfig.SessionFactoryName("ConfORMando");
nhConfig.Proxy(p =>
{
p.Validation = false;
p.ProxyFactoryFactory();
}
);
nhConfig.DataBaseIntegration (db =>
{
db.Dialect();
db.Driver();
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
db.IsolationLevel = IsolationLevel.ReadCommitted;
db.ConnectionString = connectionString;
db.Timeout = 10;
db.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'";
}
);

Ahora llega el turno de ConfORM. Con este API vamos a usar dos objetos:


  • ObjectRelationalMapper: que se utiliza para indicar las reglas generales del mapeo y se le indican las clases que forman parte del dominio

  • Mapper: El encargado de generar los metadatos para el ORM concreto, en nuestro caso NHibernate. En este objeto indicaremos (si fuese necesario) las expceiones y correciones manuales de los mapeos.


Para no complicar las cosas en el primer ejemplo (a ver si soy constante y hago más) vamos a dejar las opciones por defecto, salvo el generador de IDs que por defecto utiliza HiLo y por ahora quiero utilizar native.


            
var orm = new ObjectRelationalMapper();
orm.Patterns.PoidStrategies.Add(new NativePoidPattern());
var mapper = new Mapper (orm);

IEnumerable DomainEntities = typeof(Company).Assembly.GetTypes();
//En este ejemplo utilizaremos para las dos clases Table per Class
orm.TablePerClass(DomainEntities);
//Le indicamos al Mapper que nos genere el maepo para las clases del dominio
var mapping = mapper.CompileMappingFor(DomainEntities);

System.IO.File.WriteAllText (hbmFile, mapping.AsString());


//añadimos las clases mapeadas a la configuaracion de Nhibernate que creamos antes
nhConfig.AddDeserializedMapping(mapping, "ConfORMDomain");


¡¡Ya está ya tenemos nuestras clases mapeadas con NHibernate!! ¿No me creéis? bueno, afortunadamente el Mapper de NHibernate de ConfORM tiene un método llamado AsString con el que se puede generar el fichero hbm. Así que os puedo mostrar el resultado:


<?xml version="1.0" encoding="utf-8"?>
<?XML:NAMESPACE PREFIX = [default] urn:nhibernate-mapping-2.2 NS = "urn:nhibernate-mapping-2.2" />





























Y si genero la base de datos utilizando el SchemaExport de NHibernate puedo generar la base de datos (o el script de generación) con este resultado:



CompanyEmployeeDBModel



Esto es solo la punta del iceberg, ConfORM tiene muchísimo más que ofrecer.

sábado, 23 de abril de 2011

La magia de ConfORM

Como ya comenté el el post anterior ConfORM permite configurar mediante convección el mapeo de las entidades de un dominio a un ORM.

Si a esto le sumamos el uso de NHibernate.Tool.hbm2ddl tendremos una experiencia “Code First” que tan de moda está ahora mismo.

ConfORM examina las clases que le indicamos que forman parte de nuestro dominio y genera la configuración necesaria para el ORM

Fabio Maulo hace que analizar de forma automática las clases de un dominio parezca sencillo. Identifica todo lo que necesita saber y genera un contrato de lo más sencillo para definir un DomainInspector, tal y como explica en este post.

public interface IDomainInspector
{
bool IsRootEntity(Type type);
bool IsComponent(Type type);
bool IsComplex(Type type);
bool IsEntity(Type type);

bool IsTablePerClass(Type type);
bool IsTablePerClassHierarchy(Type type);
bool IsTablePerConcreteClass(Type type);

bool IsOneToOne(Type from, Type to);
bool IsManyToOne(Type from, Type to);
bool IsManyToMany(Type role1, Type role2);
bool IsOneToMany(Type from, Type to);
bool IsHeterogeneousAssociations(MemberInfo member);
Cascade ApplyCascade(Type from, Type to);

bool IsPersistentId(MemberInfo member);
IPersistentIdStrategy GetPersistentIdStrategy(MemberInfo member);

bool IsPersistentProperty(MemberInfo role);
IDbColumnSpecification[] GetPersistentSpecification(MemberInfo role);
}


Fabio explica que una clase por si misma no puede definir como es su persistencia e identifica los casos en los que es necesario un poco de ayuda:



Por un lado están las estrategias de serialización



void TablePerClassHierarchy<TBaseEntity>();
void TablePerClass<TBaseEntity>();
void TablePerConcreteClass<TBaseEntity>();



Por otro lado, algunas relaciones que no se pueden (o no siempre) se pueden detectar automáticamente



void ManyToOne<TLeftEntity, TRigthEntity>();
void OneToOne<TLeftEntity, TRigthEntity>();
void ManyToMany<TLeftEntity, TRigthEntity>();



Y por último si los cambios en entidades desencadenan cambios en la otras (los típicos borrados en cascada, actualizaciones…)



void Cascade<TFromEntity, TToEntity>(Cascade cascadeOptions);



Con todos estos datos introducidos en el DomainInspector, ConfORM puede generar los metadatos necesarios para el ORM puede manejar la persistencia del dominio.



Fuente: http://fabiomaulo.blogspot.com/2010/02/conform-nhibernate-un-mapping.html

jueves, 10 de marzo de 2011

Estoy ConfORMe

La próxima vez que alguien me diga que no usa NHibernate porque es un coñazo mantener los hbms…

Fabio Maulo, el responsable del proyecto de NHinernate ha creado ConfORM. Este api sirve para configurar los mapeos por convenciones de nombre.

Por ejemplo, con este poco código, se mapean automáticamente todas las clases que extiendan de Entity, creando una tabla por clase.

private void DefineDomain()
{
orm.TablePerClass(GetTypes());
}

public List GetTypes()
{
return new List(Assembly.GetExecutingAssembly().GetTypes().Where
(t => typeof(Entity).IsAssignableFrom(t)&& t != typeof(Entity)));
}


Lo único que hay que mapear “a mano” pero siempre de forma muuuuuy sencilla son las Many to many y las one to one.



ConfORM tiene muchas posibilidades y es increíblemente sencillo, eso si, lo recomendaría para desarrollos nuevos y no para mantener aplicaciones antiguas. Además, se use el ORM que se use, uno nunca debe de olvidar que hay una base de datos por debajo y que aunque lo parezca, en informática no existe la magia.



A ver si me animo y escribo unos tutoriales (aunque he de decir que no lo controlo mucho)

domingo, 21 de noviembre de 2010

Formateando que es gerundio

En muchas ocasiones es necesario formatear las entradas de datos.

Por ejemplo, algunos clientes quieren que los números se muestren con separadores decimales, otros quieren que cuando se trate de dinero se muestre además de todo lo dicho anteriormente el símbolo del euro. No debemos olvidar que para muchos clientes el la aplicación de referencia es office, así que te dirán “como en office”.

En este post no me voy a detener en como hacer la máscara, existen varias soluciones (Javascript, JQuery, Ajax.Net Control Toolkit). Lo que me resulta sorprendente es la distintas formas que me he encontrado cuando en .Net (y supongo que en Java habrá una forma similar) de dar formatos a los números o recuperar el texto de nuestro interfaz y guardarlo como número en nuestra aplicación.

Primero debemos saber como transformar un número, al formato que nos pide el cliente. He visto en muchas ocasiones como los programadores parsean de forma manual los números. Imaginemos un decimal, habría que separar la parte decimal de la parte entera, dividir de tres en tres la parte entera poniendo “.” y luego poniendo una “,” y adjuntando la parte decimal. La solución nos la da el framework en una única línea.

Supongamos que tenemos una página asp.net con una caja de texto que se llama txtMoneda en la que queremos escribir un valor numérico con el formato moneda del excel, es decir solo con dos decimales, separador de millares, separador decimal y el símbolo del euro. ¿como lo hacemos? con solo una línea:

txtMoneda.Text = initMoneda.ToString("C"); //Formato moneda


Todos los tipos numéricos tienen sobrecargado el ToString, en esta entrada de la msdn se encuentra la información necesaria.


Una vez que mostramos el texto formateado, nos falta recuperar el valor del control a nuestra variable, en este caso de tipo decimal. En .Net los tipos numéricos tienen dos métodos parse y tryparse, los dos hacen lo mismo: transforman al tipo decimal concreto. A la hora de trabajar con la capa de la vista, lo recomendable es usar tryParse. Este método solo acepta strings de entrada (que es lo que nos va a dar nuestro control), y no obtiene la cultura por defecto asi que se la tenemos que especificar, pero nos ahorramos un try para comprobar si el string de entrada.


En nuestro ejemplo para recuperar del input el valor en decimal sería algo parecido a esto:


if (!decimal.TryParse(txtMoneda.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out moneda))
{
//TODO:Notificar Error
}



El enumerado NumberStyle contiene muchos estilos que se pueden utilizar de forma combinada, tal y como se muestra en su entrada de la msdn


Usando parse o tryparse, no estamos obligado a usar una mascara en nuestros controles. En nuestro ejemplo, todas esta combinaciones sería válidas:


  • 1.234,56


  • 1234,56


  • 1234


  • 1234,56 €


  • 1.234,56 €


  • 1234 €



Moraleja: para esas cosas básicas que deberían estar en el framework integradas, con casi toda seguridad lo están, consulta la msdn.

jueves, 25 de febrero de 2010

Listar Nombres de Tablas y Columnas en SQL Server

Siempre me olvido de como se listan las tablas y las columnas en SQL Server y es algo realmente útil a la hora de generar código.

Veamos como se comportan las siguientes instrucciones contra la base de datos de AdventureWorks

SELECT * FROM INFORMATION_SCHEMA.TABLES

TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE
ADVENTUREWORKS Production ProductProductPhoto BASE TABLE
ADVENTUREWORKS Sales StoreContact BASE TABLE
ADVENTUREWORKS Person Address BASE TABLE
ADVENTUREWORKS Production ProductReview BASE TABLE
ADVENTUREWORKS Production TransactionHistory BASE TABLE
ADVENTUREWORKS Person AddressType BASE TABLE
ADVENTUREWORKS Production ProductSubcategory BASE TABLE
ADVENTUREWORKS dbo AWBuildVersion BASE TABLE
ADVENTUREWORKS Production TransactionHistoryArchive BASE TABLE

SELECT *

FROM INFORMATION_SCHEMA.COLUMNS

WHERE Table_Name = 'Product'

TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE
ADVENTUREWORKS Production Product ProductID 1 NULL NO int
ADVENTUREWORKS Production Product Name 2 NULL NO nvarchar
ADVENTUREWORKS Production Product ProductNumber 3 NULL NO nvarchar
ADVENTUREWORKS Production Product MakeFlag 4 ((1)) NO bit
ADVENTUREWORKS Production Product FinishedGoodsFlag 5 ((1)) NO bit
ADVENTUREWORKS Production Product Color 6 NULL YES nvarchar
ADVENTUREWORKS Production Product SafetyStockLevel 7 NULL NO smallint
ADVENTUREWORKS Production Product ReorderPoint 8 NULL NO smallint
ADVENTUREWORKS Production Product StandardCost 9 NULL NO money
ADVENTUREWORKS Production Product ListPrice 10 NULL NO money
ADVENTUREWORKS Production Product Size 11 NULL YES nvarchar
ADVENTUREWORKS Production Product SizeUnitMeasureCode 12 NULL YES nchar
ADVENTUREWORKS Production Product WeightUnitMeasureCode 13 NULL YES nchar
ADVENTUREWORKS Production Product Weight 14 NULL YES decimal
ADVENTUREWORKS Production Product DaysToManufacture 15 NULL NO int
ADVENTUREWORKS Production Product ProductLine 16 NULL YES nchar
ADVENTUREWORKS Production Product Class 17 NULL YES nchar
ADVENTUREWORKS Production Product Style 18 NULL YES nchar
ADVENTUREWORKS Production Product ProductSubcategoryID 19 NULL YES int
ADVENTUREWORKS Production Product ProductModelID 20 NULL YES int
ADVENTUREWORKS Production Product SellStartDate 21 NULL NO datetime
ADVENTUREWORKS Production Product SellEndDate 22 NULL YES datetime
ADVENTUREWORKS Production Product DiscontinuedDate 23 NULL YES datetime
ADVENTUREWORKS Production Product rowguid 24 (newid()) NO uniqueidentifier
ADVENTUREWORKS Production Product ModifiedDate 25 (getdate()) NO datetime

En el listado de columnas no he sacado todos los campos solo los primeros, porque la información que saca es muy grande, pero esta llega para hacernos una idea de lo que podemos hacer.

En el listado de tablas (como os podéis imaginar) he recortado el número de registros mostrados ;)