Using COM+ with LLBLGen & the Adapter Pattern

Posts   
 
    
Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 15-Nov-2004 12:25:28   

I just want to say to Frans, that your work with the COM+ Support for the Adapter Pattern is outstanding.

I also wanted to share some work with the rest of the community that I did over the weekend that might save you all some time with implementing COM+ in your applications. What I have done is taken the SAF.Transaction framework component from Xin Chen's book titled "Developing Application Frameworks in .NET" and tied it to LLBLGen Controller Logic and COM+ Support for distributed transactions.

Key points in the demonstation:

  • COM+ Transaction Code is NOT tied to Controller Logic
  • All 5 COM+ 1.5 Isolation Levels are Supported
  • Multiple data controller methods can be executed in the context of a given transaction

Constraints:

  • The sample uses the default sql server northwind DB
  • The categories table is the only table used, for the sake of simplicity
  • a unique constraint was created on the category name field
  • The ComPlusAdapterContext's transaction attribute is set to Supported, using the COM+ mmc snapin
  • The ComPlusAdapterContext's transaction isolation level was set to Any using the COM+ mmc snapin

If youre interested in the code here is what you can do with it:

  • Allow your developers to use COM+ to manager transactions
  • Allow developers to choose when, where, and how transactions are used without having to create new COM+ components
  • Allow your developers to place any data controller object(s) within the scope of a COM+ transaction

The code is way too much to post here, so I will send it to Frans, and if he wants to post it on the 3rd party section or samples section then he can, or if you want to contact me directly for the code feel free.

Unit Test Sample 1: Shows the attempted update of 2 entities in the same transaction and the creation of a new entity in a new transaction. The sample shows how the 2 updates fail (because of the unique index) and are rolled back, but the new entity is created provided it doesnt run across any other exceptions:

        public void CreateDuplicateCategories_PlusNewCategory()
        {
            ITransactionController updateCategoryTxController = RequiresTxManager.SerializableTxController();
            ITransactionController createNewCategoryTxController = RequiresNewTxManager.SerializableTxController();
            CategoryController categoryController = new CategoryController();
            CategoriesEntity category1=null;
            CategoriesEntity category2=null;
            try
            {
                // do the first fetch
                category1 = updateCategoryTxController.ExecuteMethod(categoryController, "FetchCategory", new object[]{1}) as CategoriesEntity;
                Assert.IsNotNull(category1, "Category is null");

                category1.CategoryName = "My Beverages";

                // do the second fetch
                category2 = updateCategoryTxController.ExecuteMethod(categoryController, "FetchCategory", new object[]{2}) as CategoriesEntity;
                Assert.IsNotNull(category2, "Category2 is null");

                category2.CategoryName = "My Beverages";

                // do the first save
                updateCategoryTxController.ExecuteMethod(categoryController, "SaveCategory", new object[]{category1});

                // create the new category entity in a new transaction
                try
                {
                    CategoriesEntity newCategory = new CategoriesEntity();
                    newCategory.CategoryName = "Adult Beverages";
                    newCategory.IsNew=true;
                    createNewCategoryTxController.ExecuteMethod(categoryController, "SaveCategory", new object[]{newCategory});

                    RequiresNewTxManager.Commit(createNewCategoryTxController);
                    Console.WriteLine("Create New Category Succeeded");
                }
                catch
                {
                    RequiresNewTxManager.Rollback(createNewCategoryTxController);
                    Console.WriteLine("Create new category failed");
                    throw;
                }

                // do the second update
                updateCategoryTxController.ExecuteMethod(categoryController, "SaveCategory", new object[]{category2});
            }
            catch(System.Reflection.TargetInvocationException ex)
            {

                RequiresTxManager.Rollback(updateCategoryTxController);

                ORMQueryExecutionException innerEx = ex.InnerException as ORMQueryExecutionException;
                if (innerEx != null)
                {
                    Console.WriteLine("We received the proper exception");
                }
            }
            finally
            {
                RequiresTxManager.DisposeAll(updateCategoryTxController, createNewCategoryTxController);
            }
        }

** Unit Test Sample 2:** Shows how a developer can manually control the transaction isolation level to allow a dirty read. In some systems, dirty reads are sometimes required to support high levels of concurrency.

        public void PerformDirtyRead()
        {
            ITransactionController updateWithReadUC = RequiresTxManager.SerializableTxController();
            ITransactionController readUpdatesBeforeCommit = RequiresNewTxManager.ReadUncommittedTxController();
            CategoryController dataController = new CategoryController();
            try
            {

                CategoriesEntity category = updateWithReadUC.ExecuteMethod(dataController, "FetchCategory", new object[]{1}) as CategoriesEntity;
                Assert.IsNotNull(category, "Category is null") ;
                
                Console.WriteLine("Category Name before update:" + category.CategoryName);
                category.CategoryName = "New Category";
                Console.WriteLine("Category Name before setting calling the dataController.Save method: " + category.CategoryName);

                updateWithReadUC.ExecuteMethod(dataController, "SaveCategory", new object[]{category});

                CategoriesEntity tempCategory = readUpdatesBeforeCommit.ExecuteMethod(dataController, "FetchCategory", new object[]{1}) as CategoriesEntity; 
                Console.WriteLine("Category Name after dirty read in new TX: " + tempCategory.CategoryName);
                
                RequiresTxManager.Rollback(updateWithReadUC);

                tempCategory = readUpdatesBeforeCommit.ExecuteMethod(dataController, "FetchCategory", new object[]{1}) as CategoriesEntity;
                Console.WriteLine("Category Name after rollback: " + tempCategory.CategoryName);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                RequiresTxManager.DisposeAll(updateWithReadUC, readUpdatesBeforeCommit);
            }
        }
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39752
Joined: 17-Aug-2003
# Posted on: 15-Nov-2004 16:54:22   

great great stuff, Devildog! simple_smile

I've the archive here, ready for uploading, there is one file in the archive (the strong key) which isn't supposed to be there I think. I've mailed you about that. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39752
Joined: 17-Aug-2003
# Posted on: 15-Nov-2004 17:01:30   

The code is now available in the 3rd party section on the website simple_smile Thanks again!

Frans Bouma | Lead developer LLBLGen Pro