IT:AD:Patterns:UnitOfWork Pattern
Sumary
According to M.F. a UoW “keeps track of everything you do during a business transaction that can affect the Db. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.”
- Reference: http://bit.ly/v7o0Xf
- In other words, it is the EF ObjectContext that contains methods to Add, Attach, Update, Delete, Commit.
Considerations
- Creating a Repository (Repository per Aggregate Root Entity can lead to a scenario with the various Repositories are working with different Db contexts (ObjectContect (or DbContext)).
- A way to ensure they all use the same Db COntext – and syncronize their Commit across them – is the UnitOfWork Pattern.
- In essense:
- a UnitOfWork wraps a common DbContext,
- it is injected into each Repository
- The repository does not concern itself with the notion of Commit()
- When a commit is required, the Controller invokes the UnitOfWork's Commit method, not the Repository's.
- It is true that the UnitOfWork's wrapped DbContext does it's Commit all in one transaction, but The UnitOfWork
The above is a rather simplistic scenario.
- Regarding the relationship between Repository and Object Context:
- In 99% of cases, a Repository will work with only one Db Context. In which case:
- The DbContext could be injected into the Repository.
- Pros:
- Follows SOLID methodology.
- Cons:
- As Context was created prior to Repo, Repo can't close it and start another. Ie: Repository cannot work with secondary Contexts, or allow and partial rollbacks.
- A UnitOfWork (wrapping a DbContext) can be injected into the Repository:
- Pros:
- Follows SOLID methodology.
- Cons:
- As Context was created prior to Repo, Repo can't close it and start another. Ie: Repository cannot work with secondary Contexts, or allow and partial rollbacks.
- Note that if UoW has a Current property, it could wrap a IUoWManager (see below), but I consider that spaghetti (actually Chicken or the Egg) pattern.
- The Repository could have an internal property called DbContext that invokes a UnitOfWorkManager.Current that in turn wraps a UnitOfWorkFactory if the Current hasn't been created yet.
- Pros:
- Can now work with a Current (or a Stack) context.
- Cons:
- Lose SOLID pattern to some extent (DI is shifted from DbContext to a UnitOfWorkManager injection).
- But there is that other 1% case:
- It could be that in some cases one is working with an default DbContext, and a TOmbstone DbContext that mirrors the same schema, but without any triggers (this is a common design pattern for keeping track of offline delete scenarios, while being able to undelete the records).
- Therefore there is no 1-1 relationship between
Could resolve the 1% by something like this:
class ContactRepository : IContactRepository : IGenericRepository {} class TombstoneContactRepository : ITombstoneContactRepository : IContactRepository {}
that have constructor signatures for:
IUnifWork
and
ITombstoneUnitOfWork
respectively.
What's not addressed above are two other questions.
- Does a UnitOfWork have a 1-1 relationship to a DbContext?
- Transactions / Multiple DbContexts.
Relationship between UnitOfWork and DbContext
MF defined a UnitOfWork to be against a Repository. And in 99% of the cases a REpository is against a single vendor product (eg EF).
A UnitOfWork is per vendor Context.
Relationship between UnitOfWork and Transaction
But if one were to have one UnitOfWork for EF, and another for a WCF based Repo, then the Commit has to hook into something larger – a TransactionScope.
So A UoW has to look for a Current Scope, and if found, hook into it. If non existent, then it is does not hook into an ambient scope, and is a scope in itself.
Requirements: * UoW:
- UoW has 1-1 Relationship to ObjectContext (ie, it wraps it)
- UoWFactory has to hook new UoW into any available Ambient Namespace
- UoWManager, injected into Repository, and laer invoked by Repository, has to return UoW appropriate to current context.
- By Type (ie EF, other)
- By ObjectContext (Standard, or Tombstone)
public EFContactFactory : IRepository { .. IUoWFactory UoW { get { return _iowFactory.GetCurrent<ObjectContext>();} } .. } public UoWFactory : IUoWFactory { IoW GetCurrent<T> (string tag) { if (T is ObjectContext) { if (tag == "default") {...} } } }