Есть ли способ превратить список пар ключ / значение в объект передачи данных - PullRequest
4 голосов
/ 30 марта 2010

... кроме очевидного перебора списка и грязного оператора case!

Я перевернул несколько запросов 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.

Ответы [ 4 ]

3 голосов
/ 30 марта 2010

Вот элегантное, расширяемое, обслуживаемое и невероятно быстрое решение для загрузки DTO из словарей.

Создайте консольное приложение и добавьте эти два файла. Остальное самодокументируется.

Значимые пункты:

  • простые, краткие и поддерживаемые классы отображения.
  • эффективная регидратация объекта с использованием динамических методов.

ПРИМЕЧАНИЕ. Если вы скопировали предыдущий файл DynamicProperties.cs, вам нужно его получить. Я добавил флаг, чтобы разрешить генерацию частных сеттеров, которых не было в предыдущей версии.

Приветствие.

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 = "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<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 голосов
/ 09 июня 2015

В таком случае я бы использовал отражение для сопоставления пар ключ-значение со свойствами объекта. Например, проверьте принятый ответ на этот SO вопрос

0 голосов
/ 30 марта 2010

В качестве альтернативы, если вы не хотите идти с отражением, вы можете использовать это (это не будет работать невероятно быстро):

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 голосов
/ 30 марта 2010

Если вы можете сделать так, чтобы данные выглядели как ['Title':'Mr', 'Forenames':'John', 'Surname':'Doe',...], тогда вы сможете JSON десериализовать kvp в ваш исходный объект.

...