Using Unit of Work Pattern in AS
Using Unit of Work Pattern in ASP.NET MVC and Entity Framework
Recently I wrote an article explaining the
Repository Pattern and its use in
ASP.NET MVC. This article builds on that article and explains another related
design pattern - Unit of Work.
The Unit of Work pattern states its intent like this:
Maintains a list of objects affected by a business transaction and
coordinates the writing out of changes and the resolution of concurrency
problems.
Let's try to understand the above statement with an example. Let's say you
are building an order processing system that accepts orders through a shopping
cart. The whole order information is divided in two parts - OrderHeader and
OrderDetails. The OrderHeader includes details that are applicable to the order
as a whole including Customer ID, Order Date and Shipping Address. The
OrderDetails indicate the individual items that make the order. They include
details such as Product ID, Quantity and Unit Price. For one OrderHeader record
there will be one or more OrderDetails records.
Now, further suppose that you have created repositories for these two pieces
in your application as described in
this article. Let's call
them OrderHeaderRepository and OrderDetailsRepository respectively. If you
instantiate and use these two repositories independently you will be above to
place a new order but there will be a problem. Since using these repositories
indecently means using altogether different instances of the Entity Framework
data context (NorthwindEntities in the above example), you won't be able to
cancel an order if there is some error while adding an order item. This means
adding OrderHeader and adding OrderDetails won't work as a single business
transaction. Obviously, this can produce incorrect results because both of these
steps must be executed as a single unit of work.
The problem here is that both of the repositories are used independently.
Instead, if you make both of them together such that they use the same data
context you can enforce the above mentioned business rule easily. That is where
Unit of Work pattern comes into picture. A class implementing Unit of Work
pattern maintains a list of all the objects that are participating a business
transaction. It then uses them together so as to execute all the steps as a part
of single business transaction. So, in our example you need to create a class
that houses both OrderHeaderRepository and OrderDetailsRepository. This class
then supplies them the same EF data context so that all the database queries are
executed through the same data context. Since a data context is being shared by
all the participating objects you get chance to control how the individual steps
of an operation are executed or canceled.
EF data context class itself is an example of Unit of Work pattern. It
maintains one or more DbSet objects and calling SaveChanges() attempts to take
changes from all the DbSet objects to the database.
Ok. Now let's write some code that shows how Unit of Work pattern can be used
along with the Repository pattern. For the sake of simplicity I am not going to
create generic repositories for OrderHeader and OrderDetails objects. You may
read the above mentioned article for more information.
Let's add two interfaces that govern how the two repositories look like:
public interface IOrderHeaderRepository
{
IEnumerable<Order> SelectAll();
Order SelectByID(string id);
void Insert(Order obj);
void Update(Order obj);
void Delete(string id);
void Save();
}
public interface IOrderDetailsRepository
{
IEnumerable<Order_Detail> SelectAll();
Order_Detail SelectByID(string id);
void Insert(Order_Detail obj);
void Update(Order_Detail obj);
void Delete(string id);
void Save();
}
The IOrderHeaderRepository interface is implemented by the
OrderHeaderRepository class and defines methods to perform CRUD operations on
the Orders table. The OrderHeaderRepository class is shown below:
public class OrderHeaderRepository:IOrderHeaderRepository
{
private NorthwindEntities db = null;
public OrderHeaderRepository()
{
this.db = new NorthwindEntities();
}
public OrderHeaderRepository(NorthwindEntities db)
{
this.db = db;
}
public IEnumerable<Order> SelectAll()
{
return db.Orders.ToList();
}
public Order SelectByID(string id)
{
return db.Orders.Find(id);
}
public void Insert(Order obj)
{
db.Orders.Add(obj);
}
public void Update(Order obj)
{
db.Entry(obj).State = EntityState.Modified;
}
public void Delete(string id)
{
Order existing = db.Orders.Find(id);
db.Orders.Remove(existing);
}
public void Save()
{
db.SaveChanges();
}
}
We won't go into the details of these methods here as they are quite
straightforward. The OrderDetailsRepository performs CRUD on [Order Details]
table and is shown below:
public class OrderDetailsRepository : IOrderDetailsRepository
{
private NorthwindEntities db = null;
public OrderDetailsRepository()
{
this.db = new NorthwindEntities();
}
public OrderDetailsRepository(NorthwindEntities db)
{
this.db = db;
}
public IEnumerable<Order_Detail> SelectAll()
{
return db.Order_Details.ToList();
}
public Order_Detail SelectByID(string id)
{
return db.Order_Details.Find(id);
}
public void Insert(Order_Detail obj)
{
db.Order_Details.Add(obj);
}
public void Update(Order_Detail obj)
{
db.Entry(obj).State = EntityState.Modified;
}
public void Delete(string id)
{
Order_Detail existing = db.Order_Details.Find(id);
db.Order_Details.Remove(existing);
}
public void Save()
{
db.SaveChanges();
}
}
Now comes the main point of our discussion. Add a class and name it
OrderUnitOfWork. Then key-in the following code in the OrderUnitOfWork class.
public class OrderUnitOfWork
{
private NorthwindEntities db = null;
private OrderHeaderRepository ordRepository = null;
private OrderDetailsRepository orddetailsRepository = null;
public OrderUnitOfWork()
{
db = new NorthwindEntities();
ordRepository = new OrderHeaderRepository(db);
orddetailsRepository = new OrderDetailsRepository(db);
}
public OrderRepository OrderHeaders
{
get
{
return ordRepository;
}
}
public OrderDetailsRepository OrderDetails
{
get
{
return orddetailsRepository;
}
}
public void PlaceOrder()
{
//other checking or logic here
db.SaveChanges();
}
}
The OrderUnitOfWork class declares three private variables of type
NorthwindEntities, OrderHeaderRepository and OrderDetailsRepository
respectively. In the constructor of the OrderUnitOfWork class a new data context
is instantiated and the same is passed to the constructors of
OrderHeaderRepository and OrderDetailsRepository respectively.
The class also defines two public properties OrderHeaders and OrderDetails
that return the instances of OrderHeaderRepository and OrderDetailsRepository
respectively. These instances will be used by your controller code to add the
header and details information of an order. The PlaceOrder() method calls
SaveChanges() method of the data context to save all the changes made to headers
and details to the database.
Once created you can use OrderUnitOfWork class in your controller like this:
public ActionResult PlaceOrder()
{
OrderUnitOfWork workUnit = new OrderUnitOfWork();
OrderHeaderRepository ord = workUnit.OrderHeaders;
OrderDetailsRepository orddetails = workUnit.OrderDetails;
Order newOrder = new Order();
newOrder.OrderID = 100;
newOrder.CustomerID = "ALFKI";
newOrder.EmployeeID = 1;
newOrder.OrderDate = DateTime.Now;
ord.Insert(newOrder);
Order_Detail newOrderDetail = new Order_Detail();
newOrderDetail.OrderID = newOrder.OrderID;
newOrderDetail.ProductID = 200;
newOrderDetail.Quantity = 5;
newOrderDetail.UnitPrice = 1234.56M;
orddetails.Insert(newOrderDetail);
workUnit.PlaceOrder();
return View();
}
The PlaceOrder() action method creates in instance of OrderUnitOfWork class.
A reference to OrderHeaders and OrderDetails repositories are stored in two
local variables for easy access. Then a new Order is added using
OrderHeaderRepository and a new OrderD_Detail is added using
OrderDetailsRepository respectively. Finally, PlaceOrder() method of the
OrderUnitOfWork is called to save the order details.