14

我使用EF核心1.0(以前稱爲廣告EF7)和ASP.NET核心1.0(以前稱爲ASP.NET 5),用於一個RESTful API 。實體框架核心1.0 Asp.Net核心中間件或過濾器的mvc工作單元

我希望有一些工作單元的作用範圍爲http請求,這樣當響應HTTP請求時,對DbContext所做的所有更改都將保存到數據庫中,或者不會保存(例如,如果有一些例外)。

在過去,我使用WebAPI2來實現NHibernate的這個目的,通過使用Action過濾器來開始執行動作的事務,並在執行的動作上結束事務並關閉會話。這其中,我的理解,已經實現了工作單位在http://isbn.directory/book/9781484201107

但是現在推薦我使用Asp.Net核心(與Asp.Net核心的mvc雖然這不應該是相關的)的方式和Entity Framework。

我認爲有插入ASP.NET管道(MVC)之前中間件是做事情的正確方法。因此,請求會去:

管道ASP.NET:MyUnitOfWorkMiddleware ==> MVC控制器==>倉庫==> MVC控制器==> MyUnitOfWorkMiddleware

我想有這個中間件保存的DbContext的如果沒有異常發生,那麼更改,以便在我的存儲庫實現中,我甚至不需要執行dbcontext.SaveChanges(),並且所有內容都將像集中式事務一樣。在僞代碼,我想它會是這樣的:

class MyUnitOfWorkMiddleware 
{ 
    //.. 
    1-get an instance of DbContext for this request. 
    try { 
     2-await the next item in the pipeline. 
     3-dbContext.SaveChanges(); 
    } 
    catch (Exception e) { 
     2.1-rollback changes (simply by ignoring context) 
     2.2-return an http error response 
    } 
} 

這是否有意義?有人有類似的例子嗎?我無法找到任何好的做法或建議。如果我在我的MVC控制器級別上使用這種方法,我將無法訪問數據庫在創建新資源時創建的任何資源ID,因爲在保存dbContext更改之前不會生成ID(稍後在控制器完成執行後,在我的中間件管道中)。如果我需要訪問控制器中新創建的資源ID,該怎麼辦?

任何意見將不勝感激!

更新1我發現我的做法有問題使用中間件來實現這一點,因爲在中間件的DbContext實例是不一樣的MVC(和存儲庫)一生中。看到問題Entity Framework Core 1.0 DbContext not scoped to http request

更新2我還沒有找到一個好的解決辦法。基本上這些是我的選擇到目前爲止:

  1. 儘快保存DB中的更改。這意味着將其保存在存儲庫實現本身上。這種方法的問題是,對於一個Http請求,可能我想使用幾個存儲庫(即。e:將數據保存在數據庫中,然後將blob上傳到雲存儲),爲了獲得工作單元,我必須實現一個處理多個實體或甚至多個持久性方法的存儲庫(DB和Blob存儲),這會破壞整個目的
  2. 執行一個Action Filter,其中我將整個操作執行包裝在一個數據庫事務中。在控制器的動作執行結束時,如果沒有例外,我向數據庫提交chanches,但是如果有例外,我將回滾並放棄上下文。問題在於我的控制器的動作可能需要一個生成的實體的Id,以便將其返回給http客戶端(即:如果我得到一個POST/api/cars,我想返回一個帶有位置標題的201 Accepted,在/ api/cars/123和Id 123創建的新資源將不可用,因爲實體未保存在數據庫中,並且Id仍然是臨時的0)。通過以數據庫生成

    return CreatedAtRoute("GetCarById", new { carId= carSummaryCreated.Id }, carSummaryCreated); //carSummaryCreated.Id would be 0 until the changes are saved in DB

我怎麼可能把整個控制器的動作包裹在一個數據庫事務,並在同一時間有任何可用的標識:例如,在控制器的一個POST命令請求採取行動從控制器的Http Response中返回它?或者.. 是否有任何優雅的方式來覆蓋http響應,並在數據庫更改已被提交後在操作篩選器級別設置Id?

更新3:nathanaldensr「的評論,我能得到兩全其美的(包裹我的控制器的動作執行的DB交易_ UOW也知道DB前,甚至創造新的資源的ID提交更改)通過使用代碼生成的Guids而不是依賴數據庫來生成Guid。

回答

5

我也面臨同樣的問題,並不確定遵循哪種方法。 一,我所使用的方法是如下:

public class UnitOfWorkFilter : ActionFilterAttribute 
{ 
    private readonly AppDbContext _dbContext; 

    public UnitOfWorkFilter(AppDbContext dbContext,) 
    { 
     _dbContext = dbContext; 
    } 

    public override void OnActionExecuted(ActionExecutedContext context) 
    { 
     if (!context.HttpContext.Request.Method.Equals("Post", StringComparison.OrdinalIgnoreCase)) 
      return; 
     if (context.Exception == null && context.ModelState.IsValid) 
     { 
      _dbContext.Database.CommitTransaction(); 
     } 
     else 
     { 
      _dbContext.Database.RollbackTransaction(); 
     } 
    } 

    public override void OnActionExecuting(ActionExecutingContext context) 
    { 
     if (!context.HttpContext.Request.Method.Equals("Post", StringComparison.OrdinalIgnoreCase)) 
      return; 
     _dbContext.Database.BeginTransaction(); 
    } 
} 
+0

這是一個很好的解決方案,謝謝 – iberodev

+0

我喜歡你可以裝飾任何行動或控制器與工作單元過濾器在mvc生命週期中包裹交易中的一切。 – iberodev

+0

問題仍然是當你需要一個實體ID來繼續在同一個http請求中做事時該怎麼做。在數據庫中提交更改之前,不會生成Id。通過這種方法,只有控制器完成執行後才能保存更改。 – iberodev

1

我的建議,在控制器中使用dbContext.SaveChanges(),因爲它在Web上的所有示例中都有演示。你想要做的事聽起來很花哨,可能會因爲你在帖子末尾猜到的事情而適得其反。而國際海事組織,這是沒有道理的。

關於你提到的第二個問題/任務:

....響應HTTP請求要麼到的DbContext所做的所有修改都會被保存到數據庫中,或沒有將被保存時(如果有例如是一些例外)。

我認爲你需要像'每次請求交易'這樣的東西。這只是一個想法,根本沒有經過測試。我只是把代碼放在一起在此示例中間件:

public class TransactionPerRequestMiddleware 
{ 
    private readonly RequestDelegate next_; 

    public TransactionPerRequestMiddleware(RequestDelegate next) 
    { 
     next_ = next; 
    } 

    public async Task Invoke(HttpContext context, DbContext dbContext) 
    { 
     var transaction = dbContext.Database.BeginTransaction(
      System.Data.IsolationLevel.ReadCommitted); 

     await next_.Invoke(context); 

     if (context.Response.StatusCode == 200) 
     { 
      transaction.Commit(); 
     } 
     else 
     { 
      transaction.Rollback(); 
     } 
    } 
} 

好運

+0

我知道它在一些例子中顯示,但他們只是一個例子。對於真正的大型n層應用程序,我認爲將DbContext注入控制器並不是一個好主意,因爲它打破了關注點的分離,並在服務層和持久層之間創建了一個依賴關係(即使是EntityFramework的依賴關係)和這就是我使用存儲庫模式的原因。 恐怕這不是一個有效的答案:) – iberodev

+0

我的DbContext已經被作用於一個Http請求,並且在按照https://msdn.microsoft.com/zh-CN/保存更改時,DbContext(因爲EF 6) us/data/dn456843.aspx 這就是爲什麼我想知道中間件是否僅僅是一次SaveChanges(不需要顯式事務)的完美位置。但是這個想法是相似的,是的。在控制器完成執行後包裝事務內的所有內容並提交事務(保存上下文更改) – iberodev

+1

您可以在DbContext周圍實現您自己的UnitOfWork。我的評論是針對SaveChanges方法的,不管它是由DbContext還是你的unitOfWork對象公開的都沒關係。我沒有正確表達自己,因爲我也不喜歡直接在控制器中注入上下文。 :) – regnauld

12

Entity Framework Core 1.0 DbContext not scoped to http request 我不能使用中間件來實現這一點,因爲該中間件被注入的DbContext的實例不是與MVC執行過程中的DbContext相同(在我的控制器或存儲庫中)。

我不得不採用類似的方法,在控制器的動作執行後使用全局過濾器保存DbContext中的更改。 目前還沒有關於MVC 6中過濾器的官方文檔,所以如果有人對此解決方案感興趣,請參閱下面的過濾器和我將此過濾器設置爲全局的方式,以便它在任何控制器的操作之前執行。

public class UnitOfWorkFilter : ActionFilterAttribute 
{ 
    private readonly MyDbContext _dbContext; 
    private readonly ILogger _logger; 

    public UnitOfWorkFilter(MyDbContext dbContext, ILoggerFactory loggerFactory) 
    { 
     _dbContext = dbContext; 
     _logger = loggerFactory.CreateLogger<UnitOfWorkFilter>(); 
    } 

    public override async Task OnActionExecutionAsync(ActionExecutingContext executingContext, ActionExecutionDelegate next) 
    { 
     var executedContext = await next.Invoke(); //to wait until the controller's action finalizes in case there was an error 
     if (executedContext.Exception == null) 
     { 
      _logger.LogInformation("Saving changes for unit of work"); 
      await _dbContext.SaveChangesAsync(); 
     } 
     else 
     { 
      _logger.LogInformation("Avoid to save changes for unit of work due an exception"); 
     } 
    } 
} 

和過濾器被插入到我的MVC在Startup.cs配置MVC時。

public void ConfigureServices(IServiceCollection services) 
{ 
    //.. 
    //Entity Framework 7 
    services.AddEntityFramework() 
      .AddSqlServer() 
      .AddDbContext<SpeediCargoDbContext>(options => { 
       options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]); 
      }); 

     //MVC 6 
     services.AddMvc(setup => 
     { 
      setup.Filters.AddService(typeof(UnitOfWorkFilter)); 
     }); 
    //.. 
} 

這仍然留下一個問題(請參閱我的問題上的更新2)。 如果我想讓我的控制器響應一個包含在數據庫中創建的實體標識的201接受的位置標題的http POST請求,該怎麼辦?當控制器的操作完成執行時,更改尚未提交給數據庫,因此創建的實體的Id仍爲0,直到操作篩選器保存更改並且DB生成一個值。

相關問題