简体   繁体   English

将Env Conn字符串注入.NET Core 2.0 w / EF Core DbContext中的不同类lib比启动prj并实现IDesignTimeDbContextFactory

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

I honestly cannot believe how hard this is...first off the requirements that I am going for: 老实说,我不敢相信这有多难......首先是我要求的要求:

  • Implementing Entity Framework Core 2.0' IDesignTimeDbContextFactory which is IDbContextFactory renamed to be less confusing to developers as to what it does 实现实体框架Core IDesignTimeDbContextFactoryIDbContextFactory被重命名为开发人员不会混淆它的作用
  • I do not want to have to do loading of appsettings.json more than once. 我不想不止一次加载appsettings.json One reason is because my migrations are running in the domain of MyClassLibrary.Data and there is no appsettings.js file in that class library, I would have to to Copy to Output Directory appsettings.js . 一个原因是因为我的迁移在MyClassLibrary.Data域中运行,并且该类库中没有appsettings.js文件,我将不得不Copy to Output Directory appsettings.js Another reason is that it just not very elegant. 另一个原因是它不是很优雅。

So here is what I have that currently works: 所以这就是我目前的工作方式:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using AppContext = Tsl.Example.Data.AppContext;

namespace Tsl.Example
{
    public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
    {
        public AppContext CreateDbContext(string[] args)
        {
            string basePath = AppDomain.CurrentDomain.BaseDirectory;

            string envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

            IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(basePath)
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{envName}.json", true)
                .Build();

            var builder = new DbContextOptionsBuilder<AppContext>();

            var connectionString = configuration.GetConnectionString("DefaultConnection");

            builder.UseMySql(connectionString);

            return new AppContext(builder.Options);
        }
    }
}

And here is my Program.cs: 这是我的Program.cs:

using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Tsl.Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        //public static IWebHost BuildWebHost(string[] args) =>
        //    WebHost.CreateDefaultBuilder(args)
        //        .UseStartup<Startup>()
        //        .Build();

        /// <summary>
        /// This the magical WebHost.CreateDefaultBuilder method "unboxed", mostly, ConfigureServices uses an internal class so there is one piece of CreateDefaultBuilder that cannot be used here
        /// https://andrewlock.net/exploring-program-and-startup-in-asp-net-core-2-preview1-2/
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public static IWebHost BuildWebHost(string[] args)
        {
            return new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    IHostingEnvironment env = hostingContext.HostingEnvironment;

                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

                    if (env.IsDevelopment())
                    {
                        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                        if (appAssembly != null)
                        {
                            config.AddUserSecrets(appAssembly, optional: true);
                        }
                    }

                    config.AddEnvironmentVariables();

                    if (args != null)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                })
                //.UseIISIntegration()
                .UseDefaultServiceProvider((context, options) =>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                })
                .UseStartup<Startup>()
                .Build();
        }
    }
}

And here is my Startup.cs: 这是我的Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using Tsl.Example.Interfaces;
using Tsl.Example.Provider;
using AppContext = Tsl.Example.Data.AppContext;

namespace Tsl.Example
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IAppContext, AppContext>();
            services.AddTransient<IExampleDataProvider, ExampleDataProvider>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        { 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseServiceStack(new AppHost());
        }
    }
}

What I would like to do is use the IOptions pattern , so I created this class: 我想要做的是使用IOptions模式 ,所以我创建了这个类:

namespace Tsl.Example
{
    /// <summary>
    /// Strongly typed settings to share in app using the .NET Core IOptions pattern
    /// https://andrewlock.net/how-to-use-the-ioptions-pattern-for-configuration-in-asp-net-core-rc2/
    /// </summary>
    public class AppSettings
    {
        public string DefaultConnection { get; set; }
    }
}

Added this line to Startup.ConfigureServices : 将此行添加到Startup.ConfigureServices

  services.Configure<AppSettings>(options => Configuration.GetSection("AppSettings").Bind(options));

And then tried and change my implementation of IDesignTimeDbContextFactory<AppContext> to: 然后尝试将IDesignTimeDbContextFactory<AppContext>实现更改为:

public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
{
    private readonly AppSettings _appSettings;

    public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)
    {
        this._appSettings = appSettings.Value;
    }

    public AppContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<AppContext>();
        builder.UseMySql(_appSettings.DefaultConnection);
        return new AppContext(builder.Options);
    }
}

Unfortunately this did not work because the Ioptions<AppSettings> argument of public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings) constructor is not injected. 不幸的是,这不起作用,因为未注入public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)构造函数的Ioptions<AppSettings>参数。 I assume this is because implementations of IDesignTimeDbContextFactory<AppContext> are called at Design time and dependency injection is just not "ready" in .NET Core apps at design time? 我假设这是因为IDesignTimeDbContextFactory<AppContext>是在设计时调用的,并且依赖注入在.NET Core应用程序设计时并没有“准备就绪”?

I think it is kind of strange that it is so hard to inject an environment specific connection string using the Entity Framework Core 2.0 pattern of implementing IDesignTimeDbContextFactory , and also not having to copy and load settings files like appsettings.json more than once. 我认为使用实现IDesignTimeDbContextFactory的Entity Framework Core 2.0模式注入特定于环境的连接字符串是如此困难,并且不必IDesignTimeDbContextFactory复制和加载appsettings.json等设置文件。

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. 如果您正在寻找从appsettings.json文件初始化的自定义设置类中获取数据库连接字符串的解决方案 - 这就是您可以执行此操作的方法。 Unfortunatelly you can't inject IOptions via DI to your IDesignTimeDbContextFactory implementation constructor. 不幸的是,您无法通过DIIOptions注入IDesignTimeDbContextFactory实现构造函数。

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. 当然,在AppSettings类和appsettings.json您可以拥有构建连接字符串的更复杂的逻辑。 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 : 只有存储在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);

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? 您是否正在为DbContext使用依赖注入,或者您是否尝试初始化并构建特定的上下文?

I am doing what you have described in one of my solutions. 我正在做我在我的一个解决方案中描述的内容。 Here is my solution structure: 这是我的解决方案结构:

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

Startup.cs 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 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. 如果您没有使用依赖项注入来传递DbContext,则可以通过注入DbContextOptions<MyDbContext>来访问SQL属性。

In this example, the appsettings file is only every read once and everything just works. 在这个例子中,appsettings文件只是每次读取一次,一切正常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 .net 标准 2.0 库中的 EF Core DbContext - EF Core DbContext in .net standard 2.0 library .NET 5 在使用 EF Core Power Tools 时正确注入 DbContext 连接字符串 - .NET 5 properly injecting DbContext connection string when using EF Core Power Tools 将配置文件中的连接字符串注入 EF Core DbContext - Injecting Connection String from config file into EF Core DbContext 在.net Core中将帮助程序注入DbContext - Injecting helper to DbContext in .net Core 将 DbContext 注入 LoggerProvider 会在 .NET Core 中引发 StackOverflowException - Injecting DbContext into LoggerProvider throws StackOverflowException in .NET Core 用于 EF Core 和 EF 6 的 .Net 标准库 DbContext - .Net Standard Library DbContext For EF Core and EF 6 .Net 6 EF Core DbContext 和模型在单独的 Class 库中 - .Net 6 EF Core DbContext and Models In Separate Class Libary Azure Functions .NET Core 中的 EF Core 2.0 连接字符串 - EF Core 2.0 connection string in Azure Functions .NET Core EF Core 查询 .NET Core webapi 启动 - EF Core query on .NET Core webapi startup EF Core中抽象通用DbContext类的设计 - Design of abstract generic DbContext class in EF Core
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM