using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.IO; using Sarand.Net.Standard.Base.Utils; namespace Sarand.Net.Standard.Dbas.Base { //======================================================================= public class DatabaseDefinitionGenerator { //------------------------------------------------------------------- // Configuration. public enum SarandCommonVersion { VS2017, /// /// Visual Studio 2019 version. StringListUtil rather than StringListUtil. /// VS2019 } public enum DotNetVersion { Framework, Core3, } public enum Dbms { ODBC, SQL, } public class Config { public SarandCommonVersion SarandCommonVersion { get; set; } = SarandCommonVersion.VS2017; public DotNetVersion DotNetVersion { get; set; } = DotNetVersion.Framework; public SacsDatabase Database { get; set; } public Dbms DbmsOutput { get; set; } = Dbms.ODBC; public String OutputDirectory { get; set; } = ""; public bool ConfirmDirectoryClear { get; set; } = true; // Database schema. public class SchemaData { // Tables to be included (all others excluded) or those to be excluded. public List Tables_Included { get; } = new List(); public List Tables_Excluded { get; } = new List(); // Alternative table names. public Dictionary> AlternativeTableNames { get; } = new Dictionary>(); public void AddAlternativeTableName(String tableName, String tableNameAlternative) { if (!AlternativeTableNames.ContainsKey(tableName)) AlternativeTableNames[tableName] = new List(); AlternativeTableNames[tableName].Add(tableNameAlternative); } // Insert only columns - not modified on update: // Table name - regular expressions allowed. // Comma separated list of columns // eg. New StringPair(".*","CreateOn,CreateBy"); public List InsertOnlyColumns { get; } = new List(); } public SchemaData Schema { get; } = new SchemaData(); // Database definition class. public class DefinitionClassConfig { /// Using namespaces for database definition and table class usings. public List Usings { get; } = new List() { "Sarand.Net.Standard.Base.Core", "Sarand.Net.Standard.Base.Dbas" }; public String Namespace { get; set; } = "Sarand.Application.Core.Base"; public String Classname { get; set; } = "SaDbDfn"; } public DefinitionClassConfig DefinitionClass { get; } = new DefinitionClassConfig(); // Table class. public class TableClassConfig { /// Using namespaces for table classes. public List Usings { get; } = new List() { "Sarand.Net.Standard.Base.Core", "Sarand.Net.Standard.Base.Dbas" }; public String Namespace_BaseClass { get; set; } = "Sarand.Net.Standard.Dbas.Data"; public String Namespace { get; set; } = "Sarand.Application.Dbas"; public String BaseClass { get; set; } = "DbDataItem"; public String ClassName_DatabaseInfo = "IDbDataItem.IDatabaseInfo"; public bool MandatoryConstructorDbInfo = false; public bool TreatDoubleFormatAsDecimal { get; set; } = false; } public TableClassConfig TableClass { get; } = new TableClassConfig(); } //------------------------------------------------------------------- // Data members. private Config config = null; private static String /*lf = "\n",*/ crlf = "\r\n", tab1 = "\t", tab2 = "\t\t", tab3 = "\t\t\t", tab4 = "\t\t\t\t", tab5 = "\t\t\t\t\t"; // Reserved words. private static readonly String[] reservedWords = { "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AVG", "BEGIN", "BETWEEN", "BY", "CASE", "CAST", "COLUMN", "COMMIT", "CONSTRAINT", "CONVERT", "COUNT", "CREATE", "CURRENCY", "DEFAULT", "DELETE", "DESC", "DISTINCT", "DROP", "ELSE", "END", "EXECUTE", "EXISTS", "FALSE", "FROM", "GRANT", "GROUP", "HAVING", "IN", "INDEX", "INNER", "INSERT", "INTO", "IS", "JOIN", "KEY", "LEFT", "LIKE", "MAX", "MIN", "NOT", "NULL", "ON", "OR", "ORDER", "OUTER", "OUTPUT", "PRIMARY", "PROCEDURE", "REVOKE", "ROLLBACK", "ROWCOUNT", "SELECT", "SET", "SQL", "SUM", "TABLE", "THEN", "TRANSACTION", "TRUE", "UNION", "UNIQUE", "UPDATE", "USER", "VALUES", "VIEW", "WHEN", "WHERE" }; //------------------------------------------------------------------- public bool Generate(Config config) { // Validate configuration. this.config = config; if (!FileUtil.IsDirectory(config.OutputDirectory)) throw new Exception("Invalid directory specified [" + config.OutputDirectory + "]"); // Validate and cleanse configuration parameters. if (StringUtil.IsEmpty(StringUtil.TrimAll(config.TableClass.BaseClass))) config.TableClass.BaseClass = "DbDataItem"; // Clear files from output firectory. if (Directory.EnumerateFiles(config.OutputDirectory, "*", SearchOption.TopDirectoryOnly).Count() > 0) { SaLogger.Info(GetType().Name + ".Generate Deleting existing files in output folders."); DirectoryInfo di = new DirectoryInfo(config.OutputDirectory); foreach (FileInfo file in di.EnumerateFiles()) file.Delete(); } // Table definitions. List tableDfns = new List(); foreach (DbDfnTable tableDfn in config.Database.GetTableDfns()) { if (!IsValidTable(tableDfn.Name)) continue; tableDfns.Add(tableDfn); } // Database schema XML. GenerateDatabaseSchemaXml(tableDfns); // Database definition class. GenerateDatabaseDefinitionClass(tableDfns); // Database table Classes. foreach (DbDfnTable tableDfn in tableDfns) GenerateTableInterfaceClass(tableDfn); return true; } //------------------------------------------------------------------- private bool IsValidTable(String table) { if (String.IsNullOrEmpty(table)) return false; if (config.Schema.Tables_Included.Count > 0 && !config.Schema.Tables_Included.ContainsCI(table)) return false; if (config.Schema.Tables_Excluded.Count > 0 && config.Schema.Tables_Excluded.ContainsCI(table)) return false; return true; } //------------------------------------------------------------------- private void GenerateDatabaseSchemaXml(List tableDfns) { String filePath = config.OutputDirectory + Path.DirectorySeparatorChar + config.DefinitionClass.Classname + ".xml"; // Construct database definition. DbDfnDatabase databaseDfn = new DbDfnDatabase(); foreach (DbDfnTable tableDfn in tableDfns) { databaseDfn.AddTable(tableDfn); } using (StreamWriter file = new StreamWriter(filePath)) { file.Write(SerializableUtil.ToXml(databaseDfn)); } } //------------------------------------------------------------------- /// Intercept column variable type name to replace double/float with decimal where required. private String GetColumnVariableTypeName(DbDfnColumn columnDfn) { return GetColumnVariableTypeName(columnDfn, config.TableClass.TreatDoubleFormatAsDecimal); } /// Intercept column variable type name to replace double/float with decimal where required. private static String GetColumnVariableTypeName(DbDfnColumn columnDfn, bool treatDoubleFormatAsDecimal) { if (treatDoubleFormatAsDecimal) { switch (columnDfn.DataType) { case DbColumnDataType.Single: case DbColumnDataType.Double: return "Decimal"; } } return columnDfn.GetVariableTypeName(); } //------------------------------------------------------------------- /// Intercept column variable type to replace double/float with decimal where required. private String GetColumnVariableType(DbDfnColumn columnDfn) { return GetColumnVariableType(columnDfn, config.TableClass.TreatDoubleFormatAsDecimal); } /// Intercept column variable type to replace double/float with decimal where required. private static String GetColumnVariableType(DbDfnColumn columnDfn, bool treatDoubleFormatAsDecimal) { if (treatDoubleFormatAsDecimal) { switch (columnDfn.DataType) { case DbColumnDataType.Single: case DbColumnDataType.Double: return "Decimal"; } } return columnDfn.GetVariableType(); } //------------------------------------------------------------------- /// Intercept column 'Get Field Suffix' to replace double/float with decimal where required. private String GetColumnGetFieldSuffix(DbDfnColumn columnDfn) { return GetColumnGetFieldSuffix(columnDfn, config.TableClass.TreatDoubleFormatAsDecimal); } /// Intercept column 'Get Field Suffix' to replace double/float with decimal where required. private static String GetColumnGetFieldSuffix(DbDfnColumn columnDfn, bool treatDoubleFormatAsDecimal) { if (treatDoubleFormatAsDecimal) { switch (columnDfn.DataType) { case DbColumnDataType.Single: case DbColumnDataType.Double: return "Decimal"; } } return columnDfn.GetGetFieldSuffix(); } //------------------------------------------------------------------- // D a t a b a s e D e f i n i t i o n C l a s s //------------------------------------------------------------------- private void GenerateDatabaseDefinitionClass(List tableDfns) { String filePath = config.OutputDirectory + Path.DirectorySeparatorChar + config.DefinitionClass.Classname + ".cs"; using (StreamWriter file = new StreamWriter(filePath)) { // Database definition file: foreach (String namespace_Base in config.DefinitionClass.Usings.OrderBy(s => s)) file.WriteLine("using " + namespace_Base + ";"); file.WriteLine("using System;"); file.WriteLine("using System.Collections.Generic;"); file.WriteLine("using System.Linq;"); file.WriteLine(""); file.WriteLine("//---------------------------------------------------------------------------"); file.WriteLine("// Code generated by Sarand Database Utilities " + DateTime.Now.Format("ddd d-MMM-yyyy HH:mm:ss")); file.WriteLine("//---------------------------------------------------------------------------"); file.WriteLine("namespace " + config.DefinitionClass.Namespace); file.WriteLine("{"); file.WriteLine(tab1 + "public class " + config.DefinitionClass.Classname); file.WriteLine(tab1 + "{"); file.WriteLine(tab2 + "//-------------------------------------------------------------------"); GenerateDatabaseDefinitionTablesEnum(tableDfns, file); GenerateDatabaseDefinitionColumnsEnum(tableDfns, file); GenerateDatabaseDefinitionClass(tableDfns, config.DefinitionClass.Classname, file); file.WriteLine(tab1 + "}"); file.WriteLine("}"); //file.Close(); - Unecessary as the object will be disposed at the end of the 'using' block which is the same as Close. } } //------------------------------------------------------------------- // D a t a b a s e E n u m s //------------------------------------------------------------------- public static void GenerateDatabaseDefinitionTablesEnum(List tableDfns, StreamWriter file) { file.WriteLine(tab2 + "// Table identifiers."); file.WriteLine(tab2 + "public enum TableIdx"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "Unknown = 0,"); foreach (DbDfnTable tableDfn in tableDfns) { file.WriteLine(tab3 + tableDfn.Name + ","); } file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); } //------------------------------------------------------------------- public static void GenerateDatabaseDefinitionColumnsEnum(List tableDfns, StreamWriter file) { file.WriteLine(tab2 + "// Column identifiers."); file.WriteLine(tab2 + "public enum ColumnIdx"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "Unknown = 0,"); foreach (DbDfnTable tableDfn in tableDfns) { foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab3 + tableDfn.Name + "_" + columnDfn.Name + ","); } file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); } //------------------------------------------------------------------- public static void GenerateDatabaseDefinitionClass(List tableDfns, String classnameDefinition, StreamWriter file) { file.WriteLine(tab2 + "// Data members."); file.WriteLine(tab2 + "private static readonly int tableCount = " + tableDfns.Count + ";"); file.WriteLine(tab2 + "private static readonly int columnCount = " + tableDfns.GetColumnCount() + ";"); file.WriteLine(tab2 + "private static ulong maxTextLen = 0;"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Table methods. file.WriteLine(tab2 + "// Retrieve Table details (eg. name: 'Company')."); file.WriteLine(tab2 + "public static int GetTableCount ()"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "return tableCount;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static String GetTableName (TableIdx table, bool delimitReservedWords = true)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int tableIdx = (int)table - 1;"); file.WriteLine(tab3 + "if (tableIdx < 0 || tableIdx >= tableCount) return String.Empty;"); file.WriteLine(tab3 + "String tableName = tables[(int)tableIdx];"); file.WriteLine(tab3 + "if (delimitReservedWords) tableName = DelimitReservedWord(tableName);"); file.WriteLine(tab3 + "return tableName;"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Table for Column method. file.WriteLine(tab2 + "// Identify the table that corresponds with the specified column."); file.WriteLine(tab2 + "public static TableIdx GetTableForColumn (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return TableIdx.Unknown;"); file.WriteLine(tab3 + "return columns[columnIdx].TableIdx;"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Column methods. file.WriteLine(tab2 + "// Retrieve the specified Column's name (Qualified include table name: '(.Column>)')."); file.WriteLine(tab2 + "public static String GetColumnName (ColumnIdx column, bool delimitReservedWords = true)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return String.Empty;"); file.WriteLine(tab3 + "String columnName = columns[columnIdx].Name;"); file.WriteLine(tab3 + "if (delimitReservedWords) columnName = DelimitReservedWord(columnName);"); file.WriteLine(tab3 + "return columnName;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static String GetColumnNameQual (ColumnIdx column, String tableAlias = \"\", bool braces = false)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "String columnName = GetColumnName(column);"); file.WriteLine(tab3 + "if (!String.IsNullOrEmpty(columnName))"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "String tableName = StringUtil.EmptyOnNull(tableAlias);"); file.WriteLine(tab4 + "if (tableName.IsEmpty()) tableName = GetTableName(GetTableForColumn(column));"); file.WriteLine(tab4 + "columnName = tableName + (tableName.Length > 0 ? \".\" : \"\") + columnName;"); file.WriteLine(tab4 + "if (braces)"); file.WriteLine(tab5 + "columnName = \"(\" + columnName + \")\";"); file.WriteLine(tab3 + "}"); file.WriteLine(tab3 + "return columnName;"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Reserved word methods. file.WriteLine(tab2 + "// Reserved words."); file.WriteLine(tab2 + "private static DbReservedWordDelim reservedWordDelim = DbReservedWordDelim.SquareBrackets;"); file.WriteLine(tab2 + "public static void SetReservedWordDelim (DbReservedWordDelim reservedWordDelim)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + classnameDefinition + ".reservedWordDelim = reservedWordDelim;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static bool IsReservedWord (String word)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "if (String.IsNullOrEmpty(word)) return false;"); file.WriteLine(tab3 + "if (reservedWords.Contains(word, StringComparer.OrdinalIgnoreCase)) return true;"); file.WriteLine(tab3 + "return false;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static String DelimitReservedWord (String word)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "if (!IsReservedWord(word)) return word;"); file.WriteLine(tab3 + "switch (reservedWordDelim)"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "case DbReservedWordDelim.None:"); file.WriteLine(tab5 + "break;"); file.WriteLine(tab4 + "case DbReservedWordDelim.DoubleQuotes:"); file.WriteLine(tab5 + "word = \"\\\"\" + word + \"\\\"\"; "); file.WriteLine(tab5 + "break;"); file.WriteLine(tab4 + "case DbReservedWordDelim.SquareBrackets:"); file.WriteLine(tab4 + "default:"); file.WriteLine(tab5 + "word = \"[\" + word + \"]\"; "); file.WriteLine(tab5 + "break;"); file.WriteLine(tab3 + "}"); file.WriteLine(tab3 + "return word;"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Column information methods. file.WriteLine(tab2 + "// Retrieve information about the specified Column."); file.WriteLine(tab2 + "public static DbColumnDataType GetColDataType (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return DbColumnDataType.Unknown;"); file.WriteLine(tab3 + "return columns[columnIdx].DataType;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static short GetColNativeDataType (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return 0;"); file.WriteLine(tab3 + "return columns[columnIdx].NativeDataType;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static void SetMaxTextLen (ulong maxTextLen)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + classnameDefinition + ".maxTextLen = maxTextLen;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static bool IsColFmtText (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return false;"); file.WriteLine(tab3 + "return (columns[columnIdx].DataType == DbColumnDataType.VarChar);"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static ulong GetColTextLen (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return 0;"); file.WriteLine(tab3 + "if (columns[columnIdx].DataType == DbColumnDataType.VarChar)"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "ulong textLen = columns[columnIdx].DataLen;"); file.WriteLine(tab4 + "if (maxTextLen > 0 && textLen > maxTextLen) textLen = maxTextLen;"); file.WriteLine(tab4 + "return textLen;"); file.WriteLine(tab3 + "}"); file.WriteLine(tab3 + "return 0;"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static bool IsColFmtBool (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return false;"); file.WriteLine(tab3 + "return (columns[columnIdx].DataType == DbColumnDataType.Bit);"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public static bool IsColFmtNumeric (ColumnIdx column)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "int columnIdx = (int)column - 1;"); file.WriteLine(tab3 + "if (columnIdx < 0 || columnIdx >= columnCount) return false;"); file.WriteLine(tab3 + "switch (columns[columnIdx].DataType)"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "case DbColumnDataType.TinyInt:"); file.WriteLine(tab4 + "case DbColumnDataType.SmallInt:"); file.WriteLine(tab4 + "case DbColumnDataType.Int:"); file.WriteLine(tab4 + "case DbColumnDataType.BigInt:"); file.WriteLine(tab4 + "case DbColumnDataType.Single:"); file.WriteLine(tab4 + "case DbColumnDataType.Double:"); file.WriteLine(tab4 + "case DbColumnDataType.Decimal:"); file.WriteLine(tab5 + "return true;"); file.WriteLine(tab3 + "}"); file.WriteLine(tab3 + "return false;"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Table definitions. file.WriteLine(tab2 + "// Table definitions."); file.WriteLine(tab2 + "private static readonly String[] tables ="); file.WriteLine(tab2 + "{"); foreach (DbDfnTable tableDfn in tableDfns) file.WriteLine(tab3 + "\"" + tableDfn.Name + "\", "); file.WriteLine(tab2 + "};"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Table Column definitions. file.WriteLine(tab2 + "// Table column definitions."); file.WriteLine(tab2 + "private class ColumnDfn"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "internal TableIdx TableIdx { get; set; }"); file.WriteLine(tab3 + "internal String Name { get; set; }"); file.WriteLine(tab3 + "internal DbColumnDataType DataType { get; set; }"); file.WriteLine(tab3 + "internal short NativeDataType { get; set; }"); file.WriteLine(tab3 + "internal bool IsIdentity { get; set; }"); file.WriteLine(tab3 + "internal ulong DataLen { get; set; }"); file.WriteLine(tab3 + "internal ColumnDfn (int tableIdx, String name, int dataType, short nativeDataType, bool isIdentity, ulong dataLen)"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "this.TableIdx = (TableIdx)tableIdx;"); file.WriteLine(tab4 + "this.Name = name;"); file.WriteLine(tab4 + "this.DataType = (DbColumnDataType)dataType;"); file.WriteLine(tab4 + "this.NativeDataType = nativeDataType;"); file.WriteLine(tab4 + "this.IsIdentity = isIdentity;"); file.WriteLine(tab4 + "this.DataLen = dataLen;"); file.WriteLine(tab3 + "}"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "private static readonly ColumnDfn[] columns ="); file.WriteLine(tab2 + "{"); uint tableIdx = 0; foreach (DbDfnTable tableDfn in tableDfns) { tableIdx++; foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab3 + "new ColumnDfn(" + tableIdx.ToString() + ",\"" + columnDfn.Name + "\"," + ((int)columnDfn.DataType).ToString() + "," + columnDfn.DataTypeNative.ToString() + "," + (columnDfn.IsIdentity ? "true" : "false") + "," + columnDfn.Size.ToString() + "),"); } file.WriteLine(tab2 + "};"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Reserved words. file.WriteLine(tab2 + "// Reserved words."); file.WriteLine(tab2 + "private static readonly List reservedWords = new List()"); file.WriteLine(tab2 + "{"); foreach (String reservedword in reservedWords) file.WriteLine(tab3 + "\"" + reservedword + "\", "); file.WriteLine(tab2 + "};"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); } //------------------------------------------------------------------- // T a b l e C l a s s //------------------------------------------------------------------- private void GenerateTableInterfaceClass(DbDfnTable tableDfn) { String className = tableDfn.Name + "_Db"; List primaryColumnNames = tableDfn.GetPrimaryColumnNames(); String identityColumnName = tableDfn.Columns.GetIdentityColumnName(); String identityColumnVariableTypeName = tableDfn.Columns.GetIdentityColumnVariableTypeName(); String identityColumnCastFromInt64 = (!identityColumnVariableTypeName.EqualsCI("Int64") && !identityColumnVariableTypeName.EqualsCI("long") ? "(" + identityColumnVariableTypeName + ")" : ""); // Class 'using's. List usingNamespaces = new List(config.TableClass.Usings); usingNamespaces.AddRange(new List() { config.TableClass.Namespace_BaseClass, "System", "System.Collections.Generic", "System.Data.Common", config.DotNetVersion == DotNetVersion.Framework? "System.Data.Odbc" : "", config.DotNetVersion == DotNetVersion.Core3 ? "Microsoft.Data.SqlClient" : "System.Data.SqlClient", "System.Linq" }); usingNamespaces.RemoveBlanks(); usingNamespaces.RemoveDuplicatesCI(); usingNamespaces.Sort(); // Alternative table names. List alternativeTableNames = new List(); if (config.Schema.AlternativeTableNames.ContainsKey(tableDfn.Name)) alternativeTableNames.AddRange(config.Schema.AlternativeTableNames[tableDfn.Name]); // List of columns to be ignored on update. List columnsIgnoredOnUpdate = new List(); foreach (StringPair insertOnlyColumns in config.Schema.InsertOnlyColumns.FindAll(x => Regex.IsMatch(tableDfn.Name, x.Item1, RegexOptions.IgnoreCase))) columnsIgnoredOnUpdate.AddRange(insertOnlyColumns.Item2.Split(',')); for (int i = 0; i < columnsIgnoredOnUpdate.Count; i++) columnsIgnoredOnUpdate[i] = columnsIgnoredOnUpdate[i].Trim(' '); String filePath = config.OutputDirectory + Path.DirectorySeparatorChar + className + ".cs"; using (StreamWriter file = new StreamWriter(filePath)) { // Database definition file. foreach (String usingNamespace in usingNamespaces) file.WriteLine("using " + usingNamespace + ";"); file.WriteLine(""); file.WriteLine("//---------------------------------------------------------------------------"); file.WriteLine("// Code generated by Sarand Database Utilities " + DateTime.Now.Format("ddd d-MMM-yyyy HH:mm:ss")); file.WriteLine("//---------------------------------------------------------------------------"); file.WriteLine("namespace " + config.TableClass.Namespace); file.WriteLine("{"); file.WriteLine(tab1 + "internal class " + className + " : " + config.TableClass.BaseClass); file.WriteLine(tab1 + "{"); file.WriteLine(tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Column definitions."); file.WriteLine(tab2 + "public class ColumnsData"); file.WriteLine(tab2 + "{"); foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab3 + "public ColumnData" + GetColumnVariableTypeName(columnDfn) + " _" + columnDfn.Name + " { get; } = new ColumnData" + GetColumnVariableTypeName(columnDfn) + "(\"" + columnDfn.Name + "\"" + (columnDfn.IsIdentity ? ", true" : "") + ");"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Data members."); file.WriteLine(tab2 + "protected static List fields { get; } = new List()"); file.WriteLine(tab2 + "{"); foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab3 + "\"" + columnDfn.Name + "\","); file.WriteLine(tab2 + "};"); file.WriteLine(tab2 + "protected static List fieldsId { get; } = new List()"); file.WriteLine(tab2 + "{"); foreach (var columnDfn in tableDfn.Columns.FindAll(item => item.IsIdentity)) file.WriteLine(tab3 + "\"" + columnDfn.Name + "\","); file.WriteLine(tab2 + "};"); file.WriteLine(tab2 + "protected static List fieldsPrimary { get; } = new List()"); file.WriteLine(tab2 + "{"); foreach (String columnName in primaryColumnNames) file.WriteLine(tab3 + "\"" + columnName + "\","); file.WriteLine(tab2 + "};"); file.WriteLine(tab2 + "protected static List fieldsIgnoreOnUpdate { get; } = new List()"); file.WriteLine(tab2 + "{"); foreach (String columnName in columnsIgnoredOnUpdate) file.WriteLine(tab3 + "\"" + columnName + "\","); file.WriteLine(tab2 + "};"); /* file.WriteLine(tab2 + "protected static List fieldsNonId { get; } = new List()"); file.WriteLine(tab2 + "{"); foreach (var columnDfn in tableDfn.Columns.FindAll(item => !item.IsIdentity)) file.WriteLine(tab3 + "\"" + columnDfn.Name + "\","); file.WriteLine(tab2 + "};"); */ //----------------------------------------------------------- // Construction. file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Construction."); file.WriteLine(tab2 + "internal " + className + " (" + config.TableClass.ClassName_DatabaseInfo + " dbInfo" + (!config.TableClass.MandatoryConstructorDbInfo ? " = null" : "") + ") : base(dbInfo)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab2 + "}"); //----------------------------------------------------------- // Properties. file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Properties."); file.WriteLine(tab2 + "public override String SchemaName { get; } = \"" + tableDfn.Schema + "\";"); if (alternativeTableNames.Count == 0) file.WriteLine(tab2 + "public override String TableName { get; } = \"" + tableDfn.Name + "\";"); else { // Logic to handle alternative table names. file.WriteLine(tab2 + "public override String TableName { get { return GetActualTableName(\"" + tableDfn.Name + "\"); } }"); file.WriteLine(tab2 + "public override List AlternativeTableNames { get; } = new List() { " + alternativeTableNames.Concat(", ", "\"", "\"") + " };"); } file.WriteLine(tab2 + "public String TableNameFmt { get { return (!String.IsNullOrEmpty(SchemaName) ? \"[\" + SchemaName + \"].\" : \"\") + \"[\" + TableName + \"]\"; } }"); file.WriteLine(tab2 + "public static List Fields { get { return new List(fields); } }"); file.WriteLine(tab2 + "public static List FieldsId { get { return new List(fieldsId); } }"); file.WriteLine(tab2 + "public static List FieldsNonId { get { return fields.FindAll(item => !fieldsId.ContainsCI(item)); } }"); file.WriteLine(tab2 + "// Columns."); file.WriteLine(tab2 + "public ColumnsData Cols { get; } = new ColumnsData();"); foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab2 + "public " + GetColumnVariableType(columnDfn) + " _" + columnDfn.Name + " { get { return Cols._" + columnDfn.Name + ".Value; } set { Cols._" + columnDfn.Name + ".Value = value; } }"); file.WriteLine(tab2 + "// Existing columns - confirmed on database if table exists."); file.WriteLine(tab2 + "public List ExistingColumnNames { get { return GetExistingColumnNames(Fields); } }"); file.WriteLine(tab2 + "public List ExistingColumnNamesNonId { get { return GetExistingColumnNames(FieldsNonId); } }"); //----------------------------------------------------------- // Population. file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Population."); file.WriteLine(tab2 + "public override void PopulateFrom (DbCommand dbCommand, String tableAlias, String filter)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "dbCommand.CommandText ="); file.WriteLine(tab4 + "\"SELECT \" + ExistingColumnNames.Concat(\", \", (!String.IsNullOrEmpty(tableAlias) ? tableAlias + \".\" : \"\") + \"[\", \"]\") + \" \" +"); file.WriteLine(tab4 + "\"FROM \" + TableNameFmt + (!String.IsNullOrEmpty(tableAlias) ? \" \" + tableAlias : \"\") + \" \" + "); file.WriteLine(tab4 + "(!String.IsNullOrEmpty(filter) ? \"WHERE \" + filter : \"\");"); file.WriteLine(tab3 + "Log(GetType().Name + \".PopulateFrom\", dbCommand.CommandText);"); file.WriteLine(tab3 + "using (DbDataReader dbDataReader = dbCommand.ExecuteReader())"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "dbDataReader.Read();"); file.WriteLine(tab4 + "PopulateFrom(dbDataReader);"); file.WriteLine(tab3 + "}"); file.WriteLine(tab2 + "}"); file.WriteLine(tab2 + "public override void PopulateFrom (DbDataReader dbDataReader)"); file.WriteLine(tab2 + "{"); foreach (var columnDfn in tableDfn.Columns) file.WriteLine(tab3 + "_" + columnDfn.Name + " = GetField" + GetColumnGetFieldSuffix(columnDfn) + "(dbDataReader, \"" + columnDfn.Name + "\", _" + columnDfn.Name + ");"); file.WriteLine(tab2 + "}"); //----------------------------------------------------------- // Prepare Insert command. // SQL: // Parameters can be named. // Multiple queries allowed - can append 'SELECT Scope_Identity();' to insert query. // ODBC: // Parameters identified as '?' and matched by position in parameter list. // Single queries only. Use Max(Id) to retrieve identity value. //----------------------------------------------------------- file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Insert command."); file.WriteLine(tab2 + "public override bool ExecuteInsertCommand (DbCommand dbCommand)"); file.WriteLine(tab2 + "{"); file.WriteLine(tab3 + "List columnNames = ExistingColumnNamesNonId;"); if (!String.IsNullOrEmpty(identityColumnName)) { file.WriteLine(tab3 + "// Avoid insert if Identity field is already valid."); file.WriteLine(tab3 + "if (_" + identityColumnName + " != 0) throw new Exception(\"Invalid for Insert - item already has a valid Id\"); "); } file.WriteLine(tab3 + "// Construct query."); file.WriteLine(tab3 + "String query ="); file.WriteLine(tab4 + "\"INSERT INTO \" + TableNameFmt + \" \" +"); file.WriteLine(tab4 + "\"(\"+columnNames.Concat(\",\",\"[\",\"]\")+\") \";"); if (!String.IsNullOrEmpty(identityColumnName)) file.WriteLine(tab3 + "query += \"Output Inserted." + identityColumnName + " \";"); List columnsValuesToInsertSql = new List(); foreach (var columnDfn in tableDfn.Columns) { if (!columnDfn.IsIdentity) columnsValuesToInsertSql.Add("@" + columnDfn.Name); } file.WriteLine(tab3 + "if (dbCommand is SqlCommand)"); file.WriteLine(tab4 + "query += \"VALUES (\" + columnNames.Concat(\",\", \"@\", \"\") + \") \";"); file.WriteLine(tab3 + "else query += \"VALUES (\" + " + (config.SarandCommonVersion == SarandCommonVersion.VS2019 ? "StringListUtil" : "StringList") + ".CreateList(\"?\", columnNames.Count).Concat(\",\") + \") \";"); if (!String.IsNullOrEmpty(identityColumnName) && config.DbmsOutput == Dbms.SQL) file.WriteLine(tab3 + "query += SELECT Scope_Identity();"); // NB @@Identity for Access. file.WriteLine(tab3 + "dbCommand.CommandText = query;"); file.WriteLine(tab3 + "Log(GetType().Name + \".ExecuteInsertCommand\", dbCommand.CommandText);"); file.WriteLine(tab3 + "// Populate parameters."); file.WriteLine(tab3 + "dbCommand.Parameters.Clear();"); foreach (var columnDfn in tableDfn.Columns) { if (!columnDfn.IsIdentity) file.WriteLine(tab3 + "if (columnNames.ContainsCI(\"" + columnDfn.Name + "\")) AddCommandParameter(dbCommand, \"@" + columnDfn.Name + "\", _" + columnDfn.Name + ");"); } file.WriteLine(tab3 + "// Execute insert command."); file.WriteLine(tab3 + "bool commandSuccess = false;"); if (config.DbmsOutput == Dbms.SQL) { if (!String.IsNullOrEmpty(identityColumnName)) { // Execute single command to insert and query 'Scope_Identity' to retrieve Identity value. file.WriteLine(tab3 + "_" + identityColumnName + " = " + identityColumnCastFromInt64 + "Convert.ToInt64(dbCommand.ExecuteScalar());"); file.WriteLine(tab3 + "commandSuccess = (_" + identityColumnName + " > 0);"); } else { // Execute query and retrieve number of records (may return -1). file.WriteLine(tab3 + "int executeNonQuery = dbCommand.ExecuteNonQuery();"); file.WriteLine(tab3 + "commandSuccess = true;"); } } else { // Execute query. file.WriteLine(tab3 + "int executeNonQuery = dbCommand.ExecuteNonQuery();"); file.WriteLine(tab3 + "if (executeNonQuery == 1)"); // No of records inserted? Returns -1? file.WriteLine(tab3 + "{"); if (!String.IsNullOrEmpty(identityColumnName)) { // Retrieve Identity value. file.WriteLine(tab4 + "dbCommand.CommandText = \"SELECT Max(" + identityColumnName + ") FROM \" + TableNameFmt + \";\";"); file.WriteLine(tab4 + "_" + identityColumnName + " = " + identityColumnCastFromInt64 + "Convert.ToInt64(dbCommand.ExecuteScalar());"); file.WriteLine(tab4 + "commandSuccess = (_" + identityColumnName + " > 0);"); } else { file.WriteLine(tab4 + "commandSuccess = true;"); } file.WriteLine(tab3 + "}"); } file.WriteLine(tab3 + "return commandSuccess;"); file.WriteLine(tab2 + "}"); //----------------------------------------------------------- // Prepare Update command. // Filter: Primary index values. // Update: Non-primary index values. // SQL: // Parameters can be named. // ODBC: // Parameters identified as '?' and matched by position in parameter list. // Single queries only. Use Max(Id) to retrieve identity value. //----------------------------------------------------------- // Construct update command. file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); file.WriteLine(tab2 + "// Update command."); file.WriteLine(tab2 + "public override bool ExecuteUpdateCommand (DbCommand dbCommand)"); file.WriteLine(tab2 + "{"); if (!String.IsNullOrEmpty(identityColumnName)) { file.WriteLine(tab3 + "// Avoid update if Identity field isn't valid."); file.WriteLine(tab3 + "if (_" + identityColumnName + " == 0) throw new Exception(\"Invalid for Update - item has an invalid Id\"); "); } file.WriteLine(tab3 + "// Columns to update."); file.WriteLine(tab3 + "List columnNames = ExistingColumnNamesNonId.Where(item => !fieldsPrimary.ContainsCI(item) && !fieldsIgnoreOnUpdate.ContainsCI(item)).ToList();"); file.WriteLine(tab3 + "// Construct query."); file.WriteLine(tab3 + "String query = \"UPDATE \" + TableNameFmt + \" SET \";"); List columnsValuesToUpdate = new List(); List columnsValuesToUpdateSql = new List(); foreach (var columnDfn in tableDfn.Columns) { if (primaryColumnNames.Any(col => col.EqualsCI(columnDfn.Name))) continue; if (columnsIgnoredOnUpdate.Any(col => col.EqualsCI(columnDfn.Name))) continue; // Delimit reserved column names. String columnNameSql = columnDfn.Name; if (reservedWords.ToList().ContainsCI(columnNameSql)) columnNameSql = "[" + columnNameSql + "]"; columnsValuesToUpdate.Add(columnDfn.Name + "=?"); columnsValuesToUpdateSql.Add(columnNameSql + "=@" + columnDfn.Name); } //----------------------------------------------------------- // SQL Update. file.WriteLine(tab3 + "if (dbCommand is SqlCommand)"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "query += StringPairList.CreateFrom(columnNames.Bracket(\"[\",\"]\"),columnNames).Concat(\"=@\").Concat(\", \") + \" \";"); List whereFilterSql = new List(); foreach (var primaryColumnName in primaryColumnNames) whereFilterSql.Add(primaryColumnName + "=@" + primaryColumnName); file.WriteLine(tab4 + "query += \"WHERE " + whereFilterSql.Concat(" AND ") + ";\";"); file.WriteLine(tab3 + "}"); //----------------------------------------------------------- // Non-SQL Update. file.WriteLine(tab3 + "else"); file.WriteLine(tab3 + "{"); file.WriteLine(tab4 + "query += columnNames.Concat(\", \", \"\", \"=?\") + \" \";"); List whereFilterNonSql = new List(); foreach (var primaryColumnName in primaryColumnNames) whereFilterNonSql.Add(primaryColumnName + "=?"); file.WriteLine(tab4 + "query += \"WHERE " + whereFilterNonSql.Concat(" AND ") + ";\";"); file.WriteLine(tab3 + "}"); //----------------------------------------------------------- // Populate parameters and execute update command. file.WriteLine(tab3 + "dbCommand.CommandText = query;"); file.WriteLine(tab3 + "Log(GetType().Name + \".ExecuteUpdateCommand\", dbCommand.CommandText);"); file.WriteLine(tab3 + "// Populate parameters."); file.WriteLine(tab3 + "dbCommand.Parameters.Clear();"); foreach (var columnDfn in tableDfn.Columns.FindAll(item => !primaryColumnNames.Contains(item.Name) && !columnsIgnoredOnUpdate.Contains(item.Name))) file.WriteLine(tab3 + "if (columnNames.ContainsCI(\"" + columnDfn.Name + "\")) AddCommandParameter(dbCommand, \"@" + columnDfn.Name + "\", _" + columnDfn.Name + ");"); foreach (var primaryColumnName in primaryColumnNames) file.WriteLine(tab3 + "AddCommandParameter(dbCommand, \"@" + primaryColumnName + "\", _" + primaryColumnName + ");"); file.WriteLine(tab3 + "int executeNonQueryResult = dbCommand.ExecuteNonQuery();"); file.WriteLine(tab3 + "return (executeNonQueryResult == 1);"); file.WriteLine(tab2 + "}"); file.WriteLine(crlf + tab2 + "//-------------------------------------------------------------------"); // Close class. file.WriteLine(tab1 + "}"); file.WriteLine("}"); //file.Close(); - Unecessary as the object will be disposed at the end of the 'using' block. } } //------------------------------------------------------------------- } //======================================================================= }