【问题标题】:Dynamically change connection string in Asp.Net Core在 Asp.Net Core 中动态更改连接字符串
【发布时间】:2016-08-17 09:45:04
【问题描述】:

我想在控制器中更改 sql 连接字符串,而不是在 ApplicationDbContext 中。我正在使用 Asp.Net Core 和 Entity Framework Core。

例如:

public class MyController : Controller {
    private readonly ApplicationDbContext _dbContext
    public MyController(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    private void ChangeConnectionString()
    {
    // So, what should be here?
    } }

我该怎么做?

【问题讨论】:

  • 在请求期间是永久还是临时?
  • @Tseng 临时的,在控制器的生命周期内。
  • 你问这个问题是几年前的事了。请问您是否有最终解决方案?

标签: c# asp.net asp.net-core entity-framework-core


【解决方案1】:

如果您想根据活动的 http 请求的参数为每个 http 请求选择一个连接字符串,这就足够了。

    using Microsoft.AspNetCore.Http;

    //..

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services.AddDbContext<ERPContext>((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;
            var connection = GetConnection(httpRequest);
            options.UseSqlServer(connection);
        });

更新

大约一年后,我的解决方案看起来就像这里其他答案的零碎,所以请允许我为你总结一下。

您可以在启动文件中添加一个 HttpContextAccessor 的单例:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<ERPContext>();

这将解决上下文构造函数中的注入:

public class ERPContext : DbContext
{
    private readonly HttpContext _httpContext;

    public ERPContext(DbContextOptions<ERPContext> options, IHttpContextAccessor httpContextAccessor = null)
        : base(options)
    {
        _httpContext = httpContextAccessor?.HttpContext;
    }

    //..

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            var clientClaim = _httpContext?.User.Claims.Where(c => c.Type == ClaimTypes.GroupSid).Select(c => c.Value).SingleOrDefault();
            if (clientClaim == null) clientClaim = "DEBUG"; // Let's say there is no http context, like when you update-database from PMC
            optionsBuilder.UseSqlServer(RetrieveYourBeautifulClientConnection(clientClaim));
        }
    }

    //..
}

这将为您提供一种干净的方式来访问和提取声明并决定您的连接。

正如 cmets 上的 @JamesWilkins 所述,将为创建的每个上下文实例调用 OnConfiguring()。

注意可选的访问器和!optionsBuilder.IsConfigured。 您将需要它们来简化您将覆盖上下文配置的测试。

【讨论】:

  • 有趣。您应该更清楚为什么会这样;来自文档:optionsAction: An optional action to configure the DbContextOptions for the context. This provides an alternative to performing configuration of the context by overriding the OnConfiguring(DbContextOptionsBuilder) method in your derived context. ... [OnConfiguring()] is called for each instance of the context that is created
  • 喜欢这个解决方案,感谢 James 对它背后的魔力提供了一些见解。这正是我们正在寻找的解决方案!
  • 您知道 EF Core 5 是否仍需要这样做吗? devblogs.microsoft.com/dotnet/…
  • 它就像 EF Core 3.1.5 的魅力一样。谢谢!
  • 因此该解决方案使用clientClaim 通过它通过方法RetrieveYourBeautifulClientConnection(clientClaim) 获得的选项对象来初始化数据库。如果您在不同用户可以在不同数据库上的 WebAPI 中使用它会发生什么?我们仍然可以在那里使用 TryAddSingleton 还是需要限定范围或瞬态?
【解决方案2】:

我们有一个与您类似的案例。我们所做的是在Startup类的ConfigureServices方法中使用IServiceCollectionimplementationfactory重载,像这样:

//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());

我现在很难为你实现 CreateApplicationDbContext,因为它完全取决于你到底想要什么。但是一旦你弄清楚了你想要如何做这部分,无论如何,该方法的基础应该是这样的:

public ApplicationDbContext CreateApplicationDbContext(){
  //TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
} 

一旦实现,您就可以像在构造函数中那样在控制器中注入正确的 ApplicationDbContext:

public MyController(ApplicationDbContext dbContext)
{
    _dbContext = dbContext;
}

或者控制器中的动作方法:

public IActionResult([FromServices] ApplicationDbContext dbContext){
}

无论您如何实现细节,诀窍是实现工厂将在您每次注入时构建您的 ApplicationDbContext。

如果您在实施此解决方案时需要更多帮助,请告诉我。

更新 #1 Yuriy N. 询问 AddTransient 和 AddDbContext 之间有什么区别,这是一个有效的问题……但事实并非如此。让我解释一下。

这与原始问题无关。

但是...话虽如此,在这种情况下,使用实体框架实现您自己的“实现工厂”(这是我的回答中最重要的一点)可能比我们需要的更棘手。

但是,对于这样的问题,我们现在可以幸运地查看 GitHub 中的源代码,所以我查找了 AddDbContext 的确切作用。嗯......这并不难。这些“添加”(和“使用”)扩展方法只不过是方便的方法,请记住这一点。因此,您需要添加 AddDbContext 所做的所有服务以及选项。也许您甚至可以重用 AddDbContext 扩展方法,只需使用实现工厂添加您自己的重载。

所以,回到你的问题。 AddDbContext 做了一些 EF 特定的东西。如您所见,它们将允许您在以后的版本中度过一生(瞬态,单例)。 AddTransient 是 Asp.Net Core,它允许您添加所需的任何服务。你需要一个实现工厂。

这样是不是更清楚了?

【讨论】:

  • 为什么选择DbContextAddTransient?这种加法和services.AddEntityFramework().AddSqlServer().AddDbContext&lt;ApplicationDbContext&gt;(options =&gt; options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]))有什么区别?使用你的方法,我们丢失了控制台的迁移,不是吗?
  • 我已更新我的答案以回应您。希望能帮助到你。 :)
  • 真的很有帮助,谢谢!但仍然不清楚,我们应该如何处理迁移? ApplicationDbContext 不是 DbContext,因此无法从控制台迁移。
  • 你这是什么意思? ApplicationDbContext 应该/仍然可以从 DbContext 继承,对吗?也许尝试实施解决方案。看看你能走多远。然后在 SO 上发布一个新的特定问题,我们将进一步帮助您。 :)
  • 是的,你说得对,让我们尝试实施解决方案。
【解决方案3】:

通过将连接字符串逻辑移动到 DbContext 的 OnConfiguring 方法中,我能够更改每个请求的连接字符串。

Startup.cs#ConfigureServices 方法中: services.AddDbContext&lt;MyDbContext&gt;();

在 MyDbContext.cs 中,我将需要注入的服务添加到构造函数中。

    private IConfigurationRoot _config;
    private HttpContext _httpContext;

    public MyDbContext(DbContextOptions options, IConfigurationRoot config, IHttpContextAccessor httpContextAccessor) 
          : base(options)
    {
        _config = config;
        _httpContext = httpContextAccessor.HttpContext;
    }

然后覆盖 OnConfiguring:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = BuildConnectionString(); // Your connection string logic here

        optionsBuilder.UseSqlServer(connString);
    }

【讨论】:

  • 你实际上是如何进行 config 和 httpContextAccessor 的注入的?也许你使用了 AddDbContext 以外的东西?
  • 您需要在ConfigureServices() 中添加他们的注入,例如:services.AddTransient&lt;IHttpContextAccessor , HttpContextAccessor &gt;()。在做addDbContext()之前注入它们很重要。
  • @YuriyN。当我添加 services.AddDbContext() 时,它只调用 MyDbContext 类的默认构造函数,即 public MyDbContext(){}。在调用这个之前,我已经注入了 httpcontext。
  • 你实际上是如何将你的连接字符串传递给构造函数的,说“你的连接字符串逻辑在这里”不够好,我不希望我的逻辑在那里,我希望我的连接字符串是在构建上下文之前确定
  • 向 ASP.NET Core 应用程序添加每租户数据库多租户的完美解决方案!
【解决方案4】:

@ginalx 和@jcmordan 的答案非常适合我的用例。我喜欢这些答案的一点是,我可以在Startup.cs 中完成所有操作,并保持所有其他类的构造代码干净。我想为 Web Api 请求提供一个可选的查询字符串参数,并将其替换为创建 DbContext 的基本连接字符串。我将基本字符串保留在 appsettings.json 中,并根据传入的参数或默认值(如果未提供)对其进行格式化,即:

"IbmDb2Formatted": "DATABASE={0};SERVER=servername;UID=userId;PWD=password"

我的最终ConfigureServices 方法看起来像(obvs。我连接到 DB2 而不是 SQL,但这是偶然的):

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

        services.AddDbContext<Db2Context>(((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;

            // Get the 'database' querystring parameter from the request (if supplied - default is empty).
           // TODO: Swap this out for an enum.
            var databaseQuerystringParameter = httpRequest.Query["database"].ToString();

            // Get the base, formatted connection string with the 'DATABASE' paramter missing.
            var db2ConnectionString = Configuration.GetConnectionString("IbmDb2Formatted");

            if (!databaseQuerystringParameter.IsNullOrEmpty())
            {
                // We have a 'database' param, stick it in.
                db2ConnectionString = string.Format(db2ConnectionString, databaseQuerystringParameter);
            }
            else
            {
                // We havent been given a 'database' param, use the default.
                var db2DefaultDatabaseValue = Configuration.GetConnectionString("IbmDb2DefaultDatabaseValue");
                db2ConnectionString = string.Format(db2ConnectionString, db2DefaultDatabaseValue);
            }

            // Build the EF DbContext using the built conn string.
            options.UseDb2(db2ConnectionString, p => p.SetServerInfo(IBMDBServerType.OS390));
        }));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info
            {
                Title = "DB2 API",
                Version = "v1"
            });
        });
    }

【讨论】:

    【解决方案5】:

    所有其他答案都不适合我。所以我想为那些在运行时更改数据库连接字符串的人分享我的方法。

    我的应用程序是使用 asp.net core 2.2Entity FrameworkMySql.

    StartUp.cs

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddDbContext<MyDbContext>();
    
        ...
    

    MyDbContext 类

    public partial class MyDbContext : DbContext
    {
        public MyDbContext()
        {
        }
    
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (DbManager.DbName != null && !optionsBuilder.IsConfigured)
            {
                var dbName = DbManager.DbName;
                var dbConnectionString = DbManager.GetDbConnectionString(dbName);
                optionsBuilder.UseMySql(dbConnectionString);
            }
        }
    
        ...
    

    Json - 具有连接信息的文件

    [
      {
        "name": "DB1",
        "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname1"
      },
      {
        "name": "DB2",
        "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname2"
      }
    ]
    

    DbConnection 类

    using System.Collections.Generic;
    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;
    
    
    public class DbConnection
    {
        [JsonProperty("name")]
        public string Name { get; set; }
    
        [JsonProperty("dbconnection")]
        public string Dbconnection { get; set; }
    
        public static List<DbConnection> FromJson(string json) => JsonConvert.DeserializeObject<List<DbConnection>>(json, Converter.Settings);
    }
    
        internal static class Converter
        {
            public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
            {
                MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
                DateParseHandling = DateParseHandling.None,
                Converters =
                {
                    new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
                },
            };
        }
    }
    

    DbConnectionManager 类

    public static class DbConnectionManager
    {
        public static List<DbConnection> GetAllConnections()
        {
            List<DbConnection> result;
            using (StreamReader r = new StreamReader("myjsonfile.json"))
            {
                string json = r.ReadToEnd();
                result = DbConnection.FromJson(json);
            }
            return result;
        }
    
        public static string GetConnectionString(string dbName)
        {
            return GetAllConnections().FirstOrDefault(c => c.Name == dbName)?.Dbconnection;
        }
    }
    

    DbManager 类

    public static class DbManager
    {
        public static string DbName;
    
        public static string GetDbConnectionString(string dbName)
        {
            return DbConnectionManager.GetConnectionString(dbName);
        }
    }
    

    然后,您需要一些控制器来设置 dbName

    控制器类

    [Route("dbselect/{dbName}")]
    public IActionResult DbSelect(string dbName)
    {
        // Set DbName for DbManager.
        DbManager.DbName = dbName;
    
        dynamic myDynamic = new System.Dynamic.ExpandoObject();
        myDynamic.DbName = dbName;
        var json = JsonConvert.SerializeObject(myDynamic);
        return Content(json, "application/json");
    }
    

    你可能不得不在这里和那里做一些诡计。但你会得到这个想法。在应用程序的开头,它没有连接详细信息。所以你必须使用 Controller 显式设置它。希望这会对某人有所帮助。

    【讨论】:

    • 又一个很棒的方法,但为什么不简单地注入 IOptions&lt;DbConnection&gt; 并使用 ASP.NET Core 内置配置的所有强大功能来配置您的连接字符串?
    • 因为,如果是这样,在需要时添加更多连接会更加困难。如果您存储在 Json 或其他数据库中。您只需添加一个连接即可。希望这能让你明白。
    • 好文件。但为了获得价值。你需要在构造函数的某个地方创建IConfiguration config。那么您将无法将 DbManager 类和方法设为static。对于处理 dbConnection,我认为我的方法仍然是更好的选择。
    • 与 SQL Server 完美配合。
    【解决方案6】:

    虽然晚了,但 EF Core 中最简单的技巧是使用 nuget Microsoft.EntityFrameworkCore.Relational

    _dbContext.Database.GetDbConnection().ConnectionString = "NEW_CONN_STRING";
    

    当您的应用程序配置/设置中因任何原因不存在连接字符串时,或者您想使用一个 DbContext 实例(同样,出于任何原因)处理具有相同结构的多个数据库时,这很有用。

    永久临时取决于您为 DbContext 选择的注入生命周期类型。如果您将其作为 Singleton 服务注入,它将是永久,这是推荐的。

    【讨论】:

    • 谢谢,这对我来说可以在同一服务器和凭据上更改为具有相同结构的不同数据库。要保留密码,连接字符串必须包含: Persist Security Info=True;示例:public void SetDatabase(string newDatabase) { var con = _context.Database.GetDbConnection(); var csb = new SqlConnectionStringBuilder(con.ConnectionString) { InitialCatalog = newDatabase }; con.ConnectionString = csb.ConnectionString;}
    • 这是我需要的,但我在“_context.Database”中没有方法“GetDbConnection()”
    • @MariINova 请安装 nuget 包 Microsoft.EntityFrameworkCore.Relational。链接:nuget.org/packages/Microsoft.EntityFrameworkCore.Relational
    • 在基本控制器中设置连接字符串以根据拨打电话的用户使用特定数据库的好解决方案。
    • 您能解释一下如何将连接字符串传递给控制器​​构造函数吗?
    【解决方案7】:

    这对我有用:

    public void ConfigureServices(IServiceCollection services)
    {
        // .....
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<School360DbContext>(provider =>
        {
            return ResolveDbContext(provider, hostingEnv);
        });
        // ..
    }
    
    private MyDbContext ResolveDbContext(IServiceProvider provider, IHostingEnvironment hostingEnv)
    {
        string connectionString = Configuration.GetConnectionString("DefaultConnection");
    
        string SOME_DB_IDENTIFYER = httpContextAccessor.HttpContext.User.Claims
            .Where(c => c.Type == "[SOME_DB_IDENTIFYER]").Select(c => c.Value).FirstOrDefault();
        if (!string.IsNullOrWhiteSpace(SOME_DB_IDENTIFYER))
        {
            connectionString = connectionString.Replace("[DB_NAME]", $"{SOME_DB_IDENTIFYER}Db");
        }
    
        var dbContext = new DefaultDbContextFactory().CreateDbContext(connectionString);
    
        // ....
        return dbContext;
    }
    

    【讨论】:

      【解决方案8】:

      我选择了这个解决方案:

      而不是

      services.AddScoped<IMyDbContext, MyDbContext>();
      

      我去了

      services.AddTransient<IMyDbContext, MyDbContext>(resolver =>
      {
          var context= resolver.GetService<MyDbContext>();
          var config = resolver.GetService<IConfiguration>();
          var connectionString = config.GetConnectionString("MyDb");
          context.GetDbConnection().ConnectionString = connectionString;
          return context;
      });
      

      运行时覆盖设置:

      Configuration["ConnectionStrings:MyDb"] = newConnectionString;
      

      【讨论】:

        【解决方案9】:

        我创建了一个 .net6 控制台 应用程序并循环 1 到 10 以插入到 test1 数据库和 test2 数据库: Program.cs:

        Console.WriteLine("Hello, World!");
        
        for (int i = 1; i <= 10; i++)
        {
        
            if (i % 2 == 0)
            {
                var _context = new AppDbContext("Data Source=.\\SQLEXPRESS;Initial Catalog=test2;Integrated Security=True"); // test2
                _context.Tbls.Add(new Tbl { Title = i.ToString() });
                _context.SaveChanges();
            }
            else
            {
                var _context = new AppDbContext("Data Source=.\\SQLEXPRESS;Initial Catalog=test1;Integrated Security=True"); // test1
                _context.Tbls.Add(new Tbl { Title = i.ToString() });
                _context.SaveChanges();
            }
        }
        

        AppDbContext.cs:

        public partial class AppDbContext : DbContext
            {
                public AppDbContext(string connectionString) : base(GetOptions(connectionString))
                {
                }
        
                public virtual DbSet<Tbl> Tbls { get; set; }
        
                private static DbContextOptions GetOptions(string connectionString)
                {
                    return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
                }
            }
        

        【讨论】:

          【解决方案10】:

          用于静态连接的 Startup.cs

          services.AddScoped<MyContext>(_ => new MyContext(Configuration.GetConnectionString("myDB")));
          

          用于动态连接的Repository.cs

          using (var _context = new MyContext(@"server=....){
          context.Table1....
          }
          

          Table1MyContext.cs

          public MyContext(string connectionString) : base(GetOptions(connectionString))
          {
          }
          
          private static DbContextOptions GetOptions(string connectionString)
          {
              return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
          }
          

          【讨论】:

          • 一个问题 - 我从哪里得到SqlServerDbContextOptionsExtensions
          • 找到它here
          猜你喜欢
          • 1970-01-01
          • 2019-09-18
          • 2021-12-18
          • 2018-09-20
          • 1970-01-01
          • 2018-05-11
          相关资源
          最近更新 更多