How to check if a DbContext transaction has a transaction?

Background: I have a WCF service with SimpleInjector as an IoC that creates an instance of DbContext for a WCF request.

The backend itself is CQRS. CommandHandlers have many decorators (validation, authorization, logging, some general rules for different groups of handlers, etc.), and one of them is transaction Decorator:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly ICommandHandler<TCommand> _handler; private readonly IMyDbContext _context; private readonly IPrincipal _principal; public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> handler, IMyDbContext context, IPrincipal principal) { _handler = handler; _context = context; _principal = principal; } void ICommandHandler<TCommand>.Handle(TCommand command) { using (var transaction = _context.Database.BeginTransaction()) { try { var user = _context.User.Single(x => x.LoginName == _principal.Identity.Name); _handler.Handle(command); _context.SaveChangesWithinExplicitTransaction(user); transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); throw; } } } } 

The problem occurs when any team tries to link the execution of another command within the same WCF request. I got the expected exception on this line:

 using (var transaction = _context.Database.BeginTransaction()) 

because my instance of DbContext already has a transaction.

Is there a way to check the current existence of a transaction?

+5
source share
2 answers

Instead of using a transaction from a DbContext from the Entity Framework, you can, or perhaps should, use the TransactionScope class, which creates an external transaction and manages the transactions of all database connections (SQL) under covers.

It would even put the direct SqlCommand in the same transaction if you use the exact (case-sensitive) connection string for SqlCommand . Messages recorded in MessageQueue are also enclosed in a single transaction.

He could simultaneously manage the connection to various databases. He uses the DTC service for this. Beware that this is a pain when setting up if necessary. Usually with a single DB connection (or multiple connections to the same DB), you do not need a DTC.

The implementation of TransactionScopeCommandHandlerDecorator trivial:

 public class TransactionScopeCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> { private readonly ICommandHandler<TCommand> decoratee; public TransactionScopeCommandHandlerDecorator(ICommandHandler<TCommand> decoratee) { this.decoratee = decoratee; } public void Handle(TCommand command) { using (var scope = new TransactionScope()) { this.decoratee.Handle(command); scope.Complete(); } } } 

But: As mentioned in the qujck comments, you are losing the concept of ICommandHandler as an atomic operation. One command manipulator should never reference another manipulator. This is not only bad for transactions, but also takes this into account:

Imagine that the application is growing, and you will refactor some of your command handlers into a background thread that will work on some Windows services. PerWcfOperation not available PerWcfOperation this Windows service. Now you will need a LifeTimeScope lifestyle for you. Because your design allows this, which is great, by the way !, you would typically wrap your commanders in a LifetimeScopeCommandHandler decorator to run LifeTimeScope . In your current project, where one command manipulator refers to other command manipulators, you will run into a problem because each manipulator will be created in its own area. Thus, it gets a different DbContext than other manipulators!

So, you need to do some redesigns and make your cmdlets complete abstractions and create a lower level abstraction to perform DbContext operations.

+4
source

I think you are looking for the CurrentTransaction property for DbContext:

 var transaction = db.Database.CurrentTransaction; 

Then you can do the check as follows:

 using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction()) { ... } 

However, I'm not sure how you can find out when to complete a transaction if it is used in parallel methods.

+10
source

Source: https://habr.com/ru/post/1240456/


All Articles