Entity Framework Core 1.0与Asp.Net Core中间件或Mvc过滤器一起工作

ibe*_*dev 16 middleware unit-of-work entity-framework-core asp.net-core-1.0

我使用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
     }
}
Run Code Online (Sandbox Code Playgroud)

这有意义吗?有没有人有类似的例子?我无法找到任何良好的做法或建议.

此外,如果我在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,并且知道在DB提交更改之前创建的新资源的Id) Guids反而依靠数据库来生成Guid.

ibe*_*dev 13

根据实体框架核心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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs在配置MVC时,过滤器会插入我的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));
        });
   //..
}
Run Code Online (Sandbox Code Playgroud)

这仍然存在一个问题(请参阅我的问题的更新2).如果我希望我的控制器响应带有201 Accepted的http POST请求以及包含在DB中创建的实体的Id的Location头,该怎么办?当控制器的操作完成执行时,更改尚未提交给DB,因此创建的实体的Id仍为0,直到操作筛选器保存更改并且DB生成值.


小智 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 不要使用数据库生成的ID.而是使用代码生成的GUID.这样做有几个好处,您可以通过快速Google搜索阅读. (2认同)