Unit of work Design pattern with Repository and Session

Prerequisites : Repository Pattern

What is Unit of work pattern and why is it needed ??

According to Martin Fowler : ‘It Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems’. 

When you’re pulling data in and out of a database, it’s important to keep track of what you’ve changed; otherwise, that data won’t be written back into the database. Similarly you have to insert new objects you create and remove any objects you delete.

You can change the database with each change to your object model, but this can lead to lots of very small database calls, which ends up being very slow. Furthermore it requires you to have a transaction open for the whole interaction, which is impractical if you have a business transaction that spans multiple requests. The situation is even worse if you need to keep track of the objects you’ve read so you can avoid inconsistent reads.

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.

 

Unit of work shows up in all the persistence tools available.The ITransaction interface in NHibernate, the DataContext class in LINQ to SQL, and the ObjectContext class in the Entity Framework are all examples of a Unit of Work. For that matter, the venerable DataSet can be used as a Unit of Work.

Here in this example we will go ahead with the existing Repository pattern code and try to implement the Unit of work pattern with a Singleton Session pattern.  Ideally the Session should hold all the information about the Repositories as well as the Unit of work. Unit of work is ideal for tracking the object which the Repositories hold, thus minimizing the round trips.

This example is just for understanding how Unit of work gets implemented in any ORM. As depending on the necessity of the application, if you ever were to build a custom ORM you could use this approach.

We will be going ahead with the same Database structure :

Database structure

Database structure

 

When you view code in the dbml file, a partial file is created which allows you to enter custom code. Alternatively you can also double click on Product entity which generates the same below file( as we are only working with Product Entities ). We will write some properties for them as below.

 

using System.Collections.Generic;
namespace Xamlized.Database
{
    partial class CustomersDataContext
    {
              
    }

    partial class Product
    {
        public bool IsDirty { get; set; }
        public bool IsNew { get; set; }
        public bool IsDeleted { get; set; }
    }
}

As seen in the above code we have created change tracking properties viz. IsDirty,IsNew,IsDeleted . 

IsDirty : This property is used to tell the Unit of Work that the object has some changes , so that while the commit only the properties which are  changed actually goes in the SQL query.

IsNew : This property is used to indicate that the Entity is new to the Unit of work.

IsDeleted : This property is used to indicate that the Entity is deleted to the Unit of work.

We will now look at the Unit of Work(UOW) interface :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Xamlized.Repositories.Interfaces
{
    public interface IUnitOfWork<T>
    {
        void MarkDirty(T entity);
        void MarkNew(T entity);
        void MarkDeleted(T entity);
        void SaveChanges();
   
    }
}

Now we will look at UOW’s implementation :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Linq;
using System.Linq.Expressions;
using Xamlized.Repositories.Interfaces;
using System.Reflection;

namespace Xamlized.Repositories.Models
{
    public  class UnitOfWork<T>:IUnitOfWork<T> where T:class
    {

        public int ID { get; set; }
        
       internal DataContext DataContext;
        internal List<T> Entities;

        public UnitOfWork(DataContext dataContext)
        {
            DataContext = dataContext;
            
        }

        public void MarkDirty(T entity)
        {
            PropertyInfo prop = entity.GetType().GetProperty("IsDirty", BindingFlags.Public | BindingFlags.Instance);
            if (null != prop && prop.CanWrite)
            {
                prop.SetValue(entity, true, null);
            }
        }

        public  void MarkNew(T entity)
        {
            PropertyInfo prop = entity.GetType().GetProperty("IsNew", BindingFlags.Public | BindingFlags.Instance);
            if (null != prop && prop.CanWrite)
            {
                prop.SetValue(entity, true, null);
            }
        }

        public  void MarkDeleted(T entity)
        {
            PropertyInfo prop = entity.GetType().GetProperty("IsDeleted", BindingFlags.Public | BindingFlags.Instance);
            if (null != prop && prop.CanWrite)
            {
                prop.SetValue(entity, true, null);
            }            
        }

        // Prototype only for Saving changes for new entities
        //Similar code can be written for deleted and updated entities .
        public  void SaveChanges()
        {
            foreach (var entity in Entities)
            {
                // Checking all the changed entities only and saving it into Database
                PropertyInfo prop = entity.GetType().GetProperty("IsNew", BindingFlags.Public | BindingFlags.Instance);
                if (null != prop && Convert.ToBoolean(prop.GetValue(entity, null))==true)
                {
                    //Before saving make all the IsNew,IsDirty,IsDeleted properties = false;
                    if ( prop.CanWrite)
                    {
                        prop.SetValue(entity, false, null);
                    }
                    //Insert into DB this entity as this not present.
                    DataContext.GetTable<T>().InsertOnSubmit(entity);
                    
                }
            }
            DataContext.SubmitChanges();          
        }


        public IEnumerable<T> GetEntities(Func<T, bool> predicate = null)
        {
            if (predicate != null)
            {
                return Entities.Where(predicate).AsQueryable();               
            }
            return Entities.AsQueryable();          
        }


        public IEnumerable<T> GetAllEntities()
        {
            return Entities.AsQueryable();
           
        }

    }
}

IMP : I am creating a Entities (List) and not using the DataTable directly, as LINQ to SQL needs to Submit the changes to see the entites add/remove/update in the DataTable.
For more information you can see the Remarks column in the link : InsertOnSubmit

Hence we will work with Entities and only use the DataTable on Save as you can see above in the Save Method.

In all the methods viz : MarkDirty,MarkNew,MarkDeleted i am using Reflection to set the Properties to true viz. IsDirty,IsNew,IsDeleted respectively as this is a generic list.

Our Repository will also get changed slightly to incorporate UOW as below :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Xamlized.Repositories
{
    public interface IRepository<T>
    {
        void Insert(T entity);

        void Delete(T entity);

        IQueryable<T> SearchFor(Func<T, bool> predicate);

        IQueryable<T> GetAll();


    }
}

The implementation for Repository is as below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Linq;
using System.Linq.Expressions;


namespace Xamlized.Repositories.Models
{
    public class Repository<T> : IRepository<T> where T : class
    {
        public UnitOfWork<T> UnitOfWork { get; set; }

       // internal Table<T> DataTable;

       // internal List<T> Entities;
        public Repository(UnitOfWork<T> uow)
        {
            UnitOfWork = uow;
            UnitOfWork.Entities = new List<T>();
            UnitOfWork = uow;
            //DataTable = uow.DataContext.GetTable<T>();
            foreach (var item in UnitOfWork.DataContext.GetTable<T>())
            {
                UnitOfWork.Entities.Add(item);
            }
        }

               

        public void Insert(T entity)
        {
            
            UnitOfWork.Entities.Add(entity);
            UnitOfWork.MarkNew(entity);
           
        }


        public void Delete(T entity)
        {
            UnitOfWork.Entities.Remove(entity);
           
        }


        public System.Linq.IQueryable<T> SearchFor(Func<T, bool> predicate)
        {
            return UnitOfWork.Entities.Where(predicate).AsQueryable();            
        }

        public System.Linq.IQueryable<T> GetAll()
        {
            return UnitOfWork.Entities.AsQueryable();

        }
    }
}

Now let us look at the Session class which is a singleton, where i have 2 properties for Repository and Unit of Work :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamlized.Repositories.Models;

namespace Xamlized.Repositories
{
    
    public class Session<T> where T : class
    {

            public static Repository<T> ObjectStorage { get; set; }

            public static UnitOfWork<T> UnitOfWork { get; set; }
        private static Session<T> instance;

        private Session() { }

        public static Session<T> Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Session<T>();
                }
                return instance;
            }
        }
    }

}

Now we will see the client code of this :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamlized.Database;
using Xamlized.Repositories;
using Xamlized.Repositories.Models;

namespace Xamlized.RepositoryPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var dataContext = new CustomerDataContext())
            {
                Session<Product>.UnitOfWork = new UnitOfWork<Product>(dataContext);
                Session<Product>.ObjectStorage = new Repository<Product>(Session<Product>.UnitOfWork);


                var allentities = Session<Product>.UnitOfWork.GetAllEntities();
                Console.WriteLine("-------All Products from DB-----");
                foreach (var product in allentities)
                {
                    Console.WriteLine(product.Name);
                }
              

                Product p1 = new Product();
               // p1.ProductID = 1;
                p1.Name = "New Widget";
                Session<Product>.ObjectStorage.Insert(p1);         
               


                Product p2 = new Product();
                p2.Name = "New Camera";
               // p2.ProductID = 2;
                Session<Product>.ObjectStorage.Insert(p2);
               
               
                allentities = Session<Product>.UnitOfWork.GetAllEntities();
                Console.WriteLine("-------All Products after insert-----");
                foreach (var product in allentities)
                {
                    Console.WriteLine(product.Name);
                }
                //var dirtyentites = Session<Product>.UnitOfWork.GetEntities().Where(x => x.IsDirty == true);
                //Console.WriteLine("-------Dirty Products-----");
                //foreach (var product in dirtyentites)
                //{
                //    Console.WriteLine(product.Name);
                //}
                var newentites = Session<Product>.UnitOfWork.GetEntities().Where(x => x.IsNew == true);
                Console.WriteLine("-------New Products-----");
                foreach (var product in newentites)
                {
                    Console.WriteLine(product.Name);
                }
                //var deletedentites = Session<Product>.UnitOfWork.GetEntities().Where(x => x.IsDeleted == true);
                //Console.WriteLine("-------Deleted Products-----");
                //foreach (var product in deletedentites)
                //{
                //    Console.WriteLine(product.Name);
                //}

                Session<Product>.UnitOfWork.SaveChanges();
                allentities = Session<Product>.UnitOfWork.GetAllEntities();
                Console.WriteLine("-------All Products after save-----");
                foreach (var product in allentities)
                {
                    Console.WriteLine(product.Name);
                }

                 newentites = Session<Product>.UnitOfWork.GetEntities().Where(x => x.IsNew == true);
                Console.WriteLine("-------New Products after save-----");
                foreach (var product in newentites)
                {
                    Console.WriteLine(product.Name);
                }

                Console.ReadLine();
            }

            

        }
    }
}

As you can see that we are creating products and adding using the Repository , while adding we will use the Unit of work to mark that entity as New. We can use UOW to retrieve/ check the track the new,dirty and deleted entities.

The output is as shown below :

Unit of work pattern with repository and session

Unit of work pattern with repository and session

Happy coding 🙂

4 thoughts on “Unit of work Design pattern with Repository and Session

  1. Thanks for these posts. This info along with the repository post helped clean up alot of questions I had. keep it up

  2. how to you write updateChanges method , below is not working , it inserts a new record rather update , I can show my modified full code if necessary
    public void updateChanges()
    {

    try
    {
    foreach (var entity in Entities)
    {
    PropertyInfo prop = entity.GetType().GetProperty(“IsDirty”, BindingFlags.Public | BindingFlags.Instance);
    if (null != prop && Convert.ToBoolean(prop.GetValue(entity, null)) == true)
    {
    if (prop.CanWrite)
    {
    prop.SetValue(entity, false, null);
    }
    DataContext.GetTable().InsertOnSubmit(entity);
    }
    }
    DataContext.SubmitChanges();
    }
    catch (Exception ex)
    {
    throw ex;
    }
    }

    • he line below is causing the problem

      DataContext.GetTable().InsertOnSubmit(entity);

      This would insert the entity not update. Instead of above line you would need to get an entity from a Table and then update the properties in that as below

      foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
      if (propertyInfo.GetValue(updatedUser, null) == null)
      propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
      }
      db.Entry(original).CurrentValues.SetValues(updatedUser);
      db.SaveChanges();

      Please reply if you require any further clarity

Leave a comment