2017-10-12 231 views
0

我正在构建一个Asp.Net MVC应用程序。我使用Entity Framework Core进行数据库访问。我正在使用SQLServer作为数据库。所有EF Stuff都可以在控制器方法内正常工作。使用相同的交易端点和DbContext

我也有一个相关的NServiceBus服务应用程序,用于处理不需要在Web服务器上运行的东西。其中一个常见的事情是在超时后执行任务。 (例如,用户在网站上做了些什么,并在30分钟后做了一些事情。)

我正在使用SQL Transport作为我的服务总线设置,并将其与实体框架数据使用相同的数据库。

由于Web服务器不需要处理NServiceBus消息,因此它具有向IOC容器注册的仅发送IEndpointInstance。需要发送NServiceBus消息的控制器获取IEndpointInstance并将其用于发送。

现在表面上看来,这一切看起来都很整洁,控制器处理客户端请求,使用EF来更改数据库,并使用Endpoint发送NServiceBus消息,但存在问题。虽然EF和NServiceBus都使用相同的SQL数据库来存储数据和消息,但这些任务不是同一个数据库事务的一部分。这意味着存在边缘情况,如果事情出错,EF会将更改保存到数据库,但是NServiceBus消息的发送可能无法完成,现在数据和消息处于不一致状态。

所以问题是,我如何让IEndpointInstance在与EF DbContext保存相同的事务中发送?

我发现在配置EndPoint Transport时,我可以使用UseCustomSqlConnectionFactory扩展方法。我已经能够提供一个从IOC容器中获取Web请求范围中使用的DbContext的工厂,并提取它的SqlConnection并将其提供给Endpoint。这里的问题是从IEndpointInstance.Send返回的任务在完成发送时,我将不会返回.Wait()。

尽管有很多关于使用EF共享包含在IMessageHandlerContext中的事务的文档和示例,但我找不到任何有关让IEndpointInstance与EF共享事务的信息。

回答

2

你当然可以这样做。您可以在DbContext之上实现UnitOfWork,并将其与消息处理程序管道集成。这保证了如果你有多个处理程序,他们都会参与同一个事务。尽管您需要改变行为以在不同的时间为只发送端点执行,但这种方法仅适用于仅发送端点以及发送和接收(尽管底层传输支持这一点)没有“传入”消息用于只发送端点)。

你这样做的方式是,你发来的邮件中注入行为到消息处理流水线:

public class UnitOfWorkSetupBehaviorBehavior : Behavior<IIncomingLogicalMessageContext> 
{ 
    public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next) 
    { 
     var uow = new EntityFrameworkUnitOfWork(); 
     context.Extensions.Set(uow); 
     await next().ConfigureAwait(false); //executes the next op in the chain 
     context.Extensions.Remove<EntityFrameworkUnitOfWork>(); 
    } 
} 

和工作落实的实际单位会是这样(请注意,它使用环境事务):

class EntityFrameworkUnitOfWork 
{ 
    MyDataContext context; 

    public MyDataContext GetDataContext(SynchronizedStorageSession storageSession) 
    { 
     if (context == null) 
     { 
      var dbConnection = storageSession.Session().Connection; 
      context = new MyDataContext (dbConnection); 

      //Don't use transaction because connection is enlisted in the TransactionScope 
      context.Database.UseTransaction(null); 

      //Call SaveChanges before completing storage session 
      storageSession.OnSaveChanges(x => context.SaveChangesAsync()); 
     } 
     return context; 
    } 
} 

如果这一切似乎太艰巨,有一个很大的样品可以download文档网站上。

+0

我错过了一些东西。直到我调用IEndpointInstance.Send()时,才会调用该行为,这意味着工作单元在此之前不会添加到扩展中。同样,我应该如何在Send()之前访问扩展获取? –

+0

我试图在我的MVC控制器中完成的过程是: 1.查询业务数据数据库上下文 2.决定退出或继续 3.更新业务数据数据库上下文 4.发送消息。 那么,如何在端点实例中使用连接对象创建业务数据库上下文,如果该连接在发送前没有暴露在行为中? –

+0

@WilliamLeader在这种情况下,TransactionScope不会有帮助吗?只要您使用EXACT相同的连接字符串共享包含消息+业务数据的数据库,它们都将参与同一事务(不升级到DTC)。 –