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?
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.
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.
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.
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.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With