Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 288
tal Avatar asked Jan 05 '16 10:01

tal


People also ask

What is unit of work design pattern?

The Unit of Work pattern is used to group one or more operations (usually database CRUD operations) into a single transaction or “unit of work” so that all operations either pass or fail as one unit.

What is a transaction pattern?

A transactional pattern is a convergence concept between workflow patterns and advanced transactional models. It can be seen as a coordination pattern and as a structured transaction. Thus, it combines workflow flexibility and transactional processing reliability.

Is Unit of Work equals transaction or it is more than that?

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work. So this means UnitOfWork is DBTransaction and More.

What is the purpose of Unit of Work pattern?

The unit of work class serves one purpose: to make sure that when you use multiple repositories, they share a single database context. That way, when a unit of work is complete you can call the SaveChanges method on that instance of the context and be assured that all related changes will be coordinated.


1 Answers

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:

  • EF Code First DBContext and Transactions
  • Working with Transactions (EF6 Onwards):

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:

  • Optimistic Concurrency Patterns
  • Saving Changes and Managing Concurrency

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.

like image 165
JotaBe Avatar answered Oct 17 '22 08:10

JotaBe