2

I have situation like this: I have two Blazor components, both using dbContext and I get popular error

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

I want to use OwningComponentBase which actually just does ScopeFactory.CreateScope() and in theory that should work because new it will use new instance of dbContext.

However, now that creates another problem. My MyService class has a dependency on MyDbContext, but also on MyAuthState and now MyAuthState is lost since it is not resolved from rootProvider but this is new instance:

class MyService(MyDbContext ctx, MyAuthState myAuthState)
{
}

I noticed there is no public API to create all the scoped services except this one.

3
  • 1
    OwningComponentBase turns out to be pretty useless, principally for the reason you've identified. Commented Aug 21 at 9:13
  • Or you could add DbContext as transient dependency Commented Aug 21 at 9:56
  • You dont register it in Startup.cs as a service? services.AddDbContext<... And services.AddScoped<IMyService, MyService>(); Commented Aug 21 at 10:49

2 Answers 2

3

The short solution probably is to not use a separate Scope but to inject a DbContextFactory into each component. The (Owning) component should then create and dispose the DbContext.

But having a DbContext on your pages is a code smell, and setting up a scope in each component is also far from ideal.

Consider using the mediator pattern and implement a ScopedMediator around Send/Publish. That way your Services can follow traditional architectures and can also be used by controller endpoints.

I have used this in Blazor Serverside and it works well. For QuickGrid, use an ItemsProvider that sends queries.

Here is small PoC with a ScopedMediator and one simple query.

Sign up to request clarification or add additional context in comments.

3 Comments

Also the Mediator Pattern fits well with using CQS and transactional Db Contexts.
Yes, that's what I meant by "traditional architectures". I'm used to decoupling Controllers from Services with Mediatr. With a ScopedMediator everything fits together nicely.
This is what I did actaully. Injected DbContextFactory inside MyService class (which had dependency on MyDbContext). This way it behaves similar as before with only difference that each new instance of MyService gets new instance of DbContext
1

The source of the problem here is that, with Razor, each user session gets its own service scope. This means that Scoped -and even Transient- instances will be kept alive for the duration of the session. While these Scoped and Transient services will be isolated per user, they can live for hours (or even days) depending on how long the user is keep using the site. Services are reused across all requests the user makes.

And here lies the problem, because users are not guaranteed to work with the application in a single-threaded way. It's easy for the user to make a second request to the server, while the first request is still running. This is a problem for things like Entity Framework's DbContext instances, because they are not thread safe. The error message you got is an indication of this.

Not that in this respect, it doesn't matter whether you inject a DbContext directly into your pages or whether you inject the DbContext deep down into the dependency graph. The DbContext will still be cached.

Besides thread safety, there are other issues with keeping a DbContext alive for that long, because DbContext will cache entities and those get stale over time. Changes from other users won't be loaded for entities that were already cached. This will lead to pretty bugs that will never show up during debugging and are painfully difficult to find after bug reports are coming in.

The solution is to prevent DbContext instances to be injected into a part of the object tree that is kept alive for the duration of the session. Instead, during every request a new service scope needs to be created manually and from this scope you should resolve the service(s) that depend on the DbContext. This way, the DbContext is guaranteed to live for as long as a single web request and never longer.

How to efficiently fix the issue is hard to say, because it depends on the design of the application. However, if you're willing to adapt a different application design, the on design that has saved me tons of headaches over the years and also efficiently fixes the issue you are having is what I call the command/handler and query/handler patterns.

Using those patterns you route all calls through the same abstractions. This allows you to put some infrastructure in between the consumer (your page class?) and the dependency (the handler) that ensures that the dependency tree for the handler is always resolved using a fresh service scope from the DI Container. As your handler (or one of its dependencies) will depend on DbContext, this ensures that the DbContext's life ends with the web request.

There's a lot written about this kind of design and you could also take a look at library's such as MediatR that provide you with interfaces that serve as a starting point and provide integration with MS.DI by default (unfortunately, MediatR is only free for small organizations). Only thing is that I don't think that MediatR will start a new service scope for a dispatched command, but this behavior should be easily to adapt.

6 Comments

"unfortunately, MediatR is only free for small organizations". True, but if all you want s the basics you can role your own. There are a few articles out there that explain how. I have my own.
"Only thing is that I don't think that MediatR will start a new service scope for a dispatched command". If you use the DbContextFactory you can get a DbContext for each Handler instance.
The Mediator.SourceGenerator package is plug&play compatible with Mediatr and still free as in beer.
@MrC aka Shaun Curtis "but if all you want s the basics you can role your own". That's exactly what the first two links in my answer showed. I would actually encourage to "roll your own" rather than depend on a third-party library for this.
@MrCakaShaunCurtis: " If you use the DbContextFactory you can get a DbContext for each Handler instance." But that's the thing: you don't want to use a factory for your DbContext, because that makes it hard to reuse your DbContext throughout a single business operation.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.