2/24/2008 8:29:00 PM

Доработал сегодня Transaction Objects. Пофиксил старые баги, добавил новые :)

Из основных изменений:

  1. Объект теперь не надо явно регистрировать в транзакции.
    Точнее, вообще никак не надо.
    Сделал так потому, что для сложных объектов, которые "внутри" оперируют или содержат другие транзакционные объекты и т.д, явная регистрация всех этих "вложенных" транзакционных объектов в транзакции становится головной болью.
    Теперь всё, что делается внутри ObjectTransactionScope, автоматически относится только к текущей транзакции. Для TransactionObjects\TransactionProperties, разумеется :)
  2. TransactionProperties теперь обладают некоторыми метаданными.
    В частности, появилась возможность задать значение по умолчанию, возможность коррекции присваиваемого значения (CoerceValue) и возможность оповещения об изменениях свойства (PropertyChanged).
    Реализовано примерно так же, как и в случае с DependencyObjects - через коллбек-делегаты.

Так же могу отметить, что скорость работы изменилась в лучшую сторону :)

Transactions.zip (25.47 kb)

2/10/2008 9:57:00 PM

В субботу задумался, а как можно реализовать возможность объекта учавствовать в транзакциях? Чтобы можно было так же, как с БД: открыл транзакцию, понаизменял объект, потом решил: "не понравилось!" и откатил все изменения. А объект чтобы не пострадал.

Требования вырисовывались такие:

  1. До момента подтверждения транзакции все изменения, сделанные во время транзакции, должны быть видны только в рамках этой транзакции. За ее пределами объект должен выглядеть нетронутым.
  2. В случае вложенных транзакция (транзакция Б внутри транзакции А), если транзакция Б завершается, ее изменения становятся доступны для транзакции А, но по прежнему остаются невидимыми вне пределов транзакции А до ее фиксации.
  3. В случае отката транзакции объект остается в том же самом состоянии, как и до начала этой транзакции.
  4. Создавать такие объекты должно быть так же просто, как и "обычные" типы.

Задача показалась интересной, сходу решения в интернете не нашел, ну так оно и интереснее, стал делать сам.

Сначала решил было использовать механизм контекстов .NET Framework. Вроде, указал атрибутом контекст объекту - а дальше все будет делаться само. После пары часов возни с контекстами от этой идеи решил отказаться по двум причинам. Первая в том, что там объект должен наследоваться от ContextBoundObject, при этом для него, понятно, создаются Real и Transparent прокси, плюс рефлексия, короче, производительность. Вторую причину я уже не помню, что-то там толи уж очень сложно получалось, толи не получалось вовсе, уже не суть.

Пришлось стереть все и думать дальше.
В результате мысль довела до того, что я решил использовать майкрософтовский же подход, который они применили для реализации DependencyProperties.
Помните декларацию таких свойств?

// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(MyClass), new UIPropertyMetadata(0));
 
public string Name
{
    get { return (string)GetValue(NameProperty); }
    set { SetValue(NameProperty, value); }
}

То, что надо! Разработчик только декларирует свойство, а то, какие уж там транзакции и как они учитывают ему уже не надо думать.

Получается примерно следующая конструкция:

public class Person : TransactionObject
{
    private static readonly TransactionProperty NameProperty
        = TransactionProperty.Register(typeof(Person), typeof(string));
 
    public string Name
    {
        get { return (string)base.GetValue(NameProperty); }
        set { base.SetValue(NameProperty, value); }
    }
}

Похоже, правда? :)
Наследуемся от TransactionObject (у Майкрософта DependencyObject) и регистрируем TransactionProperties (у Майкрософта DependencyProperties).

Далее, в .NET Framework есть такое пространство имен: System.Transactions, в котором уже реализованы классы Transaction, CommittableTransaction, TransactionScope и другие. В принципе, все это как раз и предназначено для поддержки транзакций, и было бы грех это не использовать. Правда, здесь меня ожидали два неприятных сюрприза: ни от одного из этих классов нельзя наследоваться - раз, и замечательный во всех отношениях TransactionScope ведет себя просто безобразно: когда объекту приходит команда Commit или Rollback, то совершенно невозможно определить, в рамках какой транзакции были вызваны эти самые Commit и Rollback.

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

1. Person user = new Person();
2. user.Name = "Greg Johns";
3. Console.WriteLine("Initial value: " + user.Name);
4. Console.WriteLine();
5.  
6. using (ObjectTransactionScope tran1 = new ObjectTransactionScope(user))
7. {
8.     user.Name = "Henry Smith";
9.     Console.WriteLine("In transaction #1:\t" + user.Name);
10.     using (ObjectTransactionScope tran2 = new ObjectTransactionScope(user, TransactionScopeOption.RequiresNew))
11.     {
12.         user.Name = "Walter Simpson";
13.         Console.WriteLine("In transaction #2:\t" + user.Name);
14.         Console.WriteLine("\t(Transaction #2 is not committed)");
15.     }
16.     Console.WriteLine("In transaction #1:\t" + user.Name);
17.     Console.WriteLine("\t(Transaction #1 commit)");
18.     tran1.Complete();
19. }
20.  
21. Console.WriteLine();
22. Console.WriteLine("Finally:\t" + user.Name);

Здесь сначала мы работаем с экземпляром безо всяких транзакций (строки 2,3), затем открываем первую транзакцию и меняем имя пользователя (8). Если в этот момент кто-то обратится из другого потока к этому же объекту и прочитает свойство Name, он должен увидеть Greg Johns, а не Henry Smith, так как транзакция tran1 еще не была зафиксирована. Хотя "изнутри" этой транзакции будет "виден" именно Генри Смит (строка 9).

В строке 10 мы открываем новую транзакцию для объекта, причем требуем, чтобы была создана именно новая транзакция.
Снова меняем имя на Walter Simpson, поведение то же самое - изменение видно только внутри транзакции tran2.

В строке 15 транзакция tran2 заканчивается и, так как она не была подтверждена, происходит "откат" всех изменений (точнее сказать, изменения не фиксируются), поэтому после выхода из транзакции tran2 внутри tran1 мы продолжим видеть все того же Генри Смита.

Я знаком с Генри, он хороший парень, поэтому транзакцию tran1 фиксируем (строка 18).
Соответственно, после выхода из транзакции tran1 (строка 22) мы, уже вне всяких транзакций, увидим, что имя пользователя было изменено на Henry Smith. И все остальные эти изменения тоже увидят.

Кажется, достаточно просто и, главное, стандартно: регистрация свойств и обычное поведение из System.Transactions.

На реализацию механизма ушли суббота и воскресенье (если честно, то часа по 3-4 в день), прототип готов :)

Есть еще идеи, как и чего там сделать, на предмет дополнительных возможностей всяких, но это когда дойдут руки.

А пока код проекта - в аттачменте.
Вопросы, предложения, баги и т.д. - в комменты или на почту ;)

Transactions.zip (48.77 kb)

Powered by BlogEngine.NET 1.6.0.0

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter


Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in