在NHibernate中为长时间运行的任务限定事务和会话的范围



当在web应用程序中使用NHibernate时,我通常会让我的IoC容器负责打开和关闭每个请求的ISession,并提交/回滚事务。HTTP的本质使得在这样的应用程序中定义一个清晰的工作单元非常容易。

现在,我的任务是编写一个小程序,它将被任务调度程序定期调用,用于发送时事通讯。时事通讯和订阅者的概念在我们的领域模型中已经很好地定义了实体,向所有订阅者发送时事通讯将涉及类似于以下操作:

var subscribers = _session
    .QueryOver<Subscription>()
    .Where(s => !s.HasReceivedNewsletter)
    .List();
foreach (var subscriber in subscribers)
{
    SendNewsletterTo(subscriber);
    subscriber.HasReceivedNewsletter = true;
}

注意每个Subscriber对象是如何在循环中更新的,记录她现在已经收到了时事通讯。这个想法是,如果邮件发送程序崩溃了,它可以重新启动,并从它停止的地方继续发送时事通讯。

我面临的问题是定义和实现这里的工作单元模式。我可能需要在循环的每次迭代结束时向数据库提交更改。简单地用using (var trans = _session.BeginTransaction())块包装循环体在运行时间上似乎非常昂贵,而且我似乎也经历了这个长时间运行的进程和使用相同数据库的其他(web)应用程序之间的锁定问题。

在阅读了一些关于NHibernate事务的文章和文档之后,我开始思考,我可能需要从会话中分离订阅者列表以避免锁定问题,并将每个订阅者重新连接到循环体中的新会话。不过,我不确定这将如何提高性能。

那么,NHibernate专家们,你们将如何设计和实现这样一个长时间运行的作业呢?

您不想在这里使用异步持久消息传递吗?比如NServiceBus, Rhino ServiceBus或者masstrtransit。似乎您不必尽快发送大量消息,因此我认为您应该以每个用户为基础异步发送1个持久消息

您不认为没有事务的无状态会话在这里会做得更好吗?

在一个会话中有多个事务是没有问题的。这里将事务范围限定为更新单个订阅者是合适的,因为它是一个独立的操作。根据订阅者的数量和失败的可能性,最好一次抓取少量订阅者。

foreach (var subscriber in subscribers)
{
    using (var txn = _session.BeginTransaction())
    {
        try
        {
            SendNewsletterTo(subscriber);
            subscriber.HasReceivedNewsletter = true;
            txn.Commit();
        }
        catch (Exception ex)
        {
            txn.Rollback();
            // log exception, clean up any actions SendNewsletterTo has taken if needed
            // Dispose of session and start over
        }   
    }
}

最新更新