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.
I\'d like to have some unit of work scoped t
My advice, use dbContext.SaveChanges() in the controller as it is demonstrated in all examples over the web. What you want to do sounds quite fancy and could backfire as you guessed at the end of your post. And IMO, it doesn't make sense.
Regarding your second question/task:
....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).
I think you need something like 'transaction-per-request'. It is just an idea, haven't tested it at all. I just put the code together in this sample middleware:
public class TransactionPerRequestMiddleware
{
private readonly RequestDelegate next_;
public TransactionPerRequestMiddleware(RequestDelegate next)
{
next_ = next;
}
public async Task Invoke(HttpContext context, DbContext dbContext)
{
var transaction = dbContext.Database.BeginTransaction(
System.Data.IsolationLevel.ReadCommitted);
await next_.Invoke(context);
if (context.Response.StatusCode == 200)
{
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
}
Good luck
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();
}
}
As per Entity Framework Core 1.0 DbContext not scoped to http request I could not use a middleware to achieve this because the instance of DbContext that the middleware gets injected is not the same as the DbContext during MVC execution (in my controllers, or repositories).
I had to go with a similar approach to save the changes in DbContext after the controller's action execution using a Global Filter. There is no official documentation yet about filters in MVC 6 so if anybody is interested on this solution see below the filter and the way I make this filter global so that it executes before any controller's action.
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");
}
}
}
and the filter gets plugged into my MVC at Startup.cs
when configuring 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));
});
//..
}
This still leaves a question (see UPDATE 2 on my question). What if I want my controller to respond to an http POST request with a 201 Accepted with a Location header that includes the Id of the entity created in DB? When the controller's action finalises execution the changes have not yet been committed to DB therefore the Id of the entity created is still 0 until the action filter saves changes and the DB generates a value.