【问题标题】:Work with database transactions in a functional manner in C#在 C# 中以功能方式处理数据库事务
【发布时间】:2016-10-09 23:00:03
【问题描述】:

我在看这种代码时想知道,如果从 函数式编程 的角度来看,有什么可以改进的吗?

你不必严格地重新实现我的例子来回答,如果你有不同的例子涉及交易,那就太好了。

using (var unitOfWork = _uowManager.Begin())
{
    _paymentRepository
       .InsertOrUpdate(payment); // Returns payment instance

    // Being executed to get Payment.Id
    _uowManager
       .SaveChanges();

    _otherRepository
       .OtherMethod(payment.Id); // Could be changed as necessary

    unitOfWork
       .Complete()
}

以上代码基于ASP.NET Boilerplate 和实体框架(如果有帮助的话)。

【问题讨论】:

  • 在函数式编程语言中,这类事情通常使用 monad 来处理。但是,C# 主要不是一种 FP 语言(尽管它在一定程度上支持它),并且在其中强行插入 monad,因此您可以说您正在使用函数式似乎不自然。
  • @Jeroen Mostert 能否请您提供一些来自功能世界的示例。 :)

标签: c# design-patterns functional-programming


【解决方案1】:

我认为创建 transactional monad 是处理这个问题的好方法。不管 C# 中有多少 FP,这都会带来很多好处:

  • 以单一、全局、统一的方式处理交易
  • 不要重复自己,编写一次这个类,不要为每个控制器函数重新创建事务逻辑 n 次
  • 您可以将此类创建为接口并在集成测试中对其进行模拟,例如回滚事务,这样您的测试就不会影响数据库状态

我使用这样的东西:

public class Tx
{     
        public DbConnectionExtra _connection { get; set; } // class which has DbTransaction and DbConnection combined 

        public T UseTx<T>(Func<T> fnToCall)
        {          
            try
            {
                _connection.BeginTransaction();

                var result = fnToCall();

               _connection._transaction.Commit();
               _connection._transaction.Dispose();

                return result;
            }
            catch
            {
               _connection._transaction.Rollback();
                _connection._transaction.Dispose();

                throw;
            }
        }
}

【讨论】:

  • 好吧,如果你要写那么多代码来做这件事,我明白你为什么需要包装,但这不是正确的做法。 using (var t = _connection.BeginTransaction()) { var result = fnToCall(); t.Commit(); return result; } 就是您所需要的。没有明确的catch,没有.Dispose,没有.Rollback,所有这些都由usingTransaction.Dispose()处理。
  • 此外,即使是 那个 这些日子也太多了;推荐的方法是将您的代码包装在using (var ts = new TransactionScope()) {... ts.Commit(); } 中,而无需明确引用单个事务。在事务范围内登记的代码(默认情况下 SQL 命令将执行此操作)将成为事务的一部分,而无需将该代码更改为事务感知。
  • 好吧,我不确定使用适用于分布式事务的 TransactionScope 是否正确。你有这方面的参考吗?据我所知,如果您使用的是 EF,则绝对不建议这样做。关于包装 - 如果您在 catch 子句中有更多逻辑(例如,日志记录),它会有所帮助。
  • TransactionScope 与明确的SqlTransaction 有其自身的问题(确实有几个关于 SO),所以我不会进一步讨论。 TransactionScope 确实对分布式事务特别有用,但这不是它唯一的用例。
  • catch 中的额外逻辑是一回事,但是您当前编写的代码根本不正确——如果事务遇到数据库错误,它已经回滚,并调用 @987654333 @ 再次会产生一个无益的错误。 (当然,我假设您的交易与SqlTransaction 非常相似——我无法查看DbConnectionExtra。)如果using 是一个选项,您也绝对不想明确调用.Dispose(),如果你这样做,应该在 finally 块中完成。
猜你喜欢
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-25
  • 1970-01-01
  • 2010-12-02
相关资源
最近更新 更多