Reputation: 24759
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));
}
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
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
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
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