Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Here is an elegant, extensible, maintainable and blazingly fast solution for loading DTOs from Dictionaries.</p> <p>Create a console app and add these two files. The rest is self documenting.</p> <p>The salient points:</p> <ul> <li>simple, brief and maintainable mapping classes.</li> <li>efficient object rehydration using dynamic methods.</li> </ul> <p>NOTE: If you copied the previous DynamicProperties.cs, you will want to get this one. I added a flag to allow generation of private setters that was not in the previous version.</p> <p>Cheers.</p> <p><strong>program.cs</strong></p> <pre><code>using System.Collections.Generic; using System.Diagnostics; using Salient.Reflection; namespace KVDTO { /// &lt;summary&gt; /// This is our DTO /// &lt;/summary&gt; public class ClientCompany { public string Address1 { get; private set; } public string Address2 { get; private set; } public string AlternativeTelephoneNumber { get; private set; } public string CountyOrState { get; private set; } public string EmailAddress { get; private set; } public string Forenames { get; private set; } public string PostCode { get; private set; } public string Surname { get; private set; } public string TelephoneNumber { get; private set; } public string Title { get; private set; } public string TownOrDistrict { get; private set; } } /// &lt;summary&gt; /// This is our DTO Map /// &lt;/summary&gt; public sealed class ClientCompanyMapping : KeyValueDtoMap&lt;ClientCompany&gt; { static ClientCompanyMapping() { AddMapping("Title", "Greeting"); AddMapping("Forenames", "First"); AddMapping("Surname", "Last"); AddMapping("EmailAddress", "eMail"); AddMapping("TelephoneNumber", "Phone"); AddMapping("AlternativeTelephoneNumber", "Phone2"); AddMapping("Address1", "Address1"); AddMapping("Address2", "Address2"); AddMapping("TownOrDistrict", "City"); AddMapping("CountyOrState", "State"); AddMapping("PostCode", "Zip"); } } internal class Program { private const string Address1 = "1243 Easy Street"; private const string CountyOrState = "Az"; private const string EmailAddress = "nunya@bidnis.com"; private const string Forenames = "Sky"; private const string PostCode = "85282"; private const string Surname = "Sanders"; private const string TelephoneNumber = "800-555-1212"; private const string Title = "Mr."; private const string TownOrDistrict = "Tempe"; private static void Main(string[] args) { // this represents our input data, some discrepancies // introduced to demonstrate functionality of the map // the keys differ from the dto property names // there are missing properties // there are unrecognized properties var input = new Dictionary&lt;string, string&gt; { {"Greeting", Title}, {"First", Forenames}, {"Last", Surname}, {"eMail", EmailAddress}, {"Phone", TelephoneNumber}, // missing from this input {"Phone2", ""}, {"Address1", Address1}, // missing from this input {"Address2", ""}, {"City", TownOrDistrict}, {"State", CountyOrState}, {"Zip", PostCode}, {"SomeOtherFieldWeDontCareAbout", "qwerty"} }; // rehydration is simple and FAST // instantiate a map. You could store instances in a dictionary // but it is not really necessary for performance as all of the // work is done in the static constructors, so no matter how many // times you 'new' a map, it is only ever built once. var map = new ClientCompanyMapping(); // do the work. ClientCompany myDto = map.Load(input); // test Debug.Assert(myDto.Address1 == Address1, "Address1"); Debug.Assert(myDto.Address2 == null, "Address2"); Debug.Assert(myDto.AlternativeTelephoneNumber == null, "AlternativeTelephoneNumber"); Debug.Assert(myDto.CountyOrState == CountyOrState, "CountyOrState"); Debug.Assert(myDto.EmailAddress == EmailAddress, "EmailAddress"); Debug.Assert(myDto.Forenames == Forenames, "Forenames"); Debug.Assert(myDto.PostCode == PostCode, "PostCode"); Debug.Assert(myDto.Surname == Surname, "Surname"); Debug.Assert(myDto.TelephoneNumber == TelephoneNumber, "TelephoneNumber"); Debug.Assert(myDto.Title == Title, "Title"); Debug.Assert(myDto.TownOrDistrict == TownOrDistrict, "TownOrDistrict"); } } /// &lt;summary&gt; /// Base mapper class. /// &lt;/summary&gt; /// &lt;typeparam name="T"&gt;&lt;/typeparam&gt; public class KeyValueDtoMap&lt;T&gt; where T : class, new() { private static readonly List&lt;DynamicProperties.Property&gt; Props; private static readonly Dictionary&lt;string, string&gt; KvMap; static KeyValueDtoMap() { // this property collection is built only once Props = new List&lt;DynamicProperties.Property&gt;(DynamicProperties.CreatePropertyMethods(typeof(T))); KvMap=new Dictionary&lt;string, string&gt;(); } /// &lt;summary&gt; /// Adds a mapping between a DTO property and a KeyValue pair /// &lt;/summary&gt; /// &lt;param name="dtoPropertyName"&gt;The name of the DTO property&lt;/param&gt; /// &lt;param name="inputKey"&gt;The expected input key&lt;/param&gt; protected static void AddMapping(string dtoPropertyName,string inputKey) { KvMap.Add(dtoPropertyName,inputKey); } /// &lt;summary&gt; /// Creates and loads a DTO from a Dictionary /// &lt;/summary&gt; /// &lt;param name="input"&gt;&lt;/param&gt; /// &lt;returns&gt;&lt;/returns&gt; public T Load(Dictionary&lt;string, string&gt; input) { var result = new T(); Props.ForEach(p =&gt; { string inputKey = KvMap[p.Info.Name]; if (input.ContainsKey(inputKey)) { p.Setter.Invoke(result, input[inputKey]); } }); return result; } } } </code></pre> <p><strong>DynamicProperties.cs</strong></p> <pre><code>/*! * Project: Salient.Reflection * File : DynamicProperties.cs * http://spikes.codeplex.com * * Copyright 2010, Sky Sanders * Dual licensed under the MIT or GPL Version 2 licenses. * See LICENSE.TXT * Date: Sat Mar 28 2010 */ using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; namespace Salient.Reflection { /// &lt;summary&gt; /// Gets IL setters and getters for a property. /// &lt;/summary&gt; public static class DynamicProperties { #region Delegates public delegate object GenericGetter(object target); public delegate void GenericSetter(object target, object value); #endregion public static IList&lt;Property&gt; CreatePropertyMethods(Type T) { var returnValue = new List&lt;Property&gt;(); foreach (PropertyInfo prop in T.GetProperties()) { returnValue.Add(new Property(prop)); } return returnValue; } public static IList&lt;Property&gt; CreatePropertyMethods&lt;T&gt;() { var returnValue = new List&lt;Property&gt;(); foreach (PropertyInfo prop in typeof (T).GetProperties()) { returnValue.Add(new Property(prop)); } return returnValue; } /// &lt;summary&gt; /// Creates a dynamic setter for the property /// &lt;/summary&gt; /// &lt;param name="propertyInfo"&gt;&lt;/param&gt; /// &lt;returns&gt;&lt;/returns&gt; /// &lt;source&gt; /// http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ /// &lt;/source&gt; public static GenericSetter CreateSetMethod(PropertyInfo propertyInfo) { /* * If there's no setter return null */ MethodInfo setMethod = propertyInfo.GetSetMethod(true); if (setMethod == null) return null; /* * Create the dynamic method */ var arguments = new Type[2]; arguments[0] = arguments[1] = typeof (object); var setter = new DynamicMethod( String.Concat("_Set", propertyInfo.Name, "_"), typeof (void), arguments, propertyInfo.DeclaringType); ILGenerator generator = setter.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); generator.Emit(OpCodes.Ldarg_1); if (propertyInfo.PropertyType.IsClass) generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType); else generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); generator.EmitCall(OpCodes.Callvirt, setMethod, null); generator.Emit(OpCodes.Ret); /* * Create the delegate and return it */ return (GenericSetter) setter.CreateDelegate(typeof (GenericSetter)); } /// &lt;summary&gt; /// Creates a dynamic getter for the property /// &lt;/summary&gt; /// &lt;param name="propertyInfo"&gt;&lt;/param&gt; /// &lt;returns&gt;&lt;/returns&gt; /// &lt;source&gt; /// http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ /// &lt;/source&gt; public static GenericGetter CreateGetMethod(PropertyInfo propertyInfo) { /* * If there's no getter return null */ MethodInfo getMethod = propertyInfo.GetGetMethod(true); if (getMethod == null) return null; /* * Create the dynamic method */ var arguments = new Type[1]; arguments[0] = typeof (object); var getter = new DynamicMethod( String.Concat("_Get", propertyInfo.Name, "_"), typeof (object), arguments, propertyInfo.DeclaringType); ILGenerator generator = getter.GetILGenerator(); generator.DeclareLocal(typeof (object)); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); generator.EmitCall(OpCodes.Callvirt, getMethod, null); if (!propertyInfo.PropertyType.IsClass) generator.Emit(OpCodes.Box, propertyInfo.PropertyType); generator.Emit(OpCodes.Ret); /* * Create the delegate and return it */ return (GenericGetter) getter.CreateDelegate(typeof (GenericGetter)); } #region Nested type: Property public class Property { public GenericGetter Getter; public PropertyInfo Info; public GenericSetter Setter; public Property(PropertyInfo info) { Info = info; Setter = CreateSetMethod(info); Getter = CreateGetMethod(info); } } #endregion } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload