Repository And UnitOfWork Pattern - Part One

Repository as it implies is a place where things are stored and can be found and retrieved whenever needed. Similarly, in computing terms, repository is a central location in which data for a specific type of entity or entities is stored and managed either in a form of collection or table.

Repository Pattern

Repository, as it implies, is a place where things are stored and can be found and retrieved whenever needed. Similarly, in computing terms, a repository is a central location in which data for a specific type of entity or entities are stored and managed either in a form of collection or table.

Most of the applications we developed in the past or will develop in the future need to persist the data in any kind of storage. I am sure those applications must have business logic, data access layer implemented, and must connect between business logic and data access layer to access the data persevered in storage whenever needed. It may not be obvious at first sight, but connecting business logic to data access layer and vice-versa can lead to a lot of duplication in terms of, 

  • Data Handling
    This includes the creation of the same business logic objects from different places and connects with the data access layer to retrieve raw data, filter and make it useful.
  • Data Retrieval
    This includes writing queries to retrieve all the data or some specific information about a single subject from different places. 
  • Data Persistence
    This includes implementing a logic to persist data as needed throughout different modules and classes of our application.

In this article, we will talk about one of the most popular patterns, i.e., Repository Pattern which is designed to deal with how to connect and decouple business logic and persistence (data storage) and eventually helps us in resolving problems we described above. As defined, repository patterns allow us to:

  • define a repository which acts as a mediator between the domain and data mapping layers using a collection-like interface for accessing domain objects. 
  • define a repository which functions in two ways: data retrieval and data persistence.

Repository Pattern – Deep Dive

As we discussed, repository acts as a mediator between the domain (business layer) and data persistence layer and functions in two ways; i.e., Data Retrieval and Data Persistence.

Data Retrieval

When used to retrieve data from persistence, business layer makes a call to repository method with parameters, then repository will contact the data persistence to retrieve the raw data from the persistence. The persistence will return raw data then the repository will take this data, do the necessary transformations and construct the objects with the data and finally return them as a set of objects (like an array of objects or a collection object as defined) to the Business Logic.

Data Persistence

When used to persist data into the persistence, business layer makes a call to repository method and passes on the data object, then repository extracts the information from an object, performs the necessary transformations and finally repository will persist the data extracted from the objects into the persistence and optionally cache them in a local in-memory list.

Repository and UnitOfWork Pattern 

When to Use Repository Pattern

Repository Pattern is useful when you want your domain objects (or entities) to be persistence ignorant but have the flexibility to map your data to the choice of your data store e.g. SQL Server, Oracle, NoSQL databases, cache etc. The physical model of the stored data might vary from store to store but not the logical model. So, a repository plays the role of mapping a logical model to the physical model and vice versa. ORM (Object Relational Mapping) tools like Entity Framework do help us to achieve this and we could make use of it wherever possible in building your domain-specific repositories.

Now let’s have a look at the practical example using EntityFramework. Imagine we have an Order Management software where we have Order objects and OrderItem objects. OrderItem belongs to Order and we must find a way to persist them and to retrieve them.

First, design the database table structure, as shown below.

 Repository and UnitOfWork Pattern
 
 As we are using entity framework (ORM) we need to define DbContext class that acts as a bridge between domain or entity classes and the database. This DbContext class will be used to carry out the following activities:
  • Querying: Converts LINQ-to-Entities queries to SQL query and sends them to the database.
  • Change Tracking: Keeps track of changes that occurred on the entities after querying from the database.
  • Persisting Data: Performs the Insert, Update and Delete operations to the database, based on entity states.
  • Caching: Provides first level caching by default. It stores the entities which have been retrieved during the lifetime of a context class.
  • Manage Relationship: Manages relationships using CSDL, MSL, and SSDL in Db-First or Model-First approach and using fluent API configurations in Code-First approach.
  • Object Materialization: Converts raw data from the database into entity objects.

Now generate a OrderManagementDbContext inheriting Entity Framework DbContext class by adding ADO.Net Entity Data Model (.edmx file). 

  1. public partial class OrderManagementDbContext : DbContext  
  2. {  
  3.     public OrderManagementDbContext()  
  4.         : base("name=OrderManagementDbContext")  
  5.     {  
  6.     }  
  7.   
  8.     protected override void OnModelCreating(DbModelBuilder modelBuilder)  
  9.     {  
  10.         throw new UnintentionalCodeFirstException();  
  11.     }  
  12.   
  13.     public virtual DbSet<Order> Orders { getset; }  
  14.     public virtual DbSet<OrderItem> OrderItems { getset; }  
  15. }  
Now, as mentioned above, create a repository for Order and OrderItem to store in-memory data in form of collections. At the bare minimum, each of the repositories should be able to provide CRUD (Create Read Update Delete) options, for which we will have a generic interface defined and have an implementation which will be inherited from, for specific repositories.
  1. public interface IRepository<TEntity> where TEntity : class  
  2.     {  
  3.         IEnumerable<TEntity> Get(  
  4.            Expression<Func<TEntity, bool>> filter = null,  
  5.            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,  
  6.            string includeProperties = "");  
  7.   
  8.         /// <summary>  
  9.         /// Gets the specified identifier.  
  10.         /// </summary>  
  11.         /// <param name="id">The identifier.</param>  
  12.         /// <returns></returns>  
  13.         TEntity Get(int id);  
  14.   
  15.         /// <summary>  
  16.         /// Gets all.  
  17.         /// </summary>  
  18.         /// <returns></returns>  
  19.         IEnumerable<TEntity> GetAll();  
  20.   
  21.         /// <summary>  
  22.         /// Finds the specified predicate.  
  23.         /// </summary>  
  24.         /// <param name="predicate">The predicate.</param>  
  25.         /// <returns></returns>  
  26.         IEnumerable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate);  
  27.   
  28.         /// <summary>  
  29.         /// Singles the or default.  
  30.         /// </summary>  
  31.         /// <param name="predicate">The predicate.</param>  
  32.         /// <returns></returns>  
  33.         TEntity SingleOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate);  
  34.   
  35.         /// <summary>  
  36.         /// First the or default.  
  37.         /// </summary>  
  38.         /// <returns></returns>  
  39.         TEntity FirstOrDefault();  
  40.   
  41.         /// <summary>  
  42.         /// Adds the specified entity.  
  43.         /// </summary>  
  44.         /// <param name="entity">The entity.</param>  
  45.         void Add(TEntity entity);  
  46.   
  47.         /// <summary>  
  48.         /// Adds the range.  
  49.         /// </summary>  
  50.         /// <param name="entities">The entities.</param>  
  51.         void AddRange(IEnumerable<TEntity> entities);  
  52.   
  53.         /// <summary>  
  54.         /// Removes the specified entity.  
  55.         /// </summary>  
  56.         /// <param name="entity">The entity.</param>  
  57.         void Remove(TEntity entity);  
  58.   
  59.         /// <summary>  
  60.         /// Removes the range.  
  61.         /// </summary>  
  62.         /// <param name="entities">The entities.</param>  
  63.         void RemoveRange(IEnumerable<TEntity> entities);  
  64.   
  65.         /// <summary>  
  66.         /// Removes the Entity  
  67.         /// </summary>  
  68.         /// <param name="entityToDelete"></param>  
  69.         void RemoveEntity(TEntity entityToDelete);  
  70.   
  71.         /// <summary>  
  72.         /// Update the Entity  
  73.         /// </summary>  
  74.         /// <param name="entityToUpdate"></param>  
  75.         void UpdateEntity(TEntity entityToUpdate);  
  76.   
  77.     }  
  78.     public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class  
  79.     {  
  80.         protected readonly OrderManagementDbContext Context;  
  81.   
  82.         protected DbSet<TEntity> Entities;  
  83.   
  84.   
  85.         /// <summary>  
  86.         /// Initializes a new instance of the <see cref="BaseRepository{TEntity}"/> class.  
  87.         /// Note that here I've stored Context.Set<TEntity>() in the constructor and store it in a private field like _entities.   
  88.         /// This way, the implementation  of our methods would be cleaner:        ///   
  89.         /// _entities.ToList();  
  90.         /// _entities.Where();  
  91.         /// _entities.SingleOrDefault();  
  92.         /// </summary>  
  93.         public BaseRepository()  
  94.         {  
  95.             Context = new OrderManagementDbContext();  
  96.             Entities = Context.Set<TEntity>();  
  97.         }  
  98.   
  99.         public virtual IEnumerable<TEntity> Get(  
  100.             Expression<Func<TEntity, bool>> filter = null,  
  101.             Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,  
  102.             string includeProperties = "")  
  103.         {  
  104.             IQueryable<TEntity> query = Entities;  
  105.   
  106.             if (filter != null)  
  107.             {  
  108.                 query = query.Where(filter);  
  109.             }  
  110.   
  111.             foreach (var includeProperty in includeProperties.Split  
  112.                 (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))  
  113.             {  
  114.                 query = query.Include(includeProperty);  
  115.             }  
  116.   
  117.             if (orderBy != null)  
  118.             {  
  119.                 return orderBy(query).ToList();  
  120.             }  
  121.             else  
  122.             {  
  123.                 return query.ToList();  
  124.             }  
  125.         }  
  126.   
  127.   
  128.         /// <summary>  
  129.         /// Gets the specified identifier.  
  130.         /// </summary>  
  131.         /// <param name="id">The identifier.</param>  
  132.         /// <returns></returns>  
  133.         public virtual TEntity Get(int id)  
  134.         {  
  135.             // Here we are working with a DbContext, not specific DbContext.   
  136.             // So we don't have DbSets we need to use the generic Set() method to access them.  
  137.             return Entities.Find(id);  
  138.         }  
  139.   
  140.         /// <summary>  
  141.         /// Gets all.  
  142.         /// </summary>  
  143.         /// <returns></returns>  
  144.         public IEnumerable<TEntity> GetAll()  
  145.         {  
  146.             return Entities.ToList();  
  147.         }  
  148.   
  149.         /// <summary>  
  150.         /// Finds the specified predicate.  
  151.         /// </summary>  
  152.         /// <param name="predicate">The predicate.</param>  
  153.         /// <returns></returns>  
  154.         public IEnumerable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)  
  155.         {  
  156.             return Entities.Where(predicate);  
  157.         }  
  158.   
  159.         /// <summary>  
  160.         /// Singles the or default.  
  161.         /// </summary>  
  162.         /// <param name="predicate">The predicate.</param>  
  163.         /// <returns></returns>  
  164.         public TEntity SingleOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)  
  165.         {  
  166.             return Entities.Where(predicate).SingleOrDefault();  
  167.         }  
  168.   
  169.         /// <summary>  
  170.         /// First the or default.  
  171.         /// </summary>  
  172.         /// <returns></returns>  
  173.         public TEntity FirstOrDefault()  
  174.         {  
  175.             return Entities.SingleOrDefault();  
  176.         }  
  177.   
  178.         /// <summary>  
  179.         /// Adds the specified entity.  
  180.         /// </summary>  
  181.         /// <param name="entity">The entity.</param>  
  182.         public void Add(TEntity entity)  
  183.         {  
  184.             Entities.Add(entity);  
  185.             Context.SaveChanges();  
  186.         }  
  187.   
  188.         /// <summary>  
  189.         /// Adds the range.  
  190.         /// </summary>  
  191.         /// <param name="entities">The entities.</param>  
  192.         public void AddRange(IEnumerable<TEntity> entities)  
  193.         {  
  194.             Entities.AddRange(entities);  
  195.             Context.SaveChanges();  
  196.         }  
  197.   
  198.         /// <summary>  
  199.         /// Removes the specified entity.  
  200.         /// </summary>  
  201.         /// <param name="entity">The entity.</param>  
  202.         public void Remove(TEntity entity)  
  203.         {  
  204.             Entities.Remove(entity);  
  205.             Context.SaveChanges();  
  206.         }  
  207.   
  208.         /// <summary>  
  209.         /// Removes the range.  
  210.         /// </summary>  
  211.         /// <param name="entities">The entities.</param>  
  212.         public void RemoveRange(IEnumerable<TEntity> entities)  
  213.         {  
  214.             Entities.RemoveRange(entities);  
  215.         }  
  216.   
  217.   
  218.         /// <summary>  
  219.         /// Removes the Entity  
  220.         /// </summary>  
  221.         /// <param name="entityToDelete"></param>  
  222.         public virtual void RemoveEntity(TEntity entityToDelete)  
  223.         {  
  224.             if (Context.Entry(entityToDelete).State == EntityState.Detached)  
  225.             {  
  226.                 Entities.Attach(entityToDelete);  
  227.             }  
  228.             Entities.Remove(entityToDelete);  
  229.             Context.SaveChanges();  
  230.         }  
  231.   
  232.         /// <summary>  
  233.         /// Update the Entity  
  234.         /// </summary>  
  235.         /// <param name="entityToUpdate"></param>  
  236.         public virtual void UpdateEntity(TEntity entityToUpdate)  
  237.         {  
  238.             Entities.Attach(entityToUpdate);  
  239.             Context.Entry(entityToUpdate).State = EntityState.Modified;  
  240.             Context.SaveChanges();  
  241.         }  
  242.     }  
  243.   
  244.     public class OrderRepository : BaseRepository<Order>  
  245.     {  
  246.   
  247.     }  
  248.   
  249.     public class OrderItemRepository : BaseRepository<OrderItem>  
  250.     {  
  251.   
  252.     }  
Now look at the main program which calls different methods of repository,
  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         OrderRepository orderRepository = new OrderRepository();  
  6.   
  7.         GetOrders();  
  8.   
  9.         Order order = new Order  
  10.         {  
  11.             OrderId = Guid.NewGuid().ToString(),  
  12.             OrderDate = DateTime.Now,  
  13.             OrderStatus = "In Process"  
  14.         };  
  15.   
  16.         orderRepository.Add(order);  
  17.         order.OrderItems = new List<OrderItem>  
  18.         {  
  19.             new OrderItem{ OrderId = order.OrderId, ItemName = "Item 1",OrderItemId = Guid.NewGuid().ToString(), Quantity = 1 },  
  20.             new OrderItem{ OrderId = order.OrderId, ItemName = "Item 2",OrderItemId = Guid.NewGuid().ToString(), Quantity = 4 }  
  21.         };  
  22.   
  23.         OrderItemRepository orderItemRepository = new OrderItemRepository();  
  24.         orderItemRepository.AddRange(order.OrderItems);  
  25.   
  26.         GetOrders();  
  27.   
  28.         Console.ReadLine();  
  29.     }  
  30.   
  31.     private static void GetOrders()  
  32.     {  
  33.         OrderRepository orderRepository = new OrderRepository();  
  34.         OrderItemRepository orderItemRepository = new OrderItemRepository();  
  35.         var orders = orderRepository.GetAll();  
  36.   
  37.         IEnumerable<Order> orderDatas = orders.ToList();  
  38.         if (!orderDatas.Any())  
  39.         {  
  40.             Console.WriteLine("No Order Placed");  
  41.         }  
  42.         else  
  43.         {  
  44.             foreach (var orderData in orderDatas)  
  45.             {  
  46.                 Console.WriteLine("Order {0} placed and currently is {1}", orderData.OrderId, orderData.OrderStatus);  
  47.                   
  48.                 var orderItems = orderItemRepository.Find(x => x.OrderId == orderData.OrderId);  
  49.                 IEnumerable<OrderItem> items = orderItems as OrderItem[] ?? orderItems.ToArray();  
  50.                 if (!items.Any()) continue;  
  51.                 Console.WriteLine("Items included in the order:");  
  52.                 foreach (var orderItem in items)  
  53.                 {  
  54.                     Console.WriteLine(orderItem.ItemName);  
  55.                 }  
  56.             }  
  57.         }  
  58.     }  
  59. }  
Output
 
 Repository and UnitOfWork Pattern
 
 Repository and UnitOfWork Pattern
 
 
Repository and UnitOfWork Pattern
 
As we can see in the above output window that data is inserted into both the tables - Order and OrderItem. In the above example, it was needed to insert data into two tables, Order and OrderItem, through repositories and we can see in the above output window that data is inserted into both the tables, Order and OrderItem, through repositories.

In the above example, we inserted data into two tables separately, but it could lead to a problem where data is inserted into the one table successfully but failed to insert into the other table. So, to avoid this kind of problem, we need to have data all saved in one single transaction, which means if any error occurs in inserting or updating the data we should rollback the complete transaction. UnitOfWork (UOW), is the common pattern that is used for this scenario, by passing around a context object that knows how to commit/save after a set of activities which we will understand and learn in Part 2.

Summary

Finally, as we have seen in the above example, this design pattern can be used for all types of lists and as you start using it, you will see its usefulness. Basically, whenever you must work with several objects of the same type, you should consider introducing a Repository for them. Repositories are specialized by object type and not general. Repository helps in resolving issues of data handling, data persistence, and data retrieval by keeping implementation at one place instead of doing it in many places in the application.