您可以将事务参数添加到要在事务中运行的方法的末尾,并为其指定默认值 null。因此,如果您不想在现有事务中运行该方法,请不要使用 end 参数或显式传递 null。
在这些方法中,您可以检查参数是否为空,以确定是创建新事务还是使用传入的事务。此逻辑可以推送到基类。
这使您的方法比使用基于上下文的解决方案更纯粹,尽管后者可能更适合通用库。但是,在独立应用程序中,您知道哪些方法需要在事务中链接起来,而且不会是所有方法。
void Update(int itemId, string text, IDbTransaction trans = null) =>
RunInTransaction(ref trans, () =>
{
trans.Connection.Update("...");
});
void RunInTransaction(ref IDbTransaction transaction, Action f)
{
if (transaction == null)
{
using (var conn = DatabaseConnectionFactory.Create())
{
conn.Open();
using (transaction = conn.BeginTransaction())
{
f();
transaction.Commit();
}
}
}
else
{
f();
}
}
Update(1, "Hello World!");
Update(1, "Hello World!", transaction);
然后你可以为你的服务层设置一个事务运行器...
public class TransactionRunner : ITransactionRunner
{
readonly IDatabaseConnectionFactory databaseConnectionFactory;
public TransactionRunner(IDatabaseConnectionFactory databaseConnectionFactory) =>
this.databaseConnectionFactory = databaseConnectionFactory;
public void RunInTransaction(Action<IDbTransaction> f)
{
using (var conn = databaseConnectionFactory.Create())
{
conn.Open();
using (var transaction = conn.BeginTransaction())
{
f(transaction);
transaction.Commit();
}
}
}
public async Task RunInTransactionAsync(Func<IDbTransaction, Task> f)
{
using (var conn = databaseConnectionFactory.Create())
{
conn.Open();
using (var transaction = conn.BeginTransaction())
{
await f(transaction);
transaction.Commit();
}
}
}
}
服务方法可能看起来像这样......
void MyServiceMethod(int itemId, string text1, string text2) =>
transactionRunner.RunInTransaction(trans =>
{
repos.UpdateSomething(itemId, text1, trans);
repos.UpdateSomethingElse(itemId, text2, trans);
});
这很容易模拟单元测试...
public class MockTransactionRunner : ITransactionRunner
{
public void RunInTransaction(Action<IDbTransaction> f) => f(null);
public Task RunInTransactionAsync(Func<IDbTransaction, Task> f) => f(null);
}