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

发布于 2024-11-01 03:36:49 字数 928 浏览 2 评论 0原文

在 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()) 块包装循环体似乎在运行时间上非常昂贵,而且我似乎还遇到了这个长时间运行的进程与其他进程之间的锁定问题使用相同数据库的(网络)应用程序。

在阅读了一些有关 NHibernate 事务的文章和文档后,我开始思考,我可能需要将订阅者列表与会话分离以避免锁定问题,并将每个订阅者重新附加到循环体中的新会话。不过,我不确定这对性能有何影响。

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

When using NHibernate in web applications, I will usually let my IoC container take care of opening and closing an ISession per request and commit/rollback the transaction. The nature of HTTP makes it very easy to define a clear Unit-of-Work in such applications.

Now, I have been tasked with putting together a small program, which will be invoked regularly by a task scheduler, for sending out newsletters. The concepts of both newsletters and subscribers are already well defined entities in our domain model, and sending a newsletter to all subscribers would involve doing something similar to this:

var subscribers = _session
    .QueryOver<Subscription>()
    .Where(s => !s.HasReceivedNewsletter)
    .List();

foreach (var subscriber in subscribers)
{
    SendNewsletterTo(subscriber);
    subscriber.HasReceivedNewsletter = true;
}

Notice how each Subscriber object is updated within the loop, recording that she has now received the newsletter. The idea is, that if the mail sending program should crash, it can be restarted and continue sending newsletters from where it left off.

The problem I am facing, is in defining and implementing the Unit-of-Work pattern here. I will probably need to commit changes to the database by the end of each iteration of the loop. Simply wrapping the loop body with a using (var trans = _session.BeginTransaction()) block seems to be extremely expensive in running time, and I also seem to experience locking issues between this long running process and other (web) applications using the same database.

After reading some articles and documentation on NHibernate transactions, I have come to think, that I might need to detach the list of subscribers from the session to avoid the locking issues, and reattach each to a fresh session in the loop body. I am not sure how this will work for performance, though.

So, NHibernate experts, how would you design and implement a long running job like this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

少女情怀诗 2024-11-08 03:36:49

您不想在这里使用异步持久消息传递吗?类似 NServiceBus、Rhino Service Bus 或 MassTransit。看来您不必尽快发送大量消息,所以我认为您应该以每个用户 1 条持久消息的方式异步执行此操作

Don't you want to use asynchronous durable messaging here? Something like NServiceBus, Rhino Service Bus or MassTransit. It seems you don't have to send a lot of messages as soon as possible, so I think you should do it asynchronously with 1 durable message per user basis

蓝戈者 2024-11-08 03:36:49

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

Don't you think that Stateless session with no transaction will do better here?

最近可好 2024-11-08 03:36:49

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

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
        }   
    }
}

There's no problem having multiple transactions in a session. It's appropriate here to scope the transaction to updating a single subscriber because it's an independent operation. Depending on the number of subscribers and the likelihood of failure, it might be best to grab a small number of subscribers at a time.

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
        }   
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文