How to use DbContextPool with Singleton? - c#

In my application I'm doing integrations using NMS and ActiveMQ.
I have some listeners that are singletons listening to some queues for messages.
Upon receiving a message, the listener should process it and log it on the database.
My DbContext is configured using the DbContextPool option:
services.AddEntityFrameworkSqlServer();
services.AddDbContextPool<MyContext>((serviceProvider, options) =>
{
options.UseSqlServer(connectionString);
options.UseInternalServiceProvider(serviceProvider);
});
So, when I try to inject the DbContext into my ActiveMqListener class, I get the error:
InvalidOperationException: Cannot consume scoped service 'MyApp.Data.MyContext' from singleton 'MyApp.Integrations.ActiveMqListener'.
How can I get one of the Contexts in the pool and free it once my work is done processing one message? Is there any other recommend way of doing this?
Thanks in advance.

According to the ASP.NET Core DI Service lifetimes documentation:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
By default AddDbContext or AddDbContextPool register the DbContext as Scoped service. You are consuming your DbContext in ActiveMqListener class which has been registered as Singleton service. That's the problem!
Solution is: register your ActiveMqListener to ASP.NET Core DI as ScopedService in Startup.ConfigureServices method.
Note: If you are obliged to use ActiveMqListener as Singleton then register your DbConext as Singleton too as follows:
services.AddDbContext<MyContext>((serviceProvider, options) =>
{
options.UseSqlServer(connectionString);
options.UseInternalServiceProvider(serviceProvider);
}, ServiceLifetime.Singleton); // <-- Here it is

Related

Meaning of a Scoped Service in the context of a (non web) host

Before reporting this question as duplicate, this SO Question covers the use of Transient, Scoped, and Singleton, but the answers and discussion target meanings mainly within the context of configuring a WebHost, but my question is relevant to the meaning of these types of services within the context of a (non-web) host, specifically confusion on Scoped Services.
Let's start with the code below:
In Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
I can wrap my head around what Transient and Singleton mean in the non-web host context.
Transient lifetime services are created each time they are requested. (not to be confused with "http request", but to mean literally "each time the service is requested.")
Singleton lifetime services is for the lifetime of the application domain.
But everywhere I read, a Scoped Service in the context of http requests would mean that a new instance is created for each (http?) request. The docs and other discussion is usually geared toward behavior when dealing with a web host, so I am in need of clarification...
What then, is the meaning of Scoped within the context of configuring IServiceCollection on a Default (non-web) HostBuilder?
Host.CreateDefaultBuilder(args)..ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
});
Also, see here for a practical example.
The closest understanding I came to is "Scoped lifetime services are created once per request (again, not http-request, but request for the service) within the scope. It is equivalent to a singleton in the current scope." ... meaning that if you AddScoped when configuring services on the DefaultBuilder (non-web host), when requested from that same scope, a new, completely separate instance of the service will be generated? Is this ballpark correct?
If I am way off here, I apologize. Thank you for your time.
Yes, in a web host, a lifetime scope is created for the request.
In a console app, windows service, or any type of app, including a web hosted app, you can create a lifetime scope whenever you feel it is needed.
For example you might create a windows service that processes something on a schedule. It might make sense to create a lifetime scope each time the scheduled job is run. Now all components resolved in that scope that are registered as scoped will logically be scoped to it. This will keep your instances separate per run and they will be disposed along with the scope.
This could be useful if you have several different types of task that run independently and asynchronously. This way any component dependencies they have will be scoped properly.

EF and Multiple Instances of IHostedService

I have multiple IHostedService (BackgroundService) instances registered in my DI container.
services.AddHostedService<ResourceMatchingMessageListeningService>();
services.AddHostedService<ResourceMatchingMessageListeningService2>();
services.AddHostedService<ResourceMatchingMessageListeningService3>();
services.AddHostedService<ResourceMatchingMessageListeningService4>();
services.AddHostedService<ResourceMatchingMessageListeningService5>();
Each service listens to a different Azure Service Bus queue. When receiving a message it writes to an database through EF Core. If I run a single service it works fine, as soon as I run 2 or more I get the error:
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.
I'm assuming this error comes up because EF Core is not thread safe and the hosted services all run in different threads.
I tired to fix this by setting EF's service lifetime to be transient), but I still get the same error.
services.AddDbContext<MyDataContext>(options =>
options.UseSqlServer(connectionString, sqlServerOptions =>
sqlServerOptions.CommandTimeout(120)),
ServiceLifetime.Transient, ServiceLifetime.Transient);
Any suggestion on a potential solution here?
You'll need to Create a new service scope for each hosted service, then request your DbContext from that scope.
using var scope = services.CreateScope();
var context = scope.ServiceProvider.GetService<MyDataContext>();
You might want to create a service scope for each message you process. Perhaps even a separate scoped service to handle the processing of each message.
Unfortunately the implementation then starts to look like a service locator anti-pattern.

How to avoid reload resource in ASP.NET core controller

I am new to ASP.net core. Having a web API connect to database using EntityFramework core. The controller take request, do some analysis, and send the response as below
public class CentoDataController : ControllerBase
{
private readonly CentoWebDBContext _context;
private HPOSubSimHolder _hpoSubSimHolder;
public CentoDataController(CentoWebDBContext context)
{
_context = context;
_hpoSubSimHolder = new HPOSubSimHolder(hpofile);
}
[HttpGet("{id}")]
public ActionResult<CentoData> GetCentoData(string id)
{
IQueryable<CentoData> r = AnalysisMethod(id, _hpoSubSimHolder);
return r;
}
The code works, but _hpoSubSimHolder will be reloaded once a new request comes in. I guess I shouldn't share controller between requests. But how can I avoid reloading _hpoSubSimHolder ?
I can see that you're using .net core dependency injection
If you want that service to be shared across requests, think of making it a Singleton.
You can choose between AddScoped, AddTransient and AddSingleton when registering dependencies.
In your startup.cs class:
public void ConfigureServices(IServiceCollection services)
{
// some code
services.AddSingleton<HPOSubSimHolder>(new HPOSubSimHolder());
}
Singleton means only a single instance will ever be created. That instance is shared between all components that require it. The same instance is thus used always.
Scoped means an instance is created once per scope. A scope is created on every request to the application, thus any components registered as Scoped will be created once per request.
Transient The services created using transient lifetime will be created each time they are requested. This lifetime works best for lightweight services.
(Source)
Controllers are always instantiated per request. To control lifetime of any resources or dependencies the controller should use, you can use the build in Dependency Injection (DI).
Most examples setup DI in your startup.cs ConfigureServices method.
The DI container allows 3 different lifetime states, in your case I guess you can try to add the HPOSubSimHolder as singleton.
I have no idea what HPOSubSimHolder is and what the implementation details are, hence its hard to tell if that'll work for you.
But it would be the "normal" way of setting this up ;)

Dependency Injection ASP.NET Core Singleton

May I know what is wrong with following line of code:-
Why I can't use singleton on it?
services.AddSingleton<IProductRepository, ProductRepository>();
I have been getting 500 internal server error on the above code, however it is working fine with Transient and Scoped.
I assume based on the provided pattern that the repository depends on a DbContext?
public class ProductRepository : IProductRepository {
public ProductRepository (MyDbContext db) {
//...
}
}
Which tends to be registered as scoped.
If you try to register a singleton with scoped dependencies, it may cause the service to have incorrect state when processing subsequent requests.
Reference Dependency injection in ASP.NET Core : Service Lifetimes
According to Microsoft Documentiaon:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Now look at the following code of your ConfigureServices method in Startup class:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
There is a second parameter in the UseSqlServer method for Service Lifetime whose default value is
ServiceLifetime.Scoped. May be you didn't specify the second parameter so it is taking the default value ServiceLifetime.Scoped. That means your DbContext has been registered as Scoped service. Now if you use your DbContext in ProductRepository then your ProductRepository has to be resisted as Scoped Service too otherwise ASP.NET Core DI provider cannot resolve it.
Now If you really want to register your ProductRepository as Singleton Service then make your DbContext also Singleton as follows:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
Now it should work!
Note: I assumed that your ProductRepository is dependent on DbContext. If not then your ProductRepository repository must be dependent on a service which is registered as a Scoped Service and that's why currently you are not able to use ProductRepository as Singleton.

Cannot resolve scoped service Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope from root provider

I'm trying to use in my ASP.NET Core 2.0 web app this sample RazorViewEngineEmailTemplates to create an html email body from View. But when I run it and my controller gets an ajax request, I get this error:
Cannot resolve scoped service Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope from root provider
It's probably coming from resolving dependencies in the RazorViewToStringRenderer class but I have no idea how to fix this.
ok, the problem was I used renderer from a Singleton service (EmailerService). I changed its registration to Scoped and it all works now:
services.AddScoped<IEmailer, EmailerService>();
When a service gets injected as scoped, the dependent class must also be injected as scoped.
Unfortunately, this does not work for every use case. When creating the E-Mail service statically, you don't have an HTTP Context.
In my case, I had a scheduled Task that was executed statically by Hangfire:
var mailer = ServiceProviderSinleton.Instance.GetService(typeof(IEmailer))
When you need that scoped service from a static context, you have two options:
use a dependency injection framework that gives you more control over the injection context. I highly recommend DryIoc.Microsoft.DependencyInjection from NuGet. (Documentation)
disable the scope validation:
return WebHost.CreateDefaultBuilder()
.ConfigureLogging(builder => builder.AddSerilog(Log.Logger, dispose: true))
.UseKestrel(options => options.ConfigureEndpoints(configuration))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<TStartup>()
.UseSerilog()
.UseDefaultServiceProvider(options => options.ValidateScopes = false)
.Build();

Categories