SQL Server CE y NHibernate: Precisión y escala en columnas NUMERIC

Por completar la lista de ajustes que he tenido que hacer en NHibernate para trabajar con Sql Server CE, después de ver cómo mantener abierta la conexión y arreglar los problemas con tipos IMAGE y NTEXT, vamos a resolver un último problema.

Cuando uso NHibernate, tengo la mala costumbre de dejar que NHibernate haga todo el trabajo que pueda y así evitármelo yo. Una de las cosas que delego en NHibernate es la creación y mantenimiento del esquema de la base de datos partiendo de los mappings que tengo definidos para mis entidades.

Al generar el esquema a partir de los mappings, necesito que estos contengan toda la información necesaria para definirlo, por lo que acabo incluyendo cosas como la longitud de los strings o la precisión y escala de los decimals:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Northwind.Domain.Entities" assembly="Northwind.Domain">
    <class name="Product" table="Products">
        <id name="Id" access="field.camelcase">
            <generator class="native" />
        </id>
        <property name="Name" type="string" length="40"	not-null="true" access="field.camelcase"/>
        <property name="Price" type="Decimal(12,2)" not-null="true" access="field.camelcase"/>
    </class>	
</hibernate-mapping>

El problema es que al generar el esquema de base de datos con el dialect de Sql CE que viene incluido con NHibernate, MsSqlCe40Dialect, no se respeta la precisión y escala de las propiedades de tipo decimal. Por defecto genera columnas con escala 0, es decir, sin posiciones decimales. Esto es un poco problemático para almacenar, por ejemplo, precios en euros, así que no me quedaba más remedio que buscar la forma de solucionarlo.

Después de bucear un poco en el código de NHibernate, creo que encontré la forma de conseguir lo que quería. Y digo creo, porque NHibernate es muy potente, pero eso tiene un precio y es que su código fuente a veces no es todo lo fácil de entender que a uno le gustaría. Pero bueno, el caso es que parece que la clave a la hora de dar soporte a precisión y escala para las columnas de tipo decimal, es registrar correctamente el tipo de columna en el Dialect que estemos usando.

El dialecto por defecto de NHibernate 3 para Sql CE 4 es MsSqlCe40Dialect, que básicamente lo único que añade sobre el dialecto para Sql CE 3.5 es soporte para paginación y alguna función nueva, como concat. La miga del registro de tipos está en MsSqlCeDialect, donde se registran de la siquiente forma los tipos decimales:

public class MsSqlCeDialect : Dialect
{
    public MsSqlCeDialect()
    {
        ...
        RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)");
        RegisterColumnType(DbType.Decimal, 19, "NUMERIC(19, $l)");
        ...
    }
}

La forma en que se registran los tipos está bien explicada en el comentario al inicio del código fuente de TypeNames.cs, pero básicamente la idea es que se debe registrar la forma en que se hace la conversión entre un DbType y el string que lo representa en una sentencia DDL. La clase TypeNames, que es la que finalmente se encarga de todo esto, soporta una serie de placeholders para añadir información extra:

  • $l: longitud
  • $p: precisión
  • $s: escala

En el anterior fragmento de código se ve que MsSqlCeDialect utiliza el placeholder de longitud ($l), pero no los de precisión o escala. Para solventar esto, sólo necesitamos crear una clase derivada de MsSqlCeDialect que registre correctamente los tipos:

public class SqlCeDialect : MsSqlCe40Dialect
{
    public SqlCeDialect()
    {
        RegisterColumnType(DbType.Decimal, 19, "NUMERIC($p,$s)");
    }
}

Como siempre, ya sólo hace falta configurar NHibernate para que utilice nuestro dialecto:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
    <session-factory>
        <property name="connection.driver_class">My.Namespace.SqlCeDriver, MyAssembly</property>
        <property name="dialect">My.Namespace.SqlCeDialect, My.Assembly</property>
        <property name="connection.connection_string">data source = data.sdf</property>
        <property name="connection.provider">My.Namespace.ConnectionPerThreadConnectionProvider, My.Assembly</property>
        <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
    </session-factory>
</hibernate-configuration>

Al final no hacía falta escribir mucho código para solucionarlo, pero la parte de descifrar cómo funciona NHibernate no fue tan rápida. Lo positivo de conseguir arreglar este tipo de problemas es que te acaba proporcionando conocimientos de más bajo nivel de librerías extremadamente útiles (NHibernate en este caso), y al final eso se acaba aprovechando más veces a lo largo del tiempo.

Un comentario en “SQL Server CE y NHibernate: Precisión y escala en columnas NUMERIC

  1. Pingback: ¿Hacen los frameworks tontos a los desarrolladores? | Koalite's blog

Comentarios cerrados.