Skip to content

08 - Repository

Repository

It is not a good idea to access the database logic directly in the business logic. Tight coupling of the database logic in the business logic make applications tough to test and extend further. 

Problems:

  • Business logic is hard to unit test.
  • Business logic cannot be tested without the dependencies of external systems like database
  • Duplicate data access code throughout the business layer (Don’t Repeat Yourself in code – DRY principle)

Repository Pattern

Repository Pattern separates the data access logic and maps it to the entities in the business logic. It works with the domain entities and performs data access logic.

  • Dal intermingled within BLL

Bll

  • Repository (and mappers)

Repo

Encapsulate data processing

Capsulate data processing

Classical way:
SqlCommand("SELECT * FROM Customer where ID=" + cid).Execute
Create class instance
Copy result data into object


Repo: GetCustomerById(cid)

Repo user is not aware where and how is data stored and retrieved (sql, web-api, xml, csv files,....)

Repo types

How many and what types of repos to create?

  • One per class/table
  • Graph based
  • Write only repo, read only repo
  • One huge repo

No right answers, you must decide
Every project and team has its own customs and requirements

Repo Interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public interface IPersonRepository : IDisposable
{
    IQueryable<Person> All { get; }
    IQueryable<Person> AllIncluding(
    params Expression<Func<Person, object>>[] includeProperties);
    Person Find(int id);
    void InsertOrUpdate(Person person);
    void Delete(int id);
    void Save();
}

Next developer should only look at the repo interface. How repo operates - not important.
Interface is also needed for dependency injection

Repo code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class PersonRepository : IPersonRepository
{
    ContactContext context = new ContactContext();

    public IQueryable<Person> All {
        get { return context.People; }
    }

    public IQueryable<Person> AllIncluding(
    params Expression<Func<Person, object>>[] includeProperties) 
    {

        IQueryable<Person> query = context.People;
        foreach (var includeProperty in includeProperties) {
            query = query.Include(includeProperty);
        }
        return query;
    }

    public Person Find(int id){
        return context.People.Find(id);
    }

    public void InsertOrUpdate(Person person) {
        if (person.PersonID == default(int)) {
            // New entity
            context.People.Add(person);
        } else {
            // Existing entity
            context.Entry(person).State = EntityState.Modified;
        }
    }

    public void Delete(int id){
        var person = context.People.Find(id);
        context.People.Remove(person);
    }

    public void Save() {
        context.SaveChanges();
    }

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

Repo in console

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using ContactsLibrary;

namespace RepoConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var repo = new PersonRepository()) {
                repo.InsertOrUpdate(new Person { FirstName = "Juku", LastName = "Mänd" });
                repo.InsertOrUpdate(new Person { FirstName = "Malle", LastName = "Tamm" });
                repo.Save();
                foreach (var person in repo.All.ToList())
                {
                    Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
                }
            }

            Console.ReadLine();
        }
    }
}

Repo universal interafce

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 public interface IEntityRepository<T> : IDisposable
{
    IQueryable<T> All { get; }
    IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties);
    T Find(int id);
    void InsertOrUpdate(T entity);
    void Delete(int id);
    void Save();
}

public interface IPersonRepository : IEntityRepository<Person>
{

}

Repo problems

  • Object graphs
  • Disposed context
  • If possible, avoid graphs. Operate with single objects. (Common in web-api/mvc)

Repo recap

  • Repo – container, data storage engine capsulation
  • Typically, CRUD methods - to operate on some concrete class
  • Real data storage engine, with its implementation details is hidden from repo user
  • Possibility to easily replace storage mechanism within application

Repo generic interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public interface IRepository<TEntity> where TEntity : BaseEntity
{
    IEnumerable<TEntity> All();
    Task<IEnumerable<TEntity>> AllAsync();

    TEntity Find(params object[] id);
    Task<TEntity> FindAsync(params object[] id);

    void Add(TEntity entity);
    Task AddAsync(TEntity entity);

    TEntity Update(TEntity entity);

    void Remove(TEntity entity);

    void Remove(params object[] id);
}

Repo code 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class EFRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
{
    protected DbContext RepositoryDbContext;
    protected DbSet<TEntity> RepositoryDbSet;

    public EFRepository(IDataContext dataContext){
        RepositoryDbContext = dataContext as DbContext ?? throw new ArgumentNullException(nameof(dataContext));
        RepositoryDbSet = RepositoryDbContext.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> All() => RepositoryDbSet.ToList();
    public virtual async Task<IEnumerable<TEntity>> AllAsync() => await RepositoryDbSet.ToListAsync();
    public virtual TEntity Find(params object[] id) => RepositoryDbSet.Find(id);
    public virtual async Task<TEntity> FindAsync(params object[] id) => await RepositoryDbSet.FindAsync(id);
    public void Add(TEntity entity) => RepositoryDbSet.Add(entity);
    public virtual async Task AddAsync(TEntity entity) => await RepositoryDbSet.AddAsync(entity);
    public TEntity Update(TEntity entity) => RepositoryDbSet.Update(entity).Entity;
    public void Remove(TEntity entity) => RepositoryDbSet.Remove(entity);
    public void Remove(params object[] id){
        var entity = RepositoryDbSet.Find(id);
        Remove(entity);
    }
    
}

Repo code 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public interface IPersonRepository : IRepository<Person>
{
    // add custom methods
}

public class PersonEFRepository : EFRepository<Person>, IPersonRepository 
{
    public PersonEFRepository(IDataContext dataContext) : base(dataContext)
    {
    }

    // implement custom methods
}

UOW

The Unit of Work pattern is used to manage transactions and ensure that multiple operations are treated as a single logical unit. It provides a way to group database operations together, ensuring that they either succeed or fail as a whole. The key principle behind the Unit of Work pattern is to maintain data consistency and integrity by committing or rolling back changes in a coordinated manner.

UoW

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// <summary>
/// Unit Of Work pattern methods - atomic save of work done so far
/// </summary>
public interface IUnitOfWork
{
    Task<int> SaveChangesAsync();
    int SaveChanges();
}


public class EFBaseUOW<TDbContext> : IBaseUOW
    where TDbContext : DbContext
{
    protected readonly TDbContext UowDbContext;

    public EFBaseUOW(TDbContext dataContext)
    {
        UowDbContext = dataContext;
    }

    public virtual async Task<int> SaveChangesAsync()
    {
        return await UowDbContext.SaveChangesAsync();
    }
}

public interface IAppUOW : IBaseUOW
{
   // list your repositories here
   ISomeRepo SomeRepo { get; }
}


public class AppUOW : EFBaseUOW<ApplicationDbContext>, IAppUOW
{
    public AppUOW(ApplicationDbContext dataContext) : base(dataContext)
    {
    }

    private ISomeRepo? _someRepo;

    public ISomeRepo SomeRepo =>
        _someRepo ??= new SomeRepo(UowDbContext);
}