Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Component Services / October 2003

Tip: Looking for answers? Try searching our database.

Failed Transactional Aborts using Message Queue and SQL Server

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Julian Baden - 13 Oct 2003 11:43 GMT
This is my first post and apologies for the long message.

I'm having a nightmare trying to get a ServicedComponent
to rollback a transaction that covers the sending of
a message to a Message Queue (local or remote) AND the
deletion of a row from a table in SQL Server 2000.

Both operations must be guaranteed to either commit or
abort hence a I chose a ServicedComponent to do this.

The trouble is that no matter whether I make the Queue
transactional or non-transactional the sends to the
Queue are never rolled back. I'm forcing the attempted
rollback by renaming the stored proc so that the code can't
find the correct stored proc. The Send to the Queue is
performed before the SQL Server row deletion is
attempted. If the row deletion is performed first and I
attempt to send to a nonexistent Queue then the data
deletion is successfully rolled back so the problem is
occurring with the Queue.

The architecture of the System I am working on is
dependent upon being able to use Message Queues and use
them within distributed transactions.

I've now got down to the bare bones by including all the
code within one ServicedComponent (I originally
had a generic Message Queue Sender component, a separate
DALC for the data update and a middleman component
that initiated the transaction).

I've been through the articles on "Creating a Message
Queue Transaction with COM+ Services"
and "Reliable Messaging with MSMQ and .NET" and looked at
some of the posts relating to this (e.g. using
ContextUtil.SetAbort etc) but all to no avail.

The component I created was placed in the GAC using
GACUTIL (gacutil /i mysingletrans.dll) and I manually
registered the COM+ application using REGSVCS as a Library
component. The client is using the version
registered in the GAC (Local Copy = False). I am taking
advantage of the [AutoComplete()] attribute and
using Exceptions to cause the Abort.

I would be very grateful if someone out there could
provide assistance with this. I really do not want to have
to re-architect a System at this stage and I've spent days
trying to solve this. I must be missing something
blindingly obvious. Please help !

I have provided the ServicedComponent code and the test
client code below (connection strings etc ommitted).

ServicedComponent Code
======================

Imports System.EnterpriseServices
Imports System.Messaging
Imports System.Data
Imports System.Data.SqlClient
Imports System.Reflection

#Region "Required ServicedComponent attributes (mainly for
object construction)"

' WARNING !! The only way to get this to work was to give
it an absolute path
<Assembly: AssemblyKeyFileAttribute
("c:\dev\research\singletransactionalworkflow\mysingletrans
\mysingletrans.snk")>

<Assembly: ApplicationName
("TransactionTests.MySingleTrans")>

<Assembly: ApplicationActivation(ActivationOption.Library)

<Assembly: Description("Test component that sends to both
a message queue and updates a database")>

#End Region

Namespace TransactionTests.MySingleTrans

   <Transaction(TransactionOption.RequiresNew), _
   ConstructionEnabled(Default:="<details ommitted>",
Enabled:=True)> _
   Public Class SingleTransComponent : Inherits
System.EnterpriseServices.ServicedComponent

       Private m_connectionString As String

       Protected Overrides Sub Construct(ByVal s As
String)
           ' // Construct method is called next after
constructor.
           ' // The configured connection string is
supplied as the single argument.
           Me.m_connectionString = s
       End Sub

       <AutoComplete()> _
       Public Sub DoTransaction(ByVal leadID As Long, _
               ByVal productID As Long, _
               ByVal queueName As String, _
               ByVal messageObject As Object, _
               ByVal priority As
System.Messaging.MessagePriority, _
               ByVal useMQJournalling As Boolean, _
               ByVal useMsgJournalling As Boolean)

           Try
               ' Add to Queue
               SendMessageToQueue(queueName,
messageObject, MessagePriority.Normal, _
                       False, False)

               ' Remove Pending Lead from PendingLeads
table
               removePendingLead(leadID, productID)

           Catch ex As PendingLeadRemovalException
               Throw ex
           Catch ex As Exception
               Throw ex
           End Try

       End Sub

       <AutoComplete()> _
       Private Sub removePendingLead(ByVal leadID As
Long, ByVal productID As Long)

           Dim conn As New SqlConnection

           Try
               conn.ConnectionString = m_connectionString

               Dim cmd As SqlCommand = New SqlCommand
("usp_RemovePendingLead", conn)
               With cmd
                   .CommandType =
CommandType.StoredProcedure
                   .Parameters.Add(New SqlParameter
("@LeadID", leadID))
                   .Parameters.Add(New SqlParameter
("@ProductID", productID))
               End With

               conn.Open()
               cmd.ExecuteNonQuery()
           Catch ex As SqlException
               ' // throw new exception and propagate
SQLException
               Throw New PendingLeadRemovalException
("Exception occurred trying to delete lead from
PendingLeads table", ex)
           Finally
               conn.Close()
           End Try

       End Sub

       <AutoComplete()> _
       Private Sub SendMessageToQueue(ByVal queueName As
String, _
               ByVal messageObject As Object, _
               ByVal priority As
System.Messaging.MessagePriority, _
               ByVal useMQJournalling As Boolean, _
               ByVal useMsgJournalling As Boolean)

           Dim msgQ As MessageQueue    ' the MessageQueue
           Dim msg As New Message      ' the Message.
Using Complex method (as opposed to Simple)

           ' Get Type of the messageObject that has been
passed and specify it in the
           ' array of formatter Types.
           Dim formatterTypes As Type() = New Type()
{messageObject.GetType}

           Try
               msgQ = GetMessageQueue(queueName)

               ' // Set Journalling capability on the
MessageQueue. Will override default Queue setting.
               msgQ.UseJournalQueue = useMQJournalling

               With msg
                   .Formatter = New XmlMessageFormatter
(formatterTypes)
                   .Priority = priority
                   .Body = messageObject

                   ' // Set Journalling capability at the
individual Message level
                   .UseJournalQueue = useMsgJournalling
               End With

               If msgQ.Transactional Then

                   Dim trans As New
MessageQueueTransaction
                   Try
                       trans.Begin()
                       ' Send Message object.
                       msgQ.Send(msg, trans)
                       trans.Commit()
                   Catch ex As Exception
                       ' Abort the transaction.
                       trans.Abort()

                       Throw ex
                   Finally
                       trans = Nothing
                   End Try

               Else
                   ' Send Message object.
                   msgQ.Send(msg)
               End If

           Catch ex As Exception
               ' // Re-raise exception
               Throw ex
           Finally
               ' // <todo>Should we issue msgQ.Close ?
</todo>

               ' Clear object refs
               msg = Nothing
               msgQ = Nothing
           End Try

       End Sub

       Private Function GetMessageQueue(ByVal queueName
As String) As MessageQueue

           Dim msgQ As MessageQueue

           Try
               If Not MessageQueue.Exists(queueName) Then
                  '  User will require appropriate
permissions to create the Queue.
                   MessageQueue.Create(queueName)
               End If

               msgQ = New MessageQueue(queueName)
           Catch ex As Exception
               ' Re-raise 'wrapped' exception
               Throw New Exception("Error binding to
Queue", ex)
           End Try

           Return msgQ

       End Function

   End Class

   Public Class PendingLeadRemovalException : Inherits
System.Exception
       Sub New(ByVal msg As String)
           MyBase.New(msg)
       End Sub

       Sub New(ByVal msg As String, ByVal innerException
As System.Exception)
           MyBase.New(msg, innerException)
       End Sub
   End Class

End Namespace

Client Code
===========

' Note: PendingLeads is a component providing data
retrieval (not included)
' LeadPT.Lead is a simple class with four properties.
' It has been placed in the GAC and is used as the message
' object.

Imports TransactionTests.MySingleTrans

Imports System.Messaging

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Public Sub New()
       MyBase.New()

       'This call is required by the Windows Form
Designer.
       InitializeComponent()

       'Add any initialization after the
InitializeComponent() call

   End Sub

   'Form overrides dispose to clean up the component list.
   Protected Overloads Overrides Sub Dispose(ByVal
disposing As Boolean)
       If disposing Then
           If Not (components Is Nothing) Then
               components.Dispose()
           End If
       End If
       MyBase.Dispose(disposing)
   End Sub

   'Required by the Windows Form Designer
   Private components As System.ComponentModel.IContainer

   'NOTE: The following procedure is required by the
Windows Form Designer
   'It can be modified using the Windows Form Designer.  
   'Do not modify it using the code editor.
   Friend WithEvents Button1 As
System.Windows.Forms.Button
   <System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
       Me.Button1 = New System.Windows.Forms.Button
       Me.SuspendLayout()
       '
       'Button1
       '
       Me.Button1.Location = New System.Drawing.Point(88,
96)
       Me.Button1.Name = "Button1"
       Me.Button1.TabIndex = 0
       Me.Button1.Text = "Run Test"
       '
       'Form1
       '
       Me.AutoScaleBaseSize = New System.Drawing.Size(5,
13)
       Me.ClientSize = New System.Drawing.Size(292, 273)
       Me.Controls.Add(Me.Button1)
       Me.Name = "Form1"
       Me.Text = "Form1"
       Me.ResumeLayout(False)

   End Sub

#End Region

   Private Sub Button1_Click(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles
Button1.Click

       Dim pendingLeadsDS As DataSet
       Dim objPendingLeads As New
PendingLeads.PendingLeads
       Dim objLead As New LeadPT.Lead
       Dim objLeadTransfer As New
TransactionTests.MySingleTrans.SingleTransComponent

       Try
           pendingLeadsDS =
objPendingLeads.getPendingLeads
           Dim datatable As DataTable =
pendingLeadsDS.Tables(0)
           For Each dr As DataRow In datatable.Rows
               ' Instantiate LeadTransfer Serviced
component. Pass it the data required.
               ' For our prototype it will pass the
DataRow but an eventual solution will
               ' pass an XML document.

               With objLead
                   .Applicant = "John Doe"
                   .Postcode = "SW5 6YP"
                   .LeadID = DirectCast(dr.Item
("LeadID"), Integer)
                   Select Case dr.Item("ProductID")
                       Case 1
                           .Product = "Mortgage"
                       Case 9
                           .Product = "Term Assurance"
                       Case Else
                           .Product = "Unknown"
                   End Select
               End With

               ' When LeadID and ProductID were cast to
Long there was an InvalidCastException
               objLeadTransfer.DoTransaction(DirectCast
(dr.Item("LeadID"), Integer), _
                       DirectCast(dr.Item("ProductID"),
Integer), _
                       ".\servicedleadqueue", _
                       objLead,
Messaging.MessagePriority.Normal, _
                       False, _
                       False)

               Console.WriteLine("Processed LeadID " &
CType(dr.Item("LeadID"), String))

           Next

           MessageBox.Show("Completed lead
transfers", "Job Completed")

       Catch ex As Exception
           MessageBox.Show(ex.ToString, "Exception
occurred")
       Finally
           ' // explicitly clear reference to
PendingLeads object
           pendingLeadsDS = Nothing
           objLead = Nothing
           objPendingLeads = Nothing
           objLeadTransfer = Nothing
       End Try

   End Sub
End Class

Regards
Julian Baden
Slava Gurevich - 13 Oct 2003 15:42 GMT
Hi,

You're creating a separate manual transaction instead of
auto-enlisting in the existing DTC transaction.

Replace  the code below with this single line:

msgQ.Send(msg, MessageQueueTransactionType.Automatic)

Since the method is already marked as autocomplete you do not need to
call setabort()/setcomplete() manually as long as you do not suppress
the exceptions in your method code;  if you do, make sure it's
ContextUtil.SetAbort() or ContextUtil.SetComplete() called at the end
of the method call.

Slava Gurevich

references:
http://www.codeproject.com/dotnet/msmqpart2.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlr
fsystemmessagingmessagequeueclasssendtopic3.asp


>Dim trans As New
>MessageQueueTransaction
[quoted text clipped - 6 lines]
>                        ' Abort the transaction.
>                        trans.Abort()
Julian Baden - 16 Oct 2003 14:06 GMT
Thankyou Slava.

It worked a treat !

Regards
Julian
>-----Original Message-----
>Hi,
[quoted text clipped - 18 lines]
>
>http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/cpref/html/frlrfsystemmessagingmessagequeueclasssendtopi
c3.asp

>>Dim trans As New
>>MessageQueueTransaction
[quoted text clipped - 8 lines]
>
>.
Slava Gurevich - 16 Oct 2003 15:54 GMT
Yahoo!  Glad it worked for you

Slava

>Thankyou Slava.
>
[quoted text clipped - 44 lines]
>>
>>.

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.