我希望有一个单位的工作范围到一个http请求,在响应HTTP请求时,对DbContext所做的所有更改都将保存到数据库中,否则将保存(如果有的话)一些例外,例如).
在过去我已经使用WebAPI2与NHibernate通过使用一个Action过滤器,我开始执行操作的事务,并且执行的操作我结束事务并关闭会话.这是http://isbn.directory/book/9781484201107推荐的方式
不过现在我正在使用Asp.Net Core(Asp.Net Core Mvc,尽管这不应该是相关的),而我理解的实体框架已经实现了一个工作单元.
我认为有一个中间件插入到ASP.NET管道(在MVC之前)将是正确的办法.所以请求会去:
PIPELINE 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控制器级别使用这种方法,在POST新的资源时,将无法访问数据库创建的任何资源ID,因为在保存dbContext更改之前不会生成该ID(稍后在管道中)在控制器完成执行后的中间件中).如果我需要访问控制器中新创建的资源ID,该怎么办?
任何建议将不胜感激!
更新1:我发现使用中间件来实现这一点的问题,因为中间件中的DbContext实例与MVC(和存储库)生命周期中的实例不同.见问题Entity Framework Core 1.0 DbContext not scoped to http request
更新2:我还没有找到一个很好的解决方案.基本上这些是我的选择:
>尽快保存数据库中的更改.这意味着将其保存在存储库实现本身.这种方法的问题是,对于Http请求,也许我想使用几个存储库(即:将数据保存在数据库中,然后将BLOB上传到云存储),为了拥有工作单位,我必须实现一个处理多个实体或甚至多个持久性方法(DB和Blob存储)的存储库,其破坏了整个目的
>实现一个Action Filter,我将整个操作执行包在一个DB事务中.在控制器的动作执行结束时,如果没有异常,我向DB提交chanches,但如果有异常,我回滚并舍弃上下文.这样做的问题是,我的控制器的动作可能需要一个生成的实体的Id才能将其返回到http客户端(即:如果我得到一个POST / api / cars,我想返回一个201接受的位置标题,标识在/ api / cars / 123和Id 123创建的新资源将不可用,因为实体尚未保存在DB中,Id仍为临时0).控制器对POST动词请求的操作示例:
return CreatedAtRoute(“GetCarById”,new {carId = carSummaryCreated.Id},carSummaryCreated); //carSummaryCreated.Id将为0,直到更改保存在DB中
我如何将整个控制器的操作包装在一个DB事务中,同时可以使用数据库生成的任何Id,以便从控制器返回Http响应?或者..有没有任何优雅的方式来覆盖http响应,并在数据库更改被提交后将动作过滤器级设置为Id?
更新3:根据nathanaldensr的评论,我可以通过使用代码生成Guids来获得两个世界中最好的(将控制器的操作执行包装在DB事务_ UoW中,并且知道即将在数据库提交更改之前创建的新资源的Id)依靠数据库生成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(); }}