Feeds:
Posts
Comentários

NHibernate Custom Type

Certa vez, em um projeto onde nada era suficiente, recebi a seguinte missão:

todo percentual do sistema, deveria ser gravado no banco dividindo seu valor por 100 e ao se recuperar o valor, deveria multiplicar o mesmo também por 100.

Fiquei pensando como iria resolver isso e imaginei algumas possibilidades, analisando prós e contras:

  • Através de get’s e set’s além de depender que cada desenvolvedor o faça na implementação, iria gerar uma replicação de código terrível
  • Se fizesse os sets e gets ainda através de uma classe helper, continuaria dependendo da lembrança do desenvolvedor
  • Através de componentes não iria abranger todos os outros processos não visuais
  • e mais algumas loucuras…

Eu percebi que precisaria ir mais a fundo pra dar uma solução de uso simples, funcional e genérica.

Foi aí que surgiu a oportunidade de usar um tipo customizado do NHibernate.   A idéia é criar um tipo específico de dados, que de maneira transparente, se encarregaria de fazer o devido tratamento a informação segundo o critério estabelecido.

Para isso, são necessários basicamente três passos: criar um tipo de dado específico que seus objetos vao fazer uso através de propriedades, criar uma classe que ira tratar o uso desse tipo pelo NH e vincular o mapeamento do dado de seu objeto com a classe tratadora.

Passo 1 – Criar um tipo específico e fazer uso em nossos objetos

A criação de um tipo específico, que vamos chamar de Percent, nos dá a possibilidade de lidar com um outro conceito bem interessante no .NET: operadores implícitos. Nosso tipo precisa receber um valor conversível em percentual, seja ele inteiro, decimal, double ou quaisquer variações. O uso desse tipo está exemplificado nesse trecho de código:

<br />
public class Venda : EntityBase<br />
{<br />
  public virtual Percent Percentual { get; set; }<br />
}<br />

Em minha classe Venda, além de Id, tenho uma propriedade Percentual, do meu tipo customizado onde quero atribuir valores de maneira simples, das seguintes formas:

</p>
<p>// Decimal            <br />
venda.Percentual = 2.3M;</p>
<p>// Inteiro            <br />
venda.Percentual = 10;</p>
<p>// Double            <br />
venda.Percentual = 12.3;<br />

Como ficou meu tipo e como é possível fazer essas atribuições? Aqui está o código da classe Percent 

<br />
public class Percent<br />
{<br />
    #region Attributes</p>
<p>    public Double? Value { get; private set; }<br />
    public Boolean HasValue { get { return Value.HasValue; } }</p>
<p>    #endregion</p>
<p>    #region Operators</p>
<p>    public static implicit operator Percent(Int32? value)<br />
    {<br />
        return new Percent(value);<br />
    }</p>
<p>    public static implicit operator Percent(Double? value)<br />
    {<br />
        return new Percent(value);<br />
    }</p>
<p>    public static implicit operator Percent(Decimal? value)<br />
    {<br />
        return new Percent(value);<br />
    }</p>
<p>    #endregion</p>
<p>    #region Constructors</p>
<p>    public Percent(Int32? value)<br />
    {<br />
        this.Value = value;<br />
    }</p>
<p>    public Percent(Double? value)<br />
    {<br />
        this.Value = value;<br />
    }</p>
<p>    public Percent(Decimal? value)<br />
    {<br />
        this.Value = (Double?)value;<br />
    }</p>
<p>    #endregion</p>
<p>    #region Methods</p>
<p>    public override Boolean Equals(Object obj)<br />
    {<br />
        Percent objToCompare = obj as Percent;</p>
<p>        if (objToCompare == null)<br />
            return !this.HasValue || this.Value.IsDefaultValue();</p>
<p>        return objToCompare.Value.Equals(this.Value);<br />
    }</p>
<p>    public override int GetHashCode()<br />
    {<br />
        return Value.GetHashCode();<br />
    }</p>
<p>    public override string ToString()<br />
    {<br />
        return Value.ToString();<br />
    }</p>
<p>    #endregion<br />
}<br />

O segredo da atribuição a propriedade, está nas linhas marcadas. De 10 a 27.  Os operadores implicitos, unidos aos construtores que conseguem administrar os tipos recebidos, permitem que passemos os valores de diversas formas. Reparem que o valor em si, eu mantenho numa propriedade interna da classe.

Passo 2 – Criar uma classe que irá tratar o uso do tipo Percent pelo NHibernate

Percent por si só, ja é capaz de manter valores de tipo percentual. Agora vamos fazer com que o NH saiba o que fazer quando um valor for passado a uma propriedade desse tipo. O NHibernate tem no namespace NHibernate.UserTypes algumas interfaces que nos ajudam nesse processo. Vamos fazer uso de IUserType. Ao implementar essa interface, alguns métodos sao auto explicativos mas outros merecem maior atenção. Chamei minha classe de PercentType

<br />
[Serializable]<br />
public class PercentType : IUserType<br />
{<br />
    #region IUserType Members<br />
    private static readonly NHibernate.SqlTypes.SqlType[] SQL_TYPES = { NHibernateUtil.Double.SqlType };</p>
<p>    public SqlType[] SqlTypes<br />
    {<br />
        get { return SQL_TYPES; }<br />
    }</p>
<p>    public Type ReturnedType<br />
    {<br />
        get { return typeof(Percent); }<br />
    }</p>
<p>    public new bool Equals(Object x, Object y)<br />
    {<br />
        if (ReferenceEquals(x, y)) return true;</p>
<p>        if (x == null || y == null) return false;</p>
<p>        return x.Equals(y);<br />
    }</p>
<p>    public Object DeepCopy(Object value)<br />
    {<br />
        return value;<br />
    }</p>
<p>    public Boolean IsMutable<br />
    {<br />
        get { return false; }<br />
    }</p>
<p>    public Object NullSafeGet(IDataReader rs, String[] names, Object owner)<br />
    {<br />
        var obj = NHibernateUtil.Decimal.NullSafeGet(rs, names[0]);</p>
<p>        if (obj == null)<br />
            return new Percent(null);<br />
        {<br />
            Decimal result = (Decimal)obj;<br />
            if (result != default(Decimal))<br />
                return new Percent(result * 100);<br />
            else<br />
                return new Percent(result);<br />
        }<br />
    }</p>
<p>    public Object Assemble(Object cached, Object owner)<br />
    {<br />
        return cached;<br />
    }</p>
<p>    public Object Disassemble(Object value)<br />
    {<br />
        return value;<br />
    }</p>
<p>    public Int32 GetHashCode(Object x)<br />
    {<br />
        return x.GetHashCode();<br />
    }</p>
<p>    public void NullSafeSet(IDbCommand cmd, Object value, Int32 index)<br />
    {<br />
        if (value == null)<br />
            ((IDbDataParameter)cmd.Parameters[index]).Value = DBNull.Value;<br />
        else<br />
        {<br />
            Percent percent = value as Percent;<br />
            ((IDbDataParameter)cmd.Parameters[index]).Value = percent.Value / 100;<br />
        }<br />
    }</p>
<p>    public object Replace(object original, object target, object owner)<br />
    {<br />
        return original;<br />
    }<br />
    #endregion<br />
} <br />

Nesse bloco de código, informamos ao NH os tipos SQL que serão tratados por nossa classe

<br />
    private static readonly NHibernate.SqlTypes.SqlType[] SQL_TYPES = { NHibernateUtil.Double.SqlType };</p>
<p>    public SqlType[] SqlTypes<br />
    {<br />
        get { return SQL_TYPES; }<br />
    }<br />

Aqui, temos outros blocos importantes, nos métodos NullSafeGet e NullSaveSet. Nesses métodos, dizemos ao NH como tratar os valores no objeto:

<br />
    public Object NullSafeGet(IDataReader rs, String[] names, Object owner)<br />
    {<br />
        var obj = NHibernateUtil.Decimal.NullSafeGet(rs, names[0]);</p>
<p>        if (obj == null)<br />
            return new Percent(null);<br />
        {<br />
            Decimal result = (Decimal)obj;<br />
            if (result != default(Decimal))<br />
                return new Percent(result * 100);<br />
            else<br />
                return new Percent(result);<br />
        }<br />
    }<br />
    public void NullSafeSet(IDbCommand cmd, Object value, Int32 index)<br />
    {<br />
        if (value == null)<br />
            ((IDbDataParameter)cmd.Parameters[index]).Value = DBNull.Value;<br />
        else<br />
        {<br />
            Percent percent = value as Percent;<br />
            ((IDbDataParameter)cmd.Parameters[index]).Value = percent.Value / 100;<br />
        }<br />
    }</p>
<p>

Em NullSafeGet eu obtenho o valor que vem do banco, multiplico por 100 e devolvo uma instancia de Percent já com valor tratado. Em NullSafeSet, inverto o cálculo e passo o valor ao parametro que irá para o banco. Simples assim. Seguindo essa linha de raciocínio, as possibilidades de tratamento de tipos são incontáveis. Vai da necessidade e/ou imaginação.

Passo 3 – Mapear no NH, a associação de uma propriedade Percent com o tratamento de PercentType

Como o objetivo desse post nao é configuracação e uso de NHibernate em si, vou mostrar somente um caso de forma de mapeamento. Eu estou usando Fluent NHibernate mas é perfeitamente possível fazer com hbm ou Active Record visto que é uma funcionalidade do NH. Este é o mapeamento via fluent: 

public class VendaMap : ClassMap&lt;Venda&gt;<br />
{<br />
    public VendaMap()<br />
    {<br />
        Id(x =&gt; x.Id)<br />
            .GeneratedBy<br />
                .Native(&quot;SEQVENDA&quot;);</p>
<p>        Map(x =&gt; x.Percentual)<br />
            .CustomType&lt;PercentType&gt;();<br />
    }<br />
}</p>
<p>

Segue tambem um pequeno teste para demonstrar o uso e funcionamento do que fizemos. É um teste simples e nada abrangente mas mostra que funciona. Não explico também a gestão da sessão do NH mas ser voce tiver tudo funcionando ok na sua máquina é facil reproduzir o teste.


    [TestMethod]
    public void PercentTypeTest()
    {
        Int32 value = 20;

        Venda venda = new Venda();
        venda.Percentual = value;

        FluentHelper.Instance.CurrentSession.Save(venda);

        Assert.AreEqual(venda.Percentual, value);
    }

É isso. Se houver interesse eu disponibilizo o projeto completo pra download. Vou tentar no próximo post, falando um pouco mais de NH, mostrar como usar um interceptor para gerenciar numeração incremental customizada nas suas entidades.

Abraaaaaaaço

Eleição para presidente 2010

Eu sei que é bisonho demais iniciar um blog com foco em tecnologia, não postar nada durante meses e chegar agora e postar sobre política.

Eu concordo mas aqui é um lugar público, que ninguém vem ver, e ainda é meu. Só tenho aqui mesmo pra me manifestar publicamente sobre esse assunto.

Os atuais candidatos a presidência não me agradam. Podia dar empate sem votos e os dois desistirem. Contudo, algumas coisas que eu considero balela tem me incomodado muito. Sob o meu ponto de vista, que isso fique bem claro.

Continuar Lendo »

Agora MCPD Web 2.0

Como eu já havia previsto, e tinham me dito, dar andamento a essa parada de Blog não é mesmo fácil. E não é por falta de conteúdo ou experiências mas sim o mesmo motivo de todos: o danado do tempo.

Como tempo é o que me falta resolvi compartilhar o que anda me tomando tanto dele.

Continuar Lendo »

Hello world!

Bem vindos ao meu Blog. Como todos dizem: o primeiro post é fácil.

O objetivo é falar de tudo um pouco, do que der na cabeça mas tentar manter o foco em TI. Tentar pelo menos.

Ainda não encontrei o nome ideal, a cor ideal, tema, mas vou tentando. Tenho o objetivo também de tentar manter o Blog em uma estrutura .NET, como não podia deixar de ser.

Pra começar, vou postar a solução que encontrei para uma questão levantada essa semana no trabalho:

{
public event EventHandler OnTest;
public void Test()
{
if (OnTest != null)
OnTest(this, EventArgs.Empty);

Como saber se um evento de um objeto foi assinado?

Continuar Lendo »