views:

299

answers:

3

I have the following code which is in a transaction. I'm not sure where/when I should be commiting my unit of work.

On purpose, I've not mentioned what type of Respoistory i'm using - eg. Linq-To-Sql, Entity Framework 4, NHibernate, etc.

If someone knows where, can they please explain WHY they have said, where? (i'm trying to understand the pattern through example(s), as opposed to just getting my code to work).

Here's what i've got :-

using
(
    TransactionScope transactionScope =
        new TransactionScope
        (
            TransactionScopeOption.RequiresNew,
            new TransactionOptions
                { IsolationLevel = IsolationLevel.ReadUncommitted }
        )
)
{
    _logEntryRepository.InsertOrUpdate(logEntry);
    //_unitOfWork.Commit();  // Here, commit #1 ?

    // Now, if this log entry was a NewConnection or an LostConnection,
    // then we need to make sure we update the ConnectedClients.
    if (logEntry.EventType == EventType.NewConnection)
    {
        _connectedClientRepository.Insert(
            new ConnectedClient { LogEntryId = logEntry.LogEntryId });
        //_unitOfWork.Commit(); // Here, commit #2 ?
    }

    // A (PB) BanKick does _NOT_ register a lost connection,
    // so we need to make sure we handle those scenario's as a LostConnection.
    if (logEntry.EventType == EventType.LostConnection ||
        logEntry.EventType == EventType.BanKick)
    {
        _connectedClientRepository.Delete(
            logEntry.ClientName, logEntry.ClientIpAndPort);
        //_unitOfWork.Commit(); // Here, commit #3 ?
    }

    _unitOfWork.Commit(); // Here, commit #4 ?
    transactionScope.Complete();
}
A: 

Commit at #4 after all the operations to all the repositories are made. If you commit beforehand, the changes made after that call are not committed.

Even Mien
why wouldn't they be committed if I made changes after?
Pure.Krome
Maybe I'm missing something. Is _unitOfWork.Commit() called again later in the code? It could be that our confusion lies if UoW is transaction-aware, so it doesn't matter when/if you call Commit(). Otherwise, if it is not transaction-aware, and the repositories are tied to the UoW and Commit() is not called again, then work will only modify what was changed in the repository before Commit() is called and not afterwards.
Even Mien
I've just confirmed that the UoW is transaction aware -> it creates its own Read Commited transaction, so I don't need to do mine. This then makes FULL 100% sense now :) The commit starts a tran, does all the changes (since the last commit) .. pushes it to the db .. then commts that trans. Cheers!
Pure.Krome
A: 

Assuming that your data store is assigning ids, you have to commit #1 (with NHibernate you should even flush), then at the end #4.

WooYek
+3  A: 

A good starting point for answering this question is the definition of Unit of Work from Patterns of Enterprise Architecture (http://martinfowler.com/eaaCatalog/unitOfWork.html ):

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

The boundaries of your unit of work are defined by the boundaries of your business transaction - in this case that is synonymous with the boundaries of the database transaction ( but in the case of a long running business transaction that spans multiple requests that may not be the case ).

Working backwards from the definition above and based on my understanding of the piece of code shown, you should commit the unit of work at the end of the business transaction (#4).

As an aside, your database transaction scopes should always be less than the scope of your UoW ( i.e. the tx scope resides between the call to UoW.Begin() and UoW.Commit() ). If your UoW spans multiple database transactions you would use a compensating transaction to "rebalance" the UoW if one of the inner transactions failed. In this instance, and especially if your UoW is creating it's own database transaction boundaries at UoW.Begin() and UoW.Commit() I would remove the transaction scope as this is simply adding unnecessary noise to the code.

Neal
Great answer :) And for the record, EF actually does this under the hood, when you *SubmitChanges()* .
Pure.Krome
Yep. In NH the ISession instance is effectively the UoW and iirc it uses the associated transaction to determine when a flush is required - but I am not entirely sure on that so don't take it to the bank =)
Neal