【问题标题】:Is it possible to use one database to dynamically define the ConnectionString of another?是否可以使用一个数据库来动态定义另一个数据库的 ConnectionString?
【发布时间】:2018-04-12 22:28:46
【问题描述】:

我目前的项目有点困难。

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

  • 帐户:用于安全帐户信息,跨客户
  • 配置:用于管理我们的客户
  • 客户:这对于我们的每个客户来说都是原子的并保存他们的所有信息

我需要使用存储在“配置”数据库中的数据来修改将用于连接到“客户端”数据库的 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);
    }

显然下一阶段是深入“配置”数据库,所以我一直试图将它包含在“SelectClientDatabase()”中,它只将 IServiceCollection 作为参数并出于所有意图和目的,现在是空的。在过去的几天里,我发现了一些关于 EFC 的优秀文章,我目前正在探索 CustomConfigurationProvider 作为可能的途径,但我必须承认我在开始使用 ASP.Net Core 时有点迷茫。

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

谢谢!

编辑 1:

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

编辑 2: 我找到了另一篇文章,描述了如何"Inject DbContext when database name is only know when the controller action is called",这看起来是一个很有希望的起点;但是这是针对旧版本的 ASP.Net Core,根据https://docs.microsoft.com,“DbContextFactory”已重命名,因此我现在正在努力将给出的示例更新为可能的解决方案。

【问题讨论】:

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


    【解决方案1】:

    所以,我终于解决了所有问题。我放弃了工厂的想法,因为我对 不够满意,无法花时间解决它,而且我正匆匆赶到最后期限,所以现在更快的选择是更好的选择,我总能找到时间稍后重构代码(大声笑)。

    我的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");
    

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

    我并不热衷于控制器依赖项在 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”是 生成的类,它将它生成的所有实体联系在一起,我已将以下代码添加到该类中(我确实尝试将它放在一个单独的文件中但无法使其正常工作,所以它保持不变暂时在里面)...

    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&lt;ClientTemplateContext&gt; 接口添加到类中。所以它看起来像这样:

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

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

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

    如果有人可以对我的解决方案提出改进建议,我很乐意看到他们,我的解决方案将按原样组合在一起,并且我想在我觉得自己有足够能力这样做时立即对其进行重构。

    【讨论】:

      猜你喜欢
      • 2015-11-17
      • 1970-01-01
      • 2015-08-27
      • 1970-01-01
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多