Подтвердить что ты не робот

Транзакции для объектов С#?

Любопытно, есть ли поддержка транзакций на простых объектах С#? Как

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

Просто чтобы сделать ответы более полезными;-) есть ли что-то подобное на других языках?

Обновление STM: вот что он утверждает:

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

оставит x/y неизменным, включая вызовы с цепными методами. Похоже, что я прошу. По крайней мере, это очень интересно. Я думаю, что это достаточно близко. Кроме того, на других языках есть похожие вещи, например Haskell STM. Заметьте, я не говорю, что он должен использоваться для производства; -)

4b9b3361

Ответ 1

Microsoft работает над этим. Читайте о транзакционной памяти программного обеспечения.

Они используют несколько разных синтаксисов:

// 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

Для чего стоит, полномасштабный STM - это небольшой выход, и я настоятельно рекомендую не кататься самостоятельно.

К счастью, вы можете получить необходимую функциональность, тщательно разработав свои классы. В частности, неизменяемые классы поддерживают транзакционное поведение из коробки. Поскольку неизменяемые объекты возвращают новую копию себя каждый раз, когда свойство задано, у вас всегда есть полные истории изменений при откате, если это необходимо.

Ответ 3

Об этом написал Джувал Лоуи. Вот ссылка на его оригинальную статью MSDN (я впервые услышал об этой идее в своей отличной книге WCF). Вот пример кода из MSDN, который выглядит так, как вы хотите достичь:

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

Ответ 4

Вы можете сделать копию объекта перед выполнением методов и настройкой свойств. Затем, если вам не нравится результат, вы можете просто "откат" к копии. Предполагая, конечно, что у вас нет побочных эффектов, с которыми можно бороться.

Ответ 5

Нет, в настоящее время нет ничего подобного встроенному в .net или С#.

Однако в зависимости от ваших требований к использованию вы можете реализовать что-то, что сделало эту работу для вас.

Ваш класс ObjectTransaction будет сериализовать (или просто дублировать) объект и сохранить копию во время транзакции. Если вы вызвали commit, копия могла быть просто удалена, но если вы вызываете откат, вы можете восстановить все свойства исходного объекта из копии.

В этом предложении есть много предостережений.

  • Если вам нужно использовать отражение, чтобы получить свойства (потому что вы хотите, чтобы он обрабатывал любой тип объекта), он будет довольно медленным. Аналогично для сериализации.
  • Если у вас есть дерево объектов, а не просто простой объект с несколькими свойствами, обработка чего-то подобного в общем случае для всех типов объектов может быть довольно сложной.
  • Свойства, являющиеся списками данных, также сложны.
  • Если какие-либо свойства get/set методы вызывают изменения (или события), которые имеют эффекты, это может вызвать реальные проблемы в другом месте вашего приложения.
  • Он действительно будет работать только для публичных свойств.

Все, что сказал, проект, над которым я работал несколько лет назад, сделал что-то в этом роде. При очень жестких ограничениях он может работать действительно красиво. Мы поддерживали только внутренние объекты данных бизнес-уровня. И все они должны были унаследовать от базового интерфейса, который предоставил некоторые дополнительные метаданные по типам свойств, и были правила о том, какие события могут быть вызваны из средств определения свойств. Мы начнем транзакцию, а затем привяжем объект к графическому интерфейсу. Если пользователь нажимал "ОК", транзакция была только что закрыта, но если они отменили отмену, диспетчер транзакций отключил его от графического интерфейса пользователя и отбросил все изменения на объекте.

Ответ 6

Нет, такого типа поддержки сегодня не существует для управляемых ванили объектов.

Ответ 7

И здесь снова простое решение не должно позволять вашим объектам попадать в недействительное состояние в первую очередь. Тогда вам не нужно ничего откатывать, вам не нужно называть "Validate" и т.д. Если вы удалите сеттеры и начнете думать о том, чтобы отправлять сообщения объектам, чтобы что-то делать во внутренних данных, а не устанавливать свойства, я обнаружил бы тонкие вещи о вашем домене, иначе вы бы этого не сделали.

Ответ 8

Вот мое решение, которое я только что написал:) Должен работать также с массивами и любыми ссылочными типами.

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