在不同线程

时间:2016-07-06 17:29:03

标签: c# nhibernate nservicebus nhibernate-3 nservicebus3

我想要实现的是我的网站发出一条消息并将其放在总线上,一个服务选择它并通过审计写入数据库,自动填充该行的AddedBy / UpdatedBy字段。

我通过使用NServiceBus IMessageMutator组件来执行此操作,该组件将用户ID写入来自我的ASP.Net应用程序中的登录用户的Thread.CurrentPrincipal的邮件头。 在我的服务中,我使用IMessageModule来提取此标头并将其绑定到Thread.CurrentPrincipal。这很好用,在我的消息处理程序中,我可以看到Thread.CurrentPrincipal.Identity.Name正确绑定到在Web应用程序中引发消息的用户ID。

利用NHibernate的IPreUpdateEventListener / IPreInsertEventListener我将每个实体的AddedBy / UpdatedBy设置为写入数据库之前。这完全适用于网站,但在我的NServiceBus服务中,侦听器运行的线程与处理程序运行的线程不同,这意味着线程的CurrentPrincipal不再是我的IMessageModule中绑定的ID。

我可以看到NHibernate在调用堆栈中使用DistributedTransactionFactory,我怀疑这是我的问题的原因。我不想丢失事务性,如果提交失败,则不会重新尝试消息或将消息放入错误队列,并且如果从队列中删除消息失败并且更新未回滚到数据库。< / p>

我浏览了网页,所有示例都使用线程的CurrentPrincipal来绑定修改行的用户的id。我正在寻找的方法是将NHibernate监听器保持在与消息处理程序相同的线程上,或者将用户ID传递给监听器,以便在将其写入数据库之前将其绑定到实体。

这是我的听众,我省略了在其中找到的Set方法

public class EntityPersistenceListener : IPreUpdateEventListener, IPreInsertEventListener
{
    public bool OnPreUpdate(PreUpdateEvent @event)
    {
        var audit = @event.Entity as EntityBase;
        if (audit == null)
            return false;

        var time = DateTimeFactory.GetDateTime();

        var name = Thread.CurrentPrincipal.Identity.Name;

        Set(@event.Persister, @event.State, "AddedDate", audit.AddedDate);
        Set(@event.Persister, @event.State, "AddedBy", audit.AddedBy);
        Set(@event.Persister, @event.State, "UpdatedDate", time);
        Set(@event.Persister, @event.State, "UpdatedBy", name);

        audit.AddedDate = audit.AddedDate;
        audit.AddedBy = audit.AddedBy;
        audit.UpdatedDate= time;
        audit.UpdatedBy = name;

        return false;            
    }
}

这是NServiceBus Message模块,它提取id并将其绑定到当前线程的标识

public class TenantAndInstanceInfoExtractor : IMessageModule
{
    private readonly IBus _bus;

    public TenantAndInstanceInfoExtractor(IBus bus)
    {
        _bus = bus;
    }

    public void HandleBeginMessage()
    {
        var headers = _bus.CurrentMessageContext.Headers;

        if (headers.ContainsKey("TriggeredById"))
            Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(headers["TriggeredById"]), null);
        else
            Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), null);
    }

    public void HandleEndMessage()
    {

    }

    public void HandleError() { }
}

1 个答案:

答案 0 :(得分:0)

谢谢Simon的帮助。在广泛地解决了我的问题并讨论了NServiceBus如何在内部工作后,我深入了解了NServiceBus的工作模块。

我们正在依赖每条消息创建的事务来将我们的NHibernate会话提交给数据库。这发生在利用线程池的分布式事务控制器(特别是它发生在这里NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext)上。

通过使用NServiceBus的IManageUnitsOfWork接口,我能够在与代码示例中的消息处理程序相同的线程上显式提交事务。

作为未来读者的旁注,这里最好的解决方案是不使用Thread.CurrentPrincipal,因为这个解决方案在多线程环境中失败,就像它对我一样。

public class HiJumpNserviceBusUnitOfWork : IManageUnitsOfWork
{
    private readonly IUnitOfWork _uow;

    public HiJumpNserviceBusUnitOfWork(IUnitOfWork uow)
    {
        _uow = uow;
    }

    public void Begin()
    {
        _uow.ClearCache();
        _uow.BeginTransaction();
    }

    public void End(Exception ex = null)
    {
        if (ex != null)
        {
            _uow.ClearCache();
        }
        else
        {
            _uow.CommitTransaction();
        }
    }
}