GOTCHA #75 AutoComplete comes with undesirable side effectsThe AutoComplete attribute in Enterprise Services provides a very convenient way to communicate the intent to commit or abort a transaction. When a method marked with that attribute is invoked, the method automatically votes to commit the transaction if the method is successful. If the method fails, which is indicated by throwing an exception, the vote is automatically set to abort. This sounds pretty logical, so what's the concern? Let's examine the code in Example 8-19. Example 8-19. Effect of AutoCompleteC# (ESAutoComplete) //MyComp.cs as part of ESLib.dll using System; using System.EnterpriseServices; namespace ESLib { [Transaction(TransactionOption.Required)] public class MyComp : ServicedComponent { private string theMessage = "UnSet"; [AutoComplete] public void SetInfo(string msg) { theMessage = msg; } [AutoComplete] public string GetInfo() { return theMessage; } } } //Test.cs as part of ESUser.exe using System; using ESLib; namespace ESUser { class Test { [STAThread] static void Main(string[] args) { MyComp theComp = new MyComp(); theComp.SetInfo("hello"); Console.WriteLine(theComp.GetInfo()); } } } VB.NET (ESAutoComplete) 'MyComp.vb as part of ESLib.dll Imports System.EnterpriseServices <Transaction(TransactionOption.Required)> _ Public Class MyComp Inherits ServicedComponent Private theMessage As String = "UnSet" <AutoComplete()> _ Public Sub SetInfo(ByVal msg As String) theMessage = msg End Sub <AutoComplete()> _ Public Function GetInfo() As String Return theMessage End Function End Class 'Test.vb as part of ESUser.exe Imports ESLib Module Test Sub Main() Dim theComp As New MyComp theComp.SetInfo("hello") Console.WriteLine(theComp.GetInfo()) End Sub End Module In this example, you create an instance of MyComp in the Main() method of the Test class. (The MyComp class stores a field named theMessage and initializes it to "UnSet.") You then call SetInfo() to set theMessage to "hello," then call GetInfo() to read it back. When the above code is executed you get the output shown in Figure 8-22. Figure 8-22. Output from Example 8-19What went wrong? Why didn't you get the expected result of "hello?" The client is not dealing directly with the MyComp object, but with an invisible proxy. When the SetInfo() method is called, the method executes on the actual component. At the end of the method, due to the AutoComplete attribute, the transaction is committed by an internal call to SetComplete(). However, this call has a side effect. It not only sets the vote, it also sets the ContextUtil.DeactivateOnReturn property to TRue. As a result, the object is deactivated upon the return from the SetInfo() method. In other words, as soon as the method completes, the object is destroyed (kind of like working for the Mafia). When the GetInfo() method is invoked again using the proxy reference, another instance of the object is created automatically. theMessage is initialized to "UnSet" in this newer object, and that's what GetInfo() returns to you. Let's consider the code changes in Example 8-20. Example 8-20. Safely communicating intent to commitC# (ESAutoComplete) //MyComp.cs as part of ESLib.dll using System; using System.EnterpriseServices; namespace ESLib { [Transaction(TransactionOption.Required)] public class MyComp : ServicedComponent { private string theMessage = "UnSet"; //[AutoComplete] public void SetInfo(string msg) { ContextUtil.MyTransactionVote = TransactionVote.Abort; theMessage = msg; // If something is wrong, throw exception // and the vote will remain in Abort. ContextUtil.MyTransactionVote = TransactionVote.Commit; } //[AutoComplete] public string GetInfo() { ContextUtil.MyTransactionVote = TransactionVote.Abort; // If something is wrong, throw exception // and the vote will remain in Abort. ContextUtil.MyTransactionVote = TransactionVote.Commit; return theMessage; } } } VB.NET (ESAutoComplete) 'MyComp.vb as part of ESLib.dll Imports System.EnterpriseServices <Transaction(TransactionOption.Required)> _ Public Class MyComp Inherits ServicedComponent Private theMessage As String = "UnSet" '<AutoComplete()> _ Public Sub SetInfo(ByVal msg As String) ContextUtil.MyTransactionVote _ = TransactionVote.Abort theMessage = msg ' If something is wrong, throw exception ' and the vote will remain in Abort. ContextUtil.MyTransactionVote _ = TransactionVote.Commit End Sub '<AutoComplete()> _ Public Function GetInfo() As String ContextUtil.MyTransactionVote _ = TransactionVote.Abort ' If something is wrong, throw exception ' and the vote will remain in Abort. ContextUtil.MyTransactionVote _ = TransactionVote.Commit Return theMessage End Function End Class In the methods of the component, you first set the transaction vote to Abort. If the method is successful, you change the vote to Commit. If the method is not successful, you throw an exception, leaving the transaction vote as Abort. You do not use the AutoComplete attribute on the methods. The advantage of this is that the object is not automatically deactivated without your explicit intent. The output after the above code change is shown in Figure 8-23. Figure 8-23. Output after code change in Example 8-20The AutoComplete attribute comes with a side effect. Understand its impact on an object's lifetime. It is better to explicitly set the transaction vote to Abort or Commit, rather than using the AutoComplete attribute. Use AutoComplete only if you really want the object to be discarded when the method completes. IN A NUTSHELLInstead of using the AutoComplete attribute, directly set the transaction vote in your code. Avoid the side effect of AutoComplete that automatically deactivates the component. SEE ALSOGotcha #74, "ServicedComponents implemented inconsistently on XP and 2003." |