PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Sources/DataAccess/Utility.cs

https://github.com/tpwalke2/DataTable
C# | 334 lines | 244 code | 53 blank | 37 comment | 32 complexity | 731d6310d405ad2512a75a9f7491f06e MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Reflection;
  8. namespace DataAccess
  9. {
  10. /// <summary>
  11. /// Exception thrown on illegal user operation.
  12. /// </summary>
  13. [Serializable]
  14. public class AssertException : Exception
  15. {
  16. internal AssertException(string message)
  17. : base(message)
  18. { }
  19. }
  20. internal static class Utility
  21. {
  22. // Helper for Dictionaries.
  23. public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey lookup) where TValue : new()
  24. {
  25. TValue r;
  26. if (dict.TryGetValue(lookup, out r))
  27. {
  28. return r;
  29. }
  30. r = new TValue();
  31. dict[lookup] = r;
  32. return r;
  33. }
  34. // Helper for Dictionaries. Useful when TValue doesn't have a default ctor (such as with immutable objects like Tuples)
  35. public static TValue GetOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey lookup, TValue defaultValue)
  36. {
  37. TValue r;
  38. if (dict.TryGetValue(lookup, out r))
  39. {
  40. return r;
  41. }
  42. r = defaultValue;
  43. dict[lookup] = r;
  44. return r;
  45. }
  46. public static void Assert(bool f)
  47. {
  48. Assert(f, String.Empty);
  49. }
  50. public static void Assert(bool f, string message)
  51. {
  52. if (!f)
  53. {
  54. throw new AssertException(message);
  55. //Debugger.Break();
  56. }
  57. }
  58. // Case insensitive string compare
  59. internal static bool Compare(string a, string b)
  60. {
  61. return string.Compare(a, b, true) == 0;
  62. }
  63. // All strings become upper case (for comparison)
  64. public static Dictionary<TKey, TValue> ToDict<TKey, TValue>(MutableDataTable table, string keyName, string valueName)
  65. {
  66. // $$$ Should this be on DataTable?
  67. int cKey = Utility.GetColumnIndexFromName(table.ColumnNames, keyName);
  68. int cValue = Utility.GetColumnIndexFromName(table.ColumnNames, valueName);
  69. return ToDict<TKey, TValue>(table, cKey, cValue);
  70. }
  71. public static Dictionary<TKey, TValue> ToDict<TKey, TValue>(MutableDataTable table)
  72. {
  73. // Assume first two
  74. return ToDict<TKey, TValue>(table, 0, 1);
  75. }
  76. // column ids to use for keys and values.
  77. public static Dictionary<TKey, TValue> ToDict<TKey, TValue>(MutableDataTable table, int cKey, int cVal)
  78. {
  79. Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
  80. for (int row = 0; row < table.NumRows; row++)
  81. {
  82. TKey k = Convert<TKey>(table.Columns[cKey].Values[row]);
  83. TValue v = Convert<TValue>(table.Columns[cVal].Values[row]);
  84. d[k] = v;
  85. }
  86. return d;
  87. }
  88. static T Convert<T>(string s)
  89. {
  90. return (T)System.Convert.ChangeType(s.ToUpperInvariant(), typeof(T));
  91. }
  92. internal static MutableDataTable ToMutable(DataTable table)
  93. {
  94. MutableDataTable dt = new MutableDataTable();
  95. // Take a pass through upfront so we know how large to allocate all the column arrays
  96. int numRows = table.Rows.Count();
  97. Column[] cs = Array.ConvertAll(table.ColumnNames.ToArray(), name => new Column(name, numRows));
  98. int rowIdx = 0;
  99. foreach (Row row in table.Rows)
  100. {
  101. var values = row.Values;
  102. for (int iColumn = 0; iColumn < values.Count; iColumn++)
  103. {
  104. cs[iColumn].Values[rowIdx] = values[iColumn];
  105. }
  106. rowIdx++;
  107. }
  108. dt.Columns = cs;
  109. return dt;
  110. }
  111. // Dynamically Flatten.
  112. // $$$ Need way to gaurantee that flatten order matches column names.
  113. public static MutableDataTable ToTableX<T>(IEnumerable<T> a, params string[] columnNames)
  114. {
  115. // $$$ How to infer column names?
  116. // Flatten doesn't have a definitive order.
  117. // If we had more smart collections, we could infer.
  118. int count = a.Count();
  119. MutableDataTable d = new MutableDataTable();
  120. // Alloc columns
  121. Column[] cs = new Column[columnNames.Length];
  122. for (int i = 0; i < columnNames.Length; i++)
  123. {
  124. cs[i] = new Column(columnNames[i], count);
  125. }
  126. // Fill in rows
  127. int row = 0;
  128. foreach (T item in a)
  129. {
  130. string[] values = Flatten(item);
  131. Utility.Assert(values.Length == columnNames.Length);
  132. for (int i = 0; i < columnNames.Length; i++)
  133. {
  134. cs[i].Values[row] = values[i];
  135. }
  136. row++;
  137. }
  138. d.Columns = cs;
  139. return d;
  140. }
  141. // Given a type, get the column names. This is the corrollay to Flatten<T>, which given an instance
  142. // of the type, gets the values.
  143. // This must have parallel logic to Flatten<T>.
  144. internal static string[] InferColumnNames<T>()
  145. {
  146. Type t = typeof(T);
  147. if (t.IsPrimitive || t == typeof(string))
  148. {
  149. // No properties to lookat.
  150. return new string[1] { "value" };
  151. }
  152. if (t.IsEnum || t == typeof(DateTime))
  153. {
  154. return new string[1] { t.Name };
  155. }
  156. return Array.ConvertAll(typeof(T).GetProperties(), prop => prop.Name);
  157. }
  158. // Exposed for testing
  159. internal static string[] Flatten<T>(T item)
  160. {
  161. List<string> vals = new List<string>();
  162. FlattenWorker(item, vals);
  163. return vals.ToArray();
  164. }
  165. private static void FlattenWorker(object item, List<string> vals)
  166. {
  167. if (item == null)
  168. {
  169. vals.Add(string.Empty);
  170. return;
  171. }
  172. Type t = item.GetType();
  173. // May need to flatten recursively
  174. if (t.IsPrimitive)
  175. {
  176. vals.Add(item.ToString());
  177. return;
  178. }
  179. if ((t == typeof(string)) || (t == typeof(DateTime)) || t.IsEnum || (t == typeof(DiscreteValue)))
  180. {
  181. vals.Add(item.ToString());
  182. return;
  183. }
  184. if (t.IsGenericType)
  185. {
  186. Type t2 = t.GetGenericTypeDefinition();
  187. if (t2 == typeof(KeyValuePair<,>))
  188. {
  189. object key = GetMember(item, "Key");
  190. FlattenWorker(key, vals);
  191. object value = GetMember(item, "Value");
  192. FlattenWorker(value, vals);
  193. return;
  194. }
  195. }
  196. // It's a class, add public properties of the class.
  197. // $$$ If the class is polymorphic, then this could change for different instances.
  198. {
  199. PropertyInfo[] ps = t.GetProperties();
  200. foreach (var p in ps)
  201. {
  202. FlattenWorker(GetMember(item, p.Name), vals);
  203. }
  204. return;
  205. }
  206. // If it's a tuple,
  207. // If is a key-value pair?
  208. throw new NotImplementedException();
  209. }
  210. static object GetMember(object o, string memberName)
  211. {
  212. Type t = o.GetType();
  213. PropertyInfo p = t.GetProperty(memberName);
  214. return p.GetValue(o, null);
  215. }
  216. // $$$ Merge with the more dynamic ToTable.
  217. internal static MutableDataTable ToTable<T1, T2>(Tuple<T1, T2>[] a, string name1, string name2)
  218. {
  219. MutableDataTable d = new MutableDataTable();
  220. int count = a.Length;
  221. Column cKeys = new Column(name1, count);
  222. Column cVals = new Column(name2, count);
  223. d.Columns = new Column[] { cKeys, cVals };
  224. int i = 0;
  225. foreach (var kv in a)
  226. {
  227. cKeys.Values[i] = kv.Item1.ToString();
  228. cVals.Values[i] = kv.Item2.ToString();
  229. i++;
  230. }
  231. return d;
  232. }
  233. // Convert a 2d dict into a 2d data table.
  234. // TKey1 is rows, TKey1 is columns.
  235. // Data table column names are obtained from key values.
  236. // Column 0 is set of row values.
  237. internal static MutableDataTable ToTable<TKey1, TKey2, TValue>(Dictionary2d<TKey1, TKey2, TValue> dict)
  238. {
  239. // TKey1 is rows, TKey2 is values.
  240. MutableDataTable d = new MutableDataTable();
  241. var rows = dict.Key1;
  242. int count = rows.Count();
  243. // Set columns
  244. var columns = dict.Key2.ToArray();
  245. {
  246. Column[] cs = new Column[columns.Length + 1];
  247. cs[0] = new Column("row name", count);
  248. for (int ic = 0; ic < columns.Length; ic++)
  249. {
  250. cs[ic + 1] = new Column(columns[ic].ToString(), count);
  251. }
  252. d.Columns = cs;
  253. }
  254. // Add rows
  255. int i = 0;
  256. foreach (var row in rows)
  257. {
  258. d.Columns[0].Values[i] = row.ToString();
  259. for (int ic = 0; ic < columns.Length; ic++)
  260. {
  261. d.Columns[ic + 1].Values[i] = dict[row, columns[ic]].ToString();
  262. }
  263. i++;
  264. }
  265. return d;
  266. }
  267. internal static int GetColumnIndexFromName(IEnumerable<string> columnNames, string columnName)
  268. {
  269. int i = 0;
  270. foreach (string x in columnNames)
  271. {
  272. if (string.Compare(columnName, x, StringComparison.OrdinalIgnoreCase) == 0)
  273. {
  274. return i;
  275. }
  276. i++;
  277. }
  278. throw new InvalidOperationException("No column named '" + columnName + "'");
  279. }
  280. }
  281. }