35

I have a database object (a row), that has lots of properties (columns) that map to form fields (asp:textbox, asp:dropdownlist etc). I would like to transform this object and properties into a dictionary map to make it easier to iterate.

Example:

Dictionary<string, string> FD = new Dictionary<string,string>();
FD["name"] = data.name;
FD["age"] = data.age;
FD["occupation"] = data.occupation;
FD["email"] = data.email;
..........

How would I do this easily, without manually typing out all the various 100s of properties?

Note: FD dictionary indices are same as database column names.

caesay
  • 16,932
  • 15
  • 95
  • 160
Dexter
  • 6,170
  • 18
  • 74
  • 101

4 Answers4

72

Assuming that data is some object and that you want to put its public properties into a Dictionary then you could try:

Original - here for historical reasons (2012):

Dictionary<string, string> FD = (from x in data.GetType().GetProperties() select x)
    .ToDictionary (x => x.Name, x => (x.GetGetMethod().Invoke (data, null) == null ? "" : x.GetGetMethod().Invoke (data, null).ToString()));

Updated (2017):

Dictionary<string, string> dictionary = data.GetType().GetProperties()
    .ToDictionary(x => x.Name, x => x.GetValue(data)?.ToString() ?? "");
caesay
  • 16,932
  • 15
  • 95
  • 160
Yahia
  • 69,653
  • 9
  • 115
  • 144
  • Is it faster than just hard-coding the equal-values? Can we reverse this process, turn the FD dictionary back into an object of strings? I'll do some testing on this. – Dexter Feb 02 '12 at 16:26
  • **Your code has syntax error** CS1922: Cannot initialize type 'System.Collections.Generic.KeyValuePair' with a collection initializer because it does not implement 'System.Collections.IEnumerable' – Dexter Feb 02 '12 at 16:31
  • 1
    @Dexter corrected - had `{` instead of `(`. please try again. – Yahia Feb 02 '12 at 16:37
  • Another error it says ToDictionary cannot take zero arguments. I think it needs to have the KeyValuePair as an argument or something. – Dexter Feb 02 '12 at 16:52
  • @Dexter sorry, was typing without an IDE... corrected again (and checked in VS 2010)... please try it now. – Yahia Feb 02 '12 at 17:17
  • Thanks your algorithm is a bit slow, but works if there are no null values. However, when you have NULL values in the database, this code breaks as well. – Dexter Feb 02 '12 at 17:41
  • 1
    yeah --woah wtf... Your algorithm just went from 29million nanoseconds to 2 million nanoseconds. And it works with null values! Best answer goes to you. – Dexter Feb 02 '12 at 17:49
  • I wouldn't worry about reflection. Heavy use of reflection-driven frameworks like WCF, MVC, and WebAPI killed the reflection-is-slow myth. Just need to be careful (as with anything powerful). – David Betz Nov 01 '15 at 03:30
  • changing to `x.GetGetMethod().Invoke(data, null)?.ToString() ?? ""` should make it significantly faster and more readable. It eliminates the second reflection call – frostymarvelous Sep 28 '17 at 08:03
10

The HtmlHelper class allows a conversion of Anonymouns Object to RouteValueDictonary and I suppose you could use a .ToString() on each value to get the string repersentation:

 var linkAttributes = System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes(linkHtmlAttributes);

The down side is this is part of the ASP.NET MVC Framework. Using a .NET Reflector, the code inside of the method is as follows:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)
{
   RouteValueDictionary dictionary = new RouteValueDictionary();
  if (htmlAttributes != null)
  {
     foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(htmlAttributes))
     {
            dictionary.Add(descriptor.Name.Replace('_', '-'), descriptor.GetValue(htmlAttributes));
       }
 }
    return dictionary;
 }

You'll see that this code is identical to the answer Yahia gave you, and his answer provides a Dictonary<string,string>. With the reflected code I gave you you could easily convert a RouteValueDictionary to Dictonary<string,string> but Yahia's answer is a one liner.

EDIT - I've added the code for what could be a method to do your conversion:

EDIT 2 - I've added null checking to the code and used String.Format for the string value

    public static Dictionary<string, string> ObjectToDictionary(object value)
    {
        Dictionary<string, string> dictionary = new Dictionary<string, string>();
        if (value != null)
        {
            foreach (System.ComponentModel.PropertyDescriptor descriptor in System.ComponentModel.TypeDescriptor.GetProperties(value))
            {
                if(descriptor != null && descriptor.Name != null)
                {
                     object propValue = descriptor.GetValue(value);
                     if(propValue != null)
                          dictionary.Add(descriptor.Name,String.Format("{0}",propValue));
            }
        }
        return dictionary;
    }

And to go from a Dictionary to an object check http://automapper.org/ which was suggested in this thread Convert dictionary to anonymous object

Community
  • 1
  • 1
Nick Bork
  • 4,831
  • 1
  • 24
  • 25
  • I don't use MVC and I'm converting a data object to a dictionary rather than dealing with html yet. – Dexter Feb 02 '12 at 16:28
  • @Dexter You may not be using MVC but I gave you the code that is the "AnonymousObjectToHtmlAttributes" method to see what it does. I'm not sure what you mean by dealing with HTML. If you're refering to the "HtmlHelper" namespace, thats all it is, a namespace container for some methods. I also wrote you an example of a method identical to it that could return you a dictonary. – Nick Bork Feb 02 '12 at 16:34
  • Ok I'm testing out your code, but I think descripter can be null sometimes. So I added a if(descriptor != null) and if(descripter.GetValue(value) != null) – Dexter Feb 02 '12 at 17:16
  • GetProperties shouldn't return a null or all of our exampels woudl be broken as everyone uses it. The descriptor.GetValue(value) COULD return a null so maybe use String.Format("{0}",descriptor.GetValue(value)) would be better, then the value would be "". If you want to omit fields where the value is "" then for sure check the value first. – Nick Bork Feb 02 '12 at 17:25
  • Well, I couldn't test your code with null values. It kept giving me errors. So I just made a database row that had all field values. However, great answer. Your algorithm came in 2nd in speed/efficiency test (above), it only took 7 million nanoseconds. – Dexter Feb 02 '12 at 17:35
  • I just saw your edit. Now it works with null values great stuff! Although your algorithm is 4M nanoseconds behind the other, I'm giving you best answer for your relentless effort and checking for null values which are a likelihood in big databases. – Dexter Feb 02 '12 at 17:43
  • I can't take credit for the code that someone at Microsoft wrote. Like I said, the orginal code was from the Mvc framework and we just changed it a bit to return a Dictonary. Which line does the error occure on? I'm sure all of these examples can include logic to accomidate nulls using a WHERE or IF. – Nick Bork Feb 02 '12 at 17:44
  • See my latest comment, you've solved the null problem. I understand, it is definitely microsoft code but I'm crediting you with finding the answer. – Dexter Feb 02 '12 at 17:45
9
var myDict = myObj.ToDictionary(); //returns all public fields & properties

.

public static class MyExtensions
{
    public static Dictionary<string, object> ToDictionary(this object myObj)
    {
        return myObj.GetType()
            .GetProperties()
            .Select(pi => new { Name = pi.Name, Value = pi.GetValue(myObj, null) })
            .Union( 
                myObj.GetType()
                .GetFields()
                .Select(fi => new { Name = fi.Name, Value = fi.GetValue(myObj) })
             )
            .ToDictionary(ks => ks.Name, vs => vs.Value);
    }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • 1
    Highlighting .Select(pi => new { Name = pi.Name, Value = pi.GetValue(myObj, null).ToString() }) --- object not set to a reference. I added the ToString becuase I wanted Dictionary not . But if a database field is empty/null, then this object not set to a reference problem occurs. – Dexter Feb 02 '12 at 17:19
  • 1
    Winner for fastest algorithm here. Sorry I couldn't decide who had the best answer. All of them were good answers. First algorithm (**Splash-X**): 7,000,700 nanoseconds0.01 seconds0.00 minutes Second algorithm (**L.B**): **3,000,300 nanoseconds** 0.00 seconds0.00 minutes Third algorithm (**Yahia**): 29,002,900 nanoseconds0.03 seconds0.00 minutes – Dexter Feb 02 '12 at 17:33
  • For your information---when the database has NULL values, this code breaks as well. – Dexter Feb 02 '12 at 17:38
  • Actually Yahia edited the algorithm he submitted, now it works faster than yours by 1 million nanoseconds. – Dexter Feb 02 '12 at 17:50
0

Take a look at System.ComponentModel.TypeDescriptor.GetProperties( ... ). This is the way the normal data binding bits work. It will use reflection and return you a collection of property descriptors (which you can use to get the values). You can customize these descriptors for performace by implementing ICustomTypeDescriptor .

MaLio
  • 2,498
  • 16
  • 23