带有Asp.Net Core中间件或Mvc过滤器的Entity Framework Core 1.0工作单元 [英] Entity Framework Core 1.0 unit of work with Asp.Net Core middleware or Mvc filter

查看:77
本文介绍了带有Asp.Net Core中间件或Mvc过滤器的Entity Framework Core 1.0工作单元的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将EF Core 1.0(以前称为ad EF7)和ASP.NET Core 1.0(以前称为ASP.NET 5)用于RESTful API.

I am using EF Core 1.0 (previously known ad EF7) and ASP.NET Core 1.0 (previously known as ASP.NET 5) for a RESTful API.

我想以某种方式将某个工作单元划分为一个HTTP请求,以便在响应HTTP请求时,要么将对DbContext所做的所有更改都保存到数据库中,要么不保存任何内容(例如是否有例外).

I'd like to have some unit of work scoped to an http request in such a way that when responding to the HTTP request either ALL the changes made to the DbContext will be saved onto the database, or none will be saved (if there was some exception, for example).

过去,我通过使用Action过滤器将WebAPI2与NHibernate一起使用,在该过滤器中,我在执行动作时开始事务,而在执行动作时结束事务并关闭会话.这是 http://isbn.directory/book/9781484201107

In the past I have used WebAPI2 for this purpose with NHibernate by using an Action filter where I begin the transaction on action executing, and on action executed I end the transaction and close the session. This was the way recommended at http://isbn.directory/book/9781484201107

但是,现在我正在使用Asp.Net Core(虽然应该没有关系,但使用Asp.Net Core Mvc)和Entity Framework,据我所知,它已经实现了一个工作单元.

However now I am using Asp.Net Core (with Asp.Net Core Mvc although this should not be relevant) and Entity Framework which, I understood, already implements a unit of work.

我认为将中间件插入ASP.NET管道(在MVC之前)将是正确的方法.这样一个请求就可以了:

I think having a middleware plugged into the ASP.NET pipeline (before MVC) would be the right way to do things. So a request would go:

PIPELINE ASP.NET: MyUnitOfWorkMiddleware ==> MVC控制器==>存储库==> MVC控制器==> MyUnitOfWorkMiddleware

PIPELINE ASP.NET: MyUnitOfWorkMiddleware ==> MVC Controller ==> Repository ==> MVC Controller ==> MyUnitOfWorkMiddleware

我一直在考虑如果没有异常发生,则使该中间件保存DbContext更改,因此在我的存储库实现中,我什至无需执行dbcontext.SaveChanges(),并且一切都将像一个集中式事务.用伪代码,我想那应该是这样的:

I was thinking of having this middleware save the DbContext changes if no exception happened, so that in my repository implementations I don't even need to do dbcontext.SaveChanges() and everything would be like a centralized transaction. In pseudocode I guess it would be something like:

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
     }
}

这有意义吗?有人有类似的例子吗?在这方面,我找不到任何好的做法或建议.

Does this make sense? Does anybody have any example of something similar? I can't find any good practice or recommendation around this.

此外,如果我在MVC控制器级别采用这种方法,那么在发布新资源时我将无权访问数据库创建的任何资源ID,因为在保存dbContext更改之前,该ID不会生成(以后在控制器完成执行之后,在我的中间件中的管道中).如果需要访问控制器中新创建的资源ID,该怎么办?

Also, if I go with this approach at my MVC controller level I would not have access to any resource ID created by the database when POSTing a new resource because the ID would not be generated until the dbContext changes are saved (later on in the pipeline in my middleware AFTER the controller has finished executing). What if I needed to access the newly created ID of a resource in my controller?

任何建议将不胜感激!

更新1 :我发现使用中间件来实现此目标的方法存在问题,因为中间件中的DbContext实例与MVC(和存储库)生存期不同.请参阅问题实体框架核心1.0 DbContext未定义范围发出http请求

更新2 :我尚未找到好的解决方案.到目前为止,基本上是我的选择:

  1. 尽快将更改保存在数据库中.这意味着将其保存在存储库实现本身中.这种方法的问题在于,对于Http请求,也许我想使用几个存储库(即:将某些内容存储在数据库中,然后将blob上传到云存储中),并且为了拥有工作单元,我必须实现一个处理多个实体或什至不止一种持久性方法(DB和Blob存储)的存储库,这违反了整个目的
  2. 实施操作过滤器,其中将整个动作执行包装在数据库事务中.在控制器的动作执行结束时,如果没有异常,我会向数据库提交消息,但是如果有异常,我将回滚并丢弃上下文.问题是我的控制器的操作可能需要一个生成的实体ID才能将其返回给http客户端(即:如果我收到POST/api/cars,我想返回201接受并带有一个位置标头,该标头标识在/api/cars/123和Id 123创建的新资源将不可用,因为该实体尚未保存在DB中,并且Id仍然是临时0).控制器对POST动词请求的操作示例:

  1. Save the changes in DB as soon as possible. That means saving it on the repository implementation itself. The problem with this approach is that for an Http request maybe I want to use several repositories (i.e: save something in database and then upload a blob to a cloud storage) and in order to have a Unit of Work I would have to implement a repository that deals with more than one entity or even more than one persistance method (DB and Blob Storage), which defeats the whole purpose
  2. Implement an Action Filter where I wrap the whole action execution in a DB transaction. At the end of the controller's action execution, if there are no exceptions I commit chanches to DB but if there are exceptions I rollback and discard the context. The problem with this is that my controller's action may need a generated Entity's Id in order to return it to the http client (i.e: If I get a POST /api/cars I would like to return a 201 Accepted with a location header that identifies the new resource created at /api/cars/123 and the Id 123 would not be available yet since the entity has not been saved in DB and the Id is still a temporary 0). Example in controller's action for a POST verb request:

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

我如何将整个控制器的动作包装在DB事务中,同时又可以使用数据库生成的任何ID以便从控制器的Http响应中返回它?或者.. 一旦提交了数据库更改,是否有任何优雅的方法来覆盖http响应并在操作过滤器级别设置ID?

How could I have the whole controller's action wrapped in a DB transaction and at the same time have available any Id generated by the database in order to return it in the Http Response from the controller? Or.. is there any elegant way to overwrite the http response and set the Id at the action filter level once the DB changes have been commited?

更新3:根据 nathanaldensr 的评论,我可以同时兼顾两者通过使用代码生成的Guid而不是依靠数据库来生成Guid(通过在数据库事务_ UoW中包装控制器的动作执行,并且即使在DB提交更改之前也知道创建的新资源的ID).

UPDATE 3: As per nathanaldensr's comment I could get the best of both worlds (wrapping my controller's action execution in a DB transaction _ UoW and also knowing the Id of the new resource created even before the DB commits changes) by using code generated Guids instead relying on database to generate the Guid.

推荐答案

我也面临着同样的问题,并且不确定采用哪种方法. 我使用的一种方法如下:

I am also facing the same issue and not sure which approach to follow. One of the approach that I used is as follow:

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

这篇关于带有Asp.Net Core中间件或Mvc过滤器的Entity Framework Core 1.0工作单元的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆