Transacciones para objetos C #?

Solo curiosidad, ¿hay soporte para transacciones en objetos simples C #? Me gusta

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 restred } } 

Solo para hacer 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, incluidas las llamadas a métodos encadenados. 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 😉

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

  • STM.NET
  • Blog del equipo STM.NET
  • Canal 9 Video: STM.NET: Quién. Qué. Por qué.
  • Artículos sobre STM

Usan algunas syntax 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(); } 

Por lo que vale, un STM en toda regla está un poco alejado, y recomiendo encarecidamente no rodar el tuyo.

Afortunadamente, puede obtener la funcionalidad que desea al diseñar cuidadosamente 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.

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

 public class MyClass { Transactional m_Number = new Transactional(3); public void MyMethod() { Transactional city = new Transactional("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 } } 

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.

No, actualmente no hay nada como esto integrado en .net o C #.

Sin embargo, dependiendo de sus requisitos de uso, podría implementar algo que hiciera 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ó a commit la copia solo pudo eliminarse, pero si llamó al rollback, puede restaurar todas las propiedades del objeto original desde la copia.

Hay muchas advertencias a esta sugerencia.

  • si tiene que usar la reflexión 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 / establece métodos que desencadenen cambios (o eventos) que tengan efectos, esto podría causar problemas reales en otros lugares de la aplicación.
  • Realmente solo funcionará para propiedades públicas.

Dicho todo esto, un proyecto en el que trabajé hace unos años sí 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.

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

Y aquí nuevamente 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.

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 sourceObjRefHolder; object m_backup; object m_original; public ObjectTransaction(object obj) { sourceObjRefHolder = new Dictionary(); 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(); 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 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 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 m_refHolder; public DictionaryRefResolver(Dictionary 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 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; } } }