Search code examples
c#entity-frameworknestedusingidisposable

How to avoid rewriting identical using statements?


Is there a way to create a nested using in a Disposable object, so this code:

using (var ctx = new MyEntities())
{
    ctx.Connection.Open();
    using (var tx = dbContext.Connection.BeginTransaction())
    {
        // ... do awesome things here

        ctx.SaveChanges();
        tx.Commit();
    }
}

to something like this:

using (var txContext = new TransactionContext())
{
    // ... do awesome things here
}

?

Currently I have:

public class TransactionContext : IDisposable
{
    private MyEntities DbContext { get; set; }
    private DbTransaction Transaction { get; set; }

    public TransactionContext()
    {
        DbContext = new MyEntities();
        DbContext.Connection.Open();

        Transaction = DbContext.Connection.BeginTransaction();
    }

    void IDisposable.Dispose()
    {
        try
        {
            DbContext.SaveChanges();
            Transaction.Commit();
        }
        catch (Exception exception)
        {
            Transaction.Rollback();
            DbContext.Dispose();
        }
    }
}

I'm not sure if this is correct in ways of disposing the different Disposables, especially in case of an error/exception.


Solution

  • It would probably be better to use a method instead of a class for this.

    private static void RunInTransaction(Action<MyEntities, DbTransaction> action)
    {
        using (var ctx = new MyEntities())
        {
            ctx.Connection.Open();
            using (var tx = ctx.Connection.BeginTransaction())
            {
                action(ctx, tx);
                ctx.SaveChanges();
                tx.Commit();
            }
        }
    }
    

    Then you can call it like this.

    RunInTransaction((ctx,tx) => 
    {
        // ... do awesome things here
    }); 
    

    You can also create a version of this that returns a value

    private static T RunInTransactionAndReturn<T>(
        Func<MyEntities, DbTransaction, T> function)
    {
        using (var ctx = new MyEntities())
        {
            ctx.Connection.Open();
            using (var tx = ctx.Connection.BeginTransaction())
            {
                var result = function(ctx, tx);
                ctx.SaveChanges();
                tx.Commit();
                return result;
            }
        }
    }
    

    Then you can call it like this.

    var result = RunInTransactionAndReturn((ctx,tx) => 
    {
        // ... do awesome things here
        return someResult;
    });