question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Question] Blazor Server, Mediator and Entity Framework Core Design Issue

See original GitHub 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?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:6
  • Comments:26 (4 by maintainers)

github_iconTop GitHub Comments

4reactions
richardaubincommented, Nov 20, 2022

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();
        });
    }
}
4reactions
kowalski77commented, Aug 8, 2022

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);
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Blazor Server App and IDbContextFactory not disposing
I am using mediator which will handle all business operations. The documentation that I have seen so far recommends using IDbContextFactory.
Read more >
Blazor Server - EF Core Quirks | The Machine Spirit
By default, EF Core's DbContext is scoped. This will cause issues with asynchronous components accessing data in Blazor Server.
Read more >
Blazor Server - EF Core 6 DB Context problem
I am working on my Blazor Server application. It was working so far but I think I am having some issues with EF...
Read more >
Implementing CQRS with MediatR in ASP.NET Core
In this article let's talk about CQRS in ASP.NET Core 3.1 and it's implementation along with MediatR and Entity Framework Core - Code...
Read more >
CQRS and MediatR in ASP.NET Core
How to use the MediatR NuGet library in ASP.NET Core, and working with the CQRS and Mediator architectural patterns.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found