有没有办法将键/值对列表转换为数据传输对象
......除了明显的循环列表和肮脏的大案例语句之外!有没有办法将键/值对列表转换为数据传输对象
我已经在我的头上翻了几个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相同。
如果你能得到的数据看起来像['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
}
}
不错的代码在那里,但因为我知道键和它们的属性提前映射,我不认为我需要动态属性访问提供。尽管欢呼。 – user129345 2010-03-30 08:52:09
@ weevie - 是的,你有一半的映射,但在这种情况下,这个类的重点是速度宝贝! – 2010-03-30 08:56:17
这会教会我加快阅读代码。 _可能需要这是高性能的,所以这可能最终变得更有用。我会试试看看它的不同之处。我正在按照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,
};
您是否认为这将与问题中显示的私人套餐一起使用? – 2010-04-01 20:36:03
没有反射,没有什么能与私人定制者一起工作。这只是为了让你一个想法。 – 2010-04-07 08:00:56
通过KV对你的意思是你有像“Title”=>“something”,“Forenames”=>“别的东西”对的集合,它完全描述ClientCompany对象? – 2010-03-30 08:38:14
Ooops忘了澄清。我会编辑。 (哦,并且在这里回答你的问题,同时每个KV对都有一个映射,我事先知道这些键,他们的命名与DTO不一样) – user129345 2010-03-30 08:48:08
@Weevie - 事情是如何发生的? – 2010-04-11 17:24:03