Entity Framework Core 1.0與Asp.Net Core中間件或Mvc過濾器一起工作

asp.net-core-1.0 entity-framework-core middleware unit-of-work

我正在使用EF Core 1.0(以前稱為廣告EF7)和ASP.NET Core 1.0(以前稱為ASP.NET 5)來使用RESTful API。

我希望將一些工作單元限定為http請求,以便在響應HTTP請求時,對DbContext所做的所有更改都將保存到數據庫中,或者不保存任何更改(如果有的話)一些例外,例如)。

在過去,我通過使用一個Action過濾器將NHAPnate用於此目的的WebAPI2,其中我開始執行操作的事務,並且在執行的操作上我結束事務並關閉會話。這是http://isbn.directory/book/9781484201107推薦的方式

但是現在我使用Asp.Net Core(與Asp.Net Core Mvc雖然這不應該相關)和實體框架,據我所知,已經實現了一個工作單元。

我認為將中間件插入ASP.NET管道(在MVC之前)將是正確的做事方式。所以請求會:

PIPELINE ASP.NET:MyUnitOfWorkMiddleware ==> MVC Controller ==> Repository ==> MVC Controller ==> 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控制器級別使用此方法,則在POST新資源時無法訪問數據庫創建的任何資源ID,因為在保存dbContext更改之前不會生成ID(稍後在管道中)在我的中間件控制器完成執行後)。如果我需要在控制器中訪問新創建的資源ID,該怎麼辦?

任何建議將不勝感激!

更新1我發現使用中間件來實現此問題的方法存在問題,因為中間件中的DbContext實例與MVC(和存儲庫)生命週期中的實例不同。請參閱問題實體框架核心1.0 DbContext未限定為http請求

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

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

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

如何將整個控制器的操作包裝在數據庫事務中,同時可以使用數據庫生成的任何Id,以便從控制器的Http響應中返回它?或者.. 有沒有優雅的方法來覆蓋http響應並在提交數據庫更改後在動作過濾器級別設置Id?

更新3:根據nathanaldensr的評論,我可以通過使用生成的代碼獲得兩個世界中最好的(包括我的控制器在DB事務中的操作執行_UoW並且知道甚至在數據庫提交更改之前創建的新資源的Id) Guids反而依靠數據庫來生成Guid。

一般承認的答案

我也面臨同樣的問題,不確定採用哪種方法。我使用的方法之一如下:

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();
    }
}

熱門答案

根據Entity Framework Core 1.0 DbContext沒有限定為http請求我無法使用中間件來實現這一點,因為中間件注入的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 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");
        }
    }
}

這仍然留下一個問題(請參閱我的問題的更新2)。 如果我希望我的控制器響應帶有201 Accepted的http POST請求以及包含在DB中創建的實體的Id的Location頭,該怎麼辦?當控制器的操作完成執行時,更改尚未提交給DB,因此創建的實體的Id仍為0,直到操作篩選器保存更改並且DB生成值。




許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因