2017-10-12 256 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)。 –