Transactions for C# objects?

前端 未结 8 765
青春惊慌失措
青春惊慌失措 2020-12-01 09:26

Just curious, is there any support for transactions on plain C# objects? Like

using (var transaction = new ObjectTransaction(obj))
{
  try
  {
    obj.Prop1          


        
相关标签:
8条回答
  • 2020-12-01 09:58

    Microsoft is working on it. Read about Software Transactional Memory.

    • STM.NET
    • STM.NET Team Blog
    • Channel 9 Video: STM.NET: Who. What. Why.
    • Papers on STM

    They use a few different syntaxes:

    // 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();
    }
    
    0 讨论(0)
  • 2020-12-01 10:06

    For what its worth, a full-blown STM is a little ways out, and I would strongly recommend against rolling your own.

    Fortunately, you can get the functionality you want by carefully designing your classes. In particular, immutable classes support transaction-like behavior out of the box. Since immutable objects return a new copy of themselves each time a property is set, you always have a full-history changes to rollback on if necessary.

    0 讨论(0)
  • 2020-12-01 10:08

    Juval Lowy has written about this. Here is a link to his original MSDN article (I first heard about the idea in his excellent WCF book). Here's a code example from MSDN, which looks like what you want to achieve:

    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 讨论(0)
  • 2020-12-01 10:13

    No, there isn't currently anything like this built into .net or C#.

    However depending on your usage requirements you could implement something that did the job for you.

    Your ObjectTransaction class would serialise (or just duplicate) the object and hold the copy during the transaction. If you called commit the copy could just be deleted, but if you called rollback you could restore all of the properties on the original object from the copy.

    There are lots of caveats to this suggestion.

    • if you have to use reflection to get the properties (because you want it to handle any object type) it will be quite slow. Equally for serialisation.
    • If you have a tree of objects and not just a simple object with a few properties, handling something like that generically for all object types could be pretty tricky.
    • Properties that are lists of data are also tricky.
    • If any properties get/set methods trigger changes (or events) that have effects this could cause real issues elsewhere in your app.
    • It will only really work for public properties.

    All that said, a project I worked on a few years ago did do something exactly like this. Under very strict restrictions it can work really nicely. We only supported our internal business layer data objects. And they all had to inherit from a base interface that provided some additional meta data on property types, and there were rules on what events could be triggered from property setters. We would start the transaction, then bind the object to the GUI. If the user hit ok, the transaction was just closed, but if they hit cancel, the transaction manager unbound it from the GUI and rolled back all the changes on the object.

    0 讨论(0)
  • 2020-12-01 10:13

    Here is my solution that I just wrote:) Should work also with arrays and any reference types.

    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 讨论(0)
  • 2020-12-01 10:17

    No, this type of support does not exist today for vanilla managed objects.

    0 讨论(0)
提交回复
热议问题