February 2005 - Posts

Propagating Distributed Transactions – A .NET Remoting Solution

I have published an early draft of an article that I am working on at http://dotnetjunkies.com/WebLog/chris.taylor/articles/54503.aspx. The article describes a simple strategy to flowing distributed transactions across remoting boundaries. It also includes information on enabling TIP (Transaction Internet Protocol) on Windows 2003 server as well as Windows XP SP2. The formating still needs work, especially the code.

Distributed Transactions without COM+ Components

Update: I have posted a draft of an article that takes this topic further and addresses flowing distributed transactions. See the post at http://dotnetjunkies.com/WebLog/chris.taylor/articles/54503.aspx.

Not being in a position to wait for all the wonderful support for transactions in .NET Framework 2.0 namespace System.Transactions I have had to make use of existing services to enlist in Distributed transactions and further more to propagate those transactions across process and machine boundaries.

Initial solutions required the development of numerous COM+ components. Being a C++ developer with extensive COM experience this did not bother me but fortunately with COM+ 1.5’s Services Without Components I no longer needed jump these hoops to satisfy the transactional requirements of the software. Even better is .NET Framework 1.1 support for these services in the System.EnterpriseServices namespace. Through the next few posts I will be passing on some of the techniques I have used to propagate distributed transaction across WebServices and .NET Remoting. To kick start here is a watered down version of the TransactionContext class.

   1:    using System;
   2:    using System.EnterpriseServices;
   3:    using System.Runtime.InteropServices;
   4:   
   5:    public class TransactionContext : IDisposable
   6:    {
   7:      private bool _consistent = false;
   8:   
   9:      public TransactionContext() : this(TransactionOption.Required)
  10:      {
  11:      }
  12:   
  13:      public TransactionContext(TransactionOption option)
  14:      {
  15:        ServiceConfig config = new ServiceConfig();
  16:        config.Transaction = option;
  17:        config.TrackingEnabled = true;
  18:        ServiceDomain.Enter(config);
  19:      }
  20:   
  21:      public void Complete()
  22:      {
  23:        _consistent = true;
  24:      }
  25:   
  26:      public void Dispose()
  27:      {
  28:        try
  29:        {
  30:          GC.SuppressFinalize(this);
  31:          if (_consistent)
  32:            ContextUtil.SetComplete();
  33:          else
  34:            ContextUtil.SetAbort();
  35:        }
  36:        finally
  37:        {
  38:          ServiceDomain.Leave();  
  39:        }
  40:      }
  41:   
  42:      ~TransactionContext()
  43:      {
  44:        System.Diagnostics.Debug.Fail("TransactionContext must de explicitly Disposed");
  45:      }
  46:    }
 

To use this class is relatively simple, all you do is create an instance of the TransactionContext and if everything completes without causing a problem you put the context into a consistent state by calling Complete() when the Dispose method is called the vote for the success of the transaction is cast based on the consistency flag, you must ensure that Dispose is called as soon as your work is complete. The following example demonstrates the use of the TransactionContext class.

   1:        SqlConnection oCon;
   2:        SqlCommand oCmd;
   3:        using (TransactionContext tx = new TransactionContext())
   4:        {  
   5:          oCon = new SqlConnection("Your Connection String here");
   6:          oCmd = oCon.CreateCommand();
   7:          oCmd.CommandText = "insert into ...";
   8:          
   9:          oCon.Open(); 
  10:          oCmd.ExecuteNonQuery();
  11:          oCon.Close();
  12:   
  13:          tx.Complete();
  14:        }
 
In a future post I will address some of the more naïve solutions to propagate these transactions across process and machine boundaries.

A simple LineNumberReader

For a small personal project I have been developing a parser, as part of the parser I developed the LineNumberReader modeled after the Java(TM) class of the same name. Since the class has been useful, I thought I would share it. For the purposes of the post I have removed comments and a number of the constructors.

One of the nice features is the ability to set a marker in the stream using the Mark() method and returning to that point in the stream with the Reset() method. Enjoy!

   1:  class LineNumberReader : StreamReader
   2:      {
   3:        public LineNumberReader(Stream stream) : base(stream){}
   4:   
   5:        public LineNumberReader(
   6:          Stream stream, 
   7:          System.Text.Encoding encoding) : base(stream, encoding){}
   8:        // More constructors matching those from StreamReader ...
   9:   
  10:        public int LineNumber
  11:        {
  12:          get { return _lineNumber; }
  13:          set 
  14:          { 
  15:            if (value < 0 ) throw new ArgumentOutOfRangeException( 
  16:              "LineNumber", "Must be greater or equal to 0.");
  17:            _lineNumber = value; 
  18:          }
  19:        }
  20:   
  21:        public void Mark()
  22:        {
  23:          if (!BaseStream.CanSeek) throw new NotSupportedException(
  24:            "Mark is not supported, underlying stream is not seekable.");
  25:          _mark = new MarkData(_lineNumber, BaseStream.Position);
  26:          _markset = true;
  27:        }
  28:   
  29:        public void Reset()
  30:        {
  31:          if (!_markset) throw new IOException("Reader not marked");
  32:          _markset = false;
  33:          DiscardBufferedData();
  34:          BaseStream.Seek(_mark.Position, SeekOrigin.Begin);
  35:          _lineNumber = _mark.LineNumber;
  36:        }
  37:   
  38:        public override int Read()
  39:        {
  40:          int retval = base.Read();  
  41:          if (retval == (int)'\r')
  42:          {
  43:            if (Peek() == (int)'\n')
  44:            {
  45:              base.Read();    
  46:            }
  47:            retval = (int)'\n';
  48:          }
  49:   
  50:          if (retval == (int)'\n')
  51:            _lineNumber++;
  52:   
  53:          return retval;
  54:        }
  55:      
  56:        public override int Read(char[] buffer, int index, int count)
  57:        {
  58:          int bytesRead = base.Read(buffer, index, count);
  59:          _lineNumber += CountLinesInBuffer(buffer, index, bytesRead);
  60:          return bytesRead;
  61:        }
  62:   
  63:        public override string ReadLine()
  64:        {
  65:          string retval = base.ReadLine(); 
  66:          if (retval != null)
  67:            _lineNumber++;
  68:          return retval; 
  69:        }
  70:   
  71:        private int CountLinesInBuffer(char[] buffer, int index, int count)
  72:        {
  73:          int lineCount = 0;
  74:          int i = index;
  75:          int lastIndex = index + count;
  76:   
  77:          do
  78:          {
  79:            char ch = buffer[i];
  80:            if (ch == '\r')
  81:            {
  82:              if ((i + 1 < lastIndex) && (buffer[i + 1] == '\n'))
  83:                i++;
  84:   
  85:              ch = '\n';
  86:            }
  87:   
  88:            if (ch == '\n')
  89:              lineCount++;
  90:          } while (++i < lastIndex);
  91:          
  92:          return lineCount;
  93:        }
  94:   
  95:        private struct MarkData
  96:        {
  97:          public MarkData(int lineNumber, long position)
  98:          {
  99:            LineNumber = lineNumber;
 100:            Position = position;
 101:          }
 102:          public int LineNumber;
 103:          public long Position;
 104:        }
 105:   
 106:        private int _lineNumber = 0;
 107:        private MarkData _mark;
 108:        private bool _markset = false;
 109:      }