2009-11-19 10 views
20

Simplemente curioso, ¿hay algún soporte para las transacciones en objetos simples C#? Como¿Transacciones para objetos C#?

using (var transaction = new ObjectTransaction(obj)) 
{ 
    try 
    { 
    obj.Prop1 = value; 
    obj.Prop2 = value; 
    obj.Recalculate(); // may fire exception 
    transaction.Commit(); // now obj is saved 
    } 
    except 
    { 
    transaction.Rollback(); // now obj properties are restored 
    } 
} 

Solo para que las respuestas sean más útiles ;-) ¿Hay algo similar en otros idiomas?

Actualización sobre STM: esto es lo que dice:

atomic { 
    x++; 
    y--; 
    throw; 
} 

dejará x/y sin cambios, incluyendo encadenado métodos llamadas. Parece lo que pido. Al menos es muy interesante. Creo que eso es lo suficientemente cerca. Además, hay cosas similares en otros idiomas, por ejemplo, Haskell STM. Tenga en cuenta que no digo que deba usarse para producción ;-)

Respuesta

13

Microsoft está trabajando en ello. Lea sobre la memoria transaccional de software.

utilizan un par de sintaxis diferentes:

// For those who like arrows 
Atomic.Do(() => { 
    obj.Prop1 = value; 
    obj.Prop2 = value; 
    obj.Recalculate(); 
}); 

// For others who prefer exceptions 
try { 
    obj.Prop1 = value; 
    obj.Prop2 = value; 
    obj.Recalculate(); 
} 
catch (AtomicMarker) { 
} 

// we may get this in C#: 
atomic { 
    obj.Prop1 = value; 
    obj.Prop2 = value; 
    obj.Recalculate(); 
} 
+2

No STM tienen más que ver con el estado compartido y concurrencia de lo que hace la tradicional concepto de una transacción? –

+1

@Robert Se maneja bastante bien. Puede usarlo de un solo subproceso si solo desea salvaguardar contra excepciones (obtiene reversión), o puede ejecutarlo en múltiples subprocesos. Single-threading es solo un caso degenerado de multihilo. :-) –

2

No, este tipo de soporte no existe hoy en día para los objetos gestionados de vanilla.

3

Puede hacer una copia del objeto antes de ejecutar métodos y establecer propiedades. Luego, si no le gusta el resultado, puede simplemente "retroceder" a la copia. Suponiendo, por supuesto, que no tengas efectos secundarios con los que lidiar.

+1

Sólo asegúrese de que realmente "clon", en lugar de simplemente copiar la referencia (que no ayudará en absoluto) –

4

Juval Lo wy ha escrito sobre esto. Aquí hay un enlace a su original MSDN article (escuché por primera vez acerca de la idea en su excelente libro de WCF). Aquí está un ejemplo de código de MSDN, que se parece a lo que quiere lograr:

public class MyClass 
{ 
    Transactional<int> m_Number = new Transactional<int>(3); 


public void MyMethod() 
    { 
     Transactional<string> city = new Transactional<string>("New York"); 

     using(TransactionScope scope = new TransactionScope()) 
     { 
     city.Value = "London"; 
     m_Number.Value = 4; 
     m_Number.Value++; 
     Debug.Assert(m_Number.Value == 5); 

     //No call to scope.Complete(), transaction will abort 
     } 
} 
+0

El código se ve muy bien, pero en la práctica tiene algunos errores desagradables. La clase TransactionalLock sufre bloqueos. Las excepciones lanzadas durante una transacción causarán caos. Impiden que la transacción se confirme, pero se ejecutan fuera de la transacción, por lo que el sistema de bloqueo se colapsa. Los objetos se dejan en estados indefinidos si varios hilos están trabajando en los mismos objetos simultáneamente si alguno de ellos arroja una excepción. – Ant

+0

@Ant. Interesante- ¿has encontrado estos problemas tú mismo? No he usado las clases por mí mismo, así que no puedo comentar realmente qué tan bien funcionan. – RichardOD

+0

Sí, averigüe si funcionaría en mi proyecto actual. Retiro el segundo problema mencionado anteriormente, fue causado por mí al tratar de resolver el primer problema. Todos los problemas son causados ​​por las interacciones entre el código ejecutado dentro del alcance de una transacción y el código que se ejecuta fuera de él. Por ejemplo, la reversión de transacción no se ejecuta dentro de la transacción; ni hacen excepciones. Sin embargo, la transacción mantiene el bloqueo hasta que se complete la reversión. Estoy teniendo bloqueos por todo el lugar. Podría ser mi culpa por tratar de ser demasiado inteligente, pero esta solución se ve horriblemente específica y horriblemente frágil. – Ant

3

No, no es actualmente nada como esto integrado en .NET o C#.

Sin embargo, dependiendo de sus requisitos de uso puede implementar algo que hizo el trabajo por usted.

Su clase ObjectTransaction serializaría (o simplemente duplicaría) el objeto y mantendría la copia durante la transacción. Si llamó al commit, la copia podría simplemente eliminarse, pero si llamó a la reversión, puede restaurar todas las propiedades en el objeto original de la copia.

Hay muchas advertencias sobre esta sugerencia.

  • si tiene que usar el reflejo para obtener las propiedades (porque quiere que maneje cualquier tipo de objeto) será bastante lento. Igualmente para la serialización.
  • Si tiene un árbol de objetos y no solo un objeto simple con algunas propiedades, manejar algo así genéricamente para todos los tipos de objetos podría ser bastante complicado.
  • Las propiedades que son listas de datos también son complicadas.
  • Si alguna propiedad obtiene/configura métodos que desencadenan cambios (o eventos) que tienen efectos, esto podría causar problemas reales en otros lugares de su aplicación.
  • Realmente solo funcionará para propiedades públicas.

Dicho todo esto, un proyecto en el que trabajé hace unos años hizo algo como esto. Bajo restricciones muy estrictas puede funcionar muy bien. Solo admitimos nuestros objetos de datos internos de la capa empresarial. Y todos ellos tenían que heredar de una interfaz base que proporcionaba algunos metadatos adicionales sobre los tipos de propiedad, y había reglas sobre qué eventos podrían desencadenarse a partir de los instaladores de propiedades. Comenzaríamos la transacción, luego uniríamos el objeto a la GUI. Si el usuario presiona Aceptar, la transacción se cerró, pero si presionan cancelar, el administrador de transacciones lo desvincula de la GUI y restituye todos los cambios en el objeto.

+0

Buena descripción de muchos de los problemas relacionados. Sin embargo, hay más problemas; por ejemplo, OR/Ms tienden a usar identidad para rastrear POCO, por lo que es riesgoso volver a crear objetos.Pero es bueno saber que alguien en alguna parte realmente lo hizo ;-) – queen3

+0

¿Tiene esta implementación un aspecto similar? http://www.codeproject.com/KB/vb/Transaction.aspx – queen3

+0

Hm, no, esto parece ser demasiado simple. Perdón por muchos comentarios ;-) – queen3

5

Por lo que vale, un STM en toda regla está un poco alejado, y recomendaría encarecidamente no rodar el suyo.

Afortunadamente, puede obtener la funcionalidad que desea mediante el diseño cuidadoso de sus clases. En particular, las clases inmutables admiten un comportamiento similar a las transacciones al instante. Dado que los objetos inmutables devuelven una nueva copia de sí mismos cada vez que se establece una propiedad, siempre tiene un historial completo para revertir si es necesario.

2

Y una vez más, la solución simple es no permitir que sus objetos entren en estado inválido en primer lugar. Entonces no necesita hacer retroceder nada, no necesita llamar a "Validar", etc. Si quita sus incubadores y comienza a pensar en enviar mensajes a objetos para hacer algo en los datos internos, en lugar de establecer propiedades, ' Descubriré cosas sutiles sobre tu dominio, que de otro modo no harías.

+0

Sé que ... como dije, solo tengo curiosidad. – queen3

1

Aquí está mi solución que acabo de escribir :) Debería funcionar también con matrices y cualquier tipo de referencia.

public sealed class ObjectTransaction:IDisposable 
{ 
    bool m_isDisposed; 

    Dictionary<object,object> sourceObjRefHolder; 
    object m_backup; 
    object m_original; 

    public ObjectTransaction(object obj) 
    { 
     sourceObjRefHolder = new Dictionary<object,object>(); 
     m_backup = processRecursive(obj,sourceObjRefHolder,new CreateNewInstanceResolver()); 
     m_original = obj; 
    } 

    public void Dispose() 
    { 
     Rollback(); 
    } 

    public void Rollback() 
    { 
     if (m_isDisposed) 
      return; 

     var processRefHolder = new Dictionary<object,object>(); 
     var targetObjRefHolder = sourceObjRefHolder.ToDictionary(x=>x.Value,x=>x.Key); 
     var originalRefResolver = new DictionaryRefResolver(targetObjRefHolder); 
     processRecursive(m_backup, processRefHolder, originalRefResolver); 

     dispose(); 
    } 

    public void Commit() 
    { 
     if (m_isDisposed) 
      return; 

     //do nothing 
     dispose(); 
    } 

    void dispose() 
    { 
     sourceObjRefHolder = null; 
     m_backup = null; 
     m_original = null; 
     m_isDisposed = true; 
    } 

    object processRecursive(object objSource, Dictionary<object,object> processRefHolder, ITargetObjectResolver targetResolver) 
    { 
     if (objSource == null) return null; 
     if (objSource.GetType()==typeof(string) || objSource.GetType().IsClass == false) return objSource; 
     if (processRefHolder.ContainsKey(objSource)) return processRefHolder[objSource]; 

     Type type = objSource.GetType(); 
     object objTarget = targetResolver.Resolve(objSource); 
     processRefHolder.Add(objSource, objTarget); 

     if (type.IsArray) 
     { 
      Array objSourceArray = (Array)objSource; 
      Array objTargetArray = (Array)objTarget; 
      for(int i=0;i<objSourceArray.Length;++i) 
      { 
       object arrayItemTarget = processRecursive(objSourceArray.GetValue(i), processRefHolder, targetResolver); 
       objTargetArray.SetValue(arrayItemTarget,i); 
      } 
     } 
     else 
     { 
      IEnumerable<FieldInfo> fieldsInfo = FieldInfoEnumerable.Create(type); 

      foreach(FieldInfo f in fieldsInfo) 
      { 
       if (f.FieldType==typeof(ObjectTransaction)) continue; 

       object objSourceField = f.GetValue(objSource); 
       object objTargetField = processRecursive(objSourceField, processRefHolder, targetResolver); 

       f.SetValue(objTarget,objTargetField);      
      } 
     } 

     return objTarget; 
    } 

    interface ITargetObjectResolver 
    { 
     object Resolve(object objSource); 
    } 

    class CreateNewInstanceResolver:ITargetObjectResolver 
    { 
     public object Resolve(object sourceObj) 
     { 
      object newObject=null; 
      if (sourceObj.GetType().IsArray) 
      { 
       var length = ((Array)sourceObj).Length; 
       newObject = Activator.CreateInstance(sourceObj.GetType(),length); 
      } 
      else 
      { 
       //no constructor calling, so no side effects during instantiation 
       newObject = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(sourceObj.GetType()); 

       //newObject = Activator.CreateInstance(sourceObj.GetType()); 
      } 
      return newObject; 
     } 
    } 

    class DictionaryRefResolver:ITargetObjectResolver 
    { 
     readonly Dictionary<object,object> m_refHolder; 

     public DictionaryRefResolver(Dictionary<object,object> refHolder) 
     { 
      m_refHolder = refHolder; 
     } 

     public object Resolve(object sourceObj) 
     { 
      if (!m_refHolder.ContainsKey(sourceObj)) 
       throw new Exception("Unknown object reference"); 

      return m_refHolder[sourceObj]; 
     } 
    } 
} 

class FieldInfoEnumerable 
{ 
    public static IEnumerable<FieldInfo> Create(Type type) 
    { 
     while(type!=null) 
     { 
      var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 

      foreach(FieldInfo fi in fields) 
      { 
       yield return fi; 
      } 

      type = type.BaseType; 
     }    
    } 
} 
+0

He escalado su publicación aquí http://stackoverflow.com/questions/17280550/single-threaded-object-rollback-inc-c-sharp ¿Podría mirar allí? – user1121956

Cuestiones relacionadas