是否可以使用一个数据库动态定义另一个数据库的 ConnectionString?

Is it possible to use one database to dynamically define the ConnectionString of another?

我的当前项目遇到了一些障碍。

我有三个规范化数据库,我想动态连接到其中一个;这些是:

我需要使用存储在"Configuration"数据库中的数据来修改将用于连接到"Client"数据库的ConnectionString,但是这个是我卡住的地方。

到目前为止,我已经通过连接 EntityFrameWorkCore 工具并使用 "Scaffold-DbContext" 命令将数据库中的实体生成到一个项目中,并且可以进行简单的查找以确保数据库已连接到好的

现在我尝试通过将数据库添加到 ServiceCollection 来注册数据库,我将它们添加到 StartUp class如下:

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.Configure<MvcOptions>(options =>
        {
            options.Filters.Add(new RequireHttpsAttribute());
        });
        services.AddDbContext<Accounts>( options =>
            options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
        );
        services.AddDbContext<Support>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
        );

        // Erm?
        SelectClientDatabase(services);
    }

显然下一阶段是进入 "Configuration" 数据库,所以我一直试图将其包含在 "SelectClientDatabase()" 中,它只需要 IServiceCollection 作为参数,目前暂时为空。在过去的几天里,我发现了一些关于 EFC 的优秀文章,我目前正在探索 CustomConfigurationProvider 作为一条可能的路线,但我必须承认我在 [=65] 开始时有点迷茫=]核心.

是否可以在 ConfigureServices 方法中连接到新添加的 DbContext?或者 can/must 我稍后将此数据库添加到服务集合中?

谢谢!

编辑 1:

我刚找到 this post,其中提到 DbContext 不能在 OnConfiguring 中使用,因为它仍在配置中;这很有意义。我现在想知道是否可以将所有三个 DbContext 推送到自定义中间件中以封装、配置和使连接可用;一些新的研究。

编辑 2: 我找到了另一个 post,描述了如何 which looks like a promising starting point; however this is for an older version of ASP.Net Core, according to https://docs.microsoft.com "DbContextFactory" 已重命名,所以我现在正在努力将给出的示例更新为可能的解决方案。

所以,我终于搞定了。我放弃了工厂的想法,因为我对 感到不够舒服 table,无法花时间解决它,而且我正赶在截止日期前,所以现在选择越快越好我和我以后总能抽出时间重构代码(笑)。

我的 appsettings.json 文件目前仅包含以下内容(appsettings.Developments.json 的相关位相同):

{
    "ConnectionStrings" : {
        "Accounts": "Server=testserver;Database=Accounts;Trusted_Connection=True;",
        "Client": "Server=testserver;Database={CLIENT_DB};Trusted_Connection=True;",
        "Configuration": "Server=testserver;Database=Configuration;Trusted_Connection=True;"
    },
    "Logging": {
        "IncludeScopes": false,
        "Debug": {
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "Console": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

我已选择在 StartUpConfigureServices 方法中配置两个静态数据库,这些应该已配置好并可以使用到应用程序开始不得不做任何事情的时候。那里的代码很好很干净。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.Configure<MvcOptions>(options =>
    {
        //options.Filters.Add(new RequireHttpsAttribute());
    });
    services.AddDbContext<AccountsContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
    );
    services.AddDbContext<ConfigContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
    );
    services.AddSingleton(
        Configuration.GetSection("ConnectionStrings").Get<ConnectionStrings>()
    );
}

事实证明,在如何访问 appsettings.json 中设置的配置选项方面,人们可能会被宠坏,我目前正在努力弄清楚我是如何设法让它切换的到发布版本而不是开发版本。我想不出我做了什么来切换它...

为了获取占位符配置设置,我使用单例来保存字符串值。这只是浸入 "ConnectionStrings" 组并将 Json 填充到 "ClientConnection" 对象中(详见下文)。

    services.AddSingleton(
        Configuration.GetSection("ConnectionStrings").Get<ClientConnection>()
    );

它填充了以下结构(我刚刚在自己的文件中删除):

[DataContract(Name = "ConnectionStrings")]
public class ClientConnection
{
    [DataMember]
    public string Client { get; set; }
}

我只希望它保存动态分配的数据库的连接字符串,所以它不太时髦。 "Client" DataMember 在 Json 中选择正确的键,如果我想在 Json 中使用不同的命名节点,我会将其重命名为 "Accounts",例如.

我测试的另外几个选项是:

services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));

var derp = Configuration.GetSection("ConnectionStrings:Client");

我打了折扣,但值得了解其他选项(它们可能对以后加载其他配置选项很有用)。

我不喜欢 Controller 依赖项在 ASP.Net Core 2 中的工作方式,我希望我能够将它们隐藏在 BaseController 中,这样就不必在我淘汰了每一个控制器,但我还没有找到办法做到这一点。控制器中所需的依赖项在构造函数中传递,这让我感到奇怪了一段时间,因为它们是自动神奇地注入的。

我的BaseController设置如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore.Internal;
using ServiceLayer.Entities;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ServiceLayer.Controllers
{
    public class BaseController : Controller
    {
        private readonly ClientConnection connectionStrings;
        private readonly AccountsContext accountsContext;
        private readonly ConfigurationContext configContext;
        public ClientTemplateContext clientContext;

        private DbContextServices DbContextServices { get; set; }

        public BaseController(AccountsContext accounts, ConfigContext config, ClientConnection connection) : base()
        {
            accountsContext = accounts;
            configContext = config;
            connectionStrings = connection;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            base.OnActionExecuting(context);
        }
    }
}

选择数据库的代码在"OnActionExecuting()"方法中;事实证明这也有点麻烦,试图确保 设置正确,最后我决定:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ServiceLayer.Controllers
{
    public class BaseController : Controller
    {
        private readonly ClientConnection connectionStrings;
        private readonly AccountsContext accountsContext;
        private readonly ConfigurationContext configContext;
        public ClientTemplateContext clientContext;

        private DbContextServices DbContextServices { get; set; }

        public BaseController(AccountsContext accounts, ConfigurationContext config, ClientConnection connection) : base()
        {
            accountsContext = accounts;
            configContext= config;
            connectionStrings = connection;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            // Temporary selection identifier for the company
            Guid cack = Guid.Parse("827F79C5-821B-4819-ABB8-819CBD76372F");

            var dataSource = (from c in configContext.Clients
                              where c.Cack == cack
                              join ds in configContext.DataStorage on c.CompanyId equals ds.CompanyId
                              select ds.Name).FirstOrDefault();

            // Proto-connection string
            var cs = connectionStrings.Client;

            if (!string.IsNullOrEmpty(cs) && !string.IsNullOrEmpty(dataSource))
            {
                // Populated ConnectionString
                cs = cs.Replace("{CLIENT_DB}", dataSource);

                clientContext = new ClientTemplateContext().Initialise(cs);
            }

            base.OnActionExecuting(context);
        }
    }
}

new ClientTemplateContext().Initialise() 有点乱,但我会在重构其他所有内容时清理它。 "ClientTemplateContext" 是 生成的 class 将它生成的所有实体联系在一起,我已经将以下代码添加到 class (我确实尝试将它放在一个单独的文件,但无法让它工作,所以它暂时留在那儿)...

public ClientTemplateContext() {}

private ClientTemplateContext(DbContextOptions options) : base(options) {}

public ClientTemplateContext Initialise(string connectionString)
{
    return new ClientTemplateContext().CreateDbContext(new[] { connectionString });
}

public ClientTemplateContext CreateDbContext(string[] args)
{
    if (args == null && !args.Any())
    {
        //Log error.
        return null;
    }

    var optionsBuilder = new DbContextOptionsBuilder<ClientTemplateContext>();

    optionsBuilder.UseSqlServer(args[0]);

    return new ClientTemplateContext(optionsBuilder.Options);
}

我还包括 using Microsoft.EntityFrameworkCore.Design; 并将 IDesignTimeDbContextFactory<ClientTemplateContext> 接口添加到 class。所以它看起来像这样:

public partial class ClientTemplateContext : DbContext, IDesignTimeDbContextFactory<ClientTemplateContext>

这就是 CreateDbContext(string[] args) 的来源,它允许我们在设计时创建派生上下文的新实例。

最后,我的测试控制器代码如下:

using Microsoft.AspNetCore.Mvc;
using ServiceLayer.Entities;
using System.Collections.Generic;
using System.Linq;

namespace ServiceLayer.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : BaseController
    {
        public ValuesController(
            AccountsContext accounts,
            ConfigurationContext config,
            ClientConnection connection
        ) : base(accounts, config, connection) {}

        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            var herp = (from c in clientContext.Usage
                        select c).FirstOrDefault();

            return new string[] {
                herp.TimeStamp.ToString(),
                herp.Request,
                herp.Payload
            };
        }
    }
}

这成功地从 DataSource table Configuration 数据库中动态选择的数据库生成数据!

["01/01/2017 00:00:00","derp","derp"]

如果有人可以对我的解决方案提出改进建议,我很乐意看到他们,我的解决方案是按原样混合在一起的,一旦我觉得自己有足够的能力,我想重构它。