Repositive is a .NET Standard library that provides interfaces and implementations for setting up data access repositories following the repository pattern.
It provides many advanced methods for creating, reading, updating and deleting entities with great flexibility.
It also provides methods for controlling query pagination, change tracking, including related entities in queries, explicitly loading related entities and advanced querying.
Repositive also supports the unit of work pattern, so you can synchronize the commit operation between multiple repositories in a single operation.
The contracts are splitted in module-like interfaces, allowing to build concise repository classes that expose only the necessary methods.
All methods have it's asynchronous counterparts.
The objective of this library is to provide ready-to-use repository-pattern interfaces and implementations:
Example:
Define an interface representing your repository and let Repositive's interfaces provide all the methods:
using Repositive.Abstractions;
// ...
public interface ICarRepository : IRepository<Car>, IRelatedLoadableRepository<Car>, ISaveableRepository
{
// IRepository<T> provides basic CRUD methods.
// IRelatedLoadableRepository<T> provides methods for explicitly loading related entities.
// ISaveableRepository provides methods for saving changes directly from the repository (commit).
}
Then define an implementation for that interface and let Repositive do the heavy lifiting:
using Repositive.EntityFrameworkCore;
// ...
public class CarRepository : Repository<Car, MyDbContext>, ICarRepository
{
// Repository<TEntity, TContext> implements all repository interfaces.
public CarRepository(MyDbContext context) : base(context)
{
}
}
Now it's ready to use:
public class CarService : ICarService
{
private readonly ICarRepository _carRepository;
public CarService(ICarRepository carRepository)
{
_carRepository = carRepository;
}
public void AddCar(Car car)
{
_carRepository.Add(car);
_carRepository.Commit(); // Commit also returns the number of affected entries
}
public (IEnumerable<Car> Cars, int TotalCount) GetCars(int page, int pageSize)
{
// Returns a tuple with the paginated collection of cars and the total count of entities in the database.
// Sorts by the car's model year and includes the Manufacturer navigation property in the query.
var entitiesToSkip = (page - 1) * pageSize;
return _carRepository.Get(entitiesToSkip, pageSize, t => t.OrderBy(x => x.ModelYear), QueryTracking.NoTracking, t => t.Manufacturer);
}
public void ProcessServiceOrder(Car car)
{
// Explicitly loads 'car.Owner', 'car.Owner.Address' and
// 'car.Owner.PaymentInfo.BankCards' navigation properties.
_carRepository.LoadRelated(car, t => t.Owner, t => t.Address, t => t.PaymentInfo.BankCards);
// Explicitly loads all closed service orders into 'car.ServiceOrders' navigation property.
_carRepository.LoadRelatedCollection(car, t => t.ServiceOrders, t => t.Status == ServiceOrderStatus.Closed);
// ...
}
}
- List of ORMs supported by Repositive.
ORM | Version | Namespace/Package Name |
---|---|---|
Entity Framework Core | 3.1.x | Repositive.EntityFrameworkCore |
-
All interfaces are in the
Repositive.Abstractions
namespace. -
All methods' asynchronous counterparts names have the
Async
suffix. Ex.:GetSingleAsync
. -
The
Repositive.Abstractions
namespace/package only contains interfaces and abstractions, with no implementation or reference to any ORM or data provider whatsoever. -
The interfaces' modular design allows exposing concise repositories with only needed functionality.
- Defines a repository contract for creating, reading, updating and deleting instances of entities of type
T
. - It's a combination of the
ICreatableRepository<T>
,IReadableRepository<T>
,IUpdateableRepository<T>
andIDeletableRepository<T>
interfaces. - It does not provide methods for committing changes to the database. For that use the
ISaveableRepository
orIUnitOfWork
interfaces.
- Defines a repository contract for creating entities of type
T
. - Methods:
Add
,AddRange
.
- Defines a repository contract for reading entities of type
T
. - Methods:
Any
,Average
,Count
,Find
,Get
,GetSingle
,Max
,Min
.
- Defines a repository contract for updating entities of type
T
. - Methods:
Update
,UpdateRange
.
- Defines a repository contract for deleting entities of type
T
. - Methods:
Delete
,DeleteRange
.
- Defines a repository contract for saving changes made to a repository.
- Methods:
Commit
. - Remarks:
- Do not expose this interface in repositories that operate with unit of work, as the commit responsibility belongs to the
IUnitOfWork
interface. - Caution is advised when using this interface, as the database context may be shared between different repositories, changes from other repositories may be committed when saving changes from a specific repository, leading to unexpected and/or unintended behavior.
- Do not expose this interface in repositories that operate with unit of work, as the commit responsibility belongs to the
- Defines a repository contract for querying instances of type
T
using theIQueryable<T>
interface and projecting the results toTResult
type. - The query results are projected to the type defined by
TResult
. - Methods:
Query<TResult>
,QuerySingle<TResult>
- Defines a repository contract for explicitly loading related entities referenced by navigation properties in instances of
T
. - The navigation property type is defined by
TProperty
. - Methods:
LoadRelated<TProperty>
,LoadRelatedCollection<TProperty>
.
- Defines a contract for coordinately committing changes between repositories (unit of work pattern).
- Methods:
Commit
. - Events:
Committing
,Committed
. - Remarks:
- As the objective of this contract is to coordinate commit operations between different repositories, no commit operation should be made directly from a repository that uses the unit of work pattern, but rather use the methods provided by this interface for committing.
- Avoid making the
ISaveableRepository
contract available in repositories that use unit of work, as all commit operations should be made through this interface.
This package provides repository implementations for the Microsoft Entity Framework Core ORM.
- Provides a repository pattern implementation for querying and saving instances of
TEntity
withMicrosoft.EntityFrameworkCore
as the ORM. - The database context type is defined by
TContext
. It must derive from or be ofMicrosoft.EntityFramework.DbContext
type. - Implements
IRepository<TEntity>
,IQueryableRepository<TEntity>
,IRelatedLoadableRepository<TEntity>
,ISaveableRepository
interfaces. - Constructor:
(TContext)
or(IUnitOfWork)
.
- Implements the unit of work pattern and provides commit coordination between repositories.
- The database context type is defined by
TContext
. It must derive from or be ofMicrosoft.EntityFramework.DbContext
type. - Implements the
IUnitOfWork
interface. - Centralizes the
Micosoft.EntityFrameworkCore.DbContext
instance and shares it between repositories, so changes from multiple repositories are contained in a single database context instance. - Constructor:
(TContext)
.
nuget Install-Package Repositive.Abstractions
dotnet add package Repositive.Abstractions
2. Define your repository contracts, inheriting the desired repository interfaces from the Repositive.Abstractions
package
using Repositive.Abstractions;
// ...
public interface IFooRepository : IRepository<Foo>, ISaveableRepository
{
// Optional: any specific user defined method can go in here. Ex.: "GetFoosByDate(DateTime date)"
}
public interface IBarRepository : IReadableRepository<Bar>, IRelatedLoadableRepository<Bar>
{
// Optional: any specific user defined method can go in here. Ex.: "GetBarsByDate(DateTime date)"
}
nuget Install-Package Repositive.EntityFrameworkCore
dotnet add package Repositive.EntityFrameworkCore
4. Define the contracts' implementations, inheriting the Repository<T, C>
class from the implementation package.
using Repositive.EntityFrameworkCore;
// ...
public class FooRepository : Repository<Foo, MyDbContext>, IFooRepository
{
public FooRepository(MyDbContext context) : base(context)
{
}
// Implement here any user-defined methods in IFooRepository.
}
public class BarRepository : Repository<Bar, MyDbContext>, IBarRepository
{
public BarRepository(MyDbContext context) : base(context)
{
}
// Implement here any user-defined methods in IBarRepository.
}
services.AddScoped<IFooRepository, FooRepository>();
services.AddScoped<IBarRepository, BarRepository>();
public class BazService : IBazService
{
private readonly IFooRepository _fooRepository;
private readonly IBarRepository _barRepository;
public BazService(IFooRepository fooRepository, IBarRepository barRepository)
{
_fooRepository = fooRepository;
_barRepository = barRepository
}
public void Baz()
{
_fooRepository.Add(foo);
_fooRepository.Commit();
var bar = _barRepository.GetSingle(t => t.Name == "John Doe", QueryTracking.TrackAll, includes: t => t.Foo);
//...
_barRepository.LoadRelated(bar, t => t.Qur);
// ...
}
}
-
Repositive provides unit of work support for coordinating commit operations between multiple repositories in a single operation.
-
The advantage of this approach is data integrity: by using unit of work pattern, changes made to multiple repositories are committed in a single transaction, meaning that if something goes wrong in any repository during the operation, the whole transaction is aborted, ensuring data integrity.
1. Define your repository contracts following the basic setup.
IMPORTANT: Do not inherit the ISaveableRepository
interface when using unit of work, repositories using UoW should not expose commit methods.
2. In the repositories implementations, pass a IUnitOfWork
instance to the class constructor and base constructor.
using Repositive.Abstractions;
using Repositive.EntityFrameworkCore;
// ...
public class FooRepository : Repository<Foo, MyDbContext>, IFooRepository
{
public FooRepository(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
}
public class BarRepository : Repository<Bar, MyDbContext>, IBarRepository
{
public BarRepository(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
}
using Repositive.Abstractions;
using Repositive.EntityFrameworkCore;
// ...
services.AddDbContext<MyDbContext>();
services.AddScoped<IUnitOfWork, UnitOfWork<MyDbContext>>();
services.AddScoped<IFooRepository, FooRepository>();
services.AddScoped<IBarRepository, BarRepository>();
using Repositive.Abstractions;
// ...
public class BazService : IBazService
{
private readonly IFooRepository _fooRepository;
private readonly IBarRepository _barRepository;
private readonly IUnitOfWork _unitOfWork;
public BazService(IFooRepository fooRepository, IBarRepository barRepository, IUnitOfWork unitOfWork)
{
_fooRepository = fooRepository;
_barRepository = barRepository
_unitOfWork = unitOfWork;
}
public void Process()
{
_fooRepository.Add(foo);
// ...
_barRepository.DeleteRange(bars);
// ...
// Changes made to foo and bar repositories are committed in a single operation (transaction).
// If anything goes wrong, no changes are committed at all.
_unitOfWork.Commit();
}
}