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:我还没有找到一个好的解决方案.基本上这些是我目前的选择:
实现一个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)
| 归档时间: |
|
| 查看次数: |
8529 次 |
| 最近记录: |