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

时间:2017-10-31 17:00:33

标签: entity-framework-core dbcontext asp.net-core-2.0

我目前的项目已经达到了一点砖墙。

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

  • 帐户:有关安全帐户信息,跨越客户
  • 配置:用于管理我们的客户
  • 客户:对于我们的每个客户而言,这将是原子的。掌握所有信息

我需要使用存储在"配置"中的数据。数据库,用于修改 ConnectionString ,用于连接"客户端"数据库,但这就是我被困住了。

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

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

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

显然,下一阶段是进入&#34;配置&#34;数据库,所以我一直在努力保持包含在&#34; SelectClientDatabase()&#34;中,它只是将 IServiceCollection 作为参数,并且用于所有意图和目的为空目前。在过去的几天里,我发现了一些关于EFC的优秀文章,我现在正在探索CustomConfigurationProvider作为一条可能的路线,但我必须承认我在开始时很少迷失在ASP.Net Core中。

是否可以在 ConfigureServices 方法中挂钩新添加的DbContext?或者/我可以/必须在稍后将此数据库添加到服务集合中吗?

谢谢!

修改1:

我刚发现this帖子,其中提到DbContext无法在OnConfiguring中使用,因为它仍在配置中;这很有意义。我现在想知道我是否可以将所有三个DbContexts推送到一个自定义中间件来封装,配置和建立连接;一些新的研究方法。

编辑2: 我发现了另一篇文章,描述了"Inject DbContext when database name is only know when the controller action is called"如何看起来像一个充满希望的起点;然而,这是针对旧版本的ASP.Net Core,根据https://docs.microsoft.com&#34; DbContextFactory&#34;已经重命名,所以我现在正在努力更新给出的可能解决方案的例子。

1 个答案:

答案 0 :(得分:1)

所以,我终于彻底解决了这个问题。我放弃了工厂的想法,因为我对感到不舒服,不能花时间来解决这个问题。我赶紧进入截止日期,所以更快的选择现在是更好的选择,我总能抽出时间重构代码(lol)。

我的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"
            }
        }
    }
}

我选择在 StartUp ConfigureServices 方法中配置两个静态数据库,这些应该在应用程序获取时配置并准备好使用不得不做任何事情。那里的代码很好&amp;干净。

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中设置的配置选项,可以选择一个人,我目前正试图弄清楚我是如何设法获得它的切换到发布版本而不是开发版本。我无法想象我做了什么来切换......

要获取占位符配置设置,请使用单例来保存字符串值。这只是潜入&#34; ConnectionStrings&#34;将Json分组并填入&#34; ClientConnection&#34;对象(详见下文)。

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

其中填充了以下结构(我刚刚在自己的文件中包含了这些结构):

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

我只希望这为动态分配的数据库保存连接字符串,所以它不是太模糊。 &#34;客户&#34; DataMember就是在Json中选择正确的键,如果我想在Json中使用不同的命名节点,我可以将其重命名为&#34; Accounts&#34;,例如。

在确定Singleton选项之前,我测试的另外两个选项是:

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

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

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

我并不热衷于控制器依赖关系在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);
        }
    }
}

然后选择数据库的代码进入&#34; OnActionExecuting()&#34;方法;这也证明了一点点痛苦,试图确保设置得当,最后我决定:

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()有点乱,但是当我重构其他一切时我会清理它。 &#34; ClientTemplateContext&#34;是生成的类,它将生成的所有实体联系在一起,我已将以下代码添加到该类中(我确实尝试将其放在一个单独的文件中,但无法使其正常工作,所以它暂时待在那里)...

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>接口。所以它看起来像这样:

public partial class ClientTemplateContext : DbContext, IDesignTimeDbContextFactory<ClientTemplateContext>

CreateDbContext(string[] args)来自&amp;它允许我们在设计时创建派生上下文的新实例。

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

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

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

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

如果有人可以建议改进我的解决方案,我很乐意看到它们,我的解决方案就像现在一样被捣碎了。我想在我觉得自己足够胜任的时候立即重构它。