MediatR
MediatR copied to clipboard
[Question] Blazor Server, Mediator and Entity Framework Core Design Issue
We have run into an issue in our application that is crashing the site: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext.
Which lead me to this article: ASP.NET Core Blazor Server with Entity Framework Core (EFCore). The recommended approach is to create a DbContextFactory, which is then demonstrated like this:
using var context = DbFactory.CreateDbContext();
var contact = await context.Contacts.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
Using the Mediator pattern the code would like more like this:
var contact = await Mediator.Send(new GetContact());
The GetContactHandler's constructor would then receive the context through dependency injection. I could change the GetContactHandler to receive an IDbContextFactory instead but I don't always want to new up a new context. I think only the Blazor server interface should care about this problem and the deeper layer's should not be adjusted to accomodate it.
What is the recommended way to handle this Blazor Server-Side concern with Mediator?
Jon Hilton blogged about using Blazor server and Mediator which may be of use.:
https://jonhilton.net/mediatr-and-razor-components/
Jon Hilton blogged about using Blazor server and Mediator which may be of use.:
https://jonhilton.net/mediatr-and-razor-components/
That does not solve the problem. That example does not use EF Core. In fact, if you were to change it to EF Core, you would walk yourself right into this problem. :(
The only way we have seen to fix this problem so far is to put in an API layer like you would with Blazor WASM and call it from the server.
I left a comment on the blog post though to see if he has any feedback.
@scottkuhl which DI lifecycle are you using for your DbContext? I use XPO, another ORM, but it exhibits similar cross-threading problems. I seem to have solved them by registering the DbContext as scoped and then also registering MediatR as scoped like so:
services.AddMediatR(c => c.AsScoped(), assembliesToScan);
We are using the default:
services.AddMediatR(Assembly.GetExecutingAssembly());
I am not sure if that will fix it or not. At this point we have moved on to removing MediatR.
This sounds like a Blazor issue, but I'd remove MediatR and just use your dependencies directly to nail down the problem. I've never used Blazor so I don't know its container lifecycle design.
Yes, it is a Blazor Server issue when combined with EF. And yes, we did remove MediatR to get it working. The best route seems to be creating a Web API project with MediatR and don't even talk to the database at all from Blazor server. I was hoping there was an easier solution than that for projects that have no need for an API that run entirely on the server.
I would assume more people will run into this in the future unless Microsoft changes something in either Blazor Server or EF to remove the dependency on the EF factory pattern.
@scottkuhl Couldn't you register the DbContext as transient? This should inject a unique DbContext into each MediatR-handler, thereby avoiding any threading issues on the DbContext.
I don’t think so. See the article I first referenced. None of the lifetime registrations work with Blazor and EF. You have to use the EF factory.
I'm about to add MediatR to a Blazor Server project, so in this case, I would have to inject a IDbContextFactory
object into IRequestHandler
constructor, use it as normal and it should work right?
Edit: well I'm using the factory and the app hasn't crashed so far
I have run into this same problem, but it looks like adding the IDbContext as transient 'can' be the solution here - it depends on how you use MediatR and the IDbContext.
I am working on switching an application to Blazor, and I have a lot of MediatR handlers where the IDbContext is being injected into them.
I came across the same article recommending using the IDbContextFactory approach, which struck fear into me, as I really don't want to change all of my MediatR handlers to use a IDbContextFactory instead.
I understand why the IDbContextFactory approach is suggested where the IDbContext is used directly in the Blazor component (as per the examples within the article), however when using MediatR, and having the IDbContext as transient, it will inject a unique instance of IDbContext every time the MediatR handler is called - which should mitigate these issues. This sounds logical and certainly, from my testing so far, I have not run into any issues with doing this.
In conclusion, it seems that IDbContextFactory should be used in situations where an IDbContext is used directly in a Blazor component, but it is safe to inject the IDbContext into a MediatR handler (having the IDbContext as transient) when using MediatR in the Blazor components.
It would be interesting to hear thoughts on this and if others have also found that injecting the IDbContext as transient when using MediatR has worked for them.
I have the added complexity that I have a multi-tenant situation where the connection string may change. So I can't use DI for my DbContexts which means I can't use Mediatr. Even using an IDbContextFactory won't work as I can't set the proper connection string on the creation of the context from the factory. This is very frustrating as I am keen to use the Mediatr pipeline for logging requests.
You can make an IMultiTenantDbContextProvider and handle any connectionstring/tenant specific creation and caching of DbContext in there. Inject that into your handlers.
Don't be afraid to write a little bit of own infrastructure code.
How do you unit test the Mediatr that was being called on blazor component? I'm calling a mediatr Send on my component to fetch static data, but I ran into several issue when trying to unit test the component via VerifyBlazor because the html markup or data being rendered are randomly displayed/pickup from Handler
How do you unit test the Mediatr that was being called on blazor component? I'm calling a mediatr Send on my component to fetch static data, but I ran into several issue when trying to unit test the component via VerifyBlazor because the html markup or data being rendered are randomly displayed/pickup from Handler
You unit test the handler itself separately typically.
When you're testing the component, then you mock up the response being returned by Mediatr using a mocking tool like Moq, or NSubstitute. Then you know exactly what data you're getting back and what the resulting HTML should look like.
i am using IUnitOfWork Pattern and mediatR at the same time in blazor Server
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); services.AddScoped<IunitOfWork>(serviceProvider => serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
then i assigned to IDisposable to IunitOfWork
public interface IUnitOfWork : IDisposable
the i get MediatR from such a way
@inherits OwningComponentBase<IMediator>
but i got this error whenever MediatR runs twice:
A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext
System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.
How about creating a support/facade class to create a scope when calling the mediatr methods? Something like this:
public async Task<TResponse> SendAsync<TResponse>(
IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
using var scope = this.serviceProvider.CreateAsyncScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
return await mediator.Send(request, cancellationToken);
}
Also interested in this
+1 Just ran into the same problem...
How about creating a support/facade class to create a scope when calling the mediatr methods? Something like this:
public async Task<TResponse> SendAsync<TResponse>( IRequest<TResponse> request, CancellationToken cancellationToken = default) { using var scope = this.serviceProvider.CreateAsyncScope(); var mediator = scope.ServiceProvider.GetRequiredService<IMediator>(); return await mediator.Send(request, cancellationToken); }
This is the solution I came up with as well.
I created a AsyncScopedMediator class which implements an IScopedMediator interface that gets injected in my blazor components. The request handlers are injected with EF Core Repositories, but could easily be changed to accept a DbContext in the constructor.
The implementation has the scope logic contained within the IScopedMediator.Send method:
public class AsyncScopedMediator : IScopedMediator
{
private readonly IServiceScopeFactory _scopefactory { get; }
public AsyncScopedMediator(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
using (var scope = scopeFactory.CreateAsyncScope())
{
var mediator = scope
.ServiceProvider.GetRequiredService<Mediator>();
return await mediator.Send(request, cancellationToken);
}
}
}
And I inject it in my components where I need the scoped dbcontext.
[Inject] IScopedMediator Mediator { get; set; }
protected override async Task OnInitializedAsync()
{
var request = new GetAllProductFeedsRequest(true); // ICacheableRequest<TResponse> : IRequest<TResponse>
var result = await Mediator.Send(request);
...
}
--
I also added a MemoryCacheBehavior which uses an ICacheableRequest which my request object above implements.
I did this to address the ServerPrerender double loading of database requests that happens within the OnInitializationAsync() method.
public interface ICacheableRequest<TResponse> : IRequest<TResponse>
{
bool Cacheable { get; }
double CacheExpirySeconds { get; }
}
public class MemoryCacheBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse> where TRequest : ICacheableRequest<TResponse>
{
private readonly IMemoryCache _memoryCache;
public MemoryCacheBehavior(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (request.Cacheable == false) await next();
return await _memoryCache.GetOrCreate(request.GetType().FullName, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(request.CacheExpirySeconds),
});
// Debug.WriteLine("Uncached");
return await next();
});
}
}
@richardaubin What is IScopeFactory
? Is that the same as IServiceScopeFactory
? My implementation looked similar, but I injected the whole IServiceProvider
. Just wanted to make sure I'm not missing any advantages.
I think the correct way to handle this is to target IServiceProvider
directly in MediatR, which I'm looking at for the next major release. Then I'll be able to offer overloads or a different implementation altogether like ScopeProviderMediator
etc.
@richardaubin What is
IScopeFactory
? Is that the same asIServiceScopeFactory
? My implementation looked similar, but I injected the wholeIServiceProvider
. Just wanted to make sure I'm not missing any advantages.
Yes you are correct, I edited my comment to reflect that. Thank you.
I think the correct way to handle this is to target
IServiceProvider
directly in MediatR, which I'm looking at for the next major release. Then I'll be able to offer overloads or a different implementation altogether likeScopeProviderMediator
etc.
What do you mean by "target IServiceProvider
directly"? We can already inject IServiceProvider
, right?
I mean get rid of the custom delegate type that is injected into the Mediator class.
@jbogard The latest release notes seem to indicate that IServiceProvider is targeted directly in MediatR now. Does this mean we can conceivably inject MediatR without a problem in Blazor Server now? I didn't follow the implementation changes very closely.
@scottkuhl this way is working
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
builder =>
{
builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName);
builder.EnableRetryOnFailure(maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null);
builder.CommandTimeout(15);
});
options.EnableDetailedErrors(detailedErrorsEnabled: true);
options.EnableSensitiveDataLogging();
});
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddScoped<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>();
services.AddTransient<IApplicationDbContext>(provider => provider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
I think the correct way to handle this is to target
IServiceProvider
directly in MediatR, which I'm looking at for the next major release. Then I'll be able to offer overloads or a different implementation altogether likeScopeProviderMediator
etc.
@jbogard Hi Jimmy, looking this again since last year. Have you done anything with this yet / example(s)?