Hello,
I am currently developing an application using ABP Framework with Hexagonal (Ports & Adapters) architectural pattern. In our project, we are implementing the CQRS (Command Query Responsibility Segregation) pattern, utilizing separate DbContexts and their respective database connections for Command and Query operations.
Issue Encountered: I'm experiencing difficulties integrating ABP Framework's Unit of Work mechanism into our current architecture. Specifically, I want to implement transaction management at the controller level.
Expected Behavior:
- Initiation of transaction scope at the controller level
- Automatic rollback of all database operations (performed across multiple DbContexts) in case of any exception during the process
- Commit of all changes when the transaction is successful
Due to limitations in our current infrastructure, I am unable to meet these requirements and need assistance on how to properly implement ABP Framework's Unit of Work pattern.
Problem Description: In our project using Hexagonal architecture and CQRS pattern, we were experiencing difficulties in managing transactions across multiple DbContexts. Specifically, we needed to initiate transaction scope at the controller level and roll back all operations in case of exceptions.
Implementation Details:
DbContext Configuration: Created two separate DbContexts for Command and Query operations Both DbContexts inherit from BaseDbContext (AbpDbContext), IBaseDbContext, and IAuditLoggingDbContext Configured OnConfiguring method to use the appropriate connection string based on request type (Command/Query) Use Case Layer Implementation: Created separate Use Case classes for each entity (Example: CreateEntityNameUseCase) Injected relevant repositories into Use Case classes using Dependency Injection Configured each Use Case to call its corresponding repository methods Repository Layer Implementation: Created separate Command and Query repositories for each entity Implemented repository classes with the following structure: EntityNameDbContextNameRepository : EfCoreDbContextNameRepository<EntityName, EntityId>, IEntityNameDbContextNameRepository Designed repository methods to operate directly on the respective DbContext Example Create method: public async Task CreateEntityNameAsync(EntityName entity) { xxCommandDbContext.Set().Add(entity); await xxCommandDbContext.SaveChangesAsync(); return entity; }
Key Points: Implemented CQRS pattern using separate DbContexts for Command and Query operations Maintained clear boundaries between layers following Hexagonal architecture principles Ensured consistent repository implementation across all entities Achieved loose coupling between components using Dependency Injection Adopted standardized approach to entity operations across the application Benefits of this Implementation:
Clear separation of read and write operations Scalable and maintainable codebase Consistent data access across the application Easy integration with ABP Framework's built-in features
Technologies Used:
- ABP Framework
- .NET Core
- Entity Framework Core
- CQRS Pattern
- Hexagonal Architecture
Code views of some files are as in the links below;
https://prnt.sc/J-lLInOw3CTP https://prnt.sc/wC26zqblG6LN https://prnt.sc/iryqe56p7Rp0 https://prnt.sc/g_hRBHesKJql https://prnt.sc/yLowmTvtAkHN https://prnt.sc/KEcCC2IWOZg4 https://prnt.sc/V9CTqawtcAL2 https://prnt.sc/YzLbUE6lMfmH
- ABP Framework version: v9.0.2
- UI Type: -
- Database System: EF Core (PostgreSQL)
- Tiered (for MVC) or Auth Server Separated (for Angular): yes
- Exception message and full stack trace:
- Steps to reproduce the issue:
5 Answer(s)
-
0
hi
EF Core & ABP support this case, You can begin a new uow in a controller action.
Once a new UOW started, it creates an ambient scope that is participated by all the database operations performed in the current scope and considered as a single transaction boundary. The operations are committed (on success) or rolled back (on exception) all together.
- Initiation of transaction scope at the controller level
- Automatic rollback of all database operations (performed across multiple DbContexts) in case of any exception during the process
- Commit of all changes when the transaction is successful
[HttpPost] [Route("add-input-output-types" )] public async Task<IActionResult> CreateInputOutputType([FromBody] CreateInputOutputTypeCommand dto) { using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true)) { try { // You application service call. await uow.CompleteAsync(); } catch (Exception e) { Logger.LogException(e); await uow.RollbackAsync(); } } }
-
0
I encountered the following issues during the modifications made in the CreateInputOutputTypeAsync method:
Transaction Management Issue:
- When an error occurs during the repository.create operation in CreateInputOutputTypeAsync method
- The system fails to perform the rollback operation
- As a result, data continues to be saved to the database even in error scenarios https://prnt.sc/scsx8SSwwZCC
Usage Scenario Incompatibility:
- The proposed method is no longer working compatibly with our current usage scenarios
- This causes the method to malfunction across three different usage scenarios https://prnt.sc/Bl5txLcWPuE1
I tried an alternative approach to solve these issues, but the fundamental problem with transaction management still persists. I would appreciate your thoughts and suggestions on this matter.
-
0
-
0
hello, 3 different uses were actually because we couldn't use that. As can be seen in the image below, the parameters expected by the begin method in version 9.0.2 do not match the ones in the image. https://prnt.sc/3ADJBxdAvvWY https://prnt.sc/3ADJBxdAvvWY
-
0
hi
Its extension method of
IUnitOfWorkManager
See https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs#L9-L24