|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionOkay, this is my first article on CodeProject, but I hope it does not show. I have taken my time, having had what I thought were many good ideas for articles along the way, but being me being me, I never got around to them because I spend so much time in the lounge. Enough of the excuses, I just hope that you enjoy and find the following useful. BackgroundI had been wanting to get to grips with .NET in a more demanding environment, namely where workloads need to be spread between machines, but also where data at all costs cannot become corrupted. From my C++ experience I know that this would involve building ATL COM+ components, and implementing all kids of interfaces. There seems to be an an easier way with the .NET Enterprise services that I felt are not touched on enough, and that the MSDN documentation is lacking. So my aim was to provide a quick tour of putting a sample distributed database transaction together and walling though some of the details. I imagine there are things I forgotten to explain, so let me know and I will try and expand on it. A Brief History of Distributed Transactions on WindowsI am assuming that you are familiar with database transactions following the typical Begin, Commit and Rollback Transaction cycle. e.g. Sub UpdateDB()
Dim cnn As New ADODB.Connection
Dim rs As New ADODB.Recordset
cnn.Open "Northwind", "Admin", "Password"
cnn.BeginTrans
rs.Open "tblOrders", cnn, adOpenDynamic, adLockOptimistic
'
' Do something to the database
'
rs.Close
cnn.CommitTrans
cnn.Close
Exit Sub
err_handler:
cnn.RollbackTrans
cnn.Close
End Sub
Figure 1 - Show ADO Example - Please excuse the Visual Basic, but it’s a quick example Around the time of Windows NT4 the Distributed Transaction Coordinator (DTC) was introduced as part of SQL Server 6.5. Later the Microsoft Transaction Server (MTS) was introduced which encompassed the DTC, that provided the ability for middle-tier server-side COM object components to exist in a load balanced distributed system. These could take part in a transaction and vote on if they had completed successfully what they had been asked to do as part of the overall application in the transaction. An MTS component that a developer created could be remotely administered, with such features as set NT user security permissions at the interface level of each MTS COM object. Later in Windows 2000, MTS evolved into COM+. This had the added advantage that the security was more fine-grained, so that an admin could grant/revoke access to particular users at the method level. Previously it had not been unheard of for users to have to implement extra security because the permission levels were not detailed enough. From now on I will assume that you are targeting a Windows 2000 system or later and refer to COM+. With the advent of .NET, if it was to be used in an enterprise environment, then it needed to be stable, fault tolerant and secure. So why not just put those bits on top of what was already a very good framework – COM+. The Why a distributed transaction?In today’s large and demanding business applications were data has to be extracted from a database, complex calculations made against it with business rules applied, finalised with various record being updated, can be far too much work for one machine. Further to that, even with smaller apps hosting them on a user’s workstation from a disaster recovery point of view would worry most IT managers. COM+ provides as scalable, fault tolerant platform, in that a middle tier COM objects can be created, and a server or cluster of servers work it out between them where this lives on the network. N.B. Fault tolerance is something that has been greatly enhanced in Windows 2003 Server, which has 8 node clustering and fail over, and can cope with 3 of the machines failing, where the transaction continues unhindered. Also there is another problem COM+ solves. What happens when you need to make changes to 2 or more different databases located on different servers as part of a transaction? e.g.
The answer you are glad to hear is no. Under COM+, you don’t even need to call Begin, Commit/Rollback on the connections as they are already aware that they are running in a transaction context with the DTC has already told them about. So there is also less code to write from one perspective. All of this relies on the fact that at the bottom of the tier is a database platform that can be instructed by a DTC. SQL Server, Oracle and DB2 all play nicely. Sybase however does not as it has no clue. Implementing a Sample COM+ object in .NETI’am going to show you how to implement a very simple set of objects that can work under and as part of a transaction, and where one object will be the root business object that starts a new transaction context. New Transaction Required
Existing Transaction Required or Creates a New Transaction (if it is called directly by the client)
I’am not going to go into any details on how to manage security on a COM+ app's (maybe a later article), and for this example will assume that it is all running under the same user account, so should cause no problems. Despite this it is easy to configure. In Windows 2000 or XP, go to the Control Panel, Administrative Tools, and Component Services (See Figure 6). This is the COM+ Manager. Bring up your local computer and browse to the local COM+ Application Packages, which contain the COM objects. In these you can view the COM Interfaces they implement and the methods on those components. The Basic Layout of the Sample ApplicationThe following is my attempt at trying to diagram the layout of the system.
We are going to build a simple thick client WinForm application that will allow the user to add a user on both databases as part of a single transaction. It would be just as easy to write a web based admin page using ASP.NET that consumes this middle tier COM+ objects, but perhaps that is for another article. For a new employee the business requires that we store :
Yes not much, but it serves as an example. Both the Personnel department and Orders department database want to know about the employees name and job type, but only the Personnel department needs to know the employees home address to post their payslip to. At this point you should create a blank solution in Visual Studio .NET and give it the catchy name of MyBusinessSolution. To that add three C# class libraries:
I laid it out with three libraries because at a later date I may want to add other components for doing more than setting up employees. After all, the company is not going to make much money otherwise. This way I will have an COM+ application silo for each department. We can use the default class objects, but it will be worthwhile to place each of the default namespace's into our company name MyBusiness Ltd. You will need to add the
Also you will have to add the On to some code - The Administration ObjectAs if by magic, the main administration business object presents itself. Now this is all very contrived, but stay with me. I'm going to point out some of the features that turn a class into a class that can be used in COM+. using System;
using System.EnterpriseServices;
using MyBusiness.Personnel;
using MyBusiness.Orders;
[assembly: ApplicationName("MyBusiness.Administration")]
[assembly: ApplicationActivation(ActivationOption.Library)]
namespace MyBusiness.Administration
{
/// <summary>
/// Epmployee Administration Object
/// </summary>
[ Transaction(TransactionOption.RequiresNew) ]
[ ObjectPooling(true, 5, 10) ]
public class EmployeeMaintenance : ServicedComponent
{
public EmployeeMaintenance()
{
//
// TODO: Add constructor logic here
//
}
[ AutoComplete(true) ]
public void AddEmployee(string Name, string Address, int JobType,
Our
void Activate();
bool CanBePooled();
void Construct(string s);
void Deactivate();
Moving on, our [ Transaction(TransactionOption.RequiresNew) ]
[ ObjectPooling(true, 5, 10) ]
This object requires that it exist in its own new transaction regardless of whether it was created under another transaction context. In the other components that this class users, they are marked as Requires. They need a transaction, but are happy to use the one they are created in, otherwise they will create a new one. That is how two on more databases can share the same transaction. For the client, this class has only one method which would be of interest called [ AutoComplete(true) ]
public void AddEmployee(string Name, string Address, int JobType,
The method is marked with the public void SampleFunction()
{
try
{
// Do something to a database
// ...
// Everything okay so far Commit the transaction
ContextUtil.SetComplete();
}
catch(Exception)
{
// Something went wrong Abort and Rollback the Transaction.
ContextUtil.SetAbort();
}
}
Manually controlling the transaction as an alternative to The
If you are planning to build an object that calls multiple functions in the object before committing the transaction, then it would be quite important to use the more fine-grained approach rather than make it as AutoComplete. Back to our class, I want to point out that there are two parameters on the end of the function, that allow the client application to signal one of the components to throw an Exception. I wanted this to easily demonstrate that even after one of the components used had opened a database modified it and closed the connection successfully as far as it was concerned, the data had still not been committed until the COM+ runtime had said so. What it was waiting for was the root object that owned the transaction to tell it to commit the transaction as part of the overall context. The Personnel Maintenance ClassI will not show the whole listing, just parts. using System;
using System.Data;
using System.Data.OleDb;
using System.EnterpriseServices;
[assembly: ApplicationName("MyBusiness.Personnel")]
[assembly: ApplicationActivation(ActivationOption.Library)]
namespace MyBusiness.Personnel
{
/// <summary>
/// Payroll Specific Mainenance Object
/// </summary>
[ Transaction(TransactionOption.Required) ]
[ ObjectPooling(true, 5, 10) ]
public class PayrollMaintenance : ServicedComponent
{
public PayrollMaintenance ()
{
//
// TODO: Add constructor logic here
//
}
public void AddEmployee(string Name, string Address, int JobType,
Now notice that the [ Transaction(TransactionOption.Required) ]
In the database I also want to store some properties of the COM+ Components in .NET Are Strong Named AssembliesA component derived from
Once you have the file, for each of the Serviced Component libraries you need to reference it in each of the assembly file belonging to the library project. The attribute needed is automatically populated at the bottom of the AssemblyInfo.cs file, with the strong name key file missing. So [assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
Becomes [assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("..\\..\\..\\MyBusiness.sn")]
[assembly: AssemblyKeyName("")]
The DatabasesIf you download the sample solution from above, contained with are the scripts to create the database on SQL sever under the folder Database Creation Scripts. There are two databases required for the example, each with one table if you and to do it by hand:
You will notice that I added two extra fields. These are populated by the two data access layer objects with what they think are the Running the ApplicationIf you run the app, you will be presented with the following screen:
Remember to enter some sample text, and add the employee. If your machine is getting old like mine then it will grind for a few seconds. This is because the components are initialising for the first time. As these are pooled objects the second time and so on is much faster. You should then view the contents of the database to see that a new entry has been added to both. If you rerun the above, but with one of the forced exceptions turned on, the who transaction will rollback, even though the database connections have been opened and then closed from the code. Finally, if you go bring up the Component Manager from the Control Panel -> Administrative Tools, you will see that the 3 component libraries have been created, and from the Transaction Statistics you should be able to see the transactions whiz by, or those that aborted.
Points of Interest for Further DevelopmentSomething that work well that I have not implemented in the code is the use of Message Queues for logging the details of a failed trasaction for an Administrator to diagnose at a later date. The important point to note here would be to not use a Transactional Queue as it would run in the Transaction Context and be rolled back making it a futile exercise. Did I not mention Message Queues can be transactional? Hmm another time. History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||