代码改变世界

Creating a settings table that can handle almost any type of value

2013-06-16 23:25  yezhi  阅读(197)  评论(0编辑  收藏  举报

Update: Updated article here.

Today I wanted to be able to have a table store any type of value as a way to store some settings for an application. The table needed to be able to store basically a name/value pair.

I designed the object using a code-first approach

Initial Idea

publicclassSetting{[Key]publicstringName{get;set;}publicobjectValue{get;set;}publicstringType{get;set;}}

Simple and to the point.

Here is a snippet from my DataContext

publicclassDataContext:DbContext,IDataContext{publicDbSet<Setting>Settings{get;set;}}

To access the data I would simply cast Value to whatever type I expected it to be.

var x =(string)Settings.Single(s => s.Name=="sitename").Value;

Problems

However, with EF and probably Linq (I didn’t test that) the Value was always null after I calledSaveChanges. At first I couldn’t understand why but then it hit me. There’s no direct relationship between object and SQL. So I switched the Value to a byte[]. Here is my new class.

publicclassSetting{[Key]publicstringName{get;set;}publicbyte[]Value{get;set;}publicstringType{get;set;}}

This caused a few problems with the first method since it’s not possible to typecast from a byte[] to a DateTime, int, string…and so on. So I added a few helper methods to my DataContext.

Solution

public T Setting<T>(stringName){//grab the the setting if it exists otherwisevar value =Settings.SingleOrDefault(s => s.Name==Name);//return the default value for typeof(T)if(value ==null)returndefault(T);//If they're trying to cast to a different type we should throw an error//They can to another type conversion after grabbing the valueif(Type!=typeof(T).FullName)thrownewInvalidCastException(string.Format("Unable to cast: {0} to {1}",typeof(T).FullName,Type));//Using the BinaryFormatter, return a typecast valuereturn(T)(newBinaryFormatter().Deserialize(newSystem.IO.MemoryStream(value.Value)));}publicvoidSetting<T>(stringName, T Value){//check for an existing valuevar setting =Settings.SingleOrDefault(s => s.Name==Name);//serialize the new valueSystem.IO.MemoryStream ms =newSystem.IO.MemoryStream();newBinaryFormatter().Serialize(ms,Value);if(setting !=null){//for consistency let's make sure we're assigning the same typeif(Type!=typeof(T).FullName)thrownewInvalidCastException(string.Format("Unable to cast: {0} to {1}",typeof(T).FullName,Type));
        setting.Value= ms.ToArray();}else{//add a new value to the database
        value =newModels.Setting(){Name=Name,Type=typeof(T).Fullname,Value= ms.ToArray()};Settings.Add(value);}}

Now instead of calling the above cast we can now use the following

var x =DataContext.Setting<string>("sitename");//getDataContext.Setting<string>("sitename","buildstarted.com");//set

Hope this is helpful and inspires someone. Please comment if you have a better method. I’m not too keen on serializing all the time but it’s the best method I’ve come up with so far.

One of the problems with having values stored in byte[] form is lack of searchability of values. However, you’re unlikely to select all settings with values of “true”.

 

So after working with the Settings table from my previous post a bit, it’s clear to me that creating some extension methods would make working with settings easier. To do this though we have to modify the Model a bit first and rename “Value” to lowercase “value”. The summary xml documentation is there to inform the user there’s an Extension method

publicclassSetting{publicintSettingID{get;set;}publicstringName{get;set;}/// <summary>/// Use the extension methods instead of this value; ex: Value&lt;T&gt;/// <summary>publicbyte[] value {get;set;}publicstringType{get;set;}}

 

Creating the Extension Method

[Extension]publicstatic T Value<T>(thisSetting setting){if(setting ==null)returndefault(T);if(setting.Type!=typeof(T).FullName)thrownewInvalidCastException(string.Format("Unable to cast: {0} to {1}",typeof(T).FullName,
                setting.Type));return(T)(newBinaryFormatter().Deserialize(newSystem.IO.MemoryStream(setting.value)));}[Extension]publicstaticvoidValue<T>(thisSetting setting, T value){if(setting ==null)thrownewArgumentNullException("setting");System.IO.MemoryStream ms =newSystem.IO.MemoryStream();newBinaryFormatter().Serialize(ms, value);if(setting.Type!=typeof(T).FullName)thrownewInvalidCastException(string.Format("Unable to cast: {0} to {1}",typeof(T).FullName,
                setting.Type));

    setting.value = ms.ToArray();}

Doing this also has the added benefit of making the Generic methods in the DataContext cleaner

public T Setting<T>(stringName){var setting =Settings.SingleOrDefault(s => s.Name==Name);return setting.Value<T>();}publicvoidSetting<T>(stringName, T Value){var setting =Settings.SingleOrDefault(s => s.Name==Name);
    setting.Value<T>(Value);}

As you can see the extension methods are very easy to create. This gives us the benefit of accessing the values in a cleaner way

var setting = db.Settings.First();
setting.Value<string>("this is a new value");var theValue = setting.Value<string>();

The generic methods are what make the settings Model powerful and (almost) seamless.

– Ben

http://buildstarted.com/2010/08/09/creating-a-settings-table-that-can-handle-almost-any-type-of-value/