2010-01-21 10 views
6

Tengo un método que quiero que sea "transaccional" en el sentido abstracto. Llama a dos métodos que hacen cosas con la base de datos, pero este método no lo sabe.El método sabe demasiado sobre los métodos que está llamando

public void DoOperation() 
{ 
    using (var tx = new TransactionScope()) 
    { 
     Method1(); 
     Method2(); 

     tc.Complete(); 
    } 
} 

public void Method1() 
{ 
    using (var connection = new DbConnectionScope()) 
    { 
     // Write some data here 
    } 
} 

public void Method2() 
{ 
    using (var connection = new DbConnectionScope()) 
    { 
     // Update some data here 
    } 
} 

Debido a que en términos reales el TransactionScope significa que se utilizará una transacción de base de datos, tenemos un problema por el que bien podría ser promovido a una transacción distribuida, si tenemos dos conexiones diferentes de la piscina.

pude solucionar este problema mediante el método de embalaje DoOperation() en un ConnectionScope:

public void DoOperation() 
{ 
    using (var tx = new TransactionScope()) 
    using (var connection = new DbConnectionScope()) 
    { 
     Method1(); 
     Method2(); 

     tc.Complete(); 
    } 
} 

me hizo a mí mismo por DbConnectionScope tal propósito, por lo que no tengo que pasar objetos de conexión a los sub-métodos (Este es un ejemplo más artificial que mi verdadero problema). Tengo la idea de este artículo: http://msdn.microsoft.com/en-us/magazine/cc300805.aspx

Sin embargo no me gusta esta solución ya que significa DoOperation ahora tiene conocimiento de que los métodos se llaman pueden utilizar una conexión (y posiblemente una conexión diferente cada uno). ¿Cómo podría refactorizar esto para resolver el problema?

Una idea que estoy pensando es la creación de una más general OperationScope, de modo que cuando se asoció con una costumbre castillo de Windsor el estilo de vida que voy a escribir, significará cualquier componente solicitado del recipiente con OperationScopeLifetyle siempre obtener el mismo instancia de ese componente. Esto resuelve el problema porque OperationScope es más ambiguo que DbConnectionScope.

+0

¿Qué? ¿Alguien votó para que se cierre esta pregunta? ¿Por qué? –

Respuesta

1

Estoy viendo requisitos conflictivos aquí.

Por un lado, no desea que DoOperation tenga conocimiento de que se está utilizando una conexión de base de datos para sus suboperaciones.

Por otro lado, claramente es consciente de este hecho porque utiliza un TransactionScope.

puedo especie de entender lo que está recibiendo en cuando dice que quiere que sea transaccional en el sentido abstracto, pero mi opinión sobre esto es que es prácticamente imposible (no, cero que - completamente imposible) para describir una transacción en términos abstractos. Digamos que usted tiene una clase como esta:

class ConvolutedBusinessLogic 
{ 
    public void Splork(MyWidget widget) 
    { 
     if (widget.Validate()) 
     { 
      widgetRepository.Save(widget); 
      widget.LastSaved = DateTime.Now; 
      OnSaved(new WidgetSavedEventArgs(widget)); 
     } 
     else 
     { 
      Log.Error("Could not save MyWidget due to a validation error."); 
      SendEmailAlert(new WidgetValidationAlert(widget)); 
     } 
    } 
} 

Esta clase está haciendo al menos dos cosas que probablemente no se pueden deshacer (ajuste de la propiedad de una clase y ejecución de un controlador de eventos, lo que podría, por ejemplo, actualizar en cascada algunos controles en un formulario) y al menos dos cosas más que definitivamente no se pueden deshacer (agregar a un archivo de registro en algún lugar y enviar una alerta por correo electrónico).

Quizás esto parezca un ejemplo artificial, pero ese es realmente mi punto; no puede tratar un TransactionScope como una "caja negra".El alcance es de hecho una dependencia como cualquier otra; TransactionScope solo proporciona una abstracción conveniente para una unidad de trabajo que puede no ser siempre adecuada porque no envuelve una conexión de base de datos y no puede predecir el futuro. En particular, normalmente no es apropiado cuando una única operación lógica necesita abarcar más de una conexión de base de datos, ya sea que esas conexiones estén en la misma base de datos o diferentes. Trata de manejar este caso, por supuesto, pero como ya has aprendido, el resultado es subóptimo.

La forma en que lo veo, usted tiene algunas opciones diferentes:

  1. hacer explícito el hecho de que Method1 y Method2 requieren una conexión haciendo que toman un parámetro de conexión, o mediante refactorización ellos en una clase eso toma una dependencia de conexión (constructor o propiedad). De esta forma, la conexión se convierte en parte del contrato , por lo que Method1 ya no sabe demasiado: sabe exactamente lo que se supone que debe saber según el diseño.

  2. acepta que su método DoOperationhace tiene una conciencia de lo Method1 y Method2 lo hacen. De hecho, ¡no hay nada de malo en esto! Es cierto que no desea confiar en los detalles de implementación de una llamada futura, pero las dependencias directas en la abstracción generalmente se consideran correctas; es inversa dependencias que debe preocuparse, como cuando alguna clase profunda en el modelo de dominio intenta actualizar un control de interfaz de usuario que no tiene ningún negocio saber en primer lugar.

  3. Utilice un patrón Unit of Work más robusto (también: here). Esto se está volviendo más popular y, en general, es la dirección que Microsoft ha seguido con Linq a SQL y EF (el DataContext/ObjectContext son básicamente implementaciones de UOW). Esto encaja bien con un marco DI y básicamente te libera de la necesidad de preocuparte por cuándo comienzan y terminan las transacciones y cómo tiene que ocurrir el acceso a los datos (el término es "ignorancia de persistencia"). Esto probablemente requeriría una revisión importante de su diseño, pero una libra por libra será la más fácil de mantener a largo plazo.

Esperanza uno de los que ayuda.

+0

Creo que la cosa de la "dependencia hacia adelante" es lo que la hace o la rompe. Definitivamente, UoW es la forma en que normalmente abordaría esto, pero estoy trabajando en una aplicación heredada que usa tablas de datos (ni siquiera conjuntos de datos), así que es complicado. Gracias por la respuesta, me siento un poco más cómodo con la segunda parte de su respuesta. –

0

¿Sería posible empujar la transacción de entrega/alistamiento a la base de datos y eliminarla por completo del código?

+0

No realmente. En este caso, se emiten dos comandos DB completamente separados, pero en este caso quiero que ambos o ninguno de ellos se comprometan si uno o el otro falla, o si algo más arroja una excepción. –

Cuestiones relacionadas