ASP.NET Core resolving DbContext in Service - c#

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; }
}

Related

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

Depency injection not resolving inherited service (DbContext)

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

How to properly configure the `services.AddDbContext` of `ConfigureServices` method

I'm trying to run an .NET Core Web application with EF Core. In order to test the repository I've added an MyDbContext that inherits the EF DbContext and interface IMyDbContext.
public interface IMyDbContext
{
DbSet<MyModel> Models { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public virtual DbSet<MyModel> Models { get; set; }
}
The context interface is injected to my generic repository:
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
{
private readonly IMyDbContext _context = null;
public GenericRepository(IMyDbContext context)
{
this._context = context;
}
}
When I use this code (without the interface) on startup.cs:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(...));
I'm getting a run-time error of:
InvalidOperationException: Unable to resolve service for type
'IMyDbContext' while attempting to activate 'GenericRepository`1[MyModel]'
And when using this line of code:
services.AddDbContext<IMyDbContext>(options =>
options.UseSqlServer(...));
I'm getting this compiled time error code of:
Cannot convert lambda expression to type 'ServiceLifetime' because it
is not a delegate type
My question is how to properly configure the services.AddDbContext of ConfigureServices method?
(Is there any changes needed inside Configure method?)
If needed I'm willing to modify the IMyDbContext
Use one of the overloads having 2 generic type arguments, which allow you to specify both the service interface/class you want to register as well as the DbContext derived class implementing it.
For instance:
services.AddDbContext<IMyDbContext, MyDbContext>(options =>
options.UseSqlServer(...));
Just found the answer:
I was missing the adding of the scope between IMyDbContext and MyDbContext.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(...));
services.AddScoped<IGenericRepository<MyModel>, GenericRepository<MyModel>>();
services.AddScoped<IMyDbContext, MyDbContext>();
}

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.

Access DBContext using EF7 and VNext

In my MVC 6 project I have my ApplicationDBContext class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(ModelBuilder builder)
{
}
}
This is added to my services in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
//Other configurations removed for brevity
}
Now when I create a new Controller, it asks me if I want to use the Entity Framework, and I can choose my data context. When that controller is created the context is passed in the constructor using what I assume is dependency injection.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
public CompanyController(ApplicationDbContext context)
{
_context = context;
}
}
Now, I don't want to do all database interactions in the controllers, but rather in my other classes. What I can't figure out, is how to get the ApplicationDbContext from my other classes. Passing it from the controller obviously won't work because classes could be called from other places than the controller.
If I just try new ApplicationDbContext(); I get the following error:
No database providers are configured. Configure a database provider by overriding OnConfiguring in your DbContext class or in the AddDbContext method when setting up services.
I feel like this should be something simple, but I am completely lost here.
ASP.NET Core is based on dependency injection, since your context has been added in your dependendy container, it's automatically injected by the framework when your controller is instanciated.
Edit based on comments :
You can setup your classes to support DI, let's suppose you have two class. One that depend on your context, and then second that depend both on your context and your first class :
public class MyClass
{
private ApplicationDbContext _context;
public MyClass(ApplicationDbContext context)
{
_context = context;
}
}
public class AnotherClass
{
private ApplicationDbContext _context;
private MyClass _myClass;
public AnotherClass(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
Add your classes as a transient dependency in the sevice collections at startup, and let the service provider resolve their dependencies for you :
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddTransient<MyClass>();
services.AddTransient<AnotherClass>();
//Other configurations removed for brevity
}
Change your controller to accept MyClass as injected dependency :
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private MyClass _myClass;
public CompanyController(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
You can also have another controller that take AnotherClass as injected dependecy :
public class AnotherController : Controller
{
private AnotherClass _anotherClass;
public AnotherController(AnotherClass anotherClass)
{
_anotherClass = anotherClass;
// _anotherClass will have both ApplicationDbContext and MyClass injected by the service provider
}
}
You should read the docs of dependency injection of ASP.NET Core, it could help to understand basics of DI. Another article from K. Scott Allen that explain some bad practice when you deal with DI.
You can create a service class that receives the DbContext in the same way as the controller.
public class SomeService
{
private ApplicationDbContext MyDbContext { get; set; }
public SomeService(ApplicationDbContext dbContext)
{
MyDbContext = dbContext;
}
public void MethodName()
{
// You can now do MyDbContext.SomeDomainModel
}
}
Then register the service in Startup.cs, in your ConfigureServices method.
public void ConfigureServices(IServiceCollection services) {
// <snipped>
services.AddTransient<SomeService>();
}
And now, in your CompanyController, you can add another parameter in the constructor for the SomeService, just as you have for the ApplicationDbContext.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private SomeService _someService;
public CompanyController(ApplicationDbContext context, SomeService someService)
{
_context = context;
_someService = someService;
}
}
All that said, I don't think there's anything wrong with doing your logic in your controller actions to build your ViewModel, accessing the DbContext. The DbContext is what's separating your business logic (in the controller) from the DAL. Some may disagree with me, but you don't need to add additional services to further separate them. Most of the code in your action methods are unique to that action and not going to be reused by other actions. IMO, those are the pieces of code to put into services. Things like sending emails and such.

Categories

Resources