I wanted to call the entity framework's SaveChangesAsync()
for every request in an IAsyncActionFilter
.
Here is my IAsyncActionFilter:
DbContextSaveChangesFilter.cs
public class DbContextSaveChangesFilter : IAsyncActionFilter
{
private readonly DbContext _dbContext;
public DbContextSaveChangesFilter(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var result = await next();
if (result.Exception == null || result.ExceptionHandled)
{
await _dbContext.SaveChangesAsync();
}
}
}
So, the SaveChangesAsync()
will be called after any changes are made by my service methods:
Service.cs
private readonly DbContext _dbContext
public MyService(DbContext dbContext) {
_dbContext = dbContext;
}
public void SomeServiceMethod() {
var myEntity = _dbContext.MyEntities.First();
myEntity.Name = model.Name;
}
Problem
The problem is that I am getting a two different references to _dbContext
in the service and in the filter. That is, the _dbContext
that is injected into my service is different instance from the one injected in the filter.
Calling _dbContext.GetHashCode()
in the filter and in the service results in two different numbers.
Versions
.NET Core: 2.2.402
ASP.NET: 2.2.0
EF Core: 2.2.6
Sql Server:2019 EXPRESS
EDIT:
This is how I am configuring my DbContext in Startup.cs
services.AddDbContext<MyDbContext>(opts =>
{
opts.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
and here is how I am registering my filter inside Startup.cs
in ConfigureServices()
method:
services.AddMvc(o =>
{
o.Filters.Add(typeof(DbContextSaveChangesFilter));
});
First, you can't do this with IAsyncActionFilter
, because OnActionExecutionAsync
is
Called asynchronously before the action, after model binding is complete.
So you're too early, which probably means that the filter has received its own context instance from the DI container. Which was also to be expected since the filter is asynchronous. But even with a shared instance SaveChanges
wouldn't do anything at that point.
So if you want to do this you need to implement IActionFilter
, and use its OnActionExecuted
method. I think the DI container will supply you with the same context as the one in the controller, when it's registered as scoped to the request. If not, you might want to play around with ActionExecutingContext
's Controller
property, but that always requires code constructs in which the controller's context must be publicly (or at least internally) accessible.
As you see, I'm not spelling this out in code, because I think you shouldn't do this. Call SaveChanges
visibly where it's needed and nowhere else. You certainly don't want to risk saving changes that aren't supposed to be saved (it's easily overlooked). Nor do you want to call SaveChanges
when a method only reads data.
I suspect you are actually injecting MyDbContext
into MyService
(not DbContext
as shown - DbContext
would not have a MyEntities
property).
The AddDbContext
method only adds an instance of MyDbContext
to your pipeline, not DbContext
.
Try to either:
Change DbContextSaveChangesFilter
to take an instance of MyDbContext
instead of DbContext
:
private readonly MyDbContext _dbContext;
public DbContextSaveChangesFilter(MyDbContext dbContext)
{
_dbContext = dbContext;
}
or:
Tell your pipeline to also resolve DbContext
to MyDbContext
:
services.AddScoped<DbContext, MyDbContext>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With