方法对它调用的方法了解得太多了

时间:2010-01-21 12:38:02

标签: 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意味着将使用数据库事务,所以如果我们从池中获得两个不同的连接,我们就会遇到一个可以提升为分布式事务的问题。

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

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生活方式合作时,我将会写出{{ {1}}将始终获得该组件的相同实例。这确实解决了这个问题,因为OperationScopeLifetyleOperationScope更加模棱两可。

2 个答案:

答案 0 :(得分:2)

我在这里看到了相互矛盾的要求。

一方面,您不希望DoOperation意识到数据库连接正在用于其子操作。

另一方面,显然 意识到这一事实,因为它使用TransactionScope

当你说你希望它在抽象意义中具有交易性时,我可以理解你所得到的,但我对此的看法是它实际上是不可能的(不,刮 - 完全不可能)以这样的抽象术语描述交易。我们假设您有一个这样的课程:

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只为工作单元提供了一个方便的抽象,这可能并不总是合适的,因为它实际上并没有包装数据库连接,也无法预测未来。特别是,当单个逻辑操作需要跨越多个数据库连接时,通常是不合适的,无论这些连接是指向同一个数据库还是不同的数据库连接。它当然试图处理这种情况,但正如你已经知道的那样,结果是次优的。

我看到它的方式,你有几个不同的选择:

  1. 通过让Method1Method2获取连接参数,或者通过将它们重构为具有连接依赖性的类(构造函数或属性)来明确Method1DoOperation需要连接的事实。 。通过这种方式,连接成为契约的一部分,因此Method1不再了解太多 - 它根据设计确切知道它应该知道什么。

  2. 接受您的Method2方法确实了解DataContextObjectContext做了什么。事实上,没有任何问题!确实,您不希望依赖未来某个调用的实现细节,但抽象中的前向依赖是一般认为没问题;它是你需要关注的反向依赖关系,比如当域模型中的某个类试图更新它首先没有业务知道的UI控件时。

  3. 使用更强大的Unit of Work模式(同时:here)。这变得越来越流行,而且它基本上是微软用Linq转向SQL和EF的方向({{1}} / {{1}}基本上是UOW实现)。这与DI框架完全吻合,基本上使您无需担心事务何时开始和结束以及数据访问必须如何发生(术语是“持久性无知”)。这可能需要对您的设计进行大量的重新设计,但是对于磅来说这将是最容易长期保持的。

  4. 希望其中一个能帮到你。

答案 1 :(得分:0)

是否可以将事务处理/登记推送到数据库并将其从代码中完全删除?