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
}