Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Injecting DbContext, not the actual implementation #4558

Closed
Muchiachio opened this issue Feb 13, 2016 · 8 comments
Closed

Injecting DbContext, not the actual implementation #4558

Muchiachio opened this issue Feb 13, 2016 · 8 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-unknown
Milestone

Comments

@Muchiachio
Copy link

Is there a way to use AddDbContext<TContext>, to inject a particular implementation of DbContext without actually forcing other layers to depend on context's implementation? Registering DbContext implementation using AddDbContext<Context>, doesn't allow using DbContext as a Service constructor parameter.

public class Statup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFramework().AddSqlServer().AddDbContext<Context>();
    }
}

public class Context : DbContext
{
    public DbSet<Item> Items { get; set; }
}

public class Service
{
    public Service(DbContext context)
    {
        // context doesn't get injected, and I don't know what type of context it will be
        // it may be Context, it may be TestingContext, DI should take care of that
        var items = context.Set<Item>().ToArray();
    }
}

I tried a lot of DI configurations and couldn't manage this to work.
I would want to wire it up by hand like this, but it doesn't work now.

public class Statup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<DbContextOptions>(provider => ...);
        services.AddTransient<DbContext, Context>();
    }
}
@techyian
Copy link

Hey,

Hopefully this may help you in some way. What I'd do in this scenario is:

  1. Interface out your Context
public interface IContext
{
    DbSet<T> dbSet<T>() where T : class; //This will be how you go about calling your models.
}
  1. Implement your interface
public Context : DbContext, IContext
{
    public DbSet<T> dbSet<T>() where T : class
    {
        return this.Set<T>();
    }
}
  1. Within Startup.cs create an implementation factory which will add your context to DI. Also note we are adding the concrete implementation to DI also using the AddDbContext helper.
public void ConfigureServices(IServiceCollection services)
{            
        // Add framework services.
        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<Context>(options =>                    
                options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])
           );

       //Other code removed for simplicity

       //Add our Interface to DI
       services.AddScoped(repoFactory);
}

private Func<IServiceProvider, IContext> repoFactory = x =>
{
        //Getting our context out of the container.
        var context = x.GetService<Context>();

            return context;

};
  1. We can now inject our Interface into our controllers
public class HomeController
{
    private readonly IContext _context;

    public HomeController(IContext context)
    {
        _context = context;
    }

    public IActionResult Index()
    {
        var myModel = _context.dbSet<MODEL>();
    }
}

Please let me know if you have any problems with this code, or if it isn't quite what you're looking for.

Cheers :)

@Muchiachio
Copy link
Author

Emm, you can't interface DbContext, because it's a class? I'm actually using unit of work pattern, so your approach seems to be on the right track, I will try this out tomorrow. Thanks.

@techyian
Copy link

Sorry, that was my stupid mistake, I put DbContext in the wrong place and it should be on the Context class. I've changed it accordingly. Good luck :)

@Muchiachio
Copy link
Author

I actually ran into #4441 problem, of contexts being scoped, which results in "object's already disposed" errors. I still baffled on why replacing AddDbContext scoped creation with your own transient AddDbContext extension doesn't work at all.

@rowanmiller
Copy link
Contributor

@ajcvickers could you comment on this one. We do something with scoping on DbContext right?

@ajcvickers
Copy link
Member

Closing this as the relevant changes in #4668 have now been merged. AddDbContext still registers the context as scoped if the context type is not yet registered, but a registration with a different lifetime can now be made either before or after with AddTransient or AddSingleton.

Your context class will now need to take a DbContextOptions<TContext> object in its constructor. This is registered as a singleton in the app container by AddDbContext, or it can be created explicitly using DbContextOptions builder.

@Ciantic
Copy link

Ciantic commented Apr 14, 2017

I think I stumbled on this, I managed to make it work like this:

Assume following classes

public class AppDbContext : DbContext {...}
public class SpecificDbContext : AppDbContext {...}

Then startup like this:

var services = new ServiceCollection();
services.AddEntityFramework().AddDbContext<SpecificDbContext>(...));

The EntityFramework uses SpecificDbContext as the injected class. This means that controllers that assumes AppDbContext being there ceases functioning.

In order to use the AppDbContext or even DbContext as a injected class (but still have SpecificDbContext instance there). One has to register it in startup like this:

services.AddScoped<AppDbContext, SpecificDbContext>(f =>
{
    return f.GetService<SpecificDbContext >();
});

Or for DbContext injection, use this:

services.AddScoped<DbContext, SpecificDbContext>(f =>
{
    return f.GetService<SpecificDbContext >();
});

This way the injection works with right instance. It won't work correctly with services.AddScoped<AppDbContext, SpecificDbContext>() alone.

@roniaxe
Copy link

roniaxe commented Jan 25, 2018

Thanks @Ciantic , nice solution.
For:

It won't work correctly with services.AddScoped<AppDbContext, SpecificDbContext>() alone.

Why? Because registering AppDbContext requires the EF DbContext service?

@ajcvickers ajcvickers added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-unknown labels Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-unknown
Projects
None yet
Development

No branches or pull requests

6 participants