PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/SubSonic.Core/Extensions/Objects.cs

https://github.com/BlackMael/SubSonic-3.0
C# | 324 lines | 216 code | 42 blank | 66 comment | 74 complexity | c31629c495a80fe75999de4b527a1136 MD5 | raw file
  1. //
  2. // SubSonic - http://subsonicproject.com
  3. //
  4. // The contents of this file are subject to the New BSD
  5. // License (the "License"); you may not use this file
  6. // except in compliance with the License. You may obtain a copy of
  7. // the License at http://www.opensource.org/licenses/bsd-license.php
  8. //
  9. // Software distributed under the License is distributed on an
  10. // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. // implied. See the License for the specific language governing
  12. // rights and limitations under the License.
  13. //
  14. using System;
  15. using System.Collections.Generic;
  16. using System.ComponentModel;
  17. using System.Data;
  18. using System.Reflection;
  19. using System.Linq;
  20. using SubSonic.DataProviders;
  21. using SubSonic.Schema;
  22. using SubSonic.SqlGeneration.Schema;
  23. namespace SubSonic.Extensions
  24. {
  25. public static class Objects
  26. {
  27. /// <summary>
  28. /// Returns an Object with the specified Type and whose value is equivalent to the specified object.
  29. /// </summary>
  30. /// <param name="value">An Object that implements the IConvertible interface.</param>
  31. /// <returns>
  32. /// An object whose Type is conversionType (or conversionType's underlying type if conversionType
  33. /// is Nullable&lt;&gt;) and whose value is equivalent to value. -or- a null reference, if value is a null
  34. /// reference and conversionType is not a value type.
  35. /// </returns>
  36. /// <remarks>
  37. /// This method exists as a workaround to System.Convert.ChangeType(Object, Type) which does not handle
  38. /// nullables as of version 2.0 (2.0.50727.42) of the .NET Framework. The idea is that this method will
  39. /// be deleted once Convert.ChangeType is updated in a future version of the .NET Framework to handle
  40. /// nullable types, so we want this to behave as closely to Convert.ChangeType as possible.
  41. /// This method was written by Peter Johnson at:
  42. /// http://aspalliance.com/author.aspx?uId=1026.
  43. /// </remarks>
  44. ///
  45. public static object ChangeTypeTo<T>(this object value)
  46. {
  47. Type conversionType = typeof(T);
  48. return ChangeTypeTo(value, conversionType);
  49. }
  50. public static object ChangeTypeTo(this object value, Type conversionType)
  51. {
  52. // Note: This if block was taken from Convert.ChangeType as is, and is needed here since we're
  53. // checking properties on conversionType below.
  54. if(conversionType == null)
  55. throw new ArgumentNullException("conversionType");
  56. // If it's not a nullable type, just pass through the parameters to Convert.ChangeType
  57. if(conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
  58. {
  59. // It's a nullable type, so instead of calling Convert.ChangeType directly which would throw a
  60. // InvalidCastException (per http://weblogs.asp.net/pjohnson/archive/2006/02/07/437631.aspx),
  61. // determine what the underlying type is
  62. // If it's null, it won't convert to the underlying type, but that's fine since nulls don't really
  63. // have a type--so just return null
  64. // Note: We only do this check if we're converting to a nullable type, since doing it outside
  65. // would diverge from Convert.ChangeType's behavior, which throws an InvalidCastException if
  66. // value is null and conversionType is a value type.
  67. if(value == null)
  68. return null;
  69. // It's a nullable type, and not null, so that means it can be converted to its underlying type,
  70. // so overwrite the passed-in conversion type with this underlying type
  71. NullableConverter nullableConverter = new NullableConverter(conversionType);
  72. conversionType = nullableConverter.UnderlyingType;
  73. }
  74. else if(conversionType == typeof(Guid))
  75. {
  76. return new Guid(value.ToString());
  77. }else if(conversionType==typeof(Int64) && value.GetType()==typeof(int))
  78. {
  79. //there is an issue with SQLite where the PK is ALWAYS int64. If this conversion type is Int64
  80. //we need to throw here - suggesting that they need to use LONG instead
  81. throw new InvalidOperationException("Can't convert an Int64 (long) to Int32(int). If you're using SQLite - this is probably due to your PK being an INTEGER, which is 64bit. You'll need to set your key to long.");
  82. }
  83. // Now that we've guaranteed conversionType is something Convert.ChangeType can handle (i.e. not a
  84. // nullable type), pass the call on to Convert.ChangeType
  85. return Convert.ChangeType(value, conversionType);
  86. }
  87. public static Dictionary<string, object> ToDictionary(this object value)
  88. {
  89. Dictionary<string, object> result = new Dictionary<string, object>();
  90. PropertyInfo[] props = value.GetType().GetProperties();
  91. foreach(PropertyInfo pi in props)
  92. {
  93. try
  94. {
  95. result.Add(pi.Name, pi.GetValue(value, null));
  96. }
  97. catch {}
  98. }
  99. return result;
  100. }
  101. public static T FromDictionary<T>(this Dictionary<string, object> settings, T item) where T : class
  102. {
  103. PropertyInfo[] props = item.GetType().GetProperties();
  104. //FieldInfo[] fields = item.GetType().GetFields();
  105. foreach(PropertyInfo pi in props)
  106. {
  107. if(settings.ContainsKey(pi.Name))
  108. {
  109. if(pi.CanWrite)
  110. pi.SetValue(item, settings[pi.Name], null);
  111. }
  112. }
  113. return item;
  114. }
  115. public static T CopyTo<T>(this object From, T to) where T : class
  116. {
  117. Type t = From.GetType();
  118. var settings = From.ToDictionary();
  119. to = settings.FromDictionary(to);
  120. return to;
  121. }
  122. private static bool CanGenerateSchemaFor(Type type)
  123. {
  124. return type == typeof (string) ||
  125. type == typeof (Guid) ||
  126. type == typeof (Guid?) ||
  127. type == typeof (decimal) ||
  128. type == typeof (decimal?) ||
  129. type == typeof (double) ||
  130. type == typeof (double?) ||
  131. type == typeof (DateTime) ||
  132. type == typeof (DateTime?) ||
  133. type == typeof (bool) ||
  134. type == typeof (bool?) ||
  135. type == typeof (Int16) ||
  136. type == typeof (Int16?) ||
  137. type == typeof (Int32) ||
  138. type == typeof (Int32?) ||
  139. type == typeof (Int64) ||
  140. type == typeof (Int64?) ||
  141. type == typeof (float?) ||
  142. type == typeof (float) ||
  143. type == typeof(byte[]) ||
  144. type.IsEnum || IsNullableEnum(type);
  145. }
  146. internal static bool IsNullableEnum(Type type)
  147. {
  148. var enumType = Nullable.GetUnderlyingType(type);
  149. return enumType != null && enumType.IsEnum;
  150. }
  151. public static ITable ToSchemaTable(this Type type, IDataProvider provider)
  152. {
  153. string tableName = type.Name;
  154. tableName = tableName.MakePlural();
  155. var typeAttributes = type.GetCustomAttributes(false);
  156. var tableNameAttr = (SubSonicTableNameOverrideAttribute)typeAttributes.FirstOrDefault(x => x.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicTableNameOverrideAttribute"));
  157. if (tableNameAttr != null && tableNameAttr.IsSet)
  158. {
  159. tableName = tableNameAttr.TableName;
  160. }
  161. var result = new DatabaseTable(tableName, provider);
  162. result.ClassName = type.Name;
  163. var props = type.GetProperties();
  164. foreach(var prop in props)
  165. {
  166. var attributes = prop.GetCustomAttributes(false);
  167. bool isIgnored = false;
  168. foreach(var att in attributes)
  169. {
  170. isIgnored = att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicIgnoreAttribute");
  171. if(isIgnored)
  172. break;
  173. }
  174. if(CanGenerateSchemaFor(prop.PropertyType) & !isIgnored)
  175. {
  176. var column = new DatabaseColumn(prop.Name, result);
  177. bool isNullable = prop.PropertyType.Name.Contains("Nullable");
  178. column.DataType = IdentifyColumnDataType(prop.PropertyType, isNullable);
  179. if(column.DataType == DbType.Decimal || column.DataType == DbType.Double)
  180. {
  181. //default to most common;
  182. column.NumberScale = 2;
  183. column.NumericPrecision = 10;
  184. //loop the attributes to see if there's a length
  185. foreach(var att in attributes)
  186. {
  187. if (att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicNumericPrecisionAttribute"))
  188. {
  189. var precision = (SubSonicNumericPrecisionAttribute)att;
  190. column.NumberScale = precision.Scale;
  191. column.NumericPrecision = precision.Precision;
  192. }
  193. }
  194. }
  195. else if(column.DataType == DbType.String)
  196. {
  197. column.MaxLength = 255;
  198. //loop the attributes to see if there's a length
  199. foreach(var att in attributes)
  200. {
  201. if (att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicStringLengthAttribute"))
  202. {
  203. var lengthAtt = (SubSonicStringLengthAttribute)att;
  204. column.MaxLength = lengthAtt.Length;
  205. }
  206. if (att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicNullStringAttribute"))
  207. {
  208. isNullable = true;
  209. }
  210. }
  211. }
  212. else if (column.DataType == DbType.Binary)
  213. {
  214. isNullable = true;
  215. }
  216. if(isNullable)
  217. column.IsNullable = true;
  218. //set the length on this if it's text - specifically we want to know
  219. //if the LongString attribute is used - we'll set to nvarchar MAX or ntext depending
  220. foreach(var att in attributes)
  221. {
  222. if (att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicLongStringAttribute"))
  223. column.MaxLength = 8001;
  224. }
  225. //loop the attributes - see if a PK attribute was set
  226. foreach(var att in attributes)
  227. {
  228. if (att.ToString().Equals("SubSonic.SqlGeneration.Schema.SubSonicPrimaryKeyAttribute")) {
  229. column.IsPrimaryKey = true;
  230. column.IsNullable = false;
  231. if(column.IsNumeric)
  232. column.AutoIncrement = true;
  233. else if (column.IsString && column.MaxLength == 0)
  234. column.MaxLength = 255;
  235. }
  236. }
  237. result.Columns.Add(column);
  238. }
  239. }
  240. //if the PK is still null-look for a column called [tableName]ID - if it's there then make it PK
  241. if(result.PrimaryKey == null)
  242. {
  243. var pk = (result.GetColumn(type.Name + "ID") ?? result.GetColumn("ID")) ?? result.GetColumn("Key");
  244. if(pk != null)
  245. {
  246. pk.IsPrimaryKey = true;
  247. //if it's an INT then AutoIncrement it
  248. if(pk.IsNumeric)
  249. pk.AutoIncrement = true;
  250. else if(pk.IsString && pk.MaxLength == 0)
  251. pk.MaxLength = 255;
  252. //} else {
  253. // pk = new DatabaseColumn(type.Name + "ID", result);
  254. // pk.DataType = DbType.Int32;
  255. // pk.IsPrimaryKey = true;
  256. // pk.AutoIncrement = true;
  257. // result.Columns.Insert(0, pk);
  258. }
  259. }
  260. //we should have a PK at this point
  261. //if not, throw :)
  262. if(result.PrimaryKey == null)
  263. throw new InvalidOperationException("Can't decide which property to consider the Key - you can create one called 'ID' or mark one with SubSonicPrimaryKey attribute");
  264. return result;
  265. }
  266. private static DbType IdentifyColumnDataType(Type type, bool isNullable)
  267. {
  268. //if this is a nullable type, we need to get at the underlying type
  269. if (isNullable)
  270. {
  271. var nullType = Nullable.GetUnderlyingType(type);
  272. return nullType.IsEnum ? GetEnumType(nullType) : Database.GetDbType(nullType);
  273. }
  274. return type.IsEnum ? GetEnumType(type) : Database.GetDbType(type);
  275. }
  276. private static DbType GetEnumType(Type type)
  277. {
  278. var enumType = Enum.GetUnderlyingType(type);
  279. return Database.GetDbType(enumType);
  280. }
  281. }
  282. }