Browse > Home / C#, mongodb / Convert C# classes to and from MongoDB Documents automatically using .NET reflection

| Subcribe via RSS

Convert C# classes to and from MongoDB Documents automatically using .NET reflection

There are a number of C# based MongoDB projects being actively developed right now but one thing that I needed was a way to convert a standard C# class to a MongoDB document for easy insertion.  It isn’t hard to manually type out and set each property by hand, but it certainly is not the most efficient way, especially when you know you are going to be doing it a lot.

 

For example:

Lets say I have a class called SomeClass that looks something like this:

class SomeClass {
	public string StringTest;
	public int IntTest;
}

And somewhere in my code, I have an instance of this class named someClassInstance.  If I want to create a MongoDB document from this class, I’d have to do something like this:

Document document = new Document();
document.add('StringTest', someClassInstance.StringTest);
document.add('IntTest', someClassInstance.IntTest);

So that isn’t such a big deal, right? But what about when I have a class with many more properties?  Then it starts to get messy and cumbersome.  I thought that there should be an straightforward way to easily convert any class to a mongo-csharp compatible Document object. (I am using Sam Corder’s mongo-csharp driver, so that I am targeting the Document object from that library.)

 

Default values

I also wanted to have a way to specify what the default values were for each class property so that when we did the conversion, we would (hopefully) not end up with any null values.  Plus, if for some reason there was a document in MongoDB that was missing a particular key-value pair, the DocumentConverter would automatically fill in that empty field with the default value so in the code we should never have any nulls.

This is something that I would like for my own purposes and may not suit everyone’s needs.  If it doesn’t, simply leave off the DefaultValueAttribute and you’ll never know the difference.

 

My proposed solution

I figured the easiest way to accomplish this was to create a class that would encapsulate all the functionality needed to convert to and from Document objects and have my other classes inherit from that one. I imagined that the above code would change to something like this:

class SomeClass : DocumentConverter {
	[Attributes.DefaultValue("Default StringTest value!")]
	public string StringTest;
	[Attributes.DefaultValue(16)]
	public int IntTest;
}

And to do the conversion would be very simple.  To convert from someClass to Document would be:

Document document = someClassInstance.ToMongoDocument();

To convert from a Document object to someClass would be:

SomeClass someOtherClassInstance = new SomeClass();
omeOtherClassInstance .FromMongoDocument(someDocumentObject);

 

The DefaultAttribute class

using System;
namespace MyApp.Attributes
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
    class DefaultValueAttribute : Attribute
    {
        private readonly object _value;
 
        public DefaultValueAttribute(object Value)
        {
            _value = Value;
        }
 
        public object GetDefaultValue()
        {
            return _value;
        }
    }
}

The DocumentConverter class

Reflection isn’t something that I use too often so there may be better ways of accomplishing what I am trying to do, but this is what I’ve got for now.  If there are better ways, please let me know.  Without further ado…

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using MyApp.Classes.Attributes;
using MongoDB.Driver;
 
namespace MyApp.Classes
{
    public class DocumentConverter
    {
        public void FromMongoDocument(Document document)
        {
            foreach (DictionaryEntry kvp in document)
            {
                object propertyValue;
                if (kvp.Value != null && (kvp.Value.GetType() == typeof(Document)))
                {
                    // We have a document object - Now lets get a reference to the class property's type
                    var propertyType = GetType().GetProperty(kvp.Key.ToString()).PropertyType;
 
                    // create new instance of that class
                    var propertyInstance = Activator.CreateInstance(propertyType);
 
                    // call FromMongoDocument on that class and pass in the document
                    MethodInfo method = propertyInstance.GetType().GetMethod("FromMongoDocument");
                    method.Invoke(propertyInstance, new[] { kvp.Value });
 
                    propertyValue = propertyInstance;
                }
                else
                {
                    // This is not a Document so lets just assign the value
                    propertyValue = kvp.Value;
                }
 
                GetType().GetProperty(kvp.Key.ToString()).SetValue(this, propertyValue, null);
            }
 
        }
 
        public Document ToMongoDocument()
        {
            Document document = new Document();
 
            foreach (PropertyInfo property in GetType().GetProperties())
            {
                // Get the value of this property
                object propertyValue = property.GetValue(this, null);
 
                // If this value is null, then lets try to see if there is a default value attribute and assign that
                if (propertyValue == null)
                {
                    object[] attributes = property.GetCustomAttributes(typeof(DefaultValueAttribute), true);
                    foreach (DefaultValueAttribute defaultValue in attributes.Cast<defaultvalueattribute>())
                    {
                        propertyValue = defaultValue.GetDefaultValue();
                    }
                    document.Add(property.Name, propertyValue);
                }
                else
                {
                    // We have a property, now lets see if this property has a ToMongoDocument method
                    MethodInfo method = propertyValue.GetType().GetMethod(&quot;ToMongoDocument&quot;);
 
                    if (method == null)
                    {
                        document.Add(property.Name, property.GetValue(this, null));
                    }
                    else
                    {
                        document.Add(property.Name, method.Invoke(propertyValue, null));
                    }
                }
            }
            return document;
        }
    }
}

That’s all for now

I hope this is useful for someone.  It is a rough draft of what I threw together last night at around 1AM while half asleep.  So far, it has passed all of my initial tests but if you have suggestions to make it better, please leave some comments here.

Add to Del.cio.us RSS Feed Add to Technorati Favorites Stumble It! Digg It!
    www.sajithmr.com

http://deserialized.com/wp-content/plugins/sociofluid/images/digg_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/reddit_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/dzone_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/stumbleupon_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/delicious_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/newsvine_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/technorati_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/google_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/facebook_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/yahoobuzz_48.png http://deserialized.com/wp-content/plugins/sociofluid/images/mixx_48.png

Like this post? Share it with the community!

6 Responses to “Convert C# classes to and from MongoDB Documents automatically using .NET reflection”

  1. cshipley Says:

    Very cool!

    I would suggest that rather than requiring the class be derived from DocumentConverter, you make DocumentCoverter a static class with FromMongoDocument and ToMongoDocument as generic extension methods. That way, any object within a hierarchy can be saved/loaded.

    You can then also speed up (de)serialization by caching some of the reflection information in a static member, say in a Dictionary.


  2. Andrew Powell Says:

    The System.ComponentModel Namespace already has a DefaultValueAttribute.

    http://msdn.microsoft.com/en-us/library/system.componentmodel.defaultvalueattribute.aspx


  3. tk Says:

    This is a very useful class and I have been using this for last couple of days. I am difficulty using this class for collection data types. For example, if I embed comments collection in Article document then I am able retrieve comments if there is only one but there are more than one then it is not able to handle.
    Do you have any ideas how to go about doing this.

    Thanks again for great work!

    -tk


  4. JC Says:

    when I try to insert the document after to convert the object, I had the following exception:

    Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was
    out of the range of valid values.
    Parameter name: Type: System.UInt16 not recognized
    at MongoDB.Driver.Bson.BsonWriter.TranslateToBsonType(Object val)
    at MongoDB.Driver.Bson.BsonWriter.CalculateSize(Object val)
    at MongoDB.Driver.Bson.BsonWriter.CalculateSize(Document doc)
    at MongoDB.Driver.Protocol.InsertMessage.ChunkMessage(BsonWriter writer)
    at MongoDB.Driver.Protocol.InsertMessage.Write(Stream stream)
    at MongoDB.Driver.Connections.Connection.SendMessage(IRequestMessage msg)
    at MongoDB.Driver.Collection.Insert(IEnumerable`1 docs)
    at AppSecInc.MongoDB.Test.MongoDBUtil`1.Insert(Object entity) in C:\Users\jch
    iavaro\Documents\Visual Studio 2008\Projects\MongoDBUtilities\AppSecInc.MongoDB.
    Test\MongoDBUtil.cs:line 44
    at AppSecInc.MongoDB.Test.Program.Main(String[] args) in C:\Users\jchiavaro\D
    ocuments\Visual Studio 2008\Projects\MongoDBUtilities\AppSecInc.MongoDB.Test\Pro
    gram.cs:line 35

    Do you have any idea?

    Thanks!


  5. tk Says:

    @JC

    Are you trying to insert an array or object collection?

    -tk


  6. JC Says:

    i’m trying to insert an object, like this:

    Document row = new Document();

    // I changed the method to ‘static’
    row = DocumentConverter.ToMongoDocument(entity);
    Collection.Insert(row);

    The values in ‘row’ seem to be fine, but when it tries to insert it throws that exception.

    Thanks for the response!


Leave a Reply