1

Closed

Enhanced UnitOfWork Implementation

description

Tim,
 
Your book has provided much inspiration and shown practical applications of Evan's work. The last few months has been really fun for me, and now my team is also starting to work with DDD and catching the bug, as well. I have made a modification to the UnitOfWork.cs implementation; actually it is mostly a rewrite. I wanted to share it with you and everyone else, in case in comes in handy.
 
The inspiration was that the UOW felt arbitrary, in the order that it actually persisted the entities. When starting to use (a modified version of) the code in the book, I ran into a couple of issues where I felt that the UOW would work better for my purposes if it was sequentially executed. I ran into this when I wanted to use a single UOW inside a Domain Service that coordinated calls between two different Repository implementations. The order that I needed some of the database operations to happen in were the same order by which the code called them.
 
So, this is what I came up with:
 
 
 
/// <summary>
/// Provides a distinct unit of work to be conducted for a type of entity.
/// </summary>
public sealed class UnitOfWork : IUnitOfWork
{
    #region Constructors
 
    /// <summary>
    /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    public UnitOfWork()
    {
        this.Operations = new List<Operation>();
    }
 
    #endregion
 
    #region Enumerations
 
    /// <summary>
    /// Provides the type of persistence operation to run.
    /// </summary>
    private enum OperationType
    {
        /// <summary>
        /// Operation for an add.
        /// </summary>
        Add,
 
        /// <summary>
        /// Operation for a delete.
        /// </summary>
        Delete,
 
        /// <summary>
        /// Operation for an update.
        /// </summary>
        Update
    }
 
    #endregion
 
    #region Private Property Declarations
 
    /// <summary>
    /// Gets or sets the operations.
    /// </summary>
    /// <value>The operations in this unit of work.</value>
    private List<Operation> Operations { get; set; }
 
    #endregion
 
    #region Public Methods
 
    /// <summary>
    /// Commits all batched changes within the scope of a <see cref="TransactionScope" />.
    /// </summary>
    public void Commit()
    {
        using (var scope = new TransactionScope())
        {
            foreach (var operation in this.Operations.OrderBy(o => o.ProcessDate))
            {
                switch (operation.Type)
                {
                    case OperationType.Add:
                        operation.Repository.PersistNewItem(operation.Entity);
                        break;
                    case OperationType.Delete:
                        operation.Repository.PersistDeletedItem(operation.Entity);
                        break;
                    case OperationType.Update:
                        operation.Repository.PersistUpdatedItem(operation.Entity);
                        break;
                }
            }
 
            scope.Complete();
        }
 
        this.Operations.Clear();
    }
 
    /// <summary>
    /// Registers an <see cref="IEntity" /> instance to be added through this <see cref="UnitOfWork" />.
    /// </summary>
    /// <param name="entity">The <see cref="IEntity" />.</param>
    /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
    public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository)
    {
        this.Operations.Add(
            new Operation
                {
                    Entity = entity, 
                    ProcessDate = DateTime.Now, 
                    Repository = repository, 
                    Type = OperationType.Add 
                });
    }
 
    /// <summary>
    /// Registers an <see cref="IEntity" /> instance to be changed through this <see cref="UnitOfWork" />.
    /// </summary>
    /// <param name="entity">The <see cref="IEntity" />.</param>
    /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
    public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository)
    {
        this.Operations.Add(
            new Operation
                {
                    Entity = entity,
                    ProcessDate = DateTime.Now,
                    Repository = repository,
                    Type = OperationType.Update
                });
    }
 
    /// <summary>
    /// Registers an <see cref="IEntity" /> instance to be deleted through this <see cref="UnitOfWork" />.
    /// </summary>
    /// <param name="entity">The <see cref="IEntity" />.</param>
    /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
    public void RegisterDeleted(IEntity entity, IUnitOfWorkRepository repository)
    {
        this.Operations.Add(
            new Operation
            {
                Entity = entity,
                ProcessDate = DateTime.Now,
                Repository = repository,
                Type = OperationType.Delete
            });
    }
 
    #endregion
 
    /// <summary>
    /// Provides a snapshot of an entity and the repository reference it belongs to.
    /// </summary>
    private sealed class Operation
    {
        /// <summary>
        /// Gets or sets the entity.
        /// </summary>
        /// <value>The entity.</value>
        public IEntity Entity { get; set; }
 
        /// <summary>
        /// Gets or sets the process date.
        /// </summary>
        /// <value>The process date.</value>
        public DateTime ProcessDate { get; set; }
 
        /// <summary>
        /// Gets or sets the repository.
        /// </summary>
        /// <value>The repository.</value>
        public IUnitOfWorkRepository Repository { get; set; }
 
        /// <summary>
        /// Gets or sets the type of operation.
        /// </summary>
        /// <value>The type of operation.</value>
        public OperationType Type { get; set; }
    }
}
 
 
 
 
The general concept is that instead of storing the three types of operations separately and going through the lists in a grouped order, I would contain them all with a single type of class, the private Operation class. It simply tracks the same information that the old Dictionary<> instances did, but adds a DateTime and an OperationType enumeration value. Instead of three loops (one for each of the previous operation type), it is replaced by a single loop with a switch() statement. The List<Operation> instance is just ordered by the DateTime and executed in order.
 
Two things are missing - well, one is missing, and one is my personal preference. The first, is that I don't have the ClientTransactionRepository implementation in here, as I had removed it from my local implementation. It should be very easy to add it back in, though. The second is that the UOW has an explicit Commit() method, but no explicit way to execute what is the UOW with an implied rollback (thinking about unit testing here, where I can still go through the queue but keep the database pristine to get beyond testing with mocks - but that is another story for another day.)
 
Anyhow, I hope that someone finds this useful. It is just nice to be able to give something back.
 
Thanks!
 
Joseph
Closed May 12, 2011 at 7:23 AM by tmccart1

comments

wrote May 12, 2011 at 7:23 AM

Resolved with changeset 60703.

wrote Feb 21, 2013 at 11:42 PM

wrote May 16, 2013 at 11:20 AM