Auditoria de dados, usando NHibernate

O NHibernate possui alguns eventos padrão que nos permitem fazer alguma coisa quando um objetos é incluído, excluido ou carregado do banco de dados. São os chamados EventListeners, realmente não tem muita documentação sobre o assunto, então, fazer qualquer coisa sobre isso, demanda uma boa pesquisa. Comecei aqui a implementar algo, então, vou poupar vocês de algum trabalho.

No meu caso, eu preciso registrar as mudanças de informações que uma determinada classe sofreu, e quem realizou a mudança. Isso deve ficar armazenado no banco de dados. Logo, defini a seguinte classe:

   1:  public class Historico
   2:      {
   3:          private Int64 _id;
   4:          private string _classe;
   5:          private string _alteracoes;
   6:   
   7:          public virtual Int64 Id
   8:          {
   9:              get { return _id; }
  10:              set{ _id = value;}
  11:          }
  12:   
  13:          public virtual string Classe
  14:          {
  15:              get{ return _classe;}
  16:              set{ _classe = value;}
  17:              
  18:          }
  19:   
  20:          public virtual string Alteracoes
  21:          {
  22:              get { return _alteracoes; }
  23:              set{ _alteracoes = value;}
  24:          }
  25:          
  26:          public  Historico()
  27:          {
  28:              Id = 0;
  29:              Classe = string.Empty;
  30:              Alteracoes = string.Empty;
  31:          }
  32:   
  33:          public Int64 IdObjeto { get; set; }
  34:          public string Acao { get; set; }
  35:          public DateTime Data { get; set; }
  36:          public string Usuario { get; set; }
  37:      }

 

Nada complicado. Definimos aí uma propriedade Classe que irá armazenar o nome da classe que sofre alteração, uma propriedade Historico, que irá conter as alterações feitas, IdObjeto que guarda o Id do objeto que sofre alteração; Acao, que guarda o tipo de acontecido, Data que representa quando foi alterado e Usuario, que guarda o nome do usuario que fez a alteração. Como essa classe deve ser persistida, é preciso também fazer seu mapeamento. Não vou entrar em detalhes como fazer isso…  Agora chegou a hora de implementarmos os eventos.

Para registrar o que foi alterado, é preciso implementar a interface IPostUpdateEventListener. Ela disponibiliza o método OnPostUpdate. Esse evento é disparado depois que o objeto foi atualizado. A seguir temos a classe de auditoria.

   1:   public class LogAlteracao : IPostUpdateEventListener
   2:      {
   3:          private const string _noValueString = " ";
   4:   
   5:          private static string getStringValueFromStateArray(object[] stateArray, int position)
   6:          {
   7:              var value = stateArray[position];
   8:   
   9:              return value == null || value.ToString() == string.Empty
  10:                      ? _noValueString
  11:                      : value.ToString();
  12:          }
  13:   
  14:          public void OnPostUpdate(PostUpdateEvent @event)
  15:          {
  16:              if (@event.Entity is Historico)
  17:              {
  18:                  return;
  19:              }
  20:   
  21:              var entityFullName = @event.Entity.GetType().FullName;
  22:   
  23:              if (@event.OldState == null)
  24:              {
  25:                  var session = @event.Session.GetSession(EntityMode.Poco);
  26:                  var sb = new StringBuilder();
  27:   
  28:                  for (int i = 0; i <= @event.State.Count() - 1; i++)
  29:                  {
  30:                      var newValue = getStringValueFromStateArray(@event.State, i);
  31:                      sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[i]);
  32:                      sb.AppendLine("  Novo valor..: " + newValue);
  33:                      sb.AppendLine("-------------------------------");
  34:                  }
  35:   
  36:                  var historico = new Historico();
  37:                  historico.Classe = @event.Entity.GetType().Name;
  38:                  historico.IdObjeto = (Int64)@event.Id;
  39:                  historico.Acao = "Atualização";
  40:                  historico.Alteracoes = sb.ToString();
  41:                  historico.Data = DateTime.Now;
  42:                  session.Save(historico);                     
  43:              }
  44:              else
  45:              {
  46:                  var dirtyFieldIndexes = @event.Persister.FindDirty(@event.State, @event.OldState, @event.Entity,
  47:                                                                     @event.Session);
  48:                  
  49:                  var session = @event.Session.GetSession(EntityMode.Poco);
  50:                  var sb = new StringBuilder();
  51:   
  52:                  foreach (var dirtyFieldIndex in dirtyFieldIndexes)
  53:                  {
  54:                      
  55:                      //Aqui tem que testar se for component. 
  56:                      //Se for, tem que pegar as propriedades manualmente... por reflection
  57:                    if (@event.Persister.PropertyTypes[dirtyFieldIndex] is ComponentType)
  58:                      {
  59:                          // Recupera o estado atual e o anterior da propriedade.
  60:                          var obj = @event.State[dirtyFieldIndex];
  61:                          var objAnterior = @event.OldState[dirtyFieldIndex];
  62:   
  63:                          var propertyInfos = obj.GetType().GetProperties();
  64:                          foreach (var info in propertyInfos)
  65:                          {
  66:                              // Se a propriedade não pode ser lida, não faz nada.
  67:                              if (!info.CanRead)
  68:                                  continue;
  69:   
  70:                              try
  71:                              {
  72:                                  // Recupera os valor novo e o antigo da propriedade.
  73:                                  var novoValor = info.GetValue(obj, null); 
  74:                                  var valorAntigo = info.GetValue(objAnterior, null);
  75:   
  76:                                  // Verifica se são mesmo diferentes.
  77:                                  if (valorAntigo.ToString().Equals(novoValor.ToString()))
  78:                                      continue;
  79:   
  80:                                  sb.AppendLine("Propriedadade: " + info.Name);
  81:                                  sb.AppendLine("  Valor antigo: " + valorAntigo);
  82:                                  sb.AppendLine("  Novo valor: " + novoValor);
  83:                                  sb.AppendLine("-------------------------------");
  84:                              }
  85:                              catch
  86:                              {
  87:                                  continue;
  88:                              }
  89:                          }
  90:                      }
  91:                      else
  92:                      {
  93:                          var oldValue = getStringValueFromStateArray(@event.OldState, dirtyFieldIndex);
  94:                          var newValue = getStringValueFromStateArray(@event.State, dirtyFieldIndex);
  95:   
  96:                          if (oldValue == newValue)
  97:                          {
  98:                              continue;
  99:                          }
 100:   
 101:                          sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[dirtyFieldIndex]);
 102:                          sb.AppendLine("  Valor antigo: " + oldValue);
 103:                          sb.AppendLine("  Novo valor..: " + newValue);
 104:                          sb.AppendLine("-------------------------------");
 105:                      }
 106:                  }
 107:   
 108:                  var historico = new Historico();
 109:                  historico.Classe = @event.Entity.GetType().Name;
 110:                  historico.IdObjeto = (Int64) @event.Id;
 111:                  historico.Acao = "Atualização";
 112:                  historico.Alteracoes = sb.ToString();
 113:                  historico.Data = DateTime.Now;
 114:                  session.Save(historico);
 115:                  session.Flush();
 116:              }
 117:          }
 118:      }

 

Vamos dar uma entendida no código…. Todas informações que vamos precisar estão acessíveis no parêmetro PostUpdateEvent event. A primeira coisa que verificamos é se, o objeto que está vindo é Historico. Se for, não fazemos nada… senão ia ficar em loop, certo? Na sequência verificamos se o objeto possui um OldState. Essa propriedade representa o estado do objeto antes da alteração e só é preenchida se o objeto tiver sido carregado pelo Session que está em uso.

Caso o objeto não tenha OldState, percorremos a propriedade State, que contém os valores atuais, e montamos com esses valores uma string para ser salva. Então instanciamos um objeto Historico e salvamos o objeto. Agora, se o objeto tem um OldState, podemos gerar histórico só do que foi alterado. Veja a linha 46. O NHibernate disponibiliza o método FindDirty, que retorna os índices das propriedades que sofreram alterações.  Aí é só montar novamente o texto e salvar…

Quero apontar um detalhe lá na linha 57. Estamos verificando se a propriedade que vem é do tipo ComponentType. Quando no mapeamento de uma classe, mapeamos outra nela como Component, ela aparece nesse evento PostUpdate como uma única propriedade. Assim, é necessário usarmos reflection pra extrair as propriedades….. 

Talvez precise melhorar algo aí nessa implementação, mas por enquanto… está funcionando :). Depois posto como registrar as exclusões.

 

 

Esse post foi publicado em NHibernate. Bookmark o link permanente.

6 respostas para Auditoria de dados, usando NHibernate

  1. Unknown disse:

    Ola colega. Muito bom o seu artigo, mas como voce faz para registrar fazer o registro da classes LogAlteracao ou seja como ela sera disparada? Como eu devo proceder para o nhibernate entender que esta classes deve ser usada quando os objetos sofrerem alteração?

  2. João Vitor disse:

    Basta você adicionar dentro da tag nas configurações do nhibernate a tag

    http://toranbillups.com/blog/archive/2009/09/22/How-to-audit-your-domain-model-using-NHibernate

  3. João Vitor disse:

    Basta você adicionar dentro da tag session-factory nas configurações do nhibernate a tag listener class=”WebNHibernate.LogAlteracao, WebNHibernate” type=”post-commit-update”, dá uma olhada aqui:

    http://toranbillups.com/blog/archive/2009/09/22/How-to-audit-your-domain-model-using-NHibernate

  4. Nielsen Teixeira disse:

    Artigo fantastico. Meu ajudou muito, parabéns!

  5. jefferson disse:

    Olá, sei que o artigo já é antigo… mas não consegui aplicar toda a solução: No metodo OnPostUpdate ali no inicio (if (@event.OldState == null)) está sempre retornando null, tambem gostaria de saber se voce vai disponibilizar o registro das exclusoes

  6. Alexandre Araújo disse:

    Muito bom… Ajudou bastante.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s