【问题标题】:UnitOfWork (NHibernate), only one active UoW/session at a time? (need advice)UnitOfWork(NHibernate),一次只有一个活动的 UoW/会话? (需要建议)
【发布时间】:2011-03-19 23:01:56
【问题描述】:

我正在使用 NHibernate、DI/IoC 和工作单元模式。

我见过的大多数 UoW 示例都确保同时只能有一个活动的 UoW/会话,例如 this one 和 this one。

不幸的是,我还不太明白我应该如何处理两个都使用 UoW 的服务,但一个调用另一个。
举个例子:

使用 UoW 的记录器:

public class LoggerService : ILoggerService
{
    private ILoggerRepository repo;

    public LoggerService(ILoggerRepository Repo)
    {
        this.repo = Repo;
    }

    public void WriteLog(string message)
    {
        using (UnitOfWork.Start())
        {
            // write log
        }
    }
}

...以及另一个使用 UoW 的服务并调用记录器:

public class PlaceOrderService : IPlaceOrderService
{
    private IOrderRepository repo;
    private ILoggerService service;

    public PlaceOrderService(IOrderRepository Repo, ILoggerService Service)
    {
        this.repo = Repo;
        this.service = Service;
    }

    public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            // do stuff

            this.service.WriteLog("Order placed!");  // will throw exception!!

            // do more stuff
        }
    }
}

如果我的 UoW 实现确保同时只有一个活动 UoW(如果您尝试启动另一个 UoW,则会引发异常,就像在两个链接示例中一样),我的代码将在 this.service.WriteLog 行中崩溃在 PlaceOrder 方法中:
PlaceOrder 方法已经创建了一个活动的 UoW,WriteLog 方法将尝试打开第二个,因此 UoW 实现将因此引发异常。

那么,我该怎么办呢?
我提出了两个想法,但在我看来,这两个想法都有些“老套”。

  1. 不要在 LoggerService 中启动新的 UoW,而是假设调用代码中已经有一个活动的 UoW。
    这就是我目前正在做的事情。我刚刚从 LoggerService 中删除了 using (UnitOfWork.Start()) 的东西,并确保您不能直接调用 LoggerService,只能从其他服务中调用。
    这意味着上面的代码可以工作,但是如果调用代码没有启动 UoW,LoggerService 将会崩溃,因为 LoggerService 假定已经存在。

  2. 保持示例代码不变,但更改 UoW.Start() 的实现,如下所示:
    a) 如果没有有效的 UoW,则开始一个新的
    b) 如果已经有有一个活跃的 UoW,返回这个
    这将使我能够直接和从其他服务调用 LoggerService,无论是否已经存在 UoW。
    但我从未在网上的任何示例中看到过这样的事情。

(在这个例子中,只有两个服务。它可能会变得更复杂,想想一个 PlaceSpecialOrderService 类,它做了一些特殊的事情然后调用 em> PlaceOrderService.PlaceOrder()...)

有什么建议吗?
提前致谢!


编辑:

感谢您到目前为止的回答。

好吧,也许日志记录不是最好的例子。
我明白你关于使用单独的会话进行日志记录的观点,我会看看并尝试一下。

无论如何,我仍然需要找到一种方法来使嵌套服务调用起作用。
想象一下其他示例而不是日志记录,例如我上面提到的 PlaceSpecialOrderService 示例。

对于建议我在基础设施的某个地方而不是直接在服务中启动我的 UoW 的回答者:
一方面,这也是有道理的,但另一方面,这显然意味着我不能在一个服务调用中执行两个不同的事务。
我必须考虑一下,因为我很确定我会在某个地方需要这个(例如:在一个事务中保存一个订单,然后在第二个事务中做更多的事情,即使失败,订单也不会'不要回滚)。

您是否在您的应用中这样做(每次服务调用一个 UoW)?
您不需要在同一个服务调用中启动第二个 UoW 的可能性吗?

【问题讨论】:

    标签: c# nhibernate architecture dependency-injection unit-of-work


    【解决方案1】:

    我认为您发现了一个非常特殊的场景,您永远不应该将同一个 Session 用于业务服务和日志服务。 UnitOfWork 是“业务事务”,但日志记录显然不是事务的一部分。如果您的业务逻辑抛出异常,它将回滚您的日志!!!使用单独的会话进行日志记录(使用单独的连接字符串限制当前事务的登记)。

    【讨论】:

    • 好的,有道理。但是,我仍然有同样的问题(日志记录只是一个不好的例子)见上文!
    【解决方案2】:

    UOW 的构建和生命周期管理不应成为实现业务逻辑的服务的关注点。相反,您应该设置您的基础设施来进行 UOW 管理。根据您的应用程序,您可以为每个 http 请求或每个 WCF 操作调用或 MessageModule(想想 NserviceBus)创建一个 UOW。

    大多数 DI 容器已经支持将实例的生命周期与上述上下文相关联。

    还有关于日志记录 - 在最常见的情况下,这是一个基础架构问题,在订单处理服务旁边有一个日志记录服务是一种味道。让 log4net 或 nlog 或任何你喜欢的东西来做他们被构建做的事情。

    【讨论】:

    • 我不确定每个请求只有一个 UoW 是否总是适用。我编辑了我的问题并添加了我对此的想法。但是你是对的,没有将日志记录在与业务逻辑相同的 UoW 中。
    • 通常 UOW 与一个请求非常一致,因为请求的结果(以成功或失败为单位)与 UOW 的结果相同。在一个请求中包含多个 UOW 会使处理错误变得非常棘手。我认为您需要多个 UOW 的情况可以通过消息传递来解决。
    【解决方案3】:

    就我个人而言,我不认为写订单状态是一个日志问题。对我来说,这是一个商业问题,所以我会将您的订单服务重构为这样的:

    public int PlaceOrder(int orderNumber)
        {
            using (UnitOfWork.Start())
            {           
                repository.SaveOrder(order)
    
                repository.SaveOrderStatus(order,"Order placed")
    
            }
        }
    

    以及我将用于未处理的执行、身份验证问题等的日志记录服务。

    【讨论】:

    • 我同意你的看法。在这种特殊情况下,您不想在事务回滚时记录“已下订单”消息。然而,在大多数情况下,回滚日志消息会让您不知道事务回滚时出了什么问题。
    • 你的两点对我来说都很有意义。但是,我认为我为示例选择日志记录并不是一个好主意,因为这似乎会分散我对实际问题的注意力。我编辑了我的问题以使其更清晰。
    【解决方案4】:

    我想我自己找到了解决方案。

    实际上,这是我在我的问题中提出的解决方案之一:

    保持示例代码不变,但更改 UoW.Start() 的实现,如下所示:
    a) 如果没有有效的 UoW,则开始一个新的
    b) 如果已经有一个活跃的 UoW,则返回这个
    这将使我能够直接和从其他服务调用 LoggerService,无论是否已经存在 UoW。
    但是我从来没有在网上的任何例子中看到过这样的事情。

    在我在这里提出问题之前,我已经提出了这个想法,但我不确定这是否是一个好的解决方案,因为我在网上找到了大量不同的 UoW 实现,但没有与我的想法相似。

    但我实际上找到了一个实现 - 我阅读了完整的帖子,但我只是以某种方式过度阅读了相关部分:-)
    它在the first link that I posted in my original question。
    那里的 UoW 实现有一个布尔字段“isRootUnitOfWork”。
    构造函数基本上是这样做的(简化):

    if (HasActiveSession)
    {
        isRootUnitOfWork = false;
        session = GetActiveSession();
    }
    else
    {
        isRootUnitOfWork = true;
        session = CreateSession();
    }
    

    我认为这是最灵活的解决方案。我可以调用我的一个服务,或者让一个调用另一个......而且一切正常,无需对 UoW 进行任何特殊操作。

    【讨论】:

      【解决方案5】:

      我建议在服务之外启动和停止 UnitOfWork。不知道.net 世界的适当工具,但您应该寻找一些面向Aspekt 的编程工具。或者手动创建一个包装类 foreach 服务,它只启动工作单元,然后委托给真正的服务,然后关闭工作单元。如果一个服务调用另一个服务,它使用真正的实现而不是工作单元包装器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-09
        • 1970-01-01
        • 1970-01-01
        • 2012-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多