/SqlCEDriver/Scripting/PocoCodeGenerator.cs
C# | 434 lines | 336 code | 36 blank | 62 comment | 44 complexity | 342c4eb0193e796e318c761aacb13202 MD5 | raw file
- using System;
- using System.Collections.Generic;
- using System.Text;
-
- namespace SqlCeDriver
- {
- //////////////////////////////////////////////////////////
- // basic helpers ie methods that might be in a base class
- internal class TableGeneratorHelpers
- {
- /*
- 1. Make all lower
- 2. Capitalise 1st
- 3. Capitalise all after _ space -
- 4. Remove _ space -
- */
- internal static string MakePascalCaseName(string databaseName)
- {
- return MakePascalCaseName(databaseName, false);
- }
-
- internal static string MakePascalCaseName(string databaseName, bool depluralise)
- {
- // Don't like UPPER CASE as class name.
- if (databaseName == databaseName.ToUpper())
- databaseName = databaseName.ToLower();
- string[] splittedPhrase = databaseName.Split(' ', '-', '_');
- StringBuilder sb = new StringBuilder();
- foreach (String s in splittedPhrase)
- {
- char[] splittedPhraseChars = s.ToCharArray();
- if (splittedPhraseChars.Length > 0)
- {
- splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0];
- }
- sb.Append(new String(splittedPhraseChars));
- }
- // remove s if table is pluralised
- if (depluralise)
- {
- int lastIndex = sb.Length - 1;
- if (sb[lastIndex] == 's')
- sb.Remove(lastIndex, 1);
- }
- return CleanName(sb.ToString());
- }
-
- internal static Type ClrTypeFromSqlType(String sqlType)
- {
- // use versions that can be nullable
- // See http://www.make-awesome.com/2010/07/code-first-development-with-entity-framework-4-kicking-the-tires/
- // where the guy does a lot of type mapping testinf
- String ust = sqlType.ToUpper();
- return (ust == "INT") ? typeof(Int32) :
- (ust == "TINYINT") ? typeof(Byte) :
- (ust == "SMALLINT") ? typeof(Int16) :
- (ust == "BIGINT") ? typeof(Int64) :
- (ust == "BIT") ? typeof(Boolean) :
- (ust == "FLOAT") ? typeof(Double) :
- (ust == "REAL") ? typeof(float) :
- (ust == "DECIMAL") ? typeof(Decimal) :
- (ust == "NUEMRIC") ? typeof(Decimal) :
- (ust == "MONEY") ? typeof(Decimal) :
- (ust == "SMALLMONEY") ? typeof(Decimal) :
- (ust == "DATETIME") ? typeof(DateTime) :
- (ust == "DATE") ? typeof(DateTime) :
- (ust == "UNIQUEIDENTIFIER") ? typeof(Guid) :
- typeof(String);
- }
-
- internal static String MakeTypeString(DbView.FieldInfo fi)
- {
- String typeString = TableGeneratorHelpers.ClrTypeFromSqlType(fi.SqlType)
- .ToString()
- .Replace("System.", "");
- // make nullable version if necessary
- if (fi.AllowsNulls && TableGeneratorHelpers.ClrTypeFromSqlType(fi.SqlType) != typeof(String))
- typeString += "?";
-
- return typeString;
- }
-
- // thanks to http://blog.visualt4.com/2009/02/creating-valid-c-identifiers.html
- // needs override mapping
- internal static string CleanName(string name)
- {
- //Compliant with item 2.4.2 of the C# specification
- 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}]");
- string ret = regex.Replace(name, "_");
- //The identifier must start with a character
- if (!char.IsLetter(ret, 0))
- ret = string.Concat("_", ret);
- return ret;
- }
- }
-
- //////////////////////////////////////////////////////////
- // class used for resolving tags in template strings and
- // recursively using the results as values for tags in other template strings
- // used in the EFCodeGenerator
- internal class TemplateMap : Dictionary<String, String>
- {
- // type to hold tag locations in a string.
- struct TagMap
- {
- internal String Tag;
- internal int Pos;
- }
-
- // recursively replace tags with values in the input map.
- // ignoreMissingTags = T then a tag with no value is replaced with empty
- // ignoreMissingTags = F throws error
- internal static String Resolve(String tag, TemplateMap map, bool ignoreMissingTags)
- {
- return Resolve(tag, map, ignoreMissingTags, 0);
- }
-
- // The real engine. Level used to prevent circular reference causing a stack overflow.
- private static String Resolve(String tag, TemplateMap map, bool ignoreMissingTags, int level)
- {
- // personal circular tag reference check
- if (level > 10)
- throw new StackOverflowException("Nesting too deep or circular tag reference situation when resolving tag " + tag);
- // No data for the tag
- if (!map.ContainsKey(tag))
- {
- // no tag = empty
- if (ignoreMissingTags) return "";
- // no tag = error
- throw new KeyNotFoundException("Tag " + tag + " missing from map");
- }
- // Safe to get the template like this
- String template = map[tag];
-
- // find all the tags in the string
- TagMap[] tags = ParseTemplate(template);
-
- // process in reverse order so as not to invalidate the positions
- // obtained from the parse
- for (int idx = tags.GetLength(0) - 1; idx >= 0; --idx)
- {
- TagMap t = tags[idx];
- // key is %%tag%% - the %%
- String key = t.Tag.Replace("%%", "");
- // recurse to resolve tag references in the data to replace
- String data = Resolve(key, map, ignoreMissingTags, level + 1);
- // replace the tag with the data
- template = template.Substring(0, t.Pos) + data + template.Substring(t.Pos + t.Tag.Length);
- }
- return template;
- }
-
- // find all of the tags (%%TAG%%) in a string
- private static TagMap[] ParseTemplate(string template)
- {
- List<TagMap> tags = new List<TagMap>();
- int pos = template.IndexOf("%%");
- if (pos != -1)
- {
- int pos2 = -2;
- while (true)
- {
- pos = template.IndexOf("%%", pos2 + 2);
- pos2 = template.IndexOf("%%", pos + 2);
- if (pos != -1 && pos2 > pos)
- {
- TagMap tag = new TagMap();
- tag.Pos = pos;
- tag.Tag = template.Substring(pos, pos2 - pos + 2);
- tags.Add(tag);
- }
- else
- break;
- }
- }
- return tags.ToArray();
- }
- }
-
- //////////////////////////////////////////////////////////
- // Linq POCO generator
- internal class LinqTableGenerator
- {
- public LinqTableGenerator(String tableName, SqlTableSchema schema)
- {
- this.sourceTableName = tableName;
- this.schema = schema;
- }
-
- //
- // codescope 0 class 1 sample 2 all
- public String GenerateCode(String connectionString, int codeScope)
- {
- // allow for schema
- String[] tableParts = Helper.ParseTableName(this.sourceTableName);
-
- String tableName = tableParts[0];
- String linqClassName = TableGeneratorHelpers.MakePascalCaseName(tableName);
- DbView.FieldInfo fi = this.schema[0];
-
- String classCode = this.GenerateClassCode();
- String sampleCode = String.Format(SAMPLE_TEMPLATE, linqClassName, TableGeneratorHelpers.MakePascalCaseName(fi.Name));
- String moduleCode = String.Format(MODULE_TEMPLATE, classCode, sampleCode, connectionString.Replace("\\", "\\\\"));
-
- return (codeScope == 0) ? classCode :
- (codeScope == 1) ? sampleCode + "\r\n" + classCode :
- moduleCode;
- }
-
- public String GenerateClassCode()
- {
- String code = "";
-
- List<String> codeLines = new List<String>(); // holds target Code
- String[] tableParts = Helper.ParseTableName(this.sourceTableName);
- String tableName = tableParts[0];
-
- // lets define some templates
- // 0 = table name 1 = modified table name 2 = list of column
- // indentation assumes namespace
- const String FRAME_CODE = @"
- // using System.Data.Linq;
- // Linq to SQL Class for table {0}
- [Table( Name = ""{0}"" )]
- public partial class {1}
- {{
- {2}
- }}
- ";
- // 0 = name 1 = type 2 = atts
- String columnTemplate =
- @" [Column( {2} )] public {1} {0} {{get; set;}}
- ";
- for (int idx = 0; idx < this.schema.Count; ++idx)
- {
- DbView.FieldInfo fi = this.schema[idx];
- String columnParams = this.MakeColumnParams(fi);
- String line = String.Format(columnTemplate, TableGeneratorHelpers.MakePascalCaseName(fi.Name),
- TableGeneratorHelpers.MakeTypeString(fi),
- columnParams);
- codeLines.Add(line);
- }
-
- // assemble
- code = String.Format(FRAME_CODE, this.sourceTableName,
- TableGeneratorHelpers.MakePascalCaseName(tableName),
- String.Join("", codeLines.ToArray()));
- return code;
- }
-
- private string MakeColumnParams(DbView.FieldInfo fi)
- {
- List<String> columnParms = new List<String>();
- columnParms.Add("Name=\"" + fi.Name + "\"");
- if (fi.IsPKey) columnParms.Add("IsPrimaryKey = true");
- if (fi.IsAuto) columnParms.Add("IsDbGenerated = true");
- return String.Join(", ", columnParms.ToArray());
- }
-
- String sourceTableName;
- SqlTableSchema schema;
-
- const String MODULE_TEMPLATE = @"
- using System;
- // add reference System.Data.SqlServerCe
- using System.Data.SqlServerCe;
- using System.Linq;
- // add reference to System.Data.Linq
- using System.Data.Linq;
- using System.Data.Linq.Mapping;
-
- namespace CodeGenTest
- {{
- class Program
- {{
- ///////////////////////////////////////////////////////////////
- // Test code
- static void Main(string[] args)
- {{
- String connectionString = ""{2}"";
- TestClass(connectionString);
- }}
- {1}
- }}
- ///////////////////////////////////////////////////////////////
- // Generated class
- {0}
- ///////////////////////////////////////////////////////////////
- }}
- ";
- const String SAMPLE_TEMPLATE = @"
- private static void TestClass(String connectionString)
- {{
- using (SqlCeConnection conn = new SqlCeConnection(connectionString))
- {{
- DataContext db = new DataContext(conn);
- // Top 5 of first field only
- foreach({0} rec in db.GetTable<{0}>().Take(5))
- {{
- Console.WriteLine(""{{0}} {{1}} {{2}}"", rec.{1}, """", """");
- }}
- }}
- }}
- ";
- }
-
- //////////////////////////////////////////////////////////////////////////
- // PetaPoco.Database db = new PetaPoco.Database("Connection String");
- //
- internal class PetaPocoTableGenerator
- {
- public PetaPocoTableGenerator(String tableName, SqlTableSchema schema)
- {
- this.sourceTableName = tableName;
- this.schema = schema;
- }
-
- //
- // codescope 0 class 1 sample 2 all
- public String GenerateCode(String connectionString, int codeScope)
- {
- // allow for schema
- String[] tableParts = Helper.ParseTableName(this.sourceTableName);
-
- String tableName = tableParts[0];
- String petaPocoClassName = TableGeneratorHelpers.MakePascalCaseName(tableName);
- DbView.FieldInfo fi = this.schema[0];
-
- String classCode = this.GenerateClassCode();
- String sampleCode = String.Format(SAMPLE_TEMPLATE, petaPocoClassName, TableGeneratorHelpers.MakePascalCaseName(fi.Name), tableName);
- String moduleCode = String.Format(MODULE_TEMPLATE, classCode, sampleCode, connectionString.Replace("\\", "\\\\"));
-
- return (codeScope == 0) ? classCode :
- (codeScope == 1) ? sampleCode + "\r\n" + classCode :
- moduleCode;
- }
-
- public String GenerateClassCode()
- {
- String code = "";
-
- List<String> codeLines = new List<String>(); // holds target Code
- String[] tableParts = Helper.ParseTableName(this.sourceTableName);
- String tableName = tableParts[0];
-
- // lets define some templates
- // 0 = table name 1 = modified table name 2 = list of column
- // indentation assumes namespace
- const String FRAME_CODE = @"
- // PetaPoco to SQL Class for table {0}
- [PetaPoco.TableName(""{0}"")]
- [PetaPoco.PrimaryKey(""{3}"")]
- public partial class {1}
- {{
- {2}
- }}
- ";
- // 0 = name 1 = type 2 = atts
- String columnTemplate =
- @" [PetaPoco.Column(""{2}"")] public {1} {0} {{get; set;}}
- ";
- String primaryKeys = "";
- for (int idx = 0; idx < this.schema.Count; ++idx)
- {
- DbView.FieldInfo fi = this.schema[idx];
- String columnParams = this.MakeColumnParams(fi);
- String line = String.Format(columnTemplate, TableGeneratorHelpers.MakePascalCaseName(fi.Name),
- TableGeneratorHelpers.MakeTypeString(fi),
- columnParams);
- codeLines.Add(line);
- if (fi.IsPKey)
- {
- if (primaryKeys.Length > 0) primaryKeys += ",";
- primaryKeys += fi.Name;
- }
- }
-
- // assemble
- code = String.Format(FRAME_CODE, this.sourceTableName,
- TableGeneratorHelpers.MakePascalCaseName(tableName),
- String.Join("", codeLines.ToArray()),
- primaryKeys);
- return code;
- }
-
- private string MakeColumnParams(DbView.FieldInfo fi)
- {
- List<String> columnParms = new List<String>();
- columnParms.Add(fi.Name);
- // if (fi.IsPKey) columnParms.Add("IsPrimaryKey = true");
- // if (fi.IsAuto) columnParms.Add("IsDbGenerated = true");
- return String.Join(", ", columnParms.ToArray());
- }
-
- String sourceTableName;
- SqlTableSchema schema;
-
- const String MODULE_TEMPLATE = @"
- using System;
-
- namespace CodeGenTest
- {{
- class Program
- {{
- ///////////////////////////////////////////////////////////////
- // Test code
- static void Main(string[] args)
- {{
- String connectionString = ""{2}"";
- TestClass(connectionString);
- }}
- {1}
- }}
- ///////////////////////////////////////////////////////////////
- // Generated class
- {0}
- ///////////////////////////////////////////////////////////////
- }}
- ";
- const String SAMPLE_TEMPLATE = @"
- private static void TestClass(String connectionString)
- {{
- String providerName= ""System.Data.SqlClient"";
- PetaPoco.Database db = new PetaPoco.Database(connectionString, providerName);
- // Top 5 of first field only
- String sql = ""select top 5 * from {2}"";
- foreach({0} rec in db.Query<{0}>(sql))
- {{
- Console.WriteLine(rec.{1});
- }}
- }}
- ";
- }
- }