/Rhino.Etl.Core/Row.cs

http://github.com/ayende/rhino-etl · C# · 260 lines · 166 code · 30 blank · 64 comment · 26 complexity · a67382ffb75b27c107951a674a012764 MD5 · raw file

  1. using System.Linq;
  2. using System.Linq.Expressions;
  3. using System.Runtime.CompilerServices;
  4. namespace Rhino.Etl.Core
  5. {
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Data;
  10. using System.Diagnostics;
  11. using System.Reflection;
  12. /// <summary>
  13. /// Represent a virtual row
  14. /// </summary>
  15. [DebuggerDisplay("Count = {items.Count}")]
  16. [DebuggerTypeProxy(typeof(QuackingDictionaryDebugView))]
  17. [Serializable]
  18. public class Row : QuackingDictionary, IEquatable<Row>
  19. {
  20. static readonly Dictionary<Type, List<PropertyInfo>> propertiesCache = new Dictionary<Type, List<PropertyInfo>>();
  21. static readonly Dictionary<Type, List<FieldInfo>> fieldsCache = new Dictionary<Type, List<FieldInfo>>();
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="Row"/> class.
  24. /// </summary>
  25. /// <param name="comparer">Defines key equality</param>
  26. public Row(StringComparer comparer)
  27. : base(new Hashtable(), comparer)
  28. {
  29. }
  30. /// <summary>
  31. /// Initializes a new instance of the <see cref="Row"/> class.
  32. /// </summary>
  33. public Row()
  34. : base(new Hashtable())
  35. {
  36. }
  37. /// <summary>
  38. /// Initializes a new instance of the <see cref="Row"/> class.
  39. /// </summary>
  40. /// <param name="itemsToClone">The items to clone.</param>
  41. /// <param name="comparer">Defines key equality</param>
  42. protected Row(IDictionary itemsToClone, StringComparer comparer)
  43. : base(itemsToClone, comparer)
  44. {
  45. }
  46. /// <summary>
  47. /// Creates a copy of the given source, erasing whatever is in the row currently.
  48. /// </summary>
  49. /// <param name="source">The source row.</param>
  50. public void Copy(IDictionary source)
  51. {
  52. items = new Hashtable(source, Comparer);
  53. }
  54. /// <summary>
  55. /// Gets the columns in this row.
  56. /// </summary>
  57. /// <value>The columns.</value>
  58. public IEnumerable<string> Columns
  59. {
  60. get
  61. {
  62. //We likely would want to change the row when iterating on the columns, so we
  63. //want to make sure that we send a copy, to avoid enumeration modified exception
  64. foreach (string column in new ArrayList(items.Keys))
  65. {
  66. yield return column;
  67. }
  68. }
  69. }
  70. /// <summary>
  71. /// Clones this instance.
  72. /// </summary>
  73. /// <returns></returns>
  74. public Row Clone()
  75. {
  76. Row row = new Row(this, Comparer);
  77. return row;
  78. }
  79. /// <summary>
  80. /// Indicates whether the current <see cref="Row" /> is equal to another <see cref="Row" />.
  81. /// </summary>
  82. /// <returns>
  83. /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
  84. /// </returns>
  85. /// <param name="other">An object to compare with this object.</param>
  86. public bool Equals(Row other)
  87. {
  88. if (!Comparer.Equals(other.Comparer))
  89. return false;
  90. if(Columns.SequenceEqual(other.Columns, Comparer) == false)
  91. return false;
  92. foreach (var key in items.Keys)
  93. {
  94. var item = items[key];
  95. var otherItem = other.items[key];
  96. if (item == null | otherItem == null)
  97. return item == null & otherItem == null;
  98. var equalityComparer = CreateComparer(item.GetType(), otherItem.GetType());
  99. if(equalityComparer(item, otherItem) == false)
  100. return false;
  101. }
  102. return true;
  103. }
  104. private static Func<object, object, bool> CreateComparer(Type firstType, Type secondType)
  105. {
  106. if (firstType == secondType)
  107. return Equals;
  108. var firstParameter = Expression.Parameter(typeof (object), "first");
  109. var secondParameter = Expression.Parameter(typeof (object), "second");
  110. var equalExpression = Expression.Equal(Expression.Convert(firstParameter, firstType),
  111. Expression.Convert(Expression.Convert(secondParameter, secondType), firstType));
  112. return Expression.Lambda<Func<object, object, bool>>(equalExpression, firstParameter, secondParameter).Compile();
  113. }
  114. /// <summary>
  115. /// Creates a key from the current row, suitable for use in hashtables
  116. /// </summary>
  117. public ObjectArrayKeys CreateKey()
  118. {
  119. return CreateKey(Columns.ToArray());
  120. }
  121. /// <summary>
  122. /// Creates a key that allow to do full or partial indexing on a row
  123. /// </summary>
  124. /// <param name="columns">The columns.</param>
  125. /// <returns></returns>
  126. public ObjectArrayKeys CreateKey(params string[] columns)
  127. {
  128. object[] array = new object[columns.Length];
  129. for (int i = 0; i < columns.Length; i++)
  130. {
  131. array[i] = items[columns[i]];
  132. }
  133. return new ObjectArrayKeys(array);
  134. }
  135. /// <summary>
  136. /// Copy all the public properties and fields of an object to the row
  137. /// </summary>
  138. /// <param name="obj">The obj.</param>
  139. /// <returns></returns>
  140. public static Row FromObject(object obj)
  141. {
  142. if (obj == null)
  143. throw new ArgumentNullException("obj");
  144. Row row = new Row();
  145. foreach (PropertyInfo property in GetProperties(obj))
  146. {
  147. row[property.Name] = property.GetValue(obj, new object[0]);
  148. }
  149. foreach (FieldInfo field in GetFields(obj))
  150. {
  151. row[field.Name] = field.GetValue(obj);
  152. }
  153. return row;
  154. }
  155. private static List<PropertyInfo> GetProperties(object obj)
  156. {
  157. List<PropertyInfo> properties;
  158. if (propertiesCache.TryGetValue(obj.GetType(), out properties))
  159. return properties;
  160. properties = new List<PropertyInfo>();
  161. foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))
  162. {
  163. if (property.CanRead == false || property.GetIndexParameters().Length > 0)
  164. continue;
  165. properties.Add(property);
  166. }
  167. propertiesCache[obj.GetType()] = properties;
  168. return properties;
  169. }
  170. private static List<FieldInfo> GetFields(object obj)
  171. {
  172. List<FieldInfo> fields;
  173. if (fieldsCache.TryGetValue(obj.GetType(), out fields))
  174. return fields;
  175. fields = new List<FieldInfo>();
  176. foreach (FieldInfo fieldInfo in obj.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))
  177. {
  178. if (Attribute.IsDefined(fieldInfo, typeof(CompilerGeneratedAttribute)) == false)
  179. {
  180. fields.Add(fieldInfo);
  181. }
  182. }
  183. fieldsCache[obj.GetType()] = fields;
  184. return fields;
  185. }
  186. /// <summary>
  187. /// Generate a row from the reader
  188. /// </summary>
  189. /// <param name="reader">The reader.</param>
  190. /// <returns></returns>
  191. public static Row FromReader(IDataReader reader)
  192. {
  193. Row row = new Row();
  194. for (int i = 0; i < reader.FieldCount; i++)
  195. {
  196. row[reader.GetName(i)] = reader.GetValue(i);
  197. }
  198. return row;
  199. }
  200. /// <summary>
  201. /// Create a new object of <typeparamref name="T"/> and set all
  202. /// the matching fields/properties on it.
  203. /// </summary>
  204. /// <typeparam name="T"></typeparam>
  205. /// <returns></returns>
  206. public T ToObject<T>()
  207. {
  208. return (T)ToObject(typeof(T));
  209. }
  210. /// <summary>
  211. /// Create a new object of <param name="type"/> and set all
  212. /// the matching fields/properties on it.
  213. /// </summary>
  214. public object ToObject(Type type)
  215. {
  216. object instance = Activator.CreateInstance(type);
  217. foreach (PropertyInfo info in GetProperties(instance))
  218. {
  219. if(items.Contains(info.Name) && info.CanWrite)
  220. info.SetValue(instance, items[info.Name],null);
  221. }
  222. foreach (FieldInfo info in GetFields(instance))
  223. {
  224. if(items.Contains(info.Name))
  225. info.SetValue(instance,items[info.Name]);
  226. }
  227. return instance;
  228. }
  229. }
  230. }