Click here to Skip to main content
15,860,943 members
Articles / Programming Languages / C#

Very Thin Database Layer using UnitOfWork Pattern and EntityFramework

Rate me:
Please Sign up or sign in to vote.
4.83/5 (13 votes)
27 Apr 2018CPOL2 min read 28.3K   28   10
Building very thin database layer when using unit of work pattern together with Enityframework

Introduction

I have been involved in many projects during my time as a software developer and have seen different ways of accessing databases using different patterns, sometimes too much code when the same things could be done with much less code.

One thing we developers should be aware of when using EntityFramework is that DB Context is "HOLY" and should be handled with care. Create one just before you need it and Dispose it right after you are done with it.

DO NOT share it with different threads or keeping it alive during the whole time your application instance life-cycle, you may end up getting memory leaks.

Below, you will find implementation for UnitOfWork pattern using EntityFramework and DContext.

Background

If you are using Service/Repository pattern and want to want to build a thin Repository using unit of work pattern, this should get you on the right track.

I have seen different ways exposing data from the repository, sometimes returning IQueryable directly from repository, I don't recommend this way for 3 reasons:

  1. It is harder to mock when you need to write tests.
  2. Every time you read data from database, you need to convert it to CLR type using ToList() method.
  3. Composing Query can affect performance (Read this link from MIcrosoft)

Using the Code

This time, we are going to build a simple repository. Many think that everything that handles data has to be done through repository (I have seen a project that accesses a web service using repository), I think that Repository should only handle queries against database.

Below are interfaces and classes for setting up Unit of work pattern using EntityFramework.

This interface defines the Repository:

C#
public interface IGenericRepository
{
    Task<IEnumerable<T>> FindAsync<T>(Expression<Func<T, bool>> expression) where T : class;
    Task<T> SingleOrDefaultAsync<T>(Expression<Func<T, bool>> expression) where T : class;
    void Add<T>(T entity) where T : class;
    void Update<T>(T entity) where T : class;
    void Delete<T>(T entity) where T : class;
}

This class implements IGenericRepository:

C#
public class GenericRepository : IGenericRepository
   {
       private readonly IDatabaseContext _dbContext;

       public GenericRepository(IDatabaseContext dbContext)
       {
           _dbContext = dbContext;
       }

       public async Task<IEnumerable<T>> FindAsync<T>
       (Expression<Func<T, bool>> expression) where T : class
       {
           return await _dbContext.Set<T>().Where(expression).ToListAsync();
       }

       public async Task<T> SingleOrDefaultAsync<T>
       (Expression<Func<T, bool>> expression) where T : class
       {
           return await _dbContext.Set<T>().SingleOrDefaultAsync(expression);
       }

       public void Add<T>(T entity) where T : class
       {
           _dbContext.Set<T>().Add(entity);
       }

       public void Update<T>(T entity) where T : class
       {
           _dbContext.Entry(entity).State = EntityState.Modified;
       }

       public void Delete<T>(T entity) where T : class
       {
           _dbContext.Set<T>().Remove(entity);
       }
   }

Unit of Work

Using unit of work pattern will help you make several changes to your database model and commit all at once, doing so will help you make sure that all changes have been saved properly or in other case, you can rollback.

Defining unit of work interface:

C#
public interface IUnitOfWork : IDisposable
{
    IGenericRepository Repository();
    Task CommitAsync();
}

Class that implements IUnitOfWork, you can see that I use one DB Context per UnitOfWork instance.

C#
public class UnitOfWork : IUnitOfWork
{
    private readonly IDatabaseContext _databaseContext;

    public UnitOfWork(IDatabaseContext databaseContext)
    {
        _databaseContext = databaseContext;
    }

    public IGenericRepository Repository()
    {
        return new GenericRepository(_databaseContext);
    }

    public void Dispose()
    {
        _databaseContext.Dispose();
    }

    public Task CommitAsync()
    {
        return _databaseContext.SaveChangesAsync();
    }
}

For creating a new DB Context every time we need one, I recommend using a Factory pattern.

C#
public interface IUnitOfWorkFactory
{
    IUnitOfWork Create();
}

Implementing IUnitOfWorkFactory:

C#
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    public IUnitOfWork Create()
    {
        return new UnitOfWork(new DatabaseContext());
    }
}

Recommended, define interface for exposing properties from DB Context:

C#
public interface IDatabaseContext : IDisposable
{
    DbEntityEntry Entry(object entity);
    DbSet<TEntity> Set<TEntity>() where TEntity : class;
    DbSet Set(System.Type entityType);
    int SaveChanges();
    Task<int> SaveChangesAsync();
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
C#
public class DatabaseContext : DbContext, IDatabaseContext
{
    public DatabaseContext() : base("OrganizationDb")
    {
        Database.SetInitializer<DatabaseContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // Add your model configuration here
    }
}

Now, we are able to access database using our DB models for reading data.

Below, you'll find a service as example.

C#
public interface IPersonService
{
    Task<List<PersonDto>> GetPersonsAsync(string term);
}
C#
public class PersonService : IPersonService
{
    private readonly IUnitOfWorkFactory _unitOfWorkFactory;

    public PersonService(IUnitOfWorkFactory unitOfWorkFactory)
    {
        _unitOfWorkFactory = unitOfWorkFactory;
    }
    public async Task<List<PersonDto>> GetPersonsAsync(string term)
    {
        using (var unitOfWork = _unitOfWorkFactory.Create())
        {
            var persons = await unitOfWork.Repository().FindAsync<Person>
                          (x => x.Name.Contains(term));
            return persons.Select(person => new PersonDto(person.Name)).ToList();
        }
    }
}

This is just a simple example, you can always extend repository implementation, or configure your IoC to call Dispose after every request. Sky is the limit :)

Happy coding!

License

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


Written By
Software Developer (Senior) Trigonit AB
United States United States
Developer with broad skills set and architecture awareness with more than 20 years’ experience in design and development of client/server, web based and n-tier applications. Extensive experience on windows applications as well as web applications, windows services, web services and similar

Comments and Discussions

 
PraiseExcellent Pattern Pin
Narb M9-Jul-21 22:08
Narb M9-Jul-21 22:08 
QuestionSaveChangesAsync Pin
Agha.net26-Apr-18 15:38
Agha.net26-Apr-18 15:38 
AnswerRe: SaveChangesAsync Pin
Burim Hajrizaj27-Apr-18 2:53
Burim Hajrizaj27-Apr-18 2:53 
PraiseMy Vote of 5 Pin
Ross McNichol26-Apr-18 13:26
Ross McNichol26-Apr-18 13:26 
GeneralRe: My Vote of 5 Pin
Burim Hajrizaj27-Apr-18 2:52
Burim Hajrizaj27-Apr-18 2:52 
GeneralMy vote of 5 Pin
Hyland Computer Systems26-Apr-18 5:50
Hyland Computer Systems26-Apr-18 5:50 
GeneralRe: My vote of 5 Pin
Burim Hajrizaj27-Apr-18 2:55
Burim Hajrizaj27-Apr-18 2:55 
GeneralMy vote of 4 Pin
raddevus21-Apr-18 9:27
mvaraddevus21-Apr-18 9:27 
GeneralRe: My vote of 4 Pin
Burim Hajrizaj22-Apr-18 22:15
Burim Hajrizaj22-Apr-18 22:15 
GeneralRAR file corrupt Pin
Henk Meijerink25-Apr-18 16:03
Henk Meijerink25-Apr-18 16:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.