Getting DbContext via Configuration with DbContextOptions - c#

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.

Related

Is there a way to factory inject a custom DbContext?

...Maybe using TFactory in AddDbContextFactory<TContext, TFactory> in EF Core extensions?
I've only seen AddDbContextFactory examples being used with just the TContext generic. They're always very explicit to say you have to use a using statement.
In similar situations (when I useClass in Angular or AddScoped in .NET Core), I make the variable I want to see in a constructor the first generic argument and the second generic argument what actually gets injected. You know, like:
services.AddScoped<IService, RealService>();
Obviously, this isn't the case with
services.AddDbContextFactory<ADbContextIHaveBeenInjecting, AFactoryThatWillReturnADbContextIHaveBeenInjecting>();
I was hoping this would eliminate the need to do the whole using thing.
Is there another way I can do this without having to re-write every injected DbContext to conform with their prescribed:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
As I said, my hope was to use something like this for the factory:
public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
public MyDbContextFactory(DbContextOptions options)
{
}
public MyDbContext CreateDbContext()
{
var ProviderName = GetProviderName();
switch (ProviderName)
{
case "System.Data.SqlClient":
return new SqlServerDbContext(new DbContextOptionsBuilder<SqlServerDbContext>().UseSqlServer(ConnectionString).Options);
case "Npgsql":
return new PostgreSqlDbContext(new DbContextOptionsBuilder<PostgreSqlDbContext>().UseNpgsql(ConnectionString).Options);
default:
throw new NullReferenceException("Missing provider name for DbContext. Should be Npgsql or System.Data.SqlClient");
}
}
}
Then, set it up in Startup.cs ConfigureServices like:
services.AddDbContextFactory<MyDbContext, MyDbContextFactory>();
So I could inject in a class like this:
public class MyController : BaseApiController
{
private readonly MyDbContext _myDbContext;
public MyController(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
[HttpGet("GetACount")]
public IActionResult GetACount()
{
var count = _myDbContext.MyRecord.Count();
return Ok(count);
}
...
Is there a way to do this using the AddDbContextFactory? What is TFactory actually for? Is there another way to do this?
DbContextFactory is specifically intended to require you to manage the lifecycle of your DbContext, because Blazor server apps don't use a Scope-per-Http request like ASP.NET Core does, so a Scoped DbContext won't work.
If you want a Scoped DbContext just use .AddDbContext intead of .AddDbContextFactory.
If you have registered a DbContextFactory but still want to inject a scoped DbContext direcly in your services, then register it like this:
services.AddScoped<MyDbContext>(sp =>
{
var cf = sp.GetRequiredService<IDbContextFactory<MyDbContext>>();
var db = cf.CreateDbContext();
return db;
});
Dependency inject the concrete class. create a factory to select the subclass by type. Create a parent class with a private DbContext _dbContext. Inherit the subclass from the parent class and call the parent class constructor with :base(dbContext) of the subclass. The parent class can now access in its methods the subclass context. The subclass can share the methods of the parent class for (add, select, update, and deleting by set the data context of the subclass). the subclass will dependency inject the specific dbcontext in its constructor and set the parent class dbcontext variable in its constructor. the subclass repository class can then access the base class methods within its body.
in startup define the subclass repository pattern
in public void ConfigureServices(IServiceCollection services)
var connectionString = Configuration.GetConnectionString("ABCContext_DbCoreConnectionString");
services.AddDbContext<ABCContext>(options1 => options1.UseSqlServer(connectionString));
services.AddTransient<IRepositoryMySubclass, RepositoryMySubclass>();
sub class
public RepositorySubclass(ABCContext dbContext) : base(dbContext)
{
_dbContext = dbContext;
}

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

No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring

I have this database configuration to at my Startup class in .NET Core 3.1 Web Api project.
public void ConfigureServices(IServiceCollection services)
{
var abcConnString = Configuration.GetConnectionString("abcConnString");
services.RegisterAbcSqlServer<ABCContext>(abcConnString);
var xyzConnString = Configuration.GetConnectionString("xyzConnString");
services.RegisterXyzSqlServer<XYZContext>(xyzConnString);
// code removed for brevity.
}
and
public static class RegisterDbContext
{
public static void RegisterAbcSqlServer<T>(this IServiceCollection services, string connectionString)
where T : DbContext, IAbcContext
{
var migrationsAssembly = typeof(RegisterDbContext).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<T>(options => options.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)));
}
public static void RegisterXyzSqlServer<T>(this IServiceCollection services, string connectionString)
where T : DbContext, IXyzContext
{
var migrationsAssembly = typeof(RegisterDbContext).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<T>(options => options.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)));
}
}
I can build without error. But when I perform EF Core add-migration, it hits error:
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.
You can get this error when there is no way for the DI framework to configure the provider by injecting a DbContextOptions. So you just need to add a constructor that will accept one, or add a parameter for a DbContextOptions to be passed in. For example:
public class XYZContext : DbContext
{
// Add this constructor
public XYZContext (DbContextOptions options) : base(options)
{
}
}

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

Register generic EntityFrameworkCore DbContext in ASP.Net Core 1.0

I am trying to use generic DbContext in ASP.Net Core 1.0 however I am getting following error:
ArgumentException: Type 'idmin.data.IdminDataContext`1[System.Int32]' does not have a default constructor Parameter name: type
Here is how IdminDataContext is defined:
public class IdminDataContext<TKey> : DbContext where TKey : IEquatable<TKey>
{
public IdminDataContext(DbContextOptions<IdminDataContext<TKey>> options) : base(options)
{
}
public DbSet<Idmin.Models.Client<TKey>> Clients { get; set; }
}
and here is how I am registering it in startup class.
public void ConfigureServices(IServiceCollection services)
{
var connection = "my connection string";
services.AddDbContext<IdminDataContext<int>>(options => options.UseSqlServer(connection));
services.AddMvc();
}
I tried adding default constructor in IdminDataContext class but it didn't help.
Any idea what am I missing here?
I found issue. It was silly mistake on my part. Instead of using constructor injection in Controller I was using action method injection which was causing exception in Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder's CreateModel method. Fixed this issue by moving dependency injection to constructor instead of action method injection.

Categories

Resources