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();

执行后发现:
图像 1.png

发现前后两次打印出来的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()即可。

修改后测试结果如下:
result2.png

最后,放上整个文件:

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

        }
    }
}

Reference

ConfigurationManager

Stackoverflower

entityframework.net

标签: c#, ConnectionString, EntityFramework, 动态修改

添加新评论