- Collection-like interface — A repository exposes familiar collection operations:
csharp
public interface IOrderRepository
{
Order GetById(int id);
IEnumerable<Order> GetByCustomer(int customerId);
IEnumerable<Order> FindBySpecification(ISpecification<Order> spec);
void Add(Order order);
void Remove(Order order);
}
The domain layer uses this interface without knowing whether data comes from SQL Server, MongoDB, an in-memory cache, or a file system.
- Encapsulating query logic — Repositories centralize query construction. Instead of scattering SQL or LINQ queries throughout service classes, the repository owns all queries for its aggregate. This means changing a query's implementation (adding an index, switching to a stored procedure, adding caching) happens in one place.
- Specification pattern integration — For complex queries, repositories can accept Specification objects that encapsulate query criteria:
csharp
var highValueOrders = orderRepo.FindBySpecification(
new OrderValueAbove(1000).And(new OrderCreatedAfter(DateTime.Today.AddDays(-30)))
);
This keeps query logic composable and testable without exposing the underlying query language.
- Repository vs. DAO (Data Access Object) — A DAO is a data-centric abstraction (CRUD operations on a table). A Repository is a domain-centric abstraction (operations on an aggregate). The distinction matters: a Repository returns fully hydrated domain objects ready for business logic; a DAO may return raw data or DTOs. In practice, many codebases label their DAOs as "repositories," missing the domain-oriented intent.
- Testing advantage — Because the repository is an interface, tests can substitute an in-memory implementation:
csharp
var fakeRepo = new InMemoryOrderRepository();
fakeRepo.Add(new Order(1, customerId: 5, total: 250m));
var service = new OrderService(fakeRepo);
// Test domain logic without any database
This enables fast, deterministic unit tests of domain logic.