Managed and unamanged code updating the DB within one Transaction?

1.6k views Asked by At

Within C#, I have an OracleConnection that updates the DB, and a reference to a legacy VB6 DLL which the C# calls into to update the DB (within the DLL, it uses an ADODB.Connection object).

I need to wrap them both in one big transaction, so that both the managed and unmanaged updates rollback or commit together.

I tried switching the C# class so that it inherits from System.EnterpriseServices.ServicedComponent and is decorated with [Transaction(TransactionOption.Required)], and then using [AutoComplete] on the method that starts the calling sequence which eventually hits the OracleConnection and VB6 DLL invocation.

Like this:

using System.EnterpriseServices;

{
    [Transaction(TransactionOption.Required)]
    public class MyClassTx: ServicedComponent
    {
        private MyClass1 _myClass1;

        public MyClassTx()
        {
        }

        // This method automatically commits the transaction if it succeeds.
        [AutoComplete]
        public void DoStuffTransactionally()
        {
        // Calls into different objects, doing some work that I'd like to have
        // a big transaction around.
        _MyClass1 = new MyClass1()
        _MyClass1.DoSomeStuff();
        }
    }
}

However, when my test harness tries to instantiate MyClassTx, I get this error:

{System.EnterpriseServices.RegistrationException: Invalid ServicedComponent-derived classes were found in the assembly.
(Classes must be public, concrete, have a public default constructor, and meet all other ComVisibility requirements)

I have verified that my class is public, concrete, and has a parameter-less constructor. Still, it won't instantiate.

Do I need to strong type my assembly and put it into a COM+ package before I can even debug it? I would have assumed that, using VS2010, I could just step into the ServicedComponent-inheriting code.

It's been about 8 years since I have used COM+, and this is my first time trying to get it to work in C#, so any help would be greatly appreciated!

Also, if I am heading down a silly path here and there is an easier way to get my managed and unamanaged code into the same Transaction, please enlighten me!

A few of hours with Google hasn't helped much.

Many thanks!!

1

There are 1 answers

0
TimH On BEST ANSWER

Okay, I think I have made significant progress with this.

Additional steps that I needed to do to make this all work:

  1. Assembly had to be set as ComVisible.
  2. I had to set the [assembly: System.EnterpriseServices.ApplicationName("blahblah")] value... blahblah becomes the name of the COM+ package.
  3. The assembly had to be strong-named, and the registered in COM+ using regsvcs.exe. It's set to use Library activation, but I'm not entirely sure if that's necessary. I tried it as Server activation, but the code invoking it threw a COM exception of some kind.
  4. You have to call OracleConnection.EnlistDistributedTransaction, passing in ContextUtil.Transaction (cast it as a Transaction) like this:

    connection.EnlistDistributedTransaction((ITransaction)ContextUtil.Transaction);

After this, the assembly was showing up in the COM+ Applications listing in the Component Services window. Yay!

Even better, when I debugged in VS2010, when I got into the DoStuffTransactionally method, a new transaction was active in the Component Services explorer.

So, that let me do my debugging.

However, to actually get the Transaction including not just the managed code but also the legacy VB6 code being invoked deeper inside DoStuffTransactionally, I needed to add the legacy VB6 COM objects to the COM+ Application that my managed code was in. And since this VB6 code was invoking Oracle, I had to modify the Connection String the VB6 code was using to have DistribTX=1 and PROMOTABLE TRANSACTION=PROMOTABLE set. After that, the code was committing and rolling back the managed & unmanaged DB changes as a single Transaction.

Yay!

I do get an error thrown at the end of my debugging session, which doesn't make sense to me. Hopefully it only affects debugging and not Release code. The error is:

DisconnectedContext was detected
Message: Context 0x4452b0' is disconnected.  Releasing the interfaces from the current context (context 0x444fd0). This may cause corruption or data loss. To avoid this problem, please ensure that all contexts/apartments stay alive until the application is completely done with the RuntimeCallableWrappers that represent COM components that live inside them.

So, I hope this helps someone someday.