-1

I have this piece of code in the startup.cs file of my ASP.Net Core Web API project:

public void ConfigureServices(IServiceCollection services)
{
    // Retrieve the database connection string
    string connectionString = "Do something to retrieve a connection string";
    services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
}

I have reasons to do this here and not in MyContext's OnConfiguring() method, but now I am getting this error:

System.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.'

This is my MyContext class's:

public partial class MyContext : DbContext
{
    public MyContext()
    {
    }

    public MyContext(DbContextOptions<MyContext> options)
            : base(options)
    {
    }
...
}

I found somewhere that I also need to do this:

public class MyController : Controller
{
   private MyContext _context;

   public MyController(MyContext context)
   {
        _context = context;
   }
}

but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:

public ActionResult MyMethod(...)
{
   MyManager.DoSomething(); // MyManager instantiates the context
   return Ok();
}

This is how I'm currently instantiating the context:

private static readonly MyContext myContext = new MyContext();

I'm guessing I need to somehow inject the options into the context, but I don't know how.

Eutherpy
  • 4,471
  • 7
  • 40
  • 64
  • Does `MyContext` inherit from `DbContext`? – dunnel123 Dec 09 '19 at 11:20
  • @dunnel123 Of course :) – Eutherpy Dec 09 '19 at 11:21
  • do you have a parameterless constructor in your `MyContext`? – dunnel123 Dec 09 '19 at 11:28
  • Does removing the parameterless constructor help? .NET DI might be using that instead, therefore there's no actual configuration to your DbContext – dunnel123 Dec 09 '19 at 11:35
  • @dunnel123 If I remove the parameterless constructor, I don't know how to instantiate the context in my `MyManager` class... – Eutherpy Dec 09 '19 at 11:39
  • Apologies I missed that. Once you've registered `MyContext` with .NETs services then it can be injected throughout the application. That means you can add a constructor to your `MyController` that passes in `MyContext`. Then you can refer to `MyContext` in your controller directly. If you still need to use it in `MyManager` then you would do the same to the constructor in there and inject `MyManager` into your controller rather than calling a static instance. – dunnel123 Dec 09 '19 at 11:43
  • @Eutherpy to manually instantiate your context check check this: https://stackoverflow.com/questions/38417051/what-goes-into-dbcontextoptions-when-invoking-a-new-dbcontext but the best way is still doing it through DI – Tubc Dec 09 '19 at 11:47
  • 2
    Dependency injection is no magic. When you use DI you have to inject it. Currently the only supported way to do this is a) constructor injection b) RequestDelegates and somespecial methods (i.e.`Startup.Configure`, or `RequestDelegate.Invoke` (middlewares). Also please keep in mind, DbContext is registered as scoped and shouldnt be used globally. DbContext **IS NOT** thread-safe and only one operation can be done at a time (execute it,await the response, execute next). With static you wouldn't have this and would block multiple requests. When you use Dependency injection, use it all the way in – Tseng Dec 09 '19 at 11:59
  • 1
    @Eutherpy remove the static method *completely*. The controller already receives a `MyContext` instance, that `Manager` class doesn't do anything useful. Even if that works, it will only cause blocking and deadlocks - a DbContext just like a DbConnection should remain alive for as little time as possible – Panagiotis Kanavos Dec 09 '19 at 12:11

5 Answers5

1

It seems that you are registering your context to services collection and it should work if your get your context from there.

But you're simply creating a new unconfigured DbContext instance and so you get an error that it's not configued:

private static readonly MyContext myContext = new MyContext();

Solution: Let the context be injected via DI: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1

public class MyServiceThatNeedsDbContext {
    private readonly MyContext _myContext;

    MyServiceThatNeedsDbContext(MyContext myContext) {
      _myContext = myContext;
    }
}

You'll have to register your service class to services collection and get instances from there to make it work.

A simple registration could look like this (Startup.cs):

 services.AddTransient<IMyServiceThatNeedsDbContext, MyServiceThatNeedsDbContext>();

.. and in your controller

public class MyController : Controller
{
   private IMyServiceThatNeedsDbContext _myService;

   public MyController(IMyServiceThatNeedsDbContext myService)
   {
        _myService = myService;
   }

}

Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
  • Could you please elaborate on how to register my service class? – Eutherpy Dec 09 '19 at 11:55
  • Awful suggestion. When injected, DbContext will be scoped (disposed at the end of request), assigning it to static just makes no sense, after the request it ends in "Obect Disposed" exceptions. – Tseng Dec 09 '19 at 12:00
  • @Tseng - thx, forgot to remove the static keyword (copy & paste from question). – Christoph Lütjen Dec 09 '19 at 13:52
1

Dependency injection works that way, by injecting via constructor (recommended approach which leads to easier to test code and ensure invariants). You should just use that.

but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:

This seems to be a wrong assumption on your side that injection only works in constructor. Any service registered with the DI can have stuff injected into it, when resolved. So if you use your MyContext in a service class, inject it there and inject the service into your controller.

Mind the lifetimes though. AddDbContext adds the context with scoped life time, means it will get disposed at the end of the request. This is by design (can be override with one of the AddDbContext overloads), since EF Core is tracking changes in memory and unless disposed can lead to memory leakage and high memory consumption.

Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 2
    The reason isn't just tracking and memory consumption. A DbContext is essentially a UoW out-of-the-box. With a global instance, that's gone and the context ends up accumulating *every* change. In the end, one has no way of knowing what `SaveChanges` is going to store. – Panagiotis Kanavos Dec 09 '19 at 12:14
0

I would suggest removing parametherless constructor because there is known issue where that's called instead of one with options in it.

Iavor Orlyov
  • 512
  • 1
  • 4
  • 15
0

Add in yourDbContext

// overload constructer
 public MyContext(DbConfig dbconfig)
{
   _dbconfig = dbconfig
}
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            switch (_dbConfig.DbType)
            {
                case DbTypes.MsSql:
                    optionsBuilder.UseSqlServer(_dbConfig.ConnectionString);
                    break;

                case DbTypes.PostgreSql:
                    optionsBuilder.UseNpgsql(_dbConfig.ConnectionString);
                    break;
                case DbTypes....
                .....
            }
            optionsBuilder.EnableSensitiveDataLogging();
        }
    }

and Create

    public class DbConfig
{
    public DbTypes DbType { get; set; }
    public string ConnectionString { get; set; }
}

public enum DbTypes
{
    MsSql,
    PostgreSql,
    .....,
    ....
}

then create global _dbConfig in MyManager and configure dbtype and constring and then

 private yourDbContext GetNewDbContext()
    {
        return new yourDbContext(_dbConfig);
    }

inject in MyManager

thegunn
  • 26
  • 3
-1

Try Unity containers to access your instance when your controller is called.

https://www.tutorialsteacher.com/ioc/unity-container

The context will be provided automatically through dependency injection after registering from your UnityConfig. (The nuget puts in the code.)

    public static void Register(HttpConfiguration httpConfiguration)
    {
        httpConfiguration.DependencyResolver = new UnityDependencyResolver(Container);
    }
JerryTheGreek
  • 160
  • 2
  • 7
  • 1
    .NET Core DI works. This isn't a question about Unity anyway. This answer doesn't help at all – Panagiotis Kanavos Dec 09 '19 at 11:24
  • Unity means you can instantiate the instance elsewhere and pass it to the controller through DI. In his question he says that he is currently instantiating his instance using a static property. Unity will allow him to do it elsewhere. He also says that "I need to somehow inject the options into the context, but I don't know how", the answer also shows him the DI. – JerryTheGreek Dec 09 '19 at 12:03
  • 1
    In that case the solution is to just let the DI container do its job, not replace it with a *different* container, only to keep the same faulty code – Panagiotis Kanavos Dec 09 '19 at 12:09
  • @Panagiotis Kanagiotis That sounds logical. – JerryTheGreek Dec 09 '19 at 14:27