Valamas
Valamas

Reputation: 24759

Func delegate for multiple method calls

I had previously asked about Converting using SqlConnection to Func delegate

How can I use the following delegate to make multiple calls using the same transaction? To complicate matters, one of the calls I wish to return a value.

I have the following Delegate function definition

protected TResult UsingSqlTransaction<TResult>(Func<SqlTransaction, TResult> myFunction)
{
    using (SqlConnection sqlConn = new SqlConnection(ConnectionString))
    {
        sqlConn.Open();
        using (SqlTransaction sqlTrans = sqlConn.BeginTransaction())
        {
            var result = myFunction(sqlTrans);
            sqlTrans.Commit();

            return result;
        }

    }
}

Current usage

public Guid? InsertUpdate(News entity)
{
    return UsingSqlTransaction(sqlTrans => data.InsertUpdate(sqlTrans, entity));
}

Solution - See the accepted answer

public Guid? InsertUpdate(News entity)
{
    return UsingSqlTransaction(sqlTrans =>
    {
        var token = data.InsertUpdate(sqlTrans, entity);
        data.DoSomethingElse(sqlTrans, entity);
        return token;
    });
}

//-- The above UsingSqlTransaction remains unchanged

Upvotes: 3

Views: 1970

Answers (3)

porges
porges

Reputation: 30590

You can use a 'block' lambda. Instead of:

public Guid? InsertUpdate(News entity)
{
    return UsingSqlTransaction(sqlTrans => data.InsertUpdate(sqlTrans, entity));
}

You can use (note curly braces and semicolons):

public List<Guid?> InsertUpdate(News entity)
{
    return UsingSqlTransaction(sqlTrans =>
    {
        var result = new List<Guid?>();
        result.Add(data.InsertUpdate(sqlTrans, entity));
        result.Add(data.DoSomethingElse(sqlTrans, entity));
        return result;
    });
}

Re: your update. I'd write that like this (note that you can store token and return it at the end of your lambda, just like a normal method):

public Guid? InsertUpdate(News entity)
{
    return UsingSqlTransaction(sqlTrans =>
    {
        var token = data.InsertUpdate(sqlTrans, entity);
        data.DoSomethingElse(sqlTrans, entity);
        return token;
    });
}

Upvotes: 2

Andrew Kennan
Andrew Kennan

Reputation: 14157

This is a pattern I've used a few times successfully:

Have a "Transaction Manager" object that internally keeps track of the current connection and transaction. It could be a simple static, for a single threaded app, or do fun things with thread-local storage or even a WCF operation context. The idea is that it gives you a single place to create connections and transactions that is separated from the code calling the database.

The transaction manager exposes a public method called BeginTransaction that returns transaction object implementing IDisposable. Each time you call BeginTransaction you get a new transaction scope instance.

The transaction scope object also provides methods for getting at the database connection and a Commit method that must be called before disposing. If Commit is not called the transaction will be rolled back on Dispose.

void M1() 
{
    using( var scope = TransactionManager.BeginTransaction() )
    {
      // Do stuff with the database.
      M2(); // M2 and M3 each create their own scopes that share the transaction
      M3(); // created by M1.

      // An exception before the commit will cause the transaction to roll back.
      scope.Commit();
    }
}

The important thing is that the TransactionManager will allow you to create nested scopes - the "Do stuff with the database" line could call a bunch of other methods that each create their own scopes that share a transaction. All the scopes must be committed or everything will roll back.

This all looks pretty similar to the way the stuff in the System.Transactions namespace works and I'd recommend you have a look at that too.

And then your utility functions to simplify the code, if you still want them, look like this:

public static void Execute(Action<ITransactionScope> action)
{
  using( var scope = TransactionManager.BeginTransaction() )
  {
    action(scope);
    scope.Commit();
  }
}

public static TResult Execute<TResult>(Func<ITransactionScope, TResult> func)
{
  using( var scope = TransactionManager.BeginTransaction() )
  {
    var result = func(scope);
    scope.Commit();
    return result;
  }
}

Upvotes: 0

Beatles1692
Beatles1692

Reputation: 5330

I have the same method in my program but it's something like this :

void RunTransaction(Action<IDbCommand> action)
{
using(var cnn=GetConnection)
cnn.Open();
using(var trans=cnn.BeginTransaction())
{
var command=cnn.CreateCommand();
action(command);
trans.Commit();
}
}

this method is only responsible for managing a transaction and nothing else.It doesn't care if we are querying the database or we are inserting a value.It's the responsibility of the caller to do take care of these things and with the beauty of closures it's an easy task to do :)

Here's what I think : Don't try to have an abstraction for everything because soon your programmer will become too complicated to extend and or maintain.Just have some basic abstraction at your disposal then you can build your application over these simple ones.

Upvotes: 0

Related Questions