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

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

问题描述

我将 EF Core 1.0(以前称为广告 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,尽管这不相关)和实体框架,据我所知,它已经实现了一个工作单元.

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 Controller ==> Repository ==> MVC Controller ==> 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(和存储库)生命周期中的实例不同.请参阅问题 Entity Framework Core 1.0 DbContext not scoped到 http 请求

更新 2:我还没有找到好的解决方案.到目前为止,基本上这些是我的选择:

  1. 尽快将更改保存在数据库中.这意味着将其保存在存储库实现本身上.这种方法的问题在于,对于 Http 请求,我可能想使用多个存储库(即:在数据库中保存某些内容,然后将 blob 上传到云存储),并且为了拥有一个工作单元,我必须实现一个处理多个实体甚至多个持久性方法(DB 和 Blob 存储)的存储库,这违背了整个目的
  2. 实施操作过滤器 我将整个动作执行包装在一个数据库事务中.在控制器的动作执行结束时,如果没有异常,我将更改提交给数据库,但如果有异常,我将回滚并丢弃上下文.问题在于我的控制器的操作可能需要一个生成的实体的 Id 才能将它返回给 http 客户端(即:如果我得到一个 POST/api/cars 我想返回一个 201 Accepted 和一个标识在/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 { car​​Id= carSummaryCreated.Id }, carSummaryCreated);//carSummaryCreated.Id 将为 0,直到更改保存在 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,worlds(将我的控制器的动作执行包装在一个 DB 事务_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();
    }
}

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

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