What is the proper way to reuse DbContext across different projects in a single solution?

.net .net-core c# entity-framework entity-framework-core

Question

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.

1
0
2/27/2020 10:41:05 PM

Accepted Answer

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.

1
2/28/2020 3:06:39 AM

Popular Answer

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.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow