Data Persistence
1. Introduction
This project adopts Domain-Driven Design (DDD) architecture. The data persistence mechanism uses Entity Framework Core (EF Core) as the core, combined with Dapper to implement high-performance queries, supporting MySQL, PostgreSQL, and SQL Server databases. The Repository Pattern encapsulates data access logic, ensuring decoupling between the domain layer and the infrastructure layer. This document provides a comprehensive analysis of the implementation details of this mechanism.
2. Repository Pattern and Interface Contract
The project adopts the standard Repository Pattern, defining data access contracts via the IMyEntityNameRepository interface, with EfCoreMyEntityNameRepository providing the EF Core implementation. The interface defines business methods such as entity query, counting, sorting acquisition, and name uniqueness validation, ensuring domain services operate on data through abstract interfaces without relying on concrete implementations.
IMyEntityNameRepository inherits from IBasicRepository<MyEntityName, Guid>, extending business-specific methods like FindByNameAsync, NameExistAsync, GetMaxSortAsync, reflecting the "Rich Repository" concept in Domain-Driven Design.
Code Paths
IMyEntityNameRepositorydefines all query contracts.EfCoreMyEntityNameRepositoryimplements these contracts.
3. EF Core Configuration and Entity Mapping
EF Core entity mapping is centrally configured via the extension method CMSPluginEfCoreExtensions.MyEntityName.cs. The ConfigureMyEntityName method defines complete database mapping rules for the MyEntityName entity on the ModelBuilder.
Table Name and Schema Mapping
When mapping entities to database tables, prefixes and schemas defined in CMSPluginDbProperties are used:
b.ToTable((CMSPluginDbProperties.DbTablePrefix + "_MyEntityNames").ToLower(), CMSPluginDbProperties.DbSchema)
The default table name is scms_myentitynames, located under the default schema, and supports modification via configuration.
Property Configuration
Key property configurations are as follows:
Code: Maximum length defined byMyEntityNameConsts.MaxCodeLength, required.Name: Maximum length defined byMyEntityNameConsts.MaxNameLength, required.Sort: Integer type, used for sorting.Remark: Optional, maximum length defined byMyEntityNameConsts.MaxRemarkLength.IsDisabled: Nullable boolean, indicating whether it is disabled.
Index Configuration
A unique index is created for the Name field to ensure name uniqueness constraints:
b.HasIndex(u => u.Name);
Relationship Definition
Currently, the MyEntityName entity does not define navigation properties. As an Aggregate Root, its internal relationships are maintained by domain logic, complying with DDD principles.
4. Database Context Management
The project defines the ICMSPluginDbContext interface as the database context contract, inheriting from IEfCoreDbContext. Concrete context implementations (such as CMSPluginDbContext) are provided in each database provider project (MySQL, PostgreSQL, SqlServer).
The OnModelCreating method calls builder.ConfigureMyEntityName() to register entity configuration and applies object extensions via builder.TryConfigureObjectExtensions<CMSPluginDbContext>(), supporting modular configuration.
The database connection string name is specified as MyPluginName via the [ConnectionStringName] attribute, defined in CMSPluginDbProperties.ConnectionStringName.
5. Multi-Database Support Implementation Principle
The project implements multi-database support by creating independent projects for different database providers (CMS.Plugin.MyPluginName.MySQL, CMS.Plugin.MyPluginName.PostgreSql, CMS.Plugin.MyPluginName.SqlServer). Each project contains a CMSPluginDbContext implementation inheriting from AbpDbContext.
At runtime, DbContextOptions<CMSPluginDbContext> will use the corresponding database driver based on the database provider registered in the dependency injection container (such as UseMySql, UseNpgsql, UseSqlServer). Since all implementations share the same ICMSPluginDbContext interface and EF Core configuration, upper-layer code is completely transparent, achieving database agnosticism.
6. Entity Extensibility Design
The project implements entity extensibility via the CMSPluginEfCoreEntityExtensionMappings class. This static class calls the Configure() method for initialization during the PreConfigureServices phase of CMSPluginEntityFrameworkCoreModule.
OneTimeRunner ensures configuration executes only once. Through ObjectExtensionManager, other modules can add extra EF Core mapping properties (such as new fields) to entities like MyEntityName without modifying core code, achieving good modularity and extensibility.
7. Dapper Usage Scenarios and Performance Optimization
The project also integrates Dapper, implemented via the ICMSPluginDapperRepository interface and CMSPluginDapperRepository. CMSPluginDapperRepository inherits from DapperRepository<ICMSPluginDbContext>, sharing the database connection and transaction with EF Core.
Usage Scenarios
Dapper is suitable for the following scenarios:
- Complex Queries: Scenarios involving multi-table joins, subqueries, window functions, etc., where EF Core generated SQL is less efficient.
- High-Performance Reads: Read-only queries requiring extremely high performance, where Dapper's mapping overhead is much lower than EF Core.
- Stored Procedure Calls: Directly calling database stored procedures.
- Batch Operations: Using
SqlBulkCopyor Dapper extensions for efficient batch insert/update.
Complementary Relationship with EF Core
- EF Core: Used for regular CRUD operations, change tracking, transaction management, suitable for write operations and simple queries.
- Dapper: Used for complex, high-performance read operations, serving as a supplement to EF Core.
Performance Optimization Suggestions
- Read-Write Separation: Use EF Core repositories for write operations and prioritize Dapper for read operations.
- **Avoid SELECT ***: Dapper queries should explicitly specify required fields.
- Parameterized Queries: Always use parameterized queries to prevent SQL injection.
- Connection Reuse: Obtain connections via
GetDbConnectionAsyncto ensure consistency within EF Core transactions. - Asynchronous Programming: Use asynchronous methods like
QueryAsyncto avoid blocking. - Caching Strategy: Combine with Redis or other caches to cache Dapper query results for data that does not change frequently.
8. Conclusion
This project builds a complete, flexible, and high-performance data persistence mechanism. It achieves decoupling of the domain layer and data access through the Repository Pattern, utilizes EF Core to provide powerful ORM features and entity mapping capabilities, and supports multiple databases. Through independent Dapper integration, it solves complex query and performance bottleneck issues. The entity extension mechanism ensures system extensibility. The overall design follows DDD principles, providing a solid data foundation for business development.