C# 如何从数据库中的XML读取配置节?

C# 如何从数据库中的XML读取配置节?,c#,.net,.net-3.5,configuration,C#,.net,.net 3.5,Configuration,我有这样一个配置类: public class MyConfig : ConfigurationSection { [ConfigurationProperty("MyProperty", IsRequired = true)] public string MyProperty { get { return (string)this["MyProperty"]; } set { this["MyPro

我有这样一个配置类:

public class MyConfig : ConfigurationSection
{
        [ConfigurationProperty("MyProperty", IsRequired = true)]
        public string MyProperty
        {
            get { return (string)this["MyProperty"]; }
            set { this["MyProperty"] = value; }
        }
}

public class ConfigurationSectionReader where T : ConfigurationSection, new()
{
    public T GetSection( string sectionXml ) 
    {
        T section = new T();
        using ( StringReader stringReader = new StringReader( sectionXml ) )
        using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) )
        {
            reader.Read();
            section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } );
        }
        return section;
    }
}
    

var reader = new ConfigurationSectionReader();
var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB
    
它被另一个类实例化,就像这样

(MyConfig)ConfigurationManager.GetSection("myConfig")
我们正在进行一些更改,现在将配置文件作为xml存储在DB中,与当前在配置文件中完全相同

为了向后兼容,我希望将MyConfig作为ConfigurationSection进行维护,但仍然能够使用从数据库检索的XML字符串对其进行实例化


可能吗?如果是,怎么做?(请记住,它仍应像上面的示例一样工作)

这是一个难题。您可能会有一个配置文件,其中包含一些故意不正确的XML,在ConfigurationSection中覆盖未识别的删除,然后有效地绕过文件到ConfigurationSection的映射(本质上是手动设置属性)-需要进行一些重构,但您仍然可以公开相同的属性等。这有点WTF,但可能是可行的

我基本上描述了。在我所有的代码中,现在我没有依赖ConfigurationSection的类,我使用博客文章中描述的技术绕过它,通过接口返回POCOs。这使得我的代码更易于单元测试,因为我可以很容易地为接口使用存根

如果我希望这样做,我也可以轻松地将配置移动到DB中——我只需创建一个实现配置接口的新类,并在IoC配置中切换它。微软没有设计灵活的配置系统,所以在自己的代码中使用它时必须考虑到这一点


我能想到的唯一其他方法是将DB配置写入文件,然后读入,但这也很奇怪

我的建议是保留当前MyConfig类,但从构造函数中的数据库加载XML,然后在MyConfig的每个属性中,如果需要从任何位置提取配置,可以放入逻辑以确定从何处获取值(数据库或.config文件),或者,如果值为空,则将其回滚

public class MyConfig : ConfigurationSection
{
    public MyConfig()
    {
        // throw some code in here to retrieve your XML from your database
        // deserialize your XML and store it 
        _myProperty = "<deserialized value from db>";
    }

    private string _myProperty = string.Empty;

    [ConfigurationProperty("MyProperty", IsRequired = true)]
    public string MyProperty
    {
        get
        {
            if (_myProperty != null && _myProperty.Length > 0)
                return _myProperty;
            else
                return (string)this["MyProperty"];
        }
        set { this["MyProperty"] = value; }
    }
}
公共类MyConfig:ConfigurationSection
{
公共MyConfig()
{
//在这里输入一些代码,从数据库中检索XML
//反序列化XML并存储它
_myProperty=“”;
}
私有字符串_myProperty=string.Empty;
[ConfigurationProperty(“MyProperty”,IsRequired=true)]
公共字符串MyProperty
{
得到
{
如果(_myProperty!=null&&u myProperty.Length>0)
归还我的财产;
其他的
返回(字符串)此[“MyProperty”];
}
设置{this[“MyProperty”]=value;}
}
}

我通常是这样做的:只需将这些成员添加到MyConfig类中:

    public class MyConfig : ConfigurationSection
    {
        private static MyConfig _current;
        public static MyConfig Current
        {
            get
            {
                if (_current == null)
                {
                    switch(ConfigurationStorageType) // where do you want read config from?
                    {
                        case ConfigFile: // from .config file
                            _current = ConfigurationManager.GetSection("MySectionName") as MyConfig;
                            break;

                        case ConfigDb: // from database
                        default:
                            using (Stream stream = GetMyStreamFromDb())
                            {
                                using (XmlTextReader reader = new XmlTextReader(stream))
                                {
                                    _current = Get(reader);
                                }
                            }
                            break;


                    }
                }
                return _current;
            }
        }

        public static MyConfig Get(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            MyConfig section = new MyConfig();
            section.DeserializeSection(reader);
            return section;
        }
    }
这样,MyConfig类中没有什么可更改的,但您仍然需要更改客户使用此类代码访问它的方式:

string myProp = MyConfig.Current.MyProperty;

如果您需要在数据库中存储任何<强>系统.SoadsCuffice部分< /St>,您可以考虑编写这样的泛型节读取器:

public class MyConfig : ConfigurationSection
{
        [ConfigurationProperty("MyProperty", IsRequired = true)]
        public string MyProperty
        {
            get { return (string)this["MyProperty"]; }
            set { this["MyProperty"] = value; }
        }
}

public class ConfigurationSectionReader where T : ConfigurationSection, new()
{
    public T GetSection( string sectionXml ) 
    {
        T section = new T();
        using ( StringReader stringReader = new StringReader( sectionXml ) )
        using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) )
        {
            reader.Read();
            section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } );
        }
        return section;
    }
}
    

var reader = new ConfigurationSectionReader();
var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB
    
这将适用于重写反序列化元素方法的所有类。e、 g


protected override void DeserializeElement( XmlReader reader, bool serializeCollectionKey )
{
    XmlDocument document = new XmlDocument();
    document.LoadXml( reader.ReadOuterXml() );
    MyProperty = document.DocumentElement.HasAttribute( "MyProperty" )
        ? document.DocumentElement.Attributes[ "MyProperty" ].Value
        : string.Empty;
}
    
你可以得到这样的一个部分:

public class MyConfig : ConfigurationSection
{
        [ConfigurationProperty("MyProperty", IsRequired = true)]
        public string MyProperty
        {
            get { return (string)this["MyProperty"]; }
            set { this["MyProperty"] = value; }
        }
}

public class ConfigurationSectionReader where T : ConfigurationSection, new()
{
    public T GetSection( string sectionXml ) 
    {
        T section = new T();
        using ( StringReader stringReader = new StringReader( sectionXml ) )
        using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) )
        {
            reader.Read();
            section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } );
        }
        return section;
    }
}
    

var reader = new ConfigurationSectionReader();
var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB
    

这是一个很老的问题,但只是在玩弄解决方案。它类似于Simon Mourier的方法(在某些方面我更喜欢这种方法——少一些黑客行为),但确实意味着调用
System.Configuration.ConfigurationManager.GetSection()
的任何代码都将继续工作,而无需更改它们以使用静态方法,因此可能会导致总体代码更改较少

第一个基本的警告是,我不知道这是否适用于嵌套部分,但我几乎可以肯定它不会。几乎主要的警告是,它需要更改config section类,因此您只能将其用于您有源代码的自定义节(并且允许更改!)

第二个也是主要的警告是,我只是在玩弄它,我在开发和生产中都没有使用它,简单地在基本功能上涂抹我自己的代码,就像这样,可能会产生我的示例中没有显示的连锁反应使用风险自担

(话虽如此,我正在一个Umbraco站点上测试它,因此其他配置部分的负载仍在继续,它们仍然可以工作,所以我认为它不会立即产生可怕的影响)

编辑:这是.NET4,而不是原始问题中的3.5。不知道这会不会有什么不同

下面是代码,非常简单,只需重写
反序列化部分
即可使用从数据库加载的XML读取器

public class TestSettings : ConfigurationSection
{
    protected override void DeserializeSection(System.Xml.XmlReader reader)
    {
        using (DbConnection conn = /* Get an open database connection from whatever provider you are using */)
        {
            DbCommand cmd = conn.CreateCommand();

            cmd.CommandText = "select ConfigFileContent from Configuration where ConfigFileName = @ConfigFileName";

            DbParameter p = cmd.CreateParameter();
            p.ParameterName = "@ConfigFileName";
            p.Value = "TestSettings.config";

            cmd.Parameters.Add(p);

            String xml = (String)cmd.ExecuteScalar();

            using(System.IO.StringReader sr = new System.IO.StringReader(xml))
            using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr))
            {
                base.DeserializeSection(xr);
            }                
        }            
    }

    // Below is all your normal existing section code

    [ConfigurationProperty("General")]
    public GeneralElement General { get { return (GeneralElement)base["General"]; } }

    [ConfigurationProperty("UI")]
    public UIElement UI { get { return (UIElement)base["UI"]; } }

    ...

    ...
}
我使用的是ASP.Net,所以为了让它正常工作,您确实需要一个
web.config
,但是,无论如何,我需要一个连接字符串的地方,否则我根本不打算连接到数据库

您的自定义节应在
中定义为普通节;实现这一点的关键是在你的正常设置中加入一个空元素;i、 e.代替
或您的内联设置,只需将

然后,configuration manager将加载所有部分,查看现有的
元素,并对其进行反序列化,此时它将点击您的覆盖并从数据库中加载XML


注意:反序列化需要一个文档片段(当读卡器已位于某个节点时,需要调用它),而不是整个文档,因此如果您的节存储在单独的文件中,则必须首先删除
声明,或者你得到
期望找到一个元素

我很想看到一个解决方案-我已经研究了一段时间,不幸的是没有太大的成功…我很高兴看到我不是一个人。我不知道这个问题的答案,因为似乎应用程序代码无法更改配置文件的位置。但我不会