"Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects" (Martin Fowler).

Repositories, in practice, are used to perform database operations for domain objects (Entity and Value types). Generally, a seperated repository is used for each Entity (or Aggregate Root).

IRepository interfaces

In ASP.NET Boilerplate, a repository class should implement IRepository interface. It's good to define an interface for each repository.

A repository definition for a Person entity is shown below:

public interface IPersonRepository : IRepository<Person>
{

}

IPersonRepository extends IRepository<TEntity>. It's used to define entities which has a primary key type of int (Int32). If your entity's primary key is not int, you can extend IRepository<TEntity, TPrimaryKey> interface as shown below:

public interface IPersonRepository : IRepository<Person, long>
{

}

IRepostory defines most common methods for a repository class such as select, insert, update and delete methods (CRUD operations). Most of time these methods will be enough for simple entities. If these methods are enough for an entity, you don't have to create repository interface/class for this entitiy. See implementation section for details.

Querying

IRepository defines common methods to retrive entities from database.

Getting single entity
TEntity Get(TPrimaryKey id);
Task<TEntity> GetAsync(TPrimaryKey id);
TEntity Single(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate);
TEntity FirstOrDefault(TPrimaryKey id);
Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id);
TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
TEntity Load(TPrimaryKey id);

Get method is used to get an Entity with given primary key (Id). It throws exception if there is no entity in database with given Id. Single method is similar to Get but takes an expression rather than an Id. So, you can write a lambda expression to get an Entity. Example usages:

var person = _personRepository.Get(42);
var person = _personRepository.Single(p => p.Name == "Halil İbrahim Kalkan");

Notice that Single method throws exception if there is no entity with given conditions or there are more than one entity.

FirstOrDefault is similar but returns null (instead of throwing exception) if there is no entity with given Id or expression. Returns first found entity if there are more than one entity for given conditions.

Load does not retrieves entity from database but creates a proxy object for lazy loading. If you only use Id property, Entity is not actually retrieved. It's retrieved from database only if you access to other properties of entity. This can be used instead of Get, for performance reasons. It's implemented in NHibernate. If ORM provider does not implements it, Load method works as identical as Get method.

Some methods have async versions to be used in async programming model (See 'about async methods' section).

Getting a list of entities
List<TEntity> GetAllList();
Task<List<TEntity>> GetAllListAsync();
List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);
Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);
IQueryable<TEntity> GetAll();

GetAllList is used to retrieve all entities from database. Overload of it can be used to filter entities. Examples:

var allPeople = _personRepository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll returns IQueryable<T>. So, you can add Linq methods after it. Examples:

//Example 1
var query = from person in _personRepository.GetAll()
            where person.IsActive
            orderby person.Name
            select person;
var people = query.ToList();

//Example 2:
List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();

With using GetAll, almost all queries can be written in Linq. Even it can be used in a join expression.

About IQueryable<T>

When you call GetAll() out of a repository method, there must be an open database connection. This is because of deferred execution of IQueryable<T>. It does not perform database query unless you call ToList() method or use the IQueryable<T> in a foreach loop (or somehow access to queried items). So, when you call ToList() method, database connection must be alive. This can be achived by marking caller method by UnitOfWork attribute of ASP.NET Boilerplate. Note that Application Service methods are already UnitOfWork as default, so, GetAll() will work without adding UnitOfWork attribute for application service methods.

Some methods have async versions to be used in async programming model (See 'about async methods' section).

Custom return value

There is also an additional method to provide power of IQueryable without using UnitOfWork attribute in the calling method.

T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);

Query method accepts a lambda (or method) that recieves IQueryable<T> and returns any type of object. Example:

var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());

Since given lamda (or method) is executed in the repository method, it's executed when database connection is open. You can return a list of entities, a single entity, a projection or something else that executes the query.

Insert

IRepository interface defines simple methods to insert an entity to database:

TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
TPrimaryKey InsertAndGetId(TEntity entity);
Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity);
TEntity InsertOrUpdate(TEntity entity);
Task<TEntity> InsertOrUpdateAsync(TEntity entity);
TPrimaryKey InsertOrUpdateAndGetId(TEntity entity);
Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);

Insert method simply inserts new entity to database and returns the same inserted entity. InsertAndGetId method returns Id of new inserted entity. This is useful if Id is auto increment and you need Id of the new inserted entity. InsertOrUpdate inserts or updated given entity by checking it's Id's value. Lastly, InsertOrUpdateAndGetId returns Id of the entity after inserting or updating.

All methods have async versions to be used in async programming model (See 'about async methods' section).

Update

IRepository defines a method to update an existing entity in the database. It gets the entity to be updated and returns the same entity object.

TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);

Delete

IRepository defines methods to delete an existing entity from the database

void Delete(TEntity entity);
Task DeleteAsync(TEntity entity);
void Delete(TPrimaryKey id);
Task DeleteAsync(TPrimaryKey id);
void Delete(Expression<Func<TEntity, bool>> predicate);
Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);

First method accepts an existing entity, second one accepts Id of the entity to delete.

The last one accepts a condition to delete all entities fit to given condition. Notice that all entities matches given predicate will be retrived from database and then deleted. So, use it carefully, it may cause performance problems if there are too many entities with given condition.

All methods have async versions to be used in async programming model (See 'about async methods' section).

Others

IRepository also provides methods to get count of entities in the table.

int Count();
Task<int> CountAsync();
int Count(Expression<Func<TEntity, bool>> predicate);
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);
long LongCount();
Task<long> LongCountAsync();
long LongCount(Expression<Func<TEntity, bool>> predicate);
Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate);

All methods have async versions to be used in async programming model (See 'about async methods' section).

About Async methods

ASP.NET Boilerplate supports async programming model. So, repository methods has Async versions. Here, a sample application service method that uses async model:

public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public async Task<GetPeopleOutput> GetAllPeople()
    {
        var people = await _personRepository.GetAllListAsync();
            
        return new GetPeopleOutput
        {
            People = Mapper.Map<List<PersonDto>>(people)
        };
    }
}

GetAllPeople method is async and uses GetAllListAsync with await keyword.

Async may not be supported by all ORM frameworks. It's supported by EntityFramework. If not supported, Async repository methods works synchronously. Also, for example, InsertAsync works same as Insert in EntityFramework since EF does not write new entities to database until unit of work completes (DbContext.SaveChanges).

Repository Implementation

ASP.NET Boilerplate is designed to be independent from a particular ORM (Object/Relational Mapping) framework or another technique to access to database. By implementing IRepository interface, any framework can be used.

Repositories are implemented in NHibernate and EntityFramework as out-of-the-box. See documents to implement repositories in ASP.NET Boilerplate in these frameworks:

When you use NHibernate or EntityFramework, you don't have to create repository classes for your entities if standard methods are enough for you. You can directly inject IRepository<TEntity> (or IRepository<TEntity, TPrimaryKey>). An example application service uses a repository to insert an entity to database:

public class PersonAppService : IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
        
        _personRepository.Insert(person);
    }
}

PersonAppService contructor-injects IRepository<Person> and uses the Insert method. In this way, you should only create a repository class for an entity when you need to create a custom repository method for that entity.

Managing database connection

A database connection is not opened or closed in a repository method. Connection management is made automatically by ASP.NET Boilerplate.

A database connection is opened and a transaction begins while entering a repository method automatically. When the method ends and returns, all changes are saved, transaction is commited and database connection is closed automatically by ASP.NET Boilerplate. If your repository method throws any type of Exception, the transaction is automatically rolled back and database connection is closed. This is true for all public methods of classes those implement IRepository interface.

If a repository method calls to another repository method (even a method of different repository) they share same connection and transaction. Connection is managed (opened/closed) by the first method that enters a repository. For more information on database connection management, see UnitOfWork documentation.

Lifecycle of a repository

All repository instances are Transient. It means, they are instantiated per usage. ASP.NET Boilerplate strongly uses Dependency Injection technique. When a repository class needed to be injected, a new instance of the class is created automatically by dependency injection container. See Dependency Injection documentation for more information.

Repository best practices