Contexts de EF e Interceptors de Unity, que pasa con las excepciones?

Todo el mundo sabe que es un contexto de entity framework… ok

http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.aspx

 

Todo el mundo sabe que son los interceptors de Unity… ¿ok? (Es un mecanismo para poder lanzar código antes y después de que un método se ejecute)

http://msdn.microsoft.com/en-us/library/ff647107.aspx

 

Si interceptamos un contexto con Unity tal que así

 

   1:              container.RegisterType<MyEntities, MyEntities>(
   2:                  "auditable",
   3:                  new PerThreadLifetimeManager(),
   4:                  new InjectionConstructor(),
   5:                  new Interceptor<VirtualMethodInterceptor>(),
   6:                  new InterceptionBehavior<AuditableContextBehavior>());

 

(MyEntities es algo que hereda de ObjectContext y tiene un método llamado SaveChanges que vamos a auditar.(

 

Y AuditableContextBehavior es algo tal que así:

 

   1:   public class AuditableContextBehavior : Microsoft.Practices.Unity.InterceptionExtension.IInterceptionBehavior
   2:      {
   3:          /// <summary>
   4:          /// Gets a value indicating whether this behavior will actually do anything when invoked.
   5:          /// </summary>
   6:          public bool WillExecute
   7:          {
   8:              get
   9:              {
  10:                  return true;
  11:              }
  12:          }
  13:   
  14:          /// <summary>
  15:          /// Returns the interfaces required by the behavior for the objects it intercepts.
  16:          /// </summary>
  17:          /// <returns>
  18:          /// The required interfaces.
  19:          /// </returns>
  20:          public IEnumerable<Type> GetRequiredInterfaces()
  21:          {
  22:              return Type.EmptyTypes;
  23:          }
  24:   
  25:          /// <summary>
  26:          /// Implement this method to execute your behavior processing.
  27:          /// </summary>
  28:          /// <param name="input">Inputs to the current call to the target.</param>
  29:          /// <param name="getNext">Delegate to execute to get the next delegate in the behavior chain.</param>
  30:          /// <returns>
  31:          /// Return value from the target.
  32:          /// </returns>
  33:          public Microsoft.Practices.Unity.InterceptionExtension.IMethodReturn Invoke(Microsoft.Practices.Unity.InterceptionExtension.IMethodInvocation input, Microsoft.Practices.Unity.InterceptionExtension.GetNextInterceptionBehaviorDelegate getNext)
  34:          {
  35:              if (input == null)
  36:              {
  37:                  throw new ArgumentNullException("input");
  38:              }
  39:   
  40:              if (getNext == null)
  41:              {
  42:                  throw new ArgumentNullException("getNext");
  43:              }
  44:   
  45:              // Audit the entities, the new entities will be delayed.
  46:              IEnumerable<ObjectStateEntry> entriesDelayed = this.PreSaveChanges(input);
  47:   
  48:              // get next handler
  49:              IMethodReturn message = getNext()(input, getNext);
  50:   
  51:                  // Audit the delayed entities
  52:              this.PostSaveChanges(entriesDelayed);
  53:   
  54:              // return the result of the interceptors
  55:              return message;
  56:          }
  57:   
  58:          /// <summary>
  59:          /// Execute code after the method
  60:          /// </summary>
  61:          /// <param name="input">The input.</param>
  62:          /// <returns>Entities delayed to audit</returns>
  63:          [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We can never stop the execution for a error in this method")]
  64:          private IEnumerable<ObjectStateEntry> PreSaveChanges(Microsoft.Practices.Unity.InterceptionExtension.IMethodInvocation input)
  65:          {
  66:              IEnumerable<ObjectStateEntry> entriesToAdd = null;
  67:   
  68:              try
  69:              {
  70:                  if (input.MethodBase.Name == "SaveChanges")
  71:                  {
  72:                     ...
  73:                      }
  74:                  }
  75:   
  76:   
  77:              }
  78:              catch (Exception ex)
  79:              {
  80:              ...
  81:              }
  82:   
  83:              return entriesToAdd;
  84:          }
  85:   
  86:          /// <summary>
  87:          /// Execute code after the method SaveChanges.
  88:          /// </summary>
  89:          /// <param name="entriesToAdd">The entries to add.</param>
  90:          [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We can never stop the execution for a error in this method")]
  91:          private void PostSaveChanges(IEnumerable<ObjectStateEntry> entriesToAdd)
  92:          {
  93:              // the entries marked to added must be logged at the end of the method to log the state
  94:              if (entriesToAdd != null && entriesToAdd.Count() > 0)
  95:              {
  96:               ...
  97:              }
  98:          }
  99:      }

 

¿Que ocurre cuando el método interceptado lanza una excepción? ¿Se para todo?

 

Pues no, si el método getNext devuelve una excepción, PostSaveChanges tambien se ejecutará y en mi caso no tiene sentido. Para evitar eso hay que hacer una comprobación tal que así (en amarillo)

 

   1:             if (input == null)
   2:              {
   3:                  throw new ArgumentNullException("input");
   4:              }
   5:   
   6:              if (getNext == null)
   7:              {
   8:                  throw new ArgumentNullException("getNext");
   9:              }
  10:   
  11:              // Audit the entities, the new entities will be delayed.
  12:              IEnumerable<ObjectStateEntry> entriesDelayed = this.PreSaveChanges(input);
  13:   
  14:              // get next handler
  15:              IMethodReturn message = getNext()(input, getNext);
  16:   
  17:              // if an exception is produced in the SaveChanges method of the context, it doesn't make sense save the state of the entities
  18:              if (message.Exception != null)
  19:              {
  20:                  // Audit the delayed entities
  21:                  this.PostSaveChanges(entriesDelayed);
  22:              }
  23:   
  24:              // return the result of the interceptors
  25:              return message;

 

Tenerlo en cuenta porque la inercia es usar el result para logear o lo que sea pensando que siempre está disponible y no, en caso de excepción, la vida sigue en el interceptor y luego, dentro del método interceptado, es cuando saltará la excepción, nunca antes