TransactionScope и свой класс
От: Аноним  
Дата: 08.02.11 13:42
Оценка:
Как правильно реализовывать класс который может быть транзакционным..
Например у меня есть некоторый Dal класс.. который что то куда то записывает... Допустим у него есть метод Add(T entity)
так вот я хочу поместить в него логику что если он выполняется в окружении TransactionScope то запись делать только если у TransactionScope вызвали Complete.
Как такое сделать правильно?
Re: TransactionScope и свой класс
От: Timur Россия  
Дата: 08.02.11 14:55
Оценка:
Да в принципе, не особо сложно.
Вот тут можно почитать.
Вот только действительно ли оно вам надо?
Re: TransactionScope и свой класс
От: Sinix  
Дата: 08.02.11 15:01
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как правильно реализовывать класс который может быть транзакционным..

Без особой причины тру-транзакционностью лучше не заморачиваться.

Неаккуратное использование System.Transactions.* слишком легко (и внезапно) создаёт тормоза или падения (при отключенном DTC) на машине клиента. Куда проще реализовать легковесную disposable-обёртку и использовать её примерно так:

using (var operation = MyCommitableOperation(someResource))
{
  // ...
  op.Commit();
}


Впрочем, этот вариант тоже следует использовать с осторожностью, т к. он разворачивается в
var operation = MyCommitableOperation(someResource);
// TreadAbortException goes gere.
try
{
  // ...

и не гарантирует вызов Dispose().

Если всё-таки припёрло — вот готовая обёртка для transacted resource (вырвано с мясом — допилите напильником)
  Скрытый текст
  [Flags]
  public enum TransactionSources
  {
    /// <summary>
    /// Does nothing.
    /// </summary>
    None = 0x0,
    /// <summary>
    /// Uses transaction passed as an argument to the Enlist method.
    /// </summary>
    Explicit = 0x1,
    /// <summary>
    /// Uses transaction scope.
    /// </summary>
    TransactionScope = 0x2,
    /// <summary>
    /// Automatically creates transaction.
    /// </summary>
    Implicit = 0x4,
    /// <summary>
    /// Uses first available source.
    /// </summary>
    All = Explicit
        | TransactionScope
        | Implicit
  }

  public abstract class TransactedOperation: CriticalFinalizerObject, IDisposable
  {
    private sealed class ResourceManager: ISinglePhaseNotification
    {
      private readonly TransactedOperation resource;

      public ResourceManager(TransactedOperation resource)
      {
        Code.NotNull(resource, "resource");
        this.resource = resource;
      }

      #region ISinglePhaseNotification Members
      void ISinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
      {
        if (resource.TryPrepareCommit(true))
        {
          try
          {
            resource.DoCommit();
          }
          finally
          {
            singlePhaseEnlistment.Committed();
            resource.Unenlist();
          }
        }
        else
        {
          try
          {
            resource.DoRollback();
          }
          finally
          {
            singlePhaseEnlistment.Aborted();
            resource.Unenlist();
          }
        }
      }
      #endregion

      #region IEnlistmentNotification Members
      void IEnlistmentNotification.Commit(Enlistment enlistment)
      {
        try
        {
          resource.DoCommit();
        }
        finally
        {
          enlistment.Done();
          resource.Unenlist();
        }
      }

      void IEnlistmentNotification.InDoubt(Enlistment enlistment)
      {
        try
        {
          resource.InDoubt();
        }
        finally
        {
          enlistment.Done();
          resource.Unenlist();
        }
      }

      void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
      {
        if (resource.TryPrepareCommit(false))
        {
          preparingEnlistment.Prepared();
        }
        else
        {
          try
          {
            resource.DoRollback();
          }
          finally
          {
            preparingEnlistment.ForceRollback();
            resource.Unenlist();
          }
        }
      }

      void IEnlistmentNotification.Rollback(Enlistment enlistment)
      {
        try
        {
          resource.DoRollback();
        }
        finally
        {
          enlistment.Done();
          resource.Unenlist();
        }
      }
      #endregion
    }

    [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields",
      Justification = CodeAnalysisConstants.CA1823Conditional)]
    private static readonly string unenlistBugMessage = Resources.TransactedOperation_UnenlistBug;
    private static readonly string transactionEnlistedMessage = Resources.TransactedOperation_TransactionEnlisted;

    private static void InitTransaction(
      ref Transaction transaction, ref TransactionSources sources)
    {
      Transaction resultingTransaction = null;
      TransactionSources resultingSource = TransactionSources.None;

      Transaction scopeTransaction = Transaction.Current;
      if (transaction != null && sources.Includes(TransactionSources.Explicit))
      {
        resultingTransaction = transaction;
        resultingSource = TransactionSources.Explicit;
      }
      else if (scopeTransaction != null && sources.Includes(TransactionSources.TransactionScope))
      {
        resultingTransaction = scopeTransaction;
        resultingSource = TransactionSources.TransactionScope;
      }
      else if (sources.Includes(TransactionSources.Implicit))
      {
        resultingTransaction = new CommittableTransaction();
        resultingSource = TransactionSources.Implicit;
      }

      transaction = resultingTransaction;
      sources = resultingSource;
    }

    #region Fields & .ctor()
    private Transaction currentTransaction;
    private TransactionSources transactionSource;

    [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
      Justification = CodeAnalysisConstants.CA1816ExplicitSuppressFinalize)]
    protected TransactedOperation()
    {
      GC.SuppressFinalize(this);
    }
    #endregion

    #region Public properties
    public Transaction Transaction
    {
      get
      {
        return currentTransaction;
      }
      set
      {
        if (currentTransaction != value)
        {
          Enlist(value);
        }
      }
    }
    public TransactionSources TransactionSource
    {
      get
      {
        return transactionSource;
      }
    }
    public bool TransactionEnlisted
    {
      get
      {
        return transactionSource != TransactionSources.None;
      }
    }
    #endregion

    #region Public methods
    public bool Enlist(TransactionSources sources)
    {
      return Enlist(null, sources);
    }
    public bool Enlist(Transaction transaction)
    {
      Code.NotNull(transaction, "transaction");
      return Enlist(transaction, TransactionSources.Explicit);
    }
    public bool Enlist(Transaction transaction, TransactionSources sources)
    {
      Code.AssertState(!TransactionEnlisted, transactionEnlistedMessage);

      InitTransaction(ref transaction, ref sources);

      bool result = sources != TransactionSources.None;
      if (result)
      {
        PrepareEnlist(transaction, sources);

        transaction.EnlistVolatile(new ResourceManager(this), EnlistmentOptions.None);

        currentTransaction = transaction;
        transactionSource = sources;

        if (sources == TransactionSources.Implicit)
        {
          GC.ReRegisterForFinalize(this);
        }
      }

      return result;
    }

    public CommittableTransaction BeginTransaction()
    {
      Enlist(TransactionSources.Implicit);
      return (CommittableTransaction)currentTransaction;
    }

    public bool CommitIfImplicitTransaction()
    {
      bool result = transactionSource == TransactionSources.Implicit;
      if (result)
      {
        ((CommittableTransaction)currentTransaction).Commit();
      }
      return result;
    }
    #endregion

    #region Overridable members
    protected abstract void PrepareEnlist(Transaction newTransaction, TransactionSources source);

    protected abstract bool TryPrepareCommit(bool singlePhase);
    protected abstract void DoCommit();
    protected abstract void DoRollback();

    protected virtual void InDoubt()
    {
      DoRollback();
    }
    protected virtual void Unenlisted()
    {
    }
    #endregion

    [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
      Justification = CodeAnalysisConstants.CA1816ExplicitSuppressFinalize)]
    private void Unenlist()
    {
      if (!TransactionEnlisted)
      {
        Code.Bug(unenlistBugMessage);
      }

      if (transactionSource == TransactionSources.Implicit)
      {
        GC.SuppressFinalize(this);
      }

      transactionSource = TransactionSources.None;
      currentTransaction = null;
      Unenlisted();
    }

    #region IDisposable Members
    protected virtual void Dispose(bool disposing)
    {
      if (transactionSource == TransactionSources.Implicit)
      {
        currentTransaction.Dispose();
      }
    }

    [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
      Justification = CodeAnalysisConstants.CA1816ExplicitSuppressFinalize)]
    public void Dispose()
    {
      Dispose(true);
    }

    ~TransactedOperation()
    {
      Dispose(false);
    }
    #endregion
  }

  public class TransactedValue<T>: TransactedOperation where T: struct
  {
    private T? oldValue;
    private T value;

    public TransactedValue(T value)
    {
      this.value = value;
      Enlist(TransactionSources.Implicit);
    }
    public TransactedValue(T value, Transaction transaction)
    {
      this.value = value;
      Enlist(transaction);
    }
    public TransactedValue(T value, TransactionSources sources)
    {
      this.value = value;
      Enlist(sources);
    }

    public T Value
    {
      get
      {
        return this.value;
      }
      set
      {
        this.value = value;
      }
    }

    protected override void PrepareEnlist(Transaction newTransaction, TransactionSources source)
    {
      oldValue = value;
    }

    protected override bool TryPrepareCommit(bool singlePhase)
    {
      return oldValue.HasValue;
    }

    protected override void DoCommit()
    {
      oldValue = null;
    }

    protected override void DoRollback()
    {
      value = oldValue.Value;
    }
  }

  public static void Main(string[] args)
  {
      TransactedValue<int> transacted = new TransactedValue<int>(100);
      try
      {
        transacted.Value = 200;
        //transacted.CommitIfImplicitTransaction();
      }
      finally
      {
        transacted.Dispose();
      }
      Console.WriteLine(transacted.Value);
  }
Re[2]: TransactionScope и свой класс
От: Sinix  
Дата: 08.02.11 15:08
Оценка:
Здравствуйте, Timur, Вы писали:

T>Да в принципе, не особо сложно.

T>Вот тут можно почитать.
T>Вот только действительно ли оно вам надо?
Не надо. Правильно сделать resource manager (особенно с 2-phase-commit довольно сложно, надо внимательно изучать документацию к каждому методу.
Re[2]: TransactionScope и свой класс
От: Sinix  
Дата: 08.02.11 15:18
Оценка:
Здравствуйте, Sinix, Вы писали:

Упс, ступил с примером использования:
  Скрытый текст
    public class TransactedValue<T>: TransactedOperation where T: struct
    {
      private T? oldValue;
      private T value;

      public TransactedValue(T value)
      {
        this.value = value;
      }

      public T Value
      {
        get
        {
          return this.value;
        }
        set
        {
          this.value = value;
        }
      }

      protected override void PrepareEnlist(Transaction newTransaction, TransactionSources source)
      {
        oldValue = value;
      }

      protected override bool TryPrepareCommit(bool singlePhase)
      {
        return oldValue.HasValue;
      }

      protected override void DoCommit()
      {
        oldValue = null;
      }

      protected override void DoRollback()
      {
        value = oldValue.Value;
      }
    }

    [STAThread]
    public static void Main(string[] args)
    {
      TransactedValue<int> transacted = new TransactedValue<int>(100);
      using (CommittableTransaction transaction = transacted.BeginTransaction())
      {
        transacted.Value = 200;
        //transaction.Commit()
      }
      Console.WriteLine(transacted.Value);
    }
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.