与启动prj& amp;在不同的类库中,将Env Conn字符串注入具有EF Core DbContext的.NET Core 2.0。实现IDesignTimeDbContextFactory [英] Injecting Env Conn String into .NET Core 2.0 w/EF Core DbContext in different class lib than Startup prj & implementing IDesignTimeDbContextFactory

查看:74
本文介绍了与启动prj& amp;在不同的类库中,将Env Conn字符串注入具有EF Core DbContext的.NET Core 2.0。实现IDesignTimeDbContextFactory的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

老实说,我不敢相信这有多难...首先要达到的要求是

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


  • 实施实体框架核心2.0' IDesignTimeDbContextFactory ,即 IDbContextFactory 重命名,以减少开发人员对其功能的混淆

  • 我不想必须多次加载 appsettings.json 。原因之一是因为我的迁移运行在 MyClassLibrary.Data 域中,并且其中没有 appsettings.js 文件该类库,我将不得不复制到输出目录 appsettings.js 。另一个原因是它并不十分优雅。

  • Implementing Entity Framework Core 2.0' IDesignTimeDbContextFactory which is IDbContextFactory renamed to be less confusing to developers as to what it does
  • I do not want to have to do loading of appsettings.json more than once. 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. 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);
        }
    }
}

这是我的程序。 cs:

And here is my 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();
        }
    }
}

这是我的创业公司。 cs:

And here is my 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());
        }
    }
}

我想做什么使用 IOptions模式,因此我创建了此类:

What I would like to do is use the IOptions pattern, so I created this class:

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; }
    }
}

将此行添加到 Startup.ConfigureServices

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

然后尝试更改我对 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);
    }
}

不幸的是,由于<$ c $不会注入 public DesignTimeDbContextFactory(IOptions< AppSettings> appSettings)构造函数的c> Ioptions< AppSettings> 参数。我认为这是因为 IDesignTimeDbContextFactory< AppContext> 的实现是在设计时调用的,而在设计时.NET Core应用程序中的依赖注入是否还没有就绪?

Unfortunately this did not work because the Ioptions<AppSettings> argument of public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings) constructor is not injected. 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 ,也不必多次复制和加载诸如 appsettings.json 之类的设置文件。

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.

推荐答案

如果您正在寻找从 appsettings.json 文件初始化的自定义设置类中获取数据库连接字符串的解决方案-就是这样你可以这样做。不幸的是,您不能通过 DI IOptions 注入到 IDesignTimeDbContextFactory

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);
   }
}

当然在您的 AppSettings中类和 appsettings.json ,您甚至可以使用更复杂的逻辑来构建连接字符串。例如,像这样:

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;
   }
}

仅将值存储在中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);

这篇关于与启动prj&amp; amp;在不同的类库中,将Env Conn字符串注入具有EF Core DbContext的.NET Core 2.0。实现IDesignTimeDbContextFactory的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆