Injecting Env Conn String into .NET Core 2.0 w/EF Core DbContext in different class lib than Startup prj & implementing IDesignTimeDbContextFactory

后端 未结 2 1116
温柔的废话
温柔的废话 2020-12-15 05:38

I honestly cannot believe how hard this is...first off the requirements that I am going for:

  • Implementing Entity Framework Core 2.0\' IDesignTimeDbContex
相关标签:
2条回答
  • 2020-12-15 06:03

    I am a bit confused with your question. Are you using dependency injection for the DbContext or are you trying to initialize and construct the context ad hoc?

    I am doing what you have described in one of my solutions. Here is my solution structure:

    • Corp.ApplicationName.Data
    • Corp.ApplicationName.Web

    Startup.cs

    public Startup(IHostingEnvironment env)
    {
        IConfigurationBuilder builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", false, true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json")
            .AddEnvironmentVariables();
        // ...
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<MyDbContext>(
            options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
            sqlOptions => sqlOptions.EnableRetryOnFailure()));
    
        // SQL configuration for non-injected dbcontext
        DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
        builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        services.AddSingleton(builder.Options);
    
        // ...
    }
    

    MyDbContext.cs

    public class MyDbContext : IdentityDbContext<ApplicationUser>
    {
        public MyDbContext(DbContextOptions options) : base(options) { }
    }
    

    If you are not using dependency injection to pass the DbContext, you can access the SQL properties by injecting DbContextOptions<MyDbContext> instead.

    In this example, the appsettings file is only every read once and everything just works.

    0 讨论(0)
  • 2020-12-15 06:05

    If you are looking for solution to get database connection string from your custom settings class initialized from appsettings.json file - that is how you can do this. Unfortunatelly you can't inject IOptions via DI to your IDesignTimeDbContextFactory implementation constructor.

    public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
    {
       public AppContext CreateDbContext(string[] args)
       {
           // IDesignTimeDbContextFactory is used usually when you execute EF Core commands like Add-Migration, Update-Database, and so on
           // So it is usually your local development machine environment
           var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    
           // Prepare configuration builder
           var configuration = new ConfigurationBuilder()
               .SetBasePath(Path.Combine(Directory.GetCurrentDirectory()))
               .AddJsonFile("appsettings.json", optional: false)
               .AddJsonFile($"appsettings.{envName}.json", optional: false)
               .Build();
    
           // Bind your custom settings class instance to values from appsettings.json
           var settingsSection = configuration.GetSection("Settings");
           var appSettings = new AppSettings();
           settingsSection.Bind(appSettings);
    
           // Create DB context with connection from your AppSettings 
           var optionsBuilder = new DbContextOptionsBuilder<AppContext>()
               .UseMySql(appSettings.DefaultConnection);
    
           return new AppContext(optionsBuilder.Options);
       }
    }
    

    Of course in your AppSettings class and appsettings.json you could have even more sophisticated logic of building the connection string. For instance, like this:

    public class AppSettings
    {
       public bool UseInMemory { get; set; }
    
       public string Server { get; set; }
       public string Port { get; set; }
       public string Database { get; set; }
       public string User { get; set; }
       public string Password { get; set; }
    
       public string BuildConnectionString()
       {
           if(UseInMemory) return null;
    
           // You can set environment variable name which stores your real value, or use as value if not configured as environment variable
           var server = Environment.GetEnvironmentVariable(Host) ?? Host;
           var port = Environment.GetEnvironmentVariable(Port) ?? Port;
           var database = Environment.GetEnvironmentVariable(Database) ?? Database;
           var user = Environment.GetEnvironmentVariable(User) ?? User;
           var password = Environment.GetEnvironmentVariable(Password) ?? Password;
    
           var connectionString = $"Server={server};Port={port};Database={database};Uid={user};Pwd={password}";
    
           return connectionString;
       }
    }
    

    With just values stored in appsettings.json:

    {
      "Settings": {
        "UseInMemory": false,
        "Server": "myserver",
        "Port": "1234",
        "Database": "mydatabase",
        "User": "dbuser",
        "Password": "dbpassw0rd"
      }
    }
    

    With password and user stored in environment variables:

    {
      "Settings": {
        "UseInMemory": false,
        "Server": "myserver",
        "Port": "1234",
        "Database": "mydatabase",
        "User": "MY-DB-UID-ENV-VAR",
        "Password": "MY-DB-PWD-ENV-VAR"
      }
    }
    

    In this case you should use it this way:

    // Create DB context with connection from your AppSettings 
    var optionsBuilder = new DbContextOptionsBuilder<AppContext>();
    if(appSettings.UseInMemory) {
    optionsBuilder = appSettings.UseInMemory
       ? optionsBuilder.UseInMemoryDatabase("MyInMemoryDB")
       : optionsBuilder.UseMySql(appSettings.BuildConnectionString());
    
    return new AppContext(optionsBuilder.Options);
    
    0 讨论(0)
提交回复
热议问题