0

I have a .Net Core solution which consists of 3 projects like below:

  1. Common : (All EF Data, migrations, Model, DbContext)
  2. WebApi : (Rest api consuming DbContext from Common)
  3. Worker : (Background services that aims to consume DbContext from Common)

I wanted to place all my EF logic and DbContext in Common and consume it from my other two projects. WebApi is working fine, but I couldn't use it from my hosted services found in Worker project. There are 4 background workers and all of them require access to database so I wanted to get access to my DbContext inside them.

So, what is the proper way to reuse a DbContext across multiple projects. It can be considered that all services need access some common tables. So isolating tables via different Dbcontexts is not an option for me.

This is my Startup.cs in WebApi:

services.AddDbContext<DataContext>(options => options
        .UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));

This is my Program.cs in Worker:

services.AddDbContext<DataContext>(options => options
        .UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));

It throws an error telling me that I can not consume a scoped service from a singleton one.

Nostromo
  • 959
  • 2
  • 7
  • 25
  • Indeed, and don't try to make db context a singleton. Must you do this with DI everywhere? – Caius Jard Feb 27 '20 at 22:48
  • Yes, I know it is a little bit dangerous to make it a singleton so i don't have any intention to do that. But, do i need to create separate scopes for each project? if yes, how can i achieve this? – Nostromo Feb 27 '20 at 22:52
  • NM didn't see this in a single solution – Train Feb 27 '20 at 22:53

3 Answers3

2

If you just want to reference a DbContext in your Web API + Background Workers, then I don't understand what the issue is: Just reference the Common project from your worker projects. From your post the issue looks to be that the service injection you are using successfully in the Web API doesn't work with the background worker services. (Assuming Windows Services?)

Start instead with a simpler scenario. Initiate the DbContext inside the service when used rather than inject it.

I.e.

using(var context = new DataContext("DefaultConnection"))
{
    // ...
}

Your connection string configuration should be identifying NPGSQL as the provider so as long as that config is all set up in your Services config then the DBContext should be able to configure by connection string name. If that works then there will probably be a different mechanism for injection. From what I could quickly find it seems examples used a service locator pattern to resolve dependencies, I don't know if there are better options these days for Windows Services.

If injection isn't really an option and you have to resort to service locator-like implementations then I would probably consider something like a Lazy Property injection pattern I've used in the past:

public class WorkerService
{
    private readonly IContainer _container = null;

    public WorkerService(IContainer container)
    {
        _container = container ?? throw new ArgumentNullException("container");
    }

    private IDataContextFactory _contextFactory = null;
    public IDataContextFactory ContextFactory
    {
        get { return _contextFactory ?? (_contextFactory = _container.Resolve<IDataContextFactory>()); }
        set { _contextFactory = value; }
    }

    public void Execute()
    {
         using(var context = ContextFactory.Create()) // returns a DataContext.
         {
             // do stuff.
         }
    }

}

Where IContainer represents a contract interface for your given DI framework. (Unity, Autofac, etc.)

Alternatively, a unit of work for scoping a DbContext. Given a Service instance will be long running we don't want to inject or resolve a DbContext, but rather a Context Factory which we can use to receive an initialized DbContext which can be used in a dispose. Normally with web requests the instance is scoped to the request and disposed by the container at the end of the request. With a service we want to ensure the DbContext is disposed regularly. A DI can be set up so that transient instances of the context are returned, but those instances need to be disposed meaning it's not suited for constructor injection, but rather via a service locator. If a single DbContext instance was used and injected in the constructor of a service, it would live until the service stopped which would see that DbContext get slower and slower as time went on due to tracked entities.

Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • I realized i somehow stucked with this di approach. I didn't think i could create and consume my instance directly inside my service. Thank you very much for pointing this out for me. – Nostromo Feb 28 '20 at 14:03
1

I think you're a bit confused here. The thing you want to reuse is the DbContext code + all EF logic. You don't want to (can't) reuse same DbContext instance across projects (apps).

So to reuse the code, you just need to put all of your Model + DBContext in a project. Then in other projects, you can add reference to it. And start using it.

Alex - Tin Le
  • 1,982
  • 1
  • 6
  • 11
  • Yes, thanks but i need to clearify a bit more. I don't want to use the same Dbcontext instance as you pointed out. But somehow I need an access a data context object in my other projects. DI doesn't helped me so far. – Nostromo Feb 27 '20 at 23:17
  • So you want to have 1 DBContext object to be accessed by many projects. That's strongly not recommended as Unit of Work design pattern. And also, why you need to do that? – Alex - Tin Le Feb 27 '20 at 23:21
  • It doesn't have to be 1 dbcontext object, maybe a scoped one like this: https://stackoverflow.com/questions/48368634/how-should-i-inject-a-dbcontext-instance-into-an-ihostedservice but didn't work. – Nostromo Feb 27 '20 at 23:38
0

Remove all AddDbContext from Startup.cs/Program.cs.

Put DbContext connectionstring in Common project instead.

Create some CRUDs in Common project. Then all other projects can use Common's CRUD which is connected to same DbContext.

Asherguru
  • 1,687
  • 1
  • 5
  • 10