The DbContext of type cannot be pooled because it does not have a single public constructor accepting a single parameter of type DbContextOptions - asp.net-core

I am trying to upgrade our current .Net Core application from 1.1 to 2.0 and am getting this runtime error: "The DbContext of type 'CoreContext' cannot be pooled because it does not have a single public constructor accepting a single parameter of type DbContextOptions".
It is caused by using the new IServiceCollection.AddDbContextPool<> function. When I use IServiceCollection.AddDbContext<> it still works.
This application is DB-First, so I generate all our contexts using 'Scaffold-DbContext'. Due to that, and the need to inject other services I have an extension on every context like this:
public partial class CoreContext
{
public CoreContext(
DbContextOptions<CoreContext> options,
IUserService userService,
IAuditRepository auditRepository
) : base(options) {...}
}
Whenever I run the Scaffold-DbContext I just remove the autogenerated Constructor from CoreContext, but even if I put it in there I still get this error.
public partial class CoreContext : DbContext
{
public CoreContext(DbContextOptions<CoreContext> options) : base(options) {}
}
I've already updated Program.cs to the new style:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
And the Startup.cs is pretty straightforward:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddDbContextPool<CoreContext>(options => options.UseSqlServer(absConnectionString));
...
}
I am using Autofac for DI if that helps. For now I'll default back to the non-Pooling alternative, but it would be nice to take advantage of this feature.

When using DbContext Pooling, your own state (e.g. private fields) in your derived DbContext class will be preserved. Which means the lifetime of your services is now singleton. That's why you shouldn't have other injected services here.
But it's possible to query the required services this way:
First we should use the UseInternalServiceProvider method on DbContextOptionsBuilder to tell EF which service provider to use for its services. This service provider must have all the services configured for EF and any providers. So we should register EF Services manually:
services.AddEntityFrameworkSqlServer();
And then introduce the application's services provider which now includes the EF Services too:
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
{
optionsBuilder.UseSqlServer("...");
optionsBuilder.UseInternalServiceProvider(serviceProvider);
});
After that define these namespaces:
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
And now you can access the registered services in the application within the
ApplicationDbContext class using the following methods
var siteSettings = this.GetService<IOptionsSnapshot<SiteSettings>>();
Or
var siteSettings = this.GetInfrastructure().GetRequiredService<IOptionsSnapshot<SiteSettings>>();
this is the current instance of the DbContext.

Remove the default constructor in the DbContext class, this worked for me

"because it does not have a single public constructor accepting a
single parameter of type DbContextOptions"
If you have any public constructors apart from one that accepts DbContextOptions, you need to remove them or make them non-public in order to use context pooling.
Also, there are restrictions on what can be done by overriding the OnConfiguring method. This is referenced in the documentation here but it isn't explicit about what those restrictions are: https://learn.microsoft.com/en-us/ef/core/what-is-new/index#dbcontext-pooling

This issue is mostly encountered when you "Scaffold-Dbcontext" and two constructors are generated.
Simple Solutions:
AddDbContextPool:
If you want to use AddDbContextPool, remove your empty constructor and maintain the one with the DbContextOptionsBuilder. Note that in this case you might have to provide the options, as suggested in the previous posts.
AddDbContext:
With AddDbContext, you can have both constructors/Overloads
Note: AddDbContextPool is preferred for performance reasons!

Try to use AddDbContext instead of AddDbContextPool. This helped me in the same situation.
services.AddDbContext<CoreContext>(options => options.UseSqlServer(absConnectionString));

in some case need to
remove the constractor with zero parameter
//public MyContext()
//{
//}
or use
"AddDbContext"
instead of
"AddDbContextPool"
in startup.cs => ConfigureServices()
services.AddDbContext(options =>
options.UseSqlServer(absConnectionString));

Related

"No database provider has been configured for this DbContext"

I'm getting this error message when trying to reach my ASP .NET Core 3.1 Web API with Postman:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
However, I do have configured it using AddDbContext in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
..
string connectionString = Configuration.GetConnectionString("ViewQlikDatabase");
services.AddDbContext<QlikDbContext>(options => options.UseSqlServer(connectionString));
}
I have checked the connection string and it is correctly retrieved.
The DbContext also has the recommended constructor:
public QlikDbContext(DbContextOptions<QlikDbContext> options)
: base(options)
{
}
The exception is raised when I try to call the context in my business class:
public string SedeGetTotaleElementiVista()
{
using (var db = new QlikDbContext())
{
// Exception raised here
int count = db.ViewQlikSede.Count();
return count.ToString();
}
}
Can someone please tell me what's wrong?
The context must be injected. If you new it up yourself, the service registration doesn't come into play at all. Here, you're creating it yourself, and not passing anything into it, so this instance definitely doesn't have a provider configured.

.NET Core DI passing info of AddHttpContextAccessor to a service

In my solution I have projects for my API, my Web App and also have another project which includes services, that are getting some information from a database and formatting them, these are currently only used by this API, but these could be used by other API projects in the future.
My API have a couple controllers that are returning JSON data from the result returned by the services.
In some cases the services needs to call the API to process some information before calling the request to the database. Since I have dev/staging/prod environment with their own URL I don't want to hardcode the URLs in the services I want to use DI to get these dynamicaly depending on the context.
In the Startup.cs of my API I have added services.AddHttpContextAccessor(); in the ConfigureServices(IServiceCollection services) section to gain access to the current http context :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
...
}
With that I know I can now access the information directly into my controller which I tried and it worked :
public class DataController : ControllerBase
{
...
private readonly string _baseUrl;
public FeaturesController(...
,IHttpContextAccessor httpContextAccessor)
{
...
_baseUrl = UrlHelpers.ShowBaseURL(httpContextAccessor) ?? throw new ArgumentNullException(nameof(_baseUrl));
}
}
public static class UrlHelpers
{
public static string ShowBaseURL(IHttpContextAccessor httpcontextaccessor)
{
var request = httpcontextaccessor.HttpContext.Request;
var absoluteUri = string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent());
return absoluteUri;
}
}
I could do just about the same thing in the services but to me they should not act directly on the httpcontext, since this is not the job they are meant to do. I am sure I could do better by adding a class injected of some sort that would have then make the specific value available to my services.
I know I could also pass the _baseUrl directly as an argument when calling the services from my controller but since I am trying to better understand DI and use it I would rather find another way if it is viable.
I can't give credit but I went with Steven solution which make the most sens

Register OpenIddict entities into DbContext in another way

Is there another way to register the entity sets needed by OpenIddict onto a DbContext except calling
options.UseOpenIddict(); in services.AddDbContext<OpenIdDictDbContext>(options => {...}).
I have trouble with this approach, because I have more DbContexts, and I want to share DbContextOptions.
In .Net Core 2, if you can use non generic DbContextOptions for all DbContexts OR you must have nongeneric DbContextOptions<T> for all DbContexts. So, I would like the first approach if it possible.
You can directly register the OpenIddict entities from OnModelCreating:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
builder.UseOpenIddict();
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
If you don't see the extension, make sure you have a Microsoft.Extensions.DependencyInjection using and that your project references OpenIddict.EntityFrameworkCore.

Add database context to service in configureServices

Is it possible to access the database context in a self created service? In the code beneath i want to set the database context with the constructor of the exampleClassService.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//Add MVC
services.AddMvc();
//Postgres connection
var connectionString = Configuration["ConnectionStrings:PostgresConnection"];
services.AddDbContext<ApplicationDbContext>(
opts => opts.UseNpgsql(connectionString)
);
//Own created service
services.AddSingleton<ExampleClassService>(
provider => new ExampleClassService(dbcontext?);
...
}
In "default" asp.net core:
If your ExampleClassService have constructor like:
public ExampleClassService(ApplicationDbContext dbContext)
then you do not need to add any special factories - this parameter will be injected by DI layer automatically (because you already registered ApplicationDbContext earlier), just write services.AddSingleton<ExampleClassService>()
If your service have some "special" constructor, then use provider param to obtain required dependencies:
services.AddSingleton<ExampleClassService>(
provider => new ExampleClassService((ApplicationDbContext)provider.GetService(typeof(ApplicationDbContext)));
But! In your sample you have registration of ApplicationDbContext as "scoped" and ExampleClassService as "singleton" - are you REALLY sure that you can/may/need use singleton class (single for whole app) ExampleClassService that use some other (DbContext) class that you require to be different for each scope/request?
May be you have design errors here, may be ExampleClassService must be "scoped" too or must not require DbContext in constructor.

ASP.NET Core: Can not resolve a service instance through CallContextServiceLocator.Locator.ServiceProvider

This is part of my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
...
//bus
services.AddSingleton<IRouteMessages, MessageRouter>();
services.AddSingleton<IBus, DirectBus>();
////
...
}
I'm trying to resolve the instance of IRouteMessages interface in my RegisterCommandHandlersInMessageRouter class:
public class RegisterCommandHandlersInMessageRouter
{
...
public static void BootStrap()
{
var router = CallContextServiceLocator.Locator.ServiceProvider.GetService(typeof (IRouteMessages));
new RegisterCommandHandlersInMessageRouter().RegisterRoutes(router as MessageRouter);
}
...
}
router variable is always null. Yet in my controllers where IRouterMessages is resolved automatically (in constructors) everything is fine.
I'm not sure what other parts of my code could be useful. I will provide more details.
Don't EVER use CallContextServiceLocator, this completely beats the purpose of having dependency injection. And NEVER relay on it.
CallContextServiceLocator is only used in some of the internal ASP.NET Core and is never be supposed to be used by developers creating ASP.NET Core applications. That being said, it can be removed, made internal or inaccessible at any time which would break existing applications.
Additionally, the CallContextServiceLocator only had runtime services registered (DNX Services, deprecated anyways). Source: David Fowl from ASP.NET Core team.
Infact CallContextServiceLocator is being removed in RC2, see the announcement.
Removed support for CallContextServiceLocator. Use PlatformServices and CompilationServices instead.
Instead, only use the built-in dependency injection, like this:
public static class RegisterCommandHandlersInMessageRouter
{
...
// This is extension method now
public static void RegisterCommandHandlers(this IServiceProvider services)
{
var router = services.GetService(typeof (IRouteMessages));
new RegisterCommandHandlersInMessageRouter().RegisterRoutes(router as MessageRouter);
}
...
}
and call it in your Startup.cs
public void Configure(IServiceProvider services)
{
...
services.RegisterCommandHandlers();
...
}