C#动态修改connectionString
使用Entity Framework方式连接数据库时,处于安全性考虑,会把App.config里面的connectionString放到配置中心或者加密。加密是为了不暴露数据库连接信息,而放到配置中心好处则更加明显,如果出现集群部署,只需要在配置中心设置一次,便能在所有服务器中生效,大大节省了维护时间。有时候,当数据库出现故障,需要手工切换数据库时,就需要在不重启程序的情况下,更改连接字符串。本篇就来谈谈C#如何在程序中动态的修改connectionString.
ConfigurationManager
项目的配置文件读写都是通过System.Configuration下面的ConfigurationManager来操作的,首先需要添加System.Configuration.dll的引用。
关于ConfigurationManager的介绍,可以参见MSDN,一般的对于config文件的appSettings节点操作较多,而我们这里今天主要关注的connectionStrings节点。
动态添加connectionString
根据MSDN上的介绍,很快就能写出添加connStr的方法:
static void addConnStr(string name, string server, string db, string username, string password)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConnectionStringSettings connSettings = new ConnectionStringSettings();
connSettings.Name = name;
connSettings.ConnectionString = string.Format(@"metadata=res://*;provider=System.Data.SqlClient;provider connection string='data source={0};initial catalog={1};persist security info=True;user id={2};password={3};MultipleActiveResultSets=True;App=EntityFramework'", server, db, username, password);
connSettings.ProviderName = "System.Data.EntityClient";
//需要先做一个删除操作
config.ConnectionStrings.ConnectionStrings.Remove(connSettings);
config.ConnectionStrings.ConnectionStrings.Add(connSettings);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("connectionStrings");
}
对于ConnectionString的格式,建议先用EF连接后,在App.config文件中Copy出来,去掉metadata下多余的节点信息即可,这样可以省很多事情。
测试:
前提条件简单说明:
笔者在test, test_1数据库中均新建了同样的paytest表,然后通过动态修改connectionString的方式,分别读取paytest表中前5条的内容。其中test数据库中有多余5条记录,而test_1中只有2条记录,具体的读取方法如下:
static void test()
{
try
{
using (var context = new testEntities())
{
//打印Connectionstring用来观察
Console.WriteLine(context.Database.Connection.ConnectionString);
var data = context.paytest.Select(r => r.cardNo).Take(5).ToList<string>();
data.ForEach(r => Console.WriteLine(r));
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
在main方法中测试:
addConnStr("test");
test();
addConnStr("test_1");
test();
执行后发现:
发现前后两次打印出来的ConnectionString都是一样的,取的都是test的表中的数据,后面的test_1表的数据并没有读到。这个说明,后续添加的test_1字符串并没有起作用。
问题在哪?
经过一阵子Google之后,终于在stackoverflower上找到了答案,现在想想自己离开了stackoverflower,还真写不了代码了。。
原因:虽然 ConfigurationManager.RefreshSection()能够指定让Configuration节点在下次使用时从磁盘读取以达到更新的目的,但是不幸的是EF6中ConfigurationManager.ConnectionStrings被存储在一个内部类的静态变量中,并不会被更新。
方法:显然我们在动态修改ConnectionString时,需要修改这个静态变量,然而它是只读的!官方并没有提供方法来修改它,因此我们只能通过一个不安全的方式来实现:反射!
从stackoverflower搬运过来大神的代码:
static readonly Type EF6AppConfigType = typeof(DbContext).Assembly.GetType("System.Data.Entity.Internal.AppConfig");
static readonly PropertyInfo EF6DefaultAppConfig = EF6AppConfigType.GetProperty("DefaultInstance", BindingFlags.Public | BindingFlags.Static);
static readonly FieldInfo EF6ConnectionStrings = EF6AppConfigType.GetField("_connectionStrings", BindingFlags.NonPublic | BindingFlags.Instance);
static void RefreshEF6ConnectionStrings()
{
EF6ConnectionStrings.SetValue(
EF6DefaultAppConfig.GetValue(null),
ConfigurationManager.ConnectionStrings
);
}
然后,在我们的AddConnStr()的最后调用RefreshEF6ConnectionStrings()即可。
修改后测试结果如下:
最后,放上整个文件:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EntityFramework.BulkInsert;
using System.Data.SqlClient;
using System.Data;
using System.Reflection;
using System.ComponentModel;
using System.Configuration;
using System.Data.Entity;
namespace batchImportTest
{
public class Program
{
static readonly Type EF6AppConfigType = typeof(DbContext).Assembly.GetType("System.Data.Entity.Internal.AppConfig");
static readonly PropertyInfo EF6DefaultAppConfig = EF6AppConfigType.GetProperty("DefaultInstance", BindingFlags.Public | BindingFlags.Static);
static readonly FieldInfo EF6ConnectionStrings = EF6AppConfigType.GetField("_connectionStrings", BindingFlags.NonPublic | BindingFlags.Instance);
static void RefreshEF6ConnectionStrings()
{
EF6ConnectionStrings.SetValue(
EF6DefaultAppConfig.GetValue(null),
ConfigurationManager.ConnectionStrings
);
}
static void Main(string[] args)
{
addConnStr("test");
test();
addConnStr("test_1");
test();
Console.ReadKey();
}
static void addConnStr(string db)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConnectionStringSettings connSettings = new ConnectionStringSettings();
connSettings.Name = "testEntities";
connSettings.ConnectionString = string.Format(@"metadata=res://*;provider=System.Data.SqlClient;provider connection string='data source=.;initial catalog={0};persist security info=True;user id=sa;password=lj123456;MultipleActiveResultSets=True;App=EntityFramework'", db);
connSettings.ProviderName = "System.Data.EntityClient";
config.ConnectionStrings.ConnectionStrings.Remove(connSettings);
config.ConnectionStrings.ConnectionStrings.Add(connSettings);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("connectionStrings");
RefreshEF6ConnectionStrings();
}
static void test()
{
try
{
using (var context = new testEntities())
{
Console.WriteLine(context.Database.Connection.ConnectionString);
var data = context.paytest.Select(r => r.cardNo).Take(5).ToList<string>();
data.ForEach(r => Console.WriteLine(r));
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}