PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/SqlCEDriver/Scripting/PocoCodeGenerator.cs

http://dbviewsharp.codeplex.com
C# | 434 lines | 336 code | 36 blank | 62 comment | 44 complexity | 342c4eb0193e796e318c761aacb13202 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace SqlCeDriver
  5. {
  6. //////////////////////////////////////////////////////////
  7. // basic helpers ie methods that might be in a base class
  8. internal class TableGeneratorHelpers
  9. {
  10. /*
  11. 1. Make all lower
  12. 2. Capitalise 1st
  13. 3. Capitalise all after _ space -
  14. 4. Remove _ space -
  15. */
  16. internal static string MakePascalCaseName(string databaseName)
  17. {
  18. return MakePascalCaseName(databaseName, false);
  19. }
  20. internal static string MakePascalCaseName(string databaseName, bool depluralise)
  21. {
  22. // Don't like UPPER CASE as class name.
  23. if (databaseName == databaseName.ToUpper())
  24. databaseName = databaseName.ToLower();
  25. string[] splittedPhrase = databaseName.Split(' ', '-', '_');
  26. StringBuilder sb = new StringBuilder();
  27. foreach (String s in splittedPhrase)
  28. {
  29. char[] splittedPhraseChars = s.ToCharArray();
  30. if (splittedPhraseChars.Length > 0)
  31. {
  32. splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0];
  33. }
  34. sb.Append(new String(splittedPhraseChars));
  35. }
  36. // remove s if table is pluralised
  37. if (depluralise)
  38. {
  39. int lastIndex = sb.Length - 1;
  40. if (sb[lastIndex] == 's')
  41. sb.Remove(lastIndex, 1);
  42. }
  43. return CleanName(sb.ToString());
  44. }
  45. internal static Type ClrTypeFromSqlType(String sqlType)
  46. {
  47. // use versions that can be nullable
  48. // See http://www.make-awesome.com/2010/07/code-first-development-with-entity-framework-4-kicking-the-tires/
  49. // where the guy does a lot of type mapping testinf
  50. String ust = sqlType.ToUpper();
  51. return (ust == "INT") ? typeof(Int32) :
  52. (ust == "TINYINT") ? typeof(Byte) :
  53. (ust == "SMALLINT") ? typeof(Int16) :
  54. (ust == "BIGINT") ? typeof(Int64) :
  55. (ust == "BIT") ? typeof(Boolean) :
  56. (ust == "FLOAT") ? typeof(Double) :
  57. (ust == "REAL") ? typeof(float) :
  58. (ust == "DECIMAL") ? typeof(Decimal) :
  59. (ust == "NUEMRIC") ? typeof(Decimal) :
  60. (ust == "MONEY") ? typeof(Decimal) :
  61. (ust == "SMALLMONEY") ? typeof(Decimal) :
  62. (ust == "DATETIME") ? typeof(DateTime) :
  63. (ust == "DATE") ? typeof(DateTime) :
  64. (ust == "UNIQUEIDENTIFIER") ? typeof(Guid) :
  65. typeof(String);
  66. }
  67. internal static String MakeTypeString(DbView.FieldInfo fi)
  68. {
  69. String typeString = TableGeneratorHelpers.ClrTypeFromSqlType(fi.SqlType)
  70. .ToString()
  71. .Replace("System.", "");
  72. // make nullable version if necessary
  73. if (fi.AllowsNulls && TableGeneratorHelpers.ClrTypeFromSqlType(fi.SqlType) != typeof(String))
  74. typeString += "?";
  75. return typeString;
  76. }
  77. // thanks to http://blog.visualt4.com/2009/02/creating-valid-c-identifiers.html
  78. // needs override mapping
  79. internal static string CleanName(string name)
  80. {
  81. //Compliant with item 2.4.2 of the C# specification
  82. System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Nl}\p{Mn}\p{Mc}\p{Cf}\p{Pc}\p{Lm}]");
  83. string ret = regex.Replace(name, "_");
  84. //The identifier must start with a character
  85. if (!char.IsLetter(ret, 0))
  86. ret = string.Concat("_", ret);
  87. return ret;
  88. }
  89. }
  90. //////////////////////////////////////////////////////////
  91. // class used for resolving tags in template strings and
  92. // recursively using the results as values for tags in other template strings
  93. // used in the EFCodeGenerator
  94. internal class TemplateMap : Dictionary<String, String>
  95. {
  96. // type to hold tag locations in a string.
  97. struct TagMap
  98. {
  99. internal String Tag;
  100. internal int Pos;
  101. }
  102. // recursively replace tags with values in the input map.
  103. // ignoreMissingTags = T then a tag with no value is replaced with empty
  104. // ignoreMissingTags = F throws error
  105. internal static String Resolve(String tag, TemplateMap map, bool ignoreMissingTags)
  106. {
  107. return Resolve(tag, map, ignoreMissingTags, 0);
  108. }
  109. // The real engine. Level used to prevent circular reference causing a stack overflow.
  110. private static String Resolve(String tag, TemplateMap map, bool ignoreMissingTags, int level)
  111. {
  112. // personal circular tag reference check
  113. if (level > 10)
  114. throw new StackOverflowException("Nesting too deep or circular tag reference situation when resolving tag " + tag);
  115. // No data for the tag
  116. if (!map.ContainsKey(tag))
  117. {
  118. // no tag = empty
  119. if (ignoreMissingTags) return "";
  120. // no tag = error
  121. throw new KeyNotFoundException("Tag " + tag + " missing from map");
  122. }
  123. // Safe to get the template like this
  124. String template = map[tag];
  125. // find all the tags in the string
  126. TagMap[] tags = ParseTemplate(template);
  127. // process in reverse order so as not to invalidate the positions
  128. // obtained from the parse
  129. for (int idx = tags.GetLength(0) - 1; idx >= 0; --idx)
  130. {
  131. TagMap t = tags[idx];
  132. // key is %%tag%% - the %%
  133. String key = t.Tag.Replace("%%", "");
  134. // recurse to resolve tag references in the data to replace
  135. String data = Resolve(key, map, ignoreMissingTags, level + 1);
  136. // replace the tag with the data
  137. template = template.Substring(0, t.Pos) + data + template.Substring(t.Pos + t.Tag.Length);
  138. }
  139. return template;
  140. }
  141. // find all of the tags (%%TAG%%) in a string
  142. private static TagMap[] ParseTemplate(string template)
  143. {
  144. List<TagMap> tags = new List<TagMap>();
  145. int pos = template.IndexOf("%%");
  146. if (pos != -1)
  147. {
  148. int pos2 = -2;
  149. while (true)
  150. {
  151. pos = template.IndexOf("%%", pos2 + 2);
  152. pos2 = template.IndexOf("%%", pos + 2);
  153. if (pos != -1 && pos2 > pos)
  154. {
  155. TagMap tag = new TagMap();
  156. tag.Pos = pos;
  157. tag.Tag = template.Substring(pos, pos2 - pos + 2);
  158. tags.Add(tag);
  159. }
  160. else
  161. break;
  162. }
  163. }
  164. return tags.ToArray();
  165. }
  166. }
  167. //////////////////////////////////////////////////////////
  168. // Linq POCO generator
  169. internal class LinqTableGenerator
  170. {
  171. public LinqTableGenerator(String tableName, SqlTableSchema schema)
  172. {
  173. this.sourceTableName = tableName;
  174. this.schema = schema;
  175. }
  176. //
  177. // codescope 0 class 1 sample 2 all
  178. public String GenerateCode(String connectionString, int codeScope)
  179. {
  180. // allow for schema
  181. String[] tableParts = Helper.ParseTableName(this.sourceTableName);
  182. String tableName = tableParts[0];
  183. String linqClassName = TableGeneratorHelpers.MakePascalCaseName(tableName);
  184. DbView.FieldInfo fi = this.schema[0];
  185. String classCode = this.GenerateClassCode();
  186. String sampleCode = String.Format(SAMPLE_TEMPLATE, linqClassName, TableGeneratorHelpers.MakePascalCaseName(fi.Name));
  187. String moduleCode = String.Format(MODULE_TEMPLATE, classCode, sampleCode, connectionString.Replace("\\", "\\\\"));
  188. return (codeScope == 0) ? classCode :
  189. (codeScope == 1) ? sampleCode + "\r\n" + classCode :
  190. moduleCode;
  191. }
  192. public String GenerateClassCode()
  193. {
  194. String code = "";
  195. List<String> codeLines = new List<String>(); // holds target Code
  196. String[] tableParts = Helper.ParseTableName(this.sourceTableName);
  197. String tableName = tableParts[0];
  198. // lets define some templates
  199. // 0 = table name 1 = modified table name 2 = list of column
  200. // indentation assumes namespace
  201. const String FRAME_CODE = @"
  202. // using System.Data.Linq;
  203. // Linq to SQL Class for table {0}
  204. [Table( Name = ""{0}"" )]
  205. public partial class {1}
  206. {{
  207. {2}
  208. }}
  209. ";
  210. // 0 = name 1 = type 2 = atts
  211. String columnTemplate =
  212. @" [Column( {2} )] public {1} {0} {{get; set;}}
  213. ";
  214. for (int idx = 0; idx < this.schema.Count; ++idx)
  215. {
  216. DbView.FieldInfo fi = this.schema[idx];
  217. String columnParams = this.MakeColumnParams(fi);
  218. String line = String.Format(columnTemplate, TableGeneratorHelpers.MakePascalCaseName(fi.Name),
  219. TableGeneratorHelpers.MakeTypeString(fi),
  220. columnParams);
  221. codeLines.Add(line);
  222. }
  223. // assemble
  224. code = String.Format(FRAME_CODE, this.sourceTableName,
  225. TableGeneratorHelpers.MakePascalCaseName(tableName),
  226. String.Join("", codeLines.ToArray()));
  227. return code;
  228. }
  229. private string MakeColumnParams(DbView.FieldInfo fi)
  230. {
  231. List<String> columnParms = new List<String>();
  232. columnParms.Add("Name=\"" + fi.Name + "\"");
  233. if (fi.IsPKey) columnParms.Add("IsPrimaryKey = true");
  234. if (fi.IsAuto) columnParms.Add("IsDbGenerated = true");
  235. return String.Join(", ", columnParms.ToArray());
  236. }
  237. String sourceTableName;
  238. SqlTableSchema schema;
  239. const String MODULE_TEMPLATE = @"
  240. using System;
  241. // add reference System.Data.SqlServerCe
  242. using System.Data.SqlServerCe;
  243. using System.Linq;
  244. // add reference to System.Data.Linq
  245. using System.Data.Linq;
  246. using System.Data.Linq.Mapping;
  247. namespace CodeGenTest
  248. {{
  249. class Program
  250. {{
  251. ///////////////////////////////////////////////////////////////
  252. // Test code
  253. static void Main(string[] args)
  254. {{
  255. String connectionString = ""{2}"";
  256. TestClass(connectionString);
  257. }}
  258. {1}
  259. }}
  260. ///////////////////////////////////////////////////////////////
  261. // Generated class
  262. {0}
  263. ///////////////////////////////////////////////////////////////
  264. }}
  265. ";
  266. const String SAMPLE_TEMPLATE = @"
  267. private static void TestClass(String connectionString)
  268. {{
  269. using (SqlCeConnection conn = new SqlCeConnection(connectionString))
  270. {{
  271. DataContext db = new DataContext(conn);
  272. // Top 5 of first field only
  273. foreach({0} rec in db.GetTable<{0}>().Take(5))
  274. {{
  275. Console.WriteLine(""{{0}} {{1}} {{2}}"", rec.{1}, """", """");
  276. }}
  277. }}
  278. }}
  279. ";
  280. }
  281. //////////////////////////////////////////////////////////////////////////
  282. // PetaPoco.Database db = new PetaPoco.Database("Connection String");
  283. //
  284. internal class PetaPocoTableGenerator
  285. {
  286. public PetaPocoTableGenerator(String tableName, SqlTableSchema schema)
  287. {
  288. this.sourceTableName = tableName;
  289. this.schema = schema;
  290. }
  291. //
  292. // codescope 0 class 1 sample 2 all
  293. public String GenerateCode(String connectionString, int codeScope)
  294. {
  295. // allow for schema
  296. String[] tableParts = Helper.ParseTableName(this.sourceTableName);
  297. String tableName = tableParts[0];
  298. String petaPocoClassName = TableGeneratorHelpers.MakePascalCaseName(tableName);
  299. DbView.FieldInfo fi = this.schema[0];
  300. String classCode = this.GenerateClassCode();
  301. String sampleCode = String.Format(SAMPLE_TEMPLATE, petaPocoClassName, TableGeneratorHelpers.MakePascalCaseName(fi.Name), tableName);
  302. String moduleCode = String.Format(MODULE_TEMPLATE, classCode, sampleCode, connectionString.Replace("\\", "\\\\"));
  303. return (codeScope == 0) ? classCode :
  304. (codeScope == 1) ? sampleCode + "\r\n" + classCode :
  305. moduleCode;
  306. }
  307. public String GenerateClassCode()
  308. {
  309. String code = "";
  310. List<String> codeLines = new List<String>(); // holds target Code
  311. String[] tableParts = Helper.ParseTableName(this.sourceTableName);
  312. String tableName = tableParts[0];
  313. // lets define some templates
  314. // 0 = table name 1 = modified table name 2 = list of column
  315. // indentation assumes namespace
  316. const String FRAME_CODE = @"
  317. // PetaPoco to SQL Class for table {0}
  318. [PetaPoco.TableName(""{0}"")]
  319. [PetaPoco.PrimaryKey(""{3}"")]
  320. public partial class {1}
  321. {{
  322. {2}
  323. }}
  324. ";
  325. // 0 = name 1 = type 2 = atts
  326. String columnTemplate =
  327. @" [PetaPoco.Column(""{2}"")] public {1} {0} {{get; set;}}
  328. ";
  329. String primaryKeys = "";
  330. for (int idx = 0; idx < this.schema.Count; ++idx)
  331. {
  332. DbView.FieldInfo fi = this.schema[idx];
  333. String columnParams = this.MakeColumnParams(fi);
  334. String line = String.Format(columnTemplate, TableGeneratorHelpers.MakePascalCaseName(fi.Name),
  335. TableGeneratorHelpers.MakeTypeString(fi),
  336. columnParams);
  337. codeLines.Add(line);
  338. if (fi.IsPKey)
  339. {
  340. if (primaryKeys.Length > 0) primaryKeys += ",";
  341. primaryKeys += fi.Name;
  342. }
  343. }
  344. // assemble
  345. code = String.Format(FRAME_CODE, this.sourceTableName,
  346. TableGeneratorHelpers.MakePascalCaseName(tableName),
  347. String.Join("", codeLines.ToArray()),
  348. primaryKeys);
  349. return code;
  350. }
  351. private string MakeColumnParams(DbView.FieldInfo fi)
  352. {
  353. List<String> columnParms = new List<String>();
  354. columnParms.Add(fi.Name);
  355. // if (fi.IsPKey) columnParms.Add("IsPrimaryKey = true");
  356. // if (fi.IsAuto) columnParms.Add("IsDbGenerated = true");
  357. return String.Join(", ", columnParms.ToArray());
  358. }
  359. String sourceTableName;
  360. SqlTableSchema schema;
  361. const String MODULE_TEMPLATE = @"
  362. using System;
  363. namespace CodeGenTest
  364. {{
  365. class Program
  366. {{
  367. ///////////////////////////////////////////////////////////////
  368. // Test code
  369. static void Main(string[] args)
  370. {{
  371. String connectionString = ""{2}"";
  372. TestClass(connectionString);
  373. }}
  374. {1}
  375. }}
  376. ///////////////////////////////////////////////////////////////
  377. // Generated class
  378. {0}
  379. ///////////////////////////////////////////////////////////////
  380. }}
  381. ";
  382. const String SAMPLE_TEMPLATE = @"
  383. private static void TestClass(String connectionString)
  384. {{
  385. String providerName= ""System.Data.SqlClient"";
  386. PetaPoco.Database db = new PetaPoco.Database(connectionString, providerName);
  387. // Top 5 of first field only
  388. String sql = ""select top 5 * from {2}"";
  389. foreach({0} rec in db.Query<{0}>(sql))
  390. {{
  391. Console.WriteLine(rec.{1});
  392. }}
  393. }}
  394. ";
  395. }
  396. }