Paulo Roberto's profileSomos a última geraçãoPhotosBlogListsMore Tools Help

Blog


    October 22

    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.

     

     

    Comments

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    Trackbacks

    The trackback URL for this entry is:
    http://pauloquicoli.spaces.live.com/blog/cns!B27CCFAA07B93BE8!10055.trak
    Weblogs that reference this entry
    • None