有没有办法将键/值对列表转换为数据传输对象

问题描述:

......除了明显的循环列表和肮脏的大案例语句之外!有没有办法将键/值对列表转换为数据传输对象

我已经在我的头上翻了几个Linq查询,但没有任何东西似乎得到任何接近。

下面是一个例子DTO是否有帮助:

class ClientCompany 
    { 
     public string Title { get; private set; } 
     public string Forenames { get; private set; } 
     public string Surname { get; private set; } 
     public string EmailAddress { get; private set; } 
     public string TelephoneNumber { get; private set; } 
     public string AlternativeTelephoneNumber { get; private set; } 
     public string Address1 { get; private set; } 
     public string Address2 { get; private set; } 
     public string TownOrDistrict { get; private set; } 
     public string CountyOrState { get; private set; } 
     public string PostCode { get; private set; } 
    } 

我们有,我们正在作为KV对,我怕获取数据的事实无法控制。

虽然每个KV对有一个有效的映射到每个属性,我事先知道键,他们没有被命名为DTO相同。

+0

通过KV对你的意思是你有像“Title”=>“something”,“Forenames”=>“别的东西”对的集合,它完全描述ClientCompany对象? – 2010-03-30 08:38:14

+0

Ooops忘了澄清。我会编辑。 (哦,并且在这里回答你的问题,同时每个KV对都有一个映射,我事先知道这些键,他们的命名与DTO不一样) – user129345 2010-03-30 08:48:08

+0

@Weevie - 事情是如何发生的? – 2010-04-11 17:24:03

如果你能得到的数据看起来像['Title':'Mr', 'Forenames':'John', 'Surname':'Doe',...],那么你应该能够将JSON反序列化为你的源对象的kvp。

这是一个优雅的,可扩展的,可维护的,速度非常快的从字典加载DTO的解决方案。

创建一个控制台应用程序并添加这两个文件。其余的是自我记录。

的要点:

  • 简单,简短和维护映射类。
  • 使用动态方法进行高效的对象补液。

注意:如果您复制了以前的DynamicProperties.cs,您将希望得到这一个。我添加了一个标志来允许生成以前版本中没有的私有setter。

干杯。

Program.cs的

using System.Collections.Generic; 
using System.Diagnostics; 
using Salient.Reflection; 

namespace KVDTO 
{ 
    /// <summary> 
    /// This is our DTO 
    /// </summary> 
    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; } 
    } 


    /// <summary> 
    /// This is our DTO Map 
    /// </summary> 
    public sealed class ClientCompanyMapping : KeyValueDtoMap<ClientCompany> 
    { 
     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 = "[email protected]"; 
     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<string, string> 
       { 
        {"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"); 
     } 
    } 

    /// <summary> 
    /// Base mapper class. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    public class KeyValueDtoMap<T> where T : class, new() 
    { 
     private static readonly List<DynamicProperties.Property> Props; 
     private static readonly Dictionary<string, string> KvMap; 

     static KeyValueDtoMap() 
     { 
      // this property collection is built only once 
      Props = new List<DynamicProperties.Property>(DynamicProperties.CreatePropertyMethods(typeof(T))); 
      KvMap=new Dictionary<string, string>(); 
     } 

     /// <summary> 
     /// Adds a mapping between a DTO property and a KeyValue pair 
     /// </summary> 
     /// <param name="dtoPropertyName">The name of the DTO property</param> 
     /// <param name="inputKey">The expected input key</param> 
     protected static void AddMapping(string dtoPropertyName,string inputKey) 
     { 
      KvMap.Add(dtoPropertyName,inputKey); 
     } 

     /// <summary> 
     /// Creates and loads a DTO from a Dictionary 
     /// </summary> 
     /// <param name="input"></param> 
     /// <returns></returns> 
     public T Load(Dictionary<string, string> input) 
     { 
      var result = new T(); 
      Props.ForEach(p => 
       { 
        string inputKey = KvMap[p.Info.Name]; 
        if (input.ContainsKey(inputKey)) 
        { 
         p.Setter.Invoke(result, input[inputKey]); 
        } 
       }); 
      return result; 
     } 
    } 
} 

DynamicProperties.cs

/*! 
* 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 
{ 
    /// <summary> 
    /// Gets IL setters and getters for a property. 
    /// </summary> 
    public static class DynamicProperties 
    { 
     #region Delegates 

     public delegate object GenericGetter(object target); 

     public delegate void GenericSetter(object target, object value); 

     #endregion 

     public static IList<Property> CreatePropertyMethods(Type T) 
     { 
      var returnValue = new List<Property>(); 

      foreach (PropertyInfo prop in T.GetProperties()) 
      { 
       returnValue.Add(new Property(prop)); 
      } 
      return returnValue; 
     } 


     public static IList<Property> CreatePropertyMethods<T>() 
     { 
      var returnValue = new List<Property>(); 

      foreach (PropertyInfo prop in typeof (T).GetProperties()) 
      { 
       returnValue.Add(new Property(prop)); 
      } 
      return returnValue; 
     } 


     /// <summary> 
     /// Creates a dynamic setter for the property 
     /// </summary> 
     /// <param name="propertyInfo"></param> 
     /// <returns></returns> 
     /// <source> 
     /// http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ 
     /// </source> 
     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)); 
     } 


     /// <summary> 
     /// Creates a dynamic getter for the property 
     /// </summary> 
     /// <param name="propertyInfo"></param> 
     /// <returns></returns> 
     /// <source> 
     /// http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ 
     /// </source> 
     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 
    } 
} 
+0

不错的代码在那里,但因为我知道键和它们的属性提前映射,我不认为我需要动态属性访问提供。尽管欢呼。 – user129345 2010-03-30 08:52:09

+0

@ weevie - 是的,你有一半的映射,但在这种情况下,这个类的重点是速度宝贝! – 2010-03-30 08:56:17

+0

这会教会我加快阅读代码。 _可能需要这是高性能的,所以这可能最终变得更有用。我会试试看看它的不同之处。我正在按照Sergej在下面建议的路线走下去,如果我不需要性能,我仍然可以使用它,因为它简洁和简单。 – user129345 2010-03-30 10:12:17

或者,如果你不想用反射去,你可以使用这个(这不会”工作速度极快):

var ccd = new List<KeyValuePair<string, string>>(); 
ccd.Add(new KeyValuePair<string, string>("Title", "")); 
ccd.Add(new KeyValuePair<string, string>("Forenames", "")); 
ccd.Add(new KeyValuePair<string, string>("Surname", "")); 
ccd.Add(new KeyValuePair<string, string>("EmailAddress", "")); 
ccd.Add(new KeyValuePair<string, string>("TelephoneNumber", "")); 
ccd.Add(new KeyValuePair<string, string>("AlternativeTelephoneNumber", "")); 
ccd.Add(new KeyValuePair<string, string>("Address1", "")); 
ccd.Add(new KeyValuePair<string, string>("Address2", "")); 
ccd.Add(new KeyValuePair<string, string>("TownOrDistrict", "")); 
ccd.Add(new KeyValuePair<string, string>("CountyOrState", "")); 
ccd.Add(new KeyValuePair<string, string>("PostCode", "")); 

var data = new List<List<KeyValuePair<string, string>>> { ccd, ccd, ccd }; 

var companies = from d in data 
       select new ClientCompany { 
        Title = d.FirstOrDefault(k => k.Key == "Title").Value, 
        Forenames = d.FirstOrDefault(k => k.Key == "Forenames").Value, 
        Surname = d.FirstOrDefault(k => k.Key == "Surname").Value, 
        EmailAddress = d.FirstOrDefault(k => k.Key == "EmailAddress").Value, 
        TelephoneNumber = d.FirstOrDefault(k => k.Key == "TelephoneNumber").Value, 
        AlternativeTelephoneNumber = d.FirstOrDefault(k => k.Key == "AlternativeTelephoneNumber").Value, 
        Address1 = d.FirstOrDefault(k => k.Key == "Address1").Value, 
        Address2 = d.FirstOrDefault(k => k.Key == "Address2").Value, 
        TownOrDistrict = d.FirstOrDefault(k => k.Key == "TownOrDistrict").Value, 
        CountyOrState = d.FirstOrDefault(k => k.Key == "CountyOrState").Value, 
        PostCode = d.FirstOrDefault(k => k.Key == "PostCode").Value, 
       }; 
+0

您是否认为这将与问题中显示的私人套餐一起使用? – 2010-04-01 20:36:03

+0

没有反射,没有什么能与私人定制者一起工作。这只是为了让你一个想法。 – 2010-04-07 08:00:56

在这种情况下,我会使用反射将Key-Value对映射到对象属性。举例来说,检查这个接受的答案SO question