Skip to main content
Version: Next

Repository Pattern

1. Introduction​

The Repository Pattern serves as the core design pattern for the data access layer in this project, undertaking the important responsibility of isolating domain logic from persistence details. By defining clear interface contracts and concrete EF Core implementations, the system achieves the architectural goals of high cohesion and low coupling. This article will deeply analyze the IMyEntityNameRepository interface and its concrete implementation in EfCoreMyEntityNameRepository, revealing how it supports flexible queries, transaction management, and collaboration mechanisms with upper-layer application services.

2. Repository Pattern Architecture Design​

3. IMyEntityNameRepository Interface Definition​

The IMyEntityNameRepository interface inherits from IBasicRepository<MyEntityName, Guid>, extending dedicated query contracts for business entities. This interface defines multiple asynchronous methods, reflecting best practices for non-blocking I/O in modern .NET applications.

The interface mainly includes the following methods:

  • FindByNameAsync: Finds a single entity by name.
  • NameExistAsync: Checks if a name already exists (used for uniqueness validation).
  • GetMaxSortAsync: Gets the current maximum sort value, used for order assignment when inserting new records.
  • GetListAsync: Supports composite queries with pagination, sorting, filtering, and specification patterns.
  • GetCountAsync: Gets the total number of records satisfying the conditions.

These methods collectively form the data access contract for the MyEntityName entity, providing a unified and semantically clear API for upper-layer services.

4. EfCoreMyEntityNameRepository Implementation Analysis​

EfCoreMyEntityNameRepository is the concrete implementation class of IMyEntityNameRepository, inheriting from EfCoreRepository<ICMSPluginDbContext, MyEntityName, Guid>, utilizing the foundational capabilities provided by the ABP framework to complete EF Core integration.

Key implementation details include:

  1. Constructor Injection: Acquires the database context via IDbContextProvider<ICMSPluginDbContext>, supporting multi-database adaptation.
  2. Asynchronous Query Implementation: All methods adopt the async/await pattern to ensure efficient thread utilization.
  3. Dynamic Sorting Support: Uses the System.Linq.Dynamic.Core package to implement string-based sort expression parsing.
  4. Pagination Processing: Implements skip and limit logic via the PageBy extension method.
  5. Specification Pattern Integration: Combines with Volo.Abp.Specifications to implement reusable query condition encapsulation.

For example, in the GetListAsync method, both Specification objects and string filter conditions are supported simultaneously, demonstrating flexible query combination capabilities.

5. Isolation Mechanism Between Repository Layer and Domain Logic​

The core value of the Repository Pattern lies in decoupling the domain model from data access technologies. In this project, this isolation is achieved through the following ways:

  • Interface Abstraction: IMyEntityNameRepository defines the data operation contract required by the domain layer without exposing any EF Core or SQL-related details.
  • Dependency Inversion: Application services (like MyEntityNameAppService) depend only on the repository interface, not the concrete implementation, facilitating unit testing and implementation replacement.
  • Entity Protection: The property setters of the MyEntityName entity are protected, ensuring state changes can only be performed through domain methods (like Update), preventing direct external modification.
  • Change Tracking Transparency: EF Core automatically manages entity state; the repository layer does not need to explicitly call SaveChanges, as it is unifiedly submitted by the ABP framework's UOW (Unit of Work).

This design allows domain logic to focus on business rules, while data persistence details are encapsulated in the infrastructure layer.

6. Query Contract Design Intent and Usage Scenarios​

The design intent of each query method is as follows:

Method NameDesign IntentUsage Scenario
FindByNameAsyncQuickly locate a uniquely named entityLogin verification, configuration item lookup
NameExistAsyncEfficiently check name uniquenessDuplicate validation during create/update
GetMaxSortAsyncGet the current maximum sort valueAutomatic sorting when inserting new records
GetListAsyncPagination query supporting complex conditionsList display, export, filtering
GetCountAsyncIndependently get total count for performance optimizationPagination navigation, statistics display

Among them, GetListAsync and GetCountAsync are usually used together to implement efficient pagination display. Separating query and counting operations avoids performance issues associated with loading all data at once.

7. Custom Query Construction and EF Core IQueryable Integration​

The repository implementation fully utilizes the deferred execution feature of IQueryable<T>, supporting dynamic query construction:

  1. Specification Pattern Application: MyEntityNameSpecification encapsulates business rules into reusable expression trees, injecting query conditions via .Where(specification.ToExpression()) in GetListAsync.
  2. Condition Concatenation: Uses the .WhereIf() method to implement conditional query fragment addition, such as adding name fuzzy matching only when filter is not empty.
  3. Dynamic Sorting: Leverages the OrderBy(string) method of System.Linq.Dynamic.Core to convert string sort parameters into expressions.
  4. Detail Inclusion Control: Controls whether to load associated data via IncludeDetails(includeDetails) to optimize query performance.

This approach allows diverse query requirements to be met through parameter combinations without modifying repository code.

8. Synchronous vs. Asynchronous Method Selection Strategy​

All repository methods in this project use asynchronous implementation, following these strategies:

  • Default Async Priority: All I/O operations (database query, insert, update, delete) use async/await to avoid thread blocking.
  • Return Task instead of Task<T>: For operations with no return value (like delete), return Task instead of void to ensure the caller can await correctly.
  • Cancellation Token Support: Key methods accept a CancellationToken parameter, supporting request cancellation and timeout control.
  • Avoid Synchronous Waiting: Always use await instead of .Result or .Wait() in application services to prevent deadlocks.

The ABP framework's UOW mechanism ensures that transactions are correctly propagated and committed even in asynchronous call chains.

9. Exception Handling and Logging Best Practices​

The repository layer itself does not directly handle exceptions but ensures reliability through the following mechanisms:

  • Exception Transparent Transmission: Database exceptions (like constraint conflicts, connection failures) are thrown by EF Core and passed up to the upper layer, unifiedly handled by application services or controllers.
  • Business Exception Encapsulation: Use UserFriendlyException in application services to provide user-readable error information, such as duplicate name prompts.
  • Automatic Logging: The ABP framework automatically records repository call logs, including execution time, SQL statements (in development environment), etc.
  • Validation Pre-check: Perform input validation (like Check.NotNull) before calling the repository to reduce invalid database access.

For example, in CreateAsync, checking if the name exists before executing insertion avoids generating database unique index conflict exceptions.

10. Complete Call Chain Diagram​