Depency injection not resolving inherited service (DbContext) - c#

I am trying to registering one of two dbcontexts I have, based on a switch I pass that means if I want to seed the database or just run the service. So, I'm doing this on ConfigureServices:
//Add DB Service for application
bool seedDb = Configuration.GetValue<bool>("seeddb");
if(seedDb)
services.AddDbContext<ApplicationDbSeedDataContext>(options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
else
services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
The class hierarchy is really simple:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
}
public class ApplicationDbSeedDataContext : ApplicationDbContext
{
public ApplicationDbSeedDataContext(DbContextOptions<ApplicationDbContext> options) : base(options, new HttpContextAccessor())
{
}
}
Then I just expect in any method requesting an ApplicationDbContext that the DI system injects the one I instantiated, but it fails when I inject ApplicationDbSeedDataContext instead of ApplicationDbContext.
But I get the error:
System.InvalidOperationException: Error while validating the service
descriptor 'ServiceType: WebAPI.Services.IUserService Lifetime: Scoped
ImplementationType: WebAPI.Services.UserService': Unable to resolve
service for type 'WebAPI.Contexts.ApplicationDbContext' while
attempting to activate 'WebAPI.Services.UserService'.
Userservice constructor is:
public class UserService : IUserService
{
public UserService(IConfiguration configuration, ApplicationDbContext dbContext)
{
_configuration = configuration;
_dbContext = dbContext;
}
...
}
Shouldn't it be resolved correclty?

With this
services.AddDbContext<ApplicationDbSeedDataContext>(...);
The container will not be aware of the base ApplicationDbContext
They will need to be registered as the same type.
Use the AddDbContext overload registration to explicitly specify the base type
//Add DB Service for application
bool seedDb = Configuration.GetValue<bool>("seeddb");
if(seedDb)
services.AddDbContext<ApplicationDbContext, ApplicationDbSeedDataContext>(options => ...);
else
services.AddDbContext<ApplicationDbContext>(options => ...);
That said, try to avoid injecting and using IConfiguration outside of Startup It really was not meant for that and can be seen as a code smell in most instances (SRP violation) consider using the Options Pattern

Related

InvalidOperationException: Unable to resolve service for type 'System.Configuration.Configuration'

Why am I getting this error?
InvalidOperationException: Unable to resolve service for type 'System.Configuration.Configuration' while attempting to activate 'HANACONNECT.Controllers.ServisController'.
My code in Program.cs
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<HANADbContext>();
var app = builder.Build();
var configuration = app.Configuration;
and code in ServisController.cs
namespace HANACONNECT.Controllers
{
public class ServisController : Controller
{
private readonly Configuration _configuration;
private readonly HANADbContext _context;
public ServisController(Configuration configuration)
{
this._configuration = configuration;
}
}
}
When using DI you're generally always going to be injecting an Interface into your constructor. The DI container will then handle assigning the correct implementation according to your setup in Program.cs
Your code should look like this:
namespace HANACONNECT.Controllers
{
public class ServisController : Controller
{
private readonly IConfiguration _configuration;
private readonly HANADbContext _context;
public ServisController(IConfiguration configuration)
{
_configuration = configuration;
}
}
}
While the other answer covers the problem with injection of configuration into the controller, in the case of connection string and the database context usually you should not do it at all. Usual approach is to set up the connection string during the service registration. Something along this lines:
// add ctor accepting options to the context
public HANADbContext(DbContextOptions<HANADbContext> options) : base(options)
// register context with config
builder.Services.AddDbContext<HANADbContext>(opts =>
opts.UseSqlServer(builder.Configuration.GetConnectionString("Hana"))); // Or UseNpgsql or any other provider
And then you just inject the context into controller and use it.
Read more:
Configuration in ASP.NET Core
DbContext Lifetime, Configuration, and Initialization
ASP.NET Core 6 how to access Configuration during startup

EF Core Data Context injection across Projects

I have project A which is a class library and project B that uses A. Project A is meant to be a generic helper library that can be used across projects (such as B).
The EF Core datacontext and the data entities need to be defined in project B (as they can vary by project) but I need to inject the datacontext in to constructors of service classes in Project A (which handle everything in a generic way).
In Project B I have the datacontext
public class MyDataContext : DbContext
{
public MyDataContext(DbContextOptions<MyDataContext> options): base(options)
{
}
public DbSet<Test> Tests { get; set; }
}
In project A I have class UnitOfWork that implements IUnitOfWork. In it's constructor I need to inject the datacontext. However since project A cannot reference project B (project A is meant to be generic), I cannot use the actual name of the datacontext in the parameter list. Since the datacontext inherits from DbContext, I tried
public UnitOfWork(DbContext dc){...}
In the startup of Project B, I have
services.AddDbContext<MyDataContext>(options =>
{
options.UseSqlServer("...<the connection string> ...");
});
services.AddScoped<IUnitOfWork, UnitOfWork>();
Everything compiles but in runtime when the UnitOfWork needs to be created, I get the error
System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: IUnitOfWork Lifetime: Scoped ImplementationType: UnitOfWork': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.)
The Inner Exception is
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.
Any help is deeply appreciated.
EDIT
I was asked for the UnitOfWork class details in the comments. So here it is
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext dc;
private readonly IServiceProvider serviceProvider;
public UnitOfWork(DbContext dc, IServiceProvider serviceProvider)
{
this.dc = dc;
this.serviceProvider = serviceProvider;
}
public void BeginTransaction()
{
dc.Database.BeginTransaction();
}
public void BeginTransaction(IsolationLevel isolationLevel)
{
dc.Database.BeginTransaction(isolationLevel);
}
public void CommitTransaction()
{
dc.Database.CommitTransaction();
}
public void RollbackTransaction()
{
dc.Database.RollbackTransaction();
}
public bool IsTransactionActive()
{
return dc.Database.CurrentTransaction != null;
}
public async Task<bool> SaveAsync()
{
return await dc.SaveChangesAsync() > 0;
}
public bool Save()
{
return dc.SaveChanges() > 0;
}
}
Your UnitOfWork service depends on a DbContext type, not the derived MyDataContext type which is registered into DI.
So you have two options:
You can modify the UnitOfWork registration like this (tell the IoC container to instantiate UnitOfWork with MyDataContext):
services.AddScoped<IUnitOfWork>(srp => new UnitOfWork(srp.GetRequiredService<MyDataContext>(), srp));
Or you can register DbContext into DI as well, so the DI container knows that when someone asks for a DbContext it should return MyDbContext:
services.AddScoped<DbContext, MyDataContext>();
Note that the ServiceProvider field seems to be unused in your UnitOfWork class.
The solution was to make two changes. First was to explicitly register the service as suggested by #fbede
services.AddScoped<DbContext, MyDataContext>();
Now when we do this, we lose the convenience of setting the DbContextOptionsBuilder options via the AddDbContext Extension method.
So we need to override the OnConfiguring method in the datacontext to set the configurations options we need. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString(<key>));
}
Of course, IConfiguration is injected in the MyDataContext constructor

Getting DbContext via Configuration with DbContextOptions

I am trying to get the DbContext I registered with options via services.AddDbContext(...) on the service provider of the project, but when calling configuration.Get<ModelContext> it can not be constructed as the options apparently weren't provided and therefore also no database provider is given.
I am using ASP.NET Core 2.2 with Entity Framework Core 2.2.3 and my DbContext is defined in a separate project.
My DbContext:
public class ModelContext : DbContext
{
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
public ModelContext() { }
}
I did not override OnConfiguring(DbContextOptionsBuilder) in ModelContext.
public class StartUp
{
public void ConfigureServices(IServiceCollection services)
{
public services.AddEntityFrameworkSqlServer();
services.AddDbContext<ModelContext>(options => options.UseSqlServer(modelConnectionString));
}
}
In the controller (or anywhere really) I call public HomeController(IConfiguration configuration) => _modelContext = configuration.Get<ModelContext>(); which throws the unexpected exception.
What I specifically get is an InvalidOperationException with the message:
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.
According to the documentation I read and examples I looked at, the ModelContext should be created with the options I defined when calling AddDbContext<ModelContext>. Is the Get method the wrong one to use?
After configuring the db context service in "ConfigureServices" method of the Startup.cs file with something like this :
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<BottinContext>(options => options.UseSqlServer(connectionString)) ;
Simply add a :
ModelContext db
parameter to the constructor of your controller and let DI magic happen.
If you've got many controllers and wish to simplify things, you can use a base contructor that holds the db context
public BaseController(ModelContext context /* as well as other injections */)
{
_db = context;
}
internal ModelContext _db;
you are trying to get dbContxt instance in a wrong way. Get method is not used to get instance of dbContext object that you registered with dependency injection container.
if you want to get instance of your dbContext class that you registered you can inject it through construction injection for example
public class RepositoryWrapper : IRepositoryWrapper
{
private readonly ModelContext _modelContext;
public RepositoryWrapper(ModelContext modelContext)
{
_modelContext= modelContext;
}
}
is something i am doing in my project.

Using ApplicationDbContext with DI from appsettings.json

I am trying to abstract any connection information away from my ApplicationDbContext class so that I can take advantage of different databases for development, staging, production. I start by registering a service from Startup.cs
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
My ApplicationDbContext class:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
When running this application I get the following error:
InvalidOperationException: Could not create an instance of type 'SquadApps.Data.ApplicationDbContext'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
So naturally I tried adding a parameterless constructor
public ApplicationDbContext() { }
Now getting another error:
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.
If I go back to having a connection string stored in the ApplicationDbContext class like so:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("........");
}
Then everything works fine but obviously this is not ideal and probably a bad practice. I think there is something i'm missing about the DI process and any suggestions or advice would be appreciated.
The solution turned out to be how I was trying to call the DI. I had incorrectly assumed DI would be able to be called per each IActionResult inside my controllers but in fact it must occur within the constructor of the controller. This makes the DI available to all IActionResult methods within the controller.
Example of the working DI call:
public class HomeController : Controller
{
private readonly ApplicationDbContext _ctx;
private readonly CompanySettings _companySettings;
public HomeController(ApplicationDbContext ctx, IOptions<CompanySettings> settings)
{
_ctx = ctx;
_companySettings = settings.Value;
}
public IActionResult Index()
{
var model = new HomeViewModel();
// _ctx and _companySettings can be used here
return View(model);
}
}

ASP.NET Core resolving DbContext in Service

I have issue with resolving DbContext in my UserManager class.
Here is UserManager class:
public class UserManager : IUserManager
{
private readonly MyContext _context;
public UserManager(MyContext context)
{
_context = context;
}
}
My DbContext class:
public class MyContext: DbContext
{
public DbSet<User> Users { get; set; }
}
Startup:
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration["database:connectionString"]));
//....
services.AddScoped<IUserManager, UserManager>();
There is errror:
Unable to resolve service for type
'MyContext' while
attempting to activate 'UserManager'.
Does anyone know where is the problem?
DI should resolve MyContext with code you provided. Resolved instance will not work (see #Nikosi answer about DbContextOptions), but it should be resolved/created.
Check your project for other/second MyContext class. I think you have two, possibly in different namespaces. "Go to declaration" (Ctrl+Click in VS) of MyContext from both Startup and UserManager files - are they referencing same class?
Documentation: Using DbContext with dependency injection
EF supports using DbContext with a dependency injection container.
Your DbContext type can be added to the service container by using
AddDbContext<TContext>.
AddDbContext will add make both your DbContext type,
TContext, and DbContextOptions<TContext> to the available for
injection from the service container.
Adding dbcontext to dependency injection
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration["database:connectionString"]));
requires adding a constructor
argument to your DbContext type that accepts DbContextOptions.
You were missing configuration options in your DbContext
public class MyContext: DbContext {
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}

Categories

Resources