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

Create Context without DI? #2718

Closed
RickStrahl opened this issue Jul 26, 2015 · 18 comments
Closed

Create Context without DI? #2718

RickStrahl opened this issue Jul 26, 2015 · 18 comments
Assignees
Labels
closed-no-further-action The issue is closed and no further action is planned.
Milestone

Comments

@RickStrahl
Copy link

If we want to create a fully self-configuring DbContext that doesn't depend on DI, what's the best way to do this?

In the previous beta I was able to access the Configuration instance directly and read the connectionstring info explicitly. However, in the current beta the configuration object appears that it needs a base path which has to come - from DI.

Here's what I would roughly like to be able to do:

protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
    base.OnConfiguring(optionsBuilder);

    if (optionsBuilder.IsConfigured)
        return;

    var builder = new ConfigurationBuilder();  // <--- this is the problem: needs basepath
    builder.AddJsonFile("config.json");
    var configuration = builder.Build();
    string conneMctionString = configuration.Get("Data:MusicStore:ConnectionString");

    optionsBuilder.UseSqlServer(connectionString);
}

My primary reason for this is that I'm not a fan of injected and scoped DbContext instances. In many applications I work on we often deal with multiple contexts in a single controller that aren't used by all actions. There's extra overhead for instantiating these instance, plus there are also few odds and ends where you need multiple contexts in order to keep multiple db operations isolated even when working against the same context type.

Anyway - in the past with EF 6 I typically build a self configuring context that runs off the parameterless constructor. Typically the code to set this up simply points at a connection strings entry and the rest is handled. I'd like to be able to do the same in EF7 as an alternative to feeding a configured instance through DI.

@RickStrahl RickStrahl changed the title Create Context with DI? Create Context without DI? Jul 26, 2015
@ajcvickers
Copy link
Member

@RickStrahl You can derive from DbContext and then just use 'new' to create an instance of your context, just like you could in older versions of EF. EF will still use DI to manage its own services, but it will be a separate container isolated from the rest of your application.

UseSqlServer just requires a connection string, which can be obtained in any way you want. In your sample code you are using ConfigurationBuilder. This is not an EF component and I don't know if it can be used independently of DI or not. @divega might know more. But you certainly don't need to use ConfigurationBuilder or any of its related classes in order to use EF.

@RickStrahl
Copy link
Author

Right I get that. My issue is that in order to build a self-contained context we typically need access to the configuration in some way. In the past we've always been able to get at configuration through the global objects, but now everything is supposed to be injected removing the ability to self-configure easily.

The workaround for now is to use the IServiceProvider overload which requires DI and that works OK for top level code that has access to that. But if you're inside of a component there's no guarantee that you can get at IServiceProvider...

What are the recommendations for dealing creating contexts inside of other components - like a business layer, or even more generically in a repository like framework? Should you then set up your own configuration that passes those things in 'globally'?

@ajcvickers
Copy link
Member

@RickStrahl When you say, "The workaround for now is to use the IServiceProvider overload" do you mean the IServiceProvider overload of DbContext or of something else? Using the IServiceProvider overload of DbContext should not be required here or have any impact on this. EF is never going to read from configuration directly anyway. EF doesn't integrate with the configuration system at all--you get your connection string in any way you want and give it to EF, and that is true however you use EF, with an external DI container or not. If the way DNX configuration is tied to DI doesn't work for you, then you might want to try posting in the Configuration repro: https://github.com/aspnet/Configuration

@RickStrahl
Copy link
Author

@ajcvickers - if you pass in IServiceProvider, EF will create the instance with the configuration info from the top level configuration applied.

So in say a controller this works if IServiceProvider is injected:

using (var ctxt = new AlbumViewerContext(serviceProvider))
{
... this works
}

But, this gets more complicated when you're inside of a component that has no knowledge of the top level application and may not have access to IServiceProvider (or any other DI created components for that matter).

@ajcvickers
Copy link
Member

@RickStrahl "if you pass in IServiceProvider, EF will create the instance with the configuration info from the top level configuration applied" EF doesn't do this. It used to do it in beta3, maybe beta4. But then all the configuration integration was removed from EF and it is now up to the application to read configuration in any way that makes sense to the application and then pass it to EF.

@rowanmiller rowanmiller added this to the Discussions milestone Jul 29, 2015
@rowanmiller rowanmiller assigned ajcvickers and divega and unassigned ajcvickers Jul 29, 2015
@rowanmiller
Copy link
Contributor

@RickStrahl another option is just to make Startup.Configuration static (I did this in an app where I wanted to be able to access config without having to get it from DI). It's always initialized as part of startup, so it should always be available when you need it.

@RickStrahl
Copy link
Author

The problem is you have to do that at the Application level not the component level. My thinking here is how to get at configuration from within a custom, generic component that might be used by many different applications.

@RickStrahl
Copy link
Author

@ajcvickers - It works for me in Beta 5. If I do this ins using ide of a controller I get a configured instance:

 using (var ctxt = new AlbumViewerContext(serviceProvider))
 { }

So somehow EF is pulling the configuration information out of the service provider instance.

@blakeholl
Copy link

If you can't set a connectionstring without using DI, there's a problem.

@rowanmiller
Copy link
Contributor

@blakeholl you can totally use EF without DI, in fact outside of ASP.NET 5 that is the default pattern we recommend folks use - here is a sample from our docs http://ef.readthedocs.org/en/latest/getting-started/full-dotnet.html.

@blakeholl
Copy link

Yes, I have seen that wiki. I just think it's a bit of a hassle that I have to write a lot of implementation details (adding a constructor and _connectionString field, and override OnConfiguring) into my DbContext subclass to support setting a connection string programmatically. I still feel like nhibernate has the better configuration model. Thanks anyway @rowanmiller

@rowanmiller
Copy link
Contributor

@blakeholl you can also just take a DbContextOptions instance in your constructor and pass that to the base constructor - then you don't need to use OnConfiguring etc. (OnConfiguring is really just a convenience method to allow you to setup the DbContextOptions inline in your context).

@tuespetre
Copy link
Contributor

Just had to work with this scenario myself and I see @rowanmiller's link is broken. Here is a sample of what I did in my DbContext using rc1, in case anyone else comes across this:

public partial class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            var config = new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .Build();

            optionsBuilder.UseSqlServer(config.Get<string>("Data:DefaultConnection:ConnectionString"));
        }

        base.OnConfiguring(optionsBuilder);
    }
}

Honestly, a lot of apps are so small or simple that they will never need this, and even if they do, this isn't very much work. It allows me to run dnx ef database update from a TFS build, so I'm happy!

@RickStrahl
Copy link
Author

@tuespetre It's been a while since I worked on this, but if I recall this only works if you already have configured your app. If there's no configuration set up (yet?) the ConfigurationBuilder() will not know where to get the connection string from for example. It all hinges on pre-configuration.

To me this is MUCH MUCH worse than what we had with web.config, because at least with that it worked as long as the setting were there. Now, you are depending on the configuration of the application to be configured in a very specific way or else self-configuration just doesn't work.

Again at the Application level this is not a problem - it's more of a problem for a component/middle tier object that needs to be able to self-configure to load into different environments without dependencies on a specific configuration setup.

@tuespetre
Copy link
Contributor

@RickStrahl

Relying on web.config (or App.config) is precisely the same thing as "[depending] on a specific configuration setup" and "depending on the configuration of the application to be configured in a very specific way." The only difference is that now instead of being required to put your configuration in a very specific format in a very specific place, both of which were already chosen, you get to decide what the format and placement is.

In the example I placed above, I am just grabbing an environment variable for the connection string. If you wanted to use a config file, you could try something like this:

public partial class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            var config = new ConfigurationBuilder()
                .SetBasePath(PlatformServices.Default.Application.ApplicationBasePath)
                .AddJsonFile("ef.json")
                .Build();

            optionsBuilder.UseSqlServer(config.Get<string>("Data:DefaultConnection:ConnectionString"));
        }

        base.OnConfiguring(optionsBuilder);
    }
}

Environment variables or JSON -- either way, no Startup class needed.

@RickStrahl
Copy link
Author

@tuespetre My point is that you can't rely on that in a generic component. Because there's no one single place that configuration comes from or at least is not guaranteed to be there you can't assume anything.

Again this works fine in application scenarios, but it's a major pain for component configuration where a component has to work in any environment and there are no real standards or even config file layout rules.

@rowanmiller
Copy link
Contributor

@RickStrahl totally get your problem, the issue is that there is no static/global configuration (i.e. ConfigurationManager). I don't think there is really anything we can do in EF since we run in different app environments that have different configuration models (i.e. Console/WinForms/ASP.NET 4 etc. all have ConfigurationManager but ASP.NET Core use the non-global Configuration API).

cc @divega as he owns Configuration in "Core World"

@divega
Copy link
Contributor

divega commented Aug 30, 2016

Just reading through discussion issues that has been sitting on my plate for a long time and trying to see if there is anything actionable.

It seems many of the EF Core related concerns mentioned in the thread been addressed by improvements, e.g.:

  • You can pass DbContextOptions in the constructor of DbContext which should make it easy to new up multiple DbContext instances in a request without changing where the DbContextOptions come (e.g. they can still com from DI)
  • If you pass DbContextOptions in the constructor you don't need to have an OnConfiguring() method.
  • Plus we added optional lifetime parameters to AddDbContext() that allow making them transient by default, making it easier to create multiple instance of DbContext in a request but still use the application's service provider.
  • DbContext now uses its own service provider by default,

The only outstanding issue seems to be the desire that the ASP.NET Core stack defined some static/global configuration mechanism which generic components can rely on always being present.

Although this is valid feedback (there is a tradeoff which leads to some loss of functionality of convenience) lack of such a static/global mechanism for configuration (or any other static/global mechanism for that matter) was actually a deliberate design decision by the ASP.NET team to favor designs that are more explicit about how configuration is passed and that are more testable.

@divega divega closed this as completed Aug 30, 2016
@divega divega added the closed-no-further-action The issue is closed and no further action is planned. label Aug 30, 2016
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned.
Projects
None yet
Development

No branches or pull requests

6 participants