Tal
Tal

Reputation: 544

Transactions in unit of work design pattern

I have having trouble understanding the transaction concept of unit of work. I use code like: the unit of work class:

public class UnitOfWork : IDisposable
{
    private readonly DbContext _context;
    private bool disposed = false;

    public UnitOfWork()
    {
        _context = new ResultsContext();
    }

    public IRepository<T> GetRepository<T>() where T : class
    {
        return new Repository<T>(_context);
    }


    public virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public Study GetStudyWithAll(string studyUid)
    {
        ResultsContext context = _context as ResultsContext;

        return context.Studies.Where(c => c.StudyUid == studyUid)
                              .Include(s => s.Tasks.Select(t => t.Plugins))
                              .Include(s => s.Findings)
                              .Include(s => s.Patient).FirstOrDefault();
    }

    public void SaveChanges()
    {
        if (_context != null)
        {
            bool saved = false;
            do
            {
                try
                {
                    _context.SaveChanges();
                    saved = true;
                }

                catch (DbUpdateException ex)
                {
                    // Get the current entity values and the values in the database 
                    var entry = ex.Entries.Single();
                    //var currentValues = entry.CurrentValues;

                    switch (entry.State)
                    {
                        case System.Data.EntityState.Added:
                            // added on client, non in store - store wins
                            entry.State = System.Data.EntityState.Modified;
                            break;
                        case System.Data.EntityState.Deleted:
                            //deleted on client, modified in store
                            entry.Reload();
                            entry.State = System.Data.EntityState.Deleted;
                            break;
                        case System.Data.EntityState.Modified:
                            DbPropertyValues currentValues = entry.CurrentValues.Clone();
                            //Modified on client, Modified in store
                                entry.Reload();
                                entry.CurrentValues.SetValues(currentValues);

                            break;
                        default:
                            //For good luck
                            entry.Reload();
                            break;
                    }
                }

                catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
                {
                    Exception raise = dbEx;
                    foreach (var validationErrors in dbEx.EntityValidationErrors)
                    {
                        foreach (var validationError in validationErrors.ValidationErrors)
                        {
                            string message = string.Format("{0}:{1}",
                                validationErrors.Entry.Entity.ToString(),
                                validationError.ErrorMessage);
                            // raise a new exception nesting
                            // the current instance as InnerException
                            raise = new InvalidOperationException(message, raise);
                        }
                    }
                    throw raise;
                }
            } while (!saved);
        }
    }

    public DbContext Context
    {
        get { return _context; }
    }
} 

The way I use it:

using (var uow = new UnitOfWork())
{

   //////some stuff///

    uow.SaveChanges();
}

The question is: is the unit of work context equals transaction, or do I need to add:

using (TransactionScope transaction = new TransactionScope()) 

Around it.

I know that the saveChanges is is wrapped with transaction, what I don't know is : is the whole context wrapped in transaction. I mean, can I be sure that the data I read (not save or update) is not changed during the life of the context?

Upvotes: 7

Views: 35186

Answers (1)

JotaBe
JotaBe

Reputation: 39025

Your unit of work implementation uses a single DbContext with a single call to .SaveChanges(). This guarantees by itself that all the work is done in a simple transaction. See, for example:

In all versions of Entity Framework, whenever you execute SaveChanges() to insert, update or delete on the database the framework will wrap that operation in a transaction. This transaction lasts only long enough to execute the operation and then completes. When you execute another such operation a new transaction is started.

You only need to use a TransactionScope if there are several .SaveChanges() or even several different DbContext instances involved (Beware that in the latter case it can even trigger a distributed transaction, which depends on MSDTC service being started).

Optimistic concurrency

I've added this note because of the comment: to avoid locking the database, EF uses a mechanism called Optimistic concurrenty which basically is checking that nothing was changed since it was read when saving changes. For more information see this two links:

Since EF6 there are ways to start your own "classical" transactions with the desired isolation level. But that usually involves locking part of the database which can have a harmful impact on the application performance. In most occasions it's much better to use optimistic concurrency. You'll find very little cases in which concurrency exception are thrown, and, as explained in the links, they can be handled. Or you can use stored procedures for particular tasks, for example to reduce the UnitsInStock of a product which is purchased in a crowded e-commerce site. I.e instead of reading the number of units, reducing it, and saving the changes, use a stored procedure that modifies the stock in a protected transaction or an UPDATE query that involves an implicit transaction.

Upvotes: 20

Related Questions