What came before
In my previous post I discussed ways of making the settings table using Generics to have typed access to our properties. This required us to still pass in the name of our property as a string to a method.
User user = db.Users.First();if(user.Setting<bool>("IsAdministrator")){//yay, this user is an admin!}
Today we’re going to take advantage of dynamic to take it one step further.
The Goal
User user = db.Users.First();if(user.Settings.IsAdministrator){//yay, this user is an admin!}
We’re going to create a new class that inherits from DynamicObject. Doing so will allow us to utilize all the power of dynamics such as getting/setting members, calling methods, or type conversions. <a
The dynamic language runtime first checks to see if the property of our DynamicObject already exists and if it doesn’t find the property it calls TryGetMember or TrySetMember. This is what we’re going to use to return our settings property. Here is a barebones DynamicObject that uses aDictionary<string, object> to store values.
//These are essentially the same//user.Settings["IsAdministrator"]//user.Settings.IsAdministratorclassSettingsDynamicObject:DynamicObject{//used for cachingDictionary<string,object> _dictionary;publicSettingsDynamicObject(User user){
_dictionary =newDictionary<string,object>();}publicoverrideboolTrySetMember(SetMemberBinder binder,object value){
_dictionary[binder.Name]= value;returntrue;}publicoverrideboolTryGetMember(GetMemberBinder binder,outobject result){if( _dictionary.TryGetValue(binder.Name,out result)){returntrue;}}}
Adding our new database overrides
We’re now going to modify our overrides to pull values from the database if they don’t already exist in the _dictionary object and add our code to save the new values.
publicoverrideboolTrySetMember(SetMemberBinder binder,object value){SetDatabaseValue(binder.Name, value);
_dictionary[binder.Name]= value;returntrue;}publicoverrideboolTryGetMember(GetMemberBinder binder,outobject result){//check to see if the property already existsif( _dictionary.ContainsKey(binder.Name)&& _dictionary.TryGetValue(binder.Name,out result)){returntrue;}else{var setting =this.user
.UserSettings.SingleOrDefault(s => s.Name== binder.Name);if(setting ==null){
result =null;}else{
result =newBinaryFormatter().Deserialize(newSystem.IO.MemoryStream(setting.Value));
_dictionary[binder.Name]= result;}returntrue;}}privatevoidSetDatabaseValue(string name,object value){var setting =this.user.UserSettings.SingleOrDefault(s => s.Name== name);System.IO.MemoryStream ms =newSystem.IO.MemoryStream();newBinaryFormatter().Serialize(ms, value);if(setting ==null){
setting =newUserSetting(){Name= name,Type= value.GetType().ToString(),
setting.Value= ms.ToArray();this.user.UserSettings.Add(setting);}else{if(value !=null&& setting.Type!= value.GetType().FullName)thrownewInvalidCastException(string.Format("Unable to cast: {0} to {1}",
value.GetType().FullName, setting.Type));
setting.Value= ms.ToArray();}}
Modifying our existing user object
Now we have a new DynamicObject class that we can use to populate our settings but we’ll need to modify our existing User class to utilize the new Settings. One of the downsides is that we’ll have two exposed properties instead of one but I think it’s negligable.
publicclassUser{publicInt32UserID{get;set;}publicstringLoginID{get;set;}publicstringName{get;set;}publicstringPage{get;set;}publicstringPassword{get;set;}publicvirtualICollection<UserSetting>UserSettings{get;set;}publicvirtualdynamicSettings{get;set;}publicUser(){//We need to initialize the Settings to our new DynamicObjectSettings=newSettingsDynamicObject(this);}}
And there you have it. We now have a dynamic enabled settings property that makes our code slightly easier to read. I hope this helps you on your way to understanding c# 4.0’s new dynamic namespace.
– Ben
Update:
Fixed Constructor – thanks yesthatmcgurk
http://buildstarted.com/2010/08/23/fun-with-dynamicobject-dynamic-and-the-settings-table/