Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check whether DbContext has transaction?

Background: I have WCF service with SimpleInjector as IoC which creates instance of DbContext per WCF request.

Backend itself is CQRS. CommandHandlers have a lot of decorators (validation, authorization, logging, some common rules for different handler groups 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;
            }
        }
    }
}

Problem occurs when any command tries to chain execute another command within the same WCF request. I got an expected exception at this line:

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

because my DbContext instance already has a transaction.

Is there any way to check current transaction existence?

like image 246
Szer Avatar asked Jan 12 '16 16:01

Szer


People also ask

How do you know if DbContext is disposed?

You don't need to check if it's disposed or not because if your DbContext is injected by the DI system then you'll never need to dispose it yourself because it will only be disposed at the end of the controller's lifespan (i.e. after the Action completes but before the View renders.

Does Entity Framework use transactions by default?

By default, for instance, on SQL Server this is READ COMMITTED. Entity Framework does not wrap queries in a transaction. This default functionality is suitable for a lot of users and if so there is no need to do anything different in EF6; just write the code as you always did.

What is my DbContext?

A DbContext instance represents a combination of the Unit Of Work and Repository patterns such that it can be used to query from a database and group together changes that will then be written back to the store as a unit. DbContext is conceptually similar to ObjectContext.

What is difference between DbSet and DbContext?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database. So it makes perfect sense that you will get a combination of both!


2 Answers

I think you're looking for the CurrentTransaction property of the DbContext:

var transaction = db.Database.CurrentTransaction;

Then you can do a check like this:

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

However I'm not sure how you can know when to commit the transaction if it's being used by concurrent methods.

like image 99
Alexander Derck Avatar answered Oct 05 '22 10:10

Alexander Derck


Instead of using the transaction from the DbContext of Entity Framework you could or maybe should use the TransactionScope class which creates an ambient transaction scope and manages transactions of all connections made to the (SQL) database under the covers.

It even would put a direct SqlCommand in the same transaction if you would use the exact (case-sensitive) connectionstring for the SqlCommand. Messages writen to the MessageQueue are also encapsulated in the same transaction

It even could manage connections to different databases at the same time. It uses the DTC windows service for this. Beware that this is a pain to configure if needed. Normally, with a single DB connection (or multiple connections to the same DB) you won't need the DTC.

The TransactionScopeCommandHandlerDecorator implementation is 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 qujck already mentioned in the comments, you are missing the concept of ICommandHandler as an atomic operation. One commandhandler should never reference another commandhandler. Not only is this bad for transactions, but also consider this:

Imagine the application grows and you would refactor some of your commandhandlers to a background thread, which will run in some windows service. In this windows service a PerWcfOperation lifestyle is not available. You would need a LifeTimeScope lifestyle for you commandhandlers now. Because your design allows it, which is great by the way!, you would typicaly wrap your commandhandlers in a LifetimeScopeCommandHandler decorator to start the LifetimeScope. In your current design where a single commandhandler references other commandhandlers you will run into a problem, because every commandhandler will be created in its own scope a thus gets an other DbContext injected than the other commandhandlers!

So you need to do some redesign and make your commandhandlers holistic abstractions and create a lower level abstraction for doing the DbContext operations.

like image 41
Ric .Net Avatar answered Oct 05 '22 10:10

Ric .Net