UnitOfWork
In the previous article, Repository And UnitOfWork Pattern - Part One, we looked at the Repository Pattern, which provides the ability to create repository class which holds the data of specific entity or entities in the form of collections. It's also used to create an abstraction layer between the data persistence (data access layer) and the business logic (business access layer) to perform operations on the data persistence.
In the previous article, we created a repository class for data table Order and OrderItem, in which each repository class created an object of DataContext which further saved the changes into the data persistence. We also discussed the problem with the repository pattern, which is that we need to have data all saved in one single transaction instead of having a separate DataContext object in every repository.
UnitOfWork (UOW) is the common pattern that is used to resolve data concurrency issues which arise when each repository implements and maintains separate Data context objects. The UnitOfWork (UOW) pattern implementation manages in-memory database operations on entities as one transaction. So, if one of the operations is failing, then the entire database operations will rollback.
According to Martin Fowler, a unit of work "maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems." The "list of objects" are the repositories, the "business transactions" are the repository-specific business rules to retrieve data, and the "coordination of the writing of changes and concurrency problems" is through the DbContext.
Now, let’s have a look at the implementation of Repository pattern example using UnitOfWork Pattern.
Make the following changes to the BaseRepository class,
- First, remove the instance of OrderManagementDbContext.
- Remove Context.SaveChanges(); call from operation
- Add a parameter of type DbContext to the constructor
- public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
- {
- protected DbSet<TEntity> Entities;
- private readonly DbContext _dbContext;
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseRepository{TEntity}"/> class.
- /// Note that here I've stored Context.Set<TEntity>() in the constructor and store it in a private field like _entities.
- /// This way, the implementation of our methods would be cleaner: ///
- /// _entities.ToList();
- /// _entities.Where();
- /// _entities.SingleOrDefault();
- /// </summary>
- public BaseRepository(DbContext dbContext)
- {
- _dbContext = dbContext;
- Entities = _dbContext.Set<TEntity>();
- }
- public virtual IEnumerable<TEntity> Get(
- Expression<Func<TEntity, bool>> filter = null,
- Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
- string includeProperties = "")
- {
- IQueryable<TEntity> query = Entities;
- if (filter != null)
- {
- query = query.Where(filter);
- }
- foreach (var includeProperty in includeProperties.Split
- (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
- {
- query = query.Include(includeProperty);
- }
- if (orderBy != null)
- {
- return orderBy(query).ToList();
- }
- else
- {
- return query.ToList();
- }
- }
- /// <summary>
- /// Gets the specified identifier.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns></returns>
- public virtual TEntity Get(int id)
- {
- // Here we are working with a DbContext, not specific DbContext.
- // So we don't have DbSets we need to use the generic Set() method to access them.
- return Entities.Find(id);
- }
- /// <summary>
- /// Gets all.
- /// </summary>
- /// <returns></returns>
- public IEnumerable<TEntity> GetAll()
- {
- return Entities.ToList();
- }
- /// <summary>
- /// Finds the specified predicate.
- /// </summary>
- /// <param name="predicate">The predicate.</param>
- /// <returns></returns>
- public IEnumerable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
- {
- return Entities.Where(predicate);
- }
- /// <summary>
- /// Singles the or default.
- /// </summary>
- /// <param name="predicate">The predicate.</param>
- /// <returns></returns>
- public TEntity SingleOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
- {
- return Entities.Where(predicate).SingleOrDefault();
- }
- /// <summary>
- /// First the or default.
- /// </summary>
- /// <returns></returns>
- public TEntity FirstOrDefault()
- {
- return Entities.SingleOrDefault();
- }
- /// <summary>
- /// Adds the specified entity.
- /// </summary>
- /// <param name="entity">The entity.</param>
- public void Add(TEntity entity)
- {
- Entities.Add(entity);
- }
- /// <summary>
- /// Adds the range.
- /// </summary>
- /// <param name="entities">The entities.</param>
- public void AddRange(IEnumerable<TEntity> entities)
- {
- Entities.AddRange(entities);
- }
- /// <summary>
- /// Removes the specified entity.
- /// </summary>
- /// <param name="entity">The entity.</param>
- public void Remove(TEntity entity)
- {
- Entities.Remove(entity);
- }
- /// <summary>
- /// Removes the range.
- /// </summary>
- /// <param name="entities">The entities.</param>
- public void RemoveRange(IEnumerable<TEntity> entities)
- {
- Entities.RemoveRange(entities);
- }
- /// <summary>
- /// Removes the Entity
- /// </summary>
- /// <param name="entityToDelete"></param>
- public virtual void RemoveEntity(TEntity entityToDelete)
- {
- if (_dbContext.Entry(entityToDelete).State == EntityState.Detached)
- {
- Entities.Attach(entityToDelete);
- }
- Entities.Remove(entityToDelete);
- }
- /// <summary>
- /// Update the Entity
- /// </summary>
- /// <param name="entityToUpdate"></param>
- public virtual void UpdateEntity(TEntity entityToUpdate)
- {
- Entities.Attach(entityToUpdate);
- _dbContext.Entry(entityToUpdate).State = EntityState.Modified;
- }
- }







DasithPosted Mar 25, 2021, 1:23 AM
This is good for learning but generic repositories like the one in your example are a code smell and should be avoided. http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/
souvik settPosted Jul 24, 2019, 2:31 PM
The example is very good for really really basic learning. You carefully skipped a concept. Okay, lets take a scenario. Order id is generated after order inserted in database (most of the cases people design like this). Now how can I use the OrderId to Order Items in a single transaction?
Dmitry KorzhPosted Aug 15, 2018, 3:25 AM
Thanks for a great article! Could you please explain the benefit of having 2 different repositories for Orders and OrderItems instead of having just one that will handle Orders and OrderItems together as they are closely related? My concern is that if I had to separate commands and queries for your example it would end up with 4 repositories (Orders read repo, Orders write repo, OrderItems read repo, OrderItems write repo) and increasing complexity as a result. Now imagine that we have not only Orders and OrderItems, but also Customers, Adressess etc.
masoud talaeiiPosted Aug 15, 2018, 1:56 AM
Hi very good.