Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Unit of Work Design Pattern with Repository and Session

4.94/5 (6 votes)
19 Oct 2014CPOL4 min read 25.5K  
This article will guide you if you want to create your own custom ORM or a wrapper over the ORM. This is only for demo towards the Unit of work pattern with Repository and a singleton Session class.

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 end 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.

C#
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:

C#
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:

C#
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();           
        }
    }
}

IMPORTANT: I am creating Entities (List) and not using the DataTable directly, as LINQ to SQL needs to Submit the changes to see the entities 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:

C#
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:

C#
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:

C#
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:

C#
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! :)

Image 3 Image 4 Image 5 Image 6 Image 7 Image 8 Image 9 Image 10

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)