views:

807

answers:

2

What is the best practice for processing a batched series of CRUD operations in a single transaction with the Enterprise Library Data Access Block that it won't be esclated to a distributed transaction?

Edit Full Source:

public void BatchInsertEvents(IList<EventItem> events)
{
    _dataAccessBase = new DataAccessBase("[dbo].[EventInsert]");
    int count = 0;

    try
    {
        using (var scope = 
                    new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            foreach (var eventItem in events)
            {
                _dataAccessBase.ClearParameters();

                _dataAccessBase.AddInParameter("@time", 
                                            DbType.String, eventItem.Time);
                ...more params

                _dataAccessBase.ExecuteNonQuery();
                count++;
            }

            scope.Complete();
        }
}

My DataAccessBase is just a wrapper class for the Database object

public class DataAccessBase
{
    private readonly DbCommand _command;
    private readonly Database _database;

    public DataAccessBase(string storedProcName) : this(null, storedProcName)
    {
    }

    public DataAccessBase(string connectionString, string storedProcName)
    {
        _database = string.IsNullOrEmpty(connectionString) ?
                     DatabaseFactory.CreateDatabase() : 
                     DatabaseFactory.CreateDatabase(connectionString);
        _command = _database.GetStoredProcCommand(storedProcName);
    }

    public void AddInParameter<T>(string parameterName, 
                                            DbType parameterType, T value)
    {
        _database.AddInParameter(_command, 
                                parameterName, parameterType, value);
    }


    public void AddOutParameter<T>(string parameterName, 
                                DbType parameterType, int parameterLength)
    {
        _database.AddOutParameter(_command, 
                                parameterName, parameterType, parameterLength);
    }

    public void ClearParameters()
    {
        _command.Parameters.Clear();
    }

    public void ExecuteNonQuery()
    {
        _database.ExecuteNonQuery(_command);
    }
}
+1  A: 

I am guessing that what is happening is that, you are using EntLib with connection pooloing.

What happens then is that you get a transaction that is spread over more than one connection. This then gets escalated to a distributed transaction.

One thing Entlib is very good at is closing connections. You need to write your code in a way that it will reuse a single connection.

Some of this is explained in this link: http://msdn.microsoft.com/en-us/library/cc511672.aspx

If you still have problems, post your code within the for each loop.

EDIT

Try moving the line:

_dataAccessBase = new DataAccessBase("[dbo].[EventInsert]");

inside the transaction scope.

EDIT 2

Can you also move the declaration of _dataAccessBase inside the transaction scope

DataAccessBase _dataAccessBase = new DataAccessBase("[dbo].[EventInsert]");

This is just to make sure that the connection is not used outside the transaction scope.

Shiraz Bhaiji
I took a look at your link and changed my declaration of the transaction scope but it still upgrades it to a distributed transaction. I included the full source I'm using to my question.
Chris Marisic
Moved that inside the scope and still same error that MSDTC is not enabled so it definitely is a distributed transaction
Chris Marisic
Your DataAccessBase(string connectionString, string storedProcName) has two parameters, but you are calling it with a single param, can you post the code. Also is this code running in any outer transaction scopes from the code it is being called from?
Shiraz Bhaiji
All the single parameter constructor does is pass null to argument one which was why i left it out, no there is no other usage of TransactionScope any where in my application
Chris Marisic
A: 

Edit: After posting this I realized I nearly fully duplicated the advice already given by Shiraz Bhaiji. Please ignore.

Second attempt:

Please provide the code for DatabaseFactory.CreateDatabase(). Do you have an additional TransactionScope in there by chance? If so, and you use the same TransactionScopeOption.RequiresNew, you could easily escalate to DTC.

Jerry Bullard
DatabaseFactory.CreateDatabase() is enterprise library's code, I supposed I could run it through reflector to see if it creates a transactionscope inside it
Chris Marisic
I tried to analzye this code in reflector however since it actually impelements dependency injection I can't figure out where exactly the code builds up the database to see if it initializes a transaction scope inside it somewhere. However I've also used .Required not just RequiresNew which should use an ambient transaction if it already existed but that didn't do anything different either.
Chris Marisic
mister, where is the final complete code ? thanks
alhambraeidos