// // Copyright (c) 2009 Microsoft Corporation. All rights reserved. // // DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You // bear the risk of using it. Microsoft gives no express warranties, // guarantees or conditions. You may have additional consumer rights // under your local laws which this agreement cannot change. To the extent // permitted under your local laws, Microsoft excludes the implied warranties // of merchantability, fitness for a particular purpose and non-infringement. namespace TransactedComment { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Transactions; /// /// A transaction-aware store for building strings. It supports appending /// and inspecting. /// /// /// The interface /// provides callbacks for the transaction system. This is a simple /// example of a transactional resource manager. If your current resource /// manager already implements , /// you probably do not need to write one yourself. /// public class TransactedComment : IEnlistmentNotification { /// /// Stores the official value of the comment. /// private StringBuilder commentValue; /// /// Stores the temprary value of the comment inside the transaction. /// private StringBuilder temporaryValue; /// /// enlistedTransaction is the one that has the right to affect /// this TransactedComment, and to see the temporary value. If /// it is set, then attempting to alter the TransactedComment from /// outside a transaction or inside a different transaction is an error. /// private Transaction enlistedTransaction = null; /// /// Initializes a new instance of the TransactedComment class. This is /// the default constructor. /// public TransactedComment() : this(string.Empty) { } /// /// Initializes a new instance of the TransactedComment class. /// /// The initial value of the comment. public TransactedComment(string value) { this.commentValue = new StringBuilder(value); this.temporaryValue = null; } /// /// Gets the length of the transacted string. If this is /// called within the transaction, it returns the length of /// the transacted value. Otherwise, it returns the length of /// the original value. /// public int Length { get { // If not in a transaction, or in a different transaction // than the one we are enlisted in, return the publicly // visible state. if ( (Transaction.Current == null) || (this.enlistedTransaction != Transaction.Current)) { return this.commentValue.Length; } else { return this.temporaryValue.Length; } } } /// /// Make the transacted changes permanent. /// /// The enlistment for the open tranacstion. void IEnlistmentNotification.Commit(Enlistment enlistment) { // This copies the value to the permanent version and wipes out // any temporary information, then informs the enlistment that we are // all set. this.commentValue = new StringBuilder(this.temporaryValue.ToString()); this.temporaryValue = null; this.enlistedTransaction = null; enlistment.Done(); } /// /// Discard the transacted changes. /// /// The enlistment for the open tranacstion. void IEnlistmentNotification.Rollback(Enlistment enlistment) { this.temporaryValue = null; this.enlistedTransaction = null; enlistment.Done(); } /// /// Discard the transacted changes. /// /// The enlistment for the open tranacstion. void IEnlistmentNotification.InDoubt(Enlistment enlistment) { enlistment.Done(); } /// /// Determine if the transaction can be committed. /// /// A PreparingEnlistment object used /// to send a response to the transaction manager. void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); } /// /// Append text to the transacted string. /// /// The text to append. public void Append(string text) { // Make sure that we are in a sensible transaction context. this.ValidateTransactionOrEnlist(); if (this.enlistedTransaction != null) { // If ValidateTransactionOrEnlist did not throw an error and // left a value in enlistedTransaction, then we are in the right // transaction and should act on the temporary value. this.temporaryValue.Append(text); } else { // If we are not in a transaction and this TransactedComment has // never been manipulated inside a transaction since its last Commit, // just act on the permanent value. this.commentValue.Append(text); } } /// /// Remove all text from the string. /// public void Clear() { // Make sure that we are in a sensible transaction context. this.ValidateTransactionOrEnlist(); if (this.enlistedTransaction != null) { // If ValidateTransactionOrEnlist did not throw an error and left // a value in enlistedTransaction, then we are in the right // transaction and should act on the temporary value. this.temporaryValue = new StringBuilder(); } else { // If we are not in a transaction and this TransactedComment has // never been manipulated inside a transaction since its last Commit, // act on the permanent value. this.commentValue = new StringBuilder(); } } /// /// Gets the System.String that represents the transacted /// transacted string. If this is called within the /// transaction, it returns the transacted value. /// Otherwise, it returns the original value. /// /// A System.String that represents the transacted /// transacted string public override string ToString() { // If we are not in a transaction, or we are in a different transaction // than the one we enlisted in, return the publicly visible state. if ( (Transaction.Current == null) || (this.enlistedTransaction != Transaction.Current)) { return this.commentValue.ToString(); } else { return this.temporaryValue.ToString(); } } /// /// Test if we are in a transaction and enlist if needed. /// private void ValidateTransactionOrEnlist() { // We are in a transaction. The using (CurrentPSTransaction) block // in the cmdlet caused Transaction.Current to be set correctly, so // we can test against it here. if (Transaction.Current != null) { // We have not yet been called inside of a transaction. So enlist // in the transaction, and store our save point. if (this.enlistedTransaction == null) { Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None); this.enlistedTransaction = Transaction.Current; this.temporaryValue = new StringBuilder(this.commentValue.ToString()); } else { // We are already enlisted in a transaction. if (Transaction.Current != this.enlistedTransaction) { throw new InvalidOperationException("Cannot modify string. It has been modified by another transaction."); } } } else { // We are not in a transaction. // If we are not subscribed to a transaction, modify the underlying value. if (this.enlistedTransaction != null) { throw new InvalidOperationException("Cannot modify string. It has been modified by another transaction."); } } } } }