c# - 方法对它正在调用的方法了解太多

标签 c# refactoring

我有一个方法,我希望它在抽象意义上是“事务性的”。它调用了两个恰好对数据库进行处理的方法,但该方法并不知道这些。

public void DoOperation()
{
    using (var tx = new TransactionScope())
    {
        Method1();
        Method2();

        tc.Complete();
    }
}

public void Method1()
{
    using (var connection = new DbConnectionScope())
    {
        // Write some data here
    }
}

public void Method2()
{
    using (var connection = new DbConnectionScope())
    {
        // Update some data here
    }
}

因为实际上 TransactionScope 意味着将使用数据库事务,如果我们从池中获得两个不同的连接,我们会遇到一个问题,它很可能会被提升为分布式事务。

我可以通过将 DoOperation() 方法包装在 ConnectionScope 中来解决这个问题:

public void DoOperation()
{
    using (var tx = new TransactionScope())
    using (var connection = new DbConnectionScope())
    {
        Method1();
        Method2();

        tc.Complete();
    }
}

我自己创建了 DbConnectionScope 就是为了这样的目的,这样我就不必将连接对象传递给子方法(这是比我的实际问题更人为的示例)。我从这篇文章中得到了这个想法:http://msdn.microsoft.com/en-us/magazine/cc300805.aspx

但是我不喜欢这种解决方法,因为这意味着 DoOperation 现在知道它正在调用的方法可能使用一个连接(并且可能每个都使用不同的连接)。我该如何重构它来解决问题?

我正在考虑的一个想法是创建一个更通用的 OperationScope,这样当与我将编写的自定义 CaSTLe Windsor 生活方式结合时,将意味着请求的任何组件具有 OperationScopeLifetyle 的容器将始终获得该组件的相同实例。这确实解决了问题,因为 OperationScopeDbConnectionScope 更模糊。

最佳答案

我在这里看到相互冲突的要求。

一方面,您不希望 DoOperation 知道数据库连接正用于其子操作这一事实。

另一方面,它显然知道这个事实,因为它使用了TransactionScope

当您说您希望它在抽象意义上是事务性的时,我能理解您的意思,但我对此的看法是这几乎是不可能的(不,从头开始- 完全不可能)用如此抽象的术语来描述交易。假设您有这样的类(class):

class ConvolutedBusinessLogic
{
    public void Splork(MyWidget widget)
    {
        if (widget.Validate())
        {
            widgetRepository.Save(widget);
            widget.LastSaved = DateTime.Now;
            OnSaved(new WidgetSavedEventArgs(widget));
        }
        else
        {
            Log.Error("Could not save MyWidget due to a validation error.");
            SendEmailAlert(new WidgetValidationAlert(widget));
        }
    }
}

这个类至少做了两件可能无法回滚的事情(设置类的属性和执行事件处理程序,例如可能级联更新表单上的某些控件),并且至少有两个更多绝对无法回滚的事情(附加到某处的日志文件并发送电子邮件警报)。

也许这看起来像是一个人为的例子,但这实际上是我的观点;您不能将 TransactionScope 视为“黑匣子”。作用域实际上和其他任何东西一样是一种依赖关系; TransactionScope 只是为可能并不总是合适的工作单元提供了一个方便的抽象,因为它实际上并不包装数据库连接并且无法预测 future 。特别是,当单个逻辑操作需要跨越多个数据库连接时通常是不合适的,无论这些连接是指向同一个数据库还是不同的数据库。它当然会尝试处理这种情况,但正如您已经了解到的那样,结果并不理想。

在我看来,您有几种不同的选择:

  1. 明确表明 Method1Method2 需要连接,方法是让它们采用连接参数,或者将它们重构为采用连接的类依赖项(构造函数或属性)。这样,连接就成为契约的一部分,因此Method1 不再知道太多 - 它确切地知道根据设计它应该知道什么。

  2. 接受您的 DoOperation 方法确实知道Method1Method2 做什么。事实上,这没有任何问题!您确实不想依赖某些 future 调用的实现细节,但抽象中的前向依赖是一般认为可以;这是您需要关注的反向依赖关系,例如当域模型深处的某个类试图更新它一开始并不知道的 UI 控件时。

    <
  3. 使用更强大的 Unit of Work模式(又名:here)。这变得越来越流行,总的来说,这是微软在 Linq to SQL 和 EF 方面的发展方向(DataContext/ObjectContext 基本上是 UOW 实现).这与 DI 框架很好地结合在一起,从本质上使您不必担心事务何时开始和结束以及数据访问必须如何发生(术语是“持久性无知”)。这可能需要对您的设计进行大量返工,但从长远来看,这将是最容易长期维护的。

希望其中之一对您有所帮助。

关于c# - 方法对它正在调用的方法了解太多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2109169/

相关文章:

c# bool 运算符 - 正常与短路

C# 函数,类、结构或接口(interface)成员声明中的无效标记 'bool'

c# - 字符串索引器如何工作?

c# - block 基构造函数到子类

java - 在Intellij IDEA中提取多个字符串到常量

javascript - 在 JS 问题中重构 jQuery

c# - 如何订购 ScriptManager 的 Register Script 注册的脚本

intellij-idea - Intellij IDEA 在整个项目中提取常量

javascript - 需要摆脱许多如果

javascript - 重构查找函数