"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 (see Entities). Generally, a separated repository is used for each aggregate root or entity.
ABP can provide a default generic repository for each aggregate root or entity. You can inject IRepository<TEntity, TKey>
into your service and perform standard CRUD operations. Example usage:
public class PersonAppService : ApplicationService
{
private readonly IRepository<Person, Guid> _personRepository;
public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
public async Task Create(CreatePersonDto input)
{
var person = new Person { Name = input.Name, Age = input.Age };
await _personRepository.InsertAsync(person);
}
public List<PersonDto> GetList(string nameFilter)
{
var people = _personRepository
.Where(p => p.Name.Contains(nameFilter))
.ToList();
return people
.Select(p => new PersonDto {Id = p.Id, Name = p.Name, Age = p.Age})
.ToList();
}
}
In this example;
PersonAppService
simply injectsIRepository<Person, Guid>
in it's constructor.Create
method usesInsertAsync
to save a newly created entity.GetList
method uses the standard LINQWhere
andToList
methods to filter and get a list of people from the data source.
The example above uses hand-made mapping between entities and DTOs. See object to object mapping document for an automatic way of mapping.
Generic Repositories provides some standard CRUD features out of the box:
- Providers
Insert
method to save a new entity. - Providers
Update
andDelete
methods to update or delete an entity by entity object or it's id. - Provides
Delete
method to delete multiple entities by a filter. - Implements
IQueryable<TEntity>
, so you can use LINQ and extension methods likeFirstOrDefault
,Where
,OrderBy
,ToList
and so on... - Have sync and async versions for all methods.
If your entity does not has an Id primary key (it may have a composite primary key for instance) then you can not use the IRepository<TEntity, TKey>
defined above. In that case, you can inject and use IRepository<TEntity>
for your entity.
IRepository<TEntity>
has a few missing methods those normally works with theId
property of an entity. Because of the entity has noId
property in that case, these methods are not available. One example is theGet
method that gets an id and returns the entity with given id. However, you can still useIQueryable<TEntity>
features to query entities by standard LINQ methods.
Standard IRepository<TEntity, TKey>
interface extends standard IQueryable<TEntity>
and you can freely query using standard LINQ methods. However, some ORM providers or database systems may not support standard IQueryable
interface.
ABP provides IBasicRepository<TEntity, TPrimaryKey>
and IBasicRepository<TEntity>
interfaces to support such scenarios. You can extend these interfaces (and optionally derive from BasicRepositoryBase
) to create custom repositories for your entities.
Depending to IBasicRepository
but not depending to IRepository
has an advantage to make possible to work with all data sources even if they don't support IQueryable
. But major vendors, like Entity Framework, NHibernate or MongoDb already support IQueryable
.
So, working with IRepository
is the suggested way for typical applications. But reusable module developers may consider IBasicRepository
to support wide range of data sources.
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.
ABP does not force you to implement any interface or inherit from any base class for a repository. It can be just a simple POCO class. However, it's suggested to inherit existing repository interface and classes to make your work easier and get the standard methods out of the box.
First, define an interface in your domain layer:
public interface IPersonRepository : IRepository<Person, Guid>
{
Task<Person> FindByNameAsync(string name);
}
This interface extends IRepository<Person, Guid>
to take advantage of pre-built repository functionality.
A custom repository tightly depends on the data access tool you are using. In this example, we will use Entity Framework Core:
public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository
{
public PersonRepository(IDbContextProvider<TestAppDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Person> FindByNameAsync(string name)
{
return await DbContext.Set<Person>()
.Where(p => p.Name == name)
.FirstOrDefaultAsync();
}
}
You can directly access to the data access provider (DbContext
in this case) to perform operations. See entity framework integration document for more about custom repositories based on EF Core.