Here’s an experiment I wrote over the weekend to create an Insert SP for a table using SQL Server System Tables.
This code generates code for a specific code pattern and won’t necessarily work for any table that is thrown at it. Supporting every possible situation can make the code really complex. If I am faced with a table that this can’t handle, I am face with the choice of expanding the program to handle the new situation or write the code by hand.
These SPs are the same except for:
- The SP name – Based on Table Name
- The Table Name
- The Fields
- Which Field is the Primary Key
- Which Fields are Nullable
From the System Tables I use:
- sysObjects.name: the name of the table
- sysColumns.name: the column name
- sysTypes.name: the type name
- sysColumns.length: the length of the field
- sysColumns.isnullable: is the field nullable?
- sysColumns.status: used to determine if this is the primary key/auto-number column (look for 0x80)
The Code
using System; using System.Data.SqlClient; using System.Text; namespace GenerateSP { class Program { /// <summary> /// The Connection String /// </summary> private const string CONNECTION_STRING = @"Data Source=localhost\SQLEXPRESS;" + "Initial Catalog=Northwind;Integrated Security=True"; /// <summary> /// Number of list items per line in the generated procedure /// </summary> private const int ITEMS_PER_LINE = 5; /// <summary> /// Shows the Code Generation in action /// </summary> static void Main(string[] args) { Console.WriteLine(CreateInsertSp("Employees")); Console.ReadKey(); } /// <summary> /// Creates an INSERT Stored Procedure for a given table as a string /// </summary> public static string CreateInsertSp(string tableName) { string identityField = string.Empty; // There are 3 places in the SP that refer to the field names // so I am using 3 string builders to generate those parts // of the SP; StringBuilder parametersSb = new StringBuilder(); StringBuilder InsertValueListSb = new StringBuilder(); StringBuilder InsertFieldListSb = new StringBuilder(); using (SqlConnection conn = new SqlConnection(CONNECTION_STRING)) { int lineNumber = 0; // Used to SqlDataReader reader = GetTableDefReader(tableName, conn); while (reader.Read()) { string colName = reader.GetString(0); string typeName = reader.GetString(1); int length = reader.GetInt32(2); int isNullable = reader.GetInt32(3); int status = reader.GetInt32(4); AddToParameterSb(parametersSb, colName, typeName, length, isNullable, status); if (status == 0x80) identityField = colName; else { lineNumber++; AddInsertFieldListToSb(InsertFieldListSb, colName, lineNumber); AddInsertValueListToSb(InsertValueListSb, colName, lineNumber); } } } // Put everything together return string.Format("\nCREATE PROCEDURE [dbo].[Insert{0}]\n", tableName) + parametersSb.ToString() + string.Format("\nAS\n\nINSERT INTO [{0}] (", tableName) + InsertFieldListSb.ToString() + string.Format(")\nVALUES (") + InsertValueListSb.ToString() + string.Format(")\n\nSET @{0} = @@IDENTITY\n\nGO\n", identityField); } /// <summary> /// Gets a DataReader containing selected column information for all of the /// columns in the indecated table from SQL Server System Tables /// </summary> private static SqlDataReader GetTableDefReader(string tableName, SqlConnection conn) { SqlDataReader reader; string getDataForTable = "SELECT c.name AS col_name, " + " t.name AS type_name, " + " CAST(c.length AS INT) AS length, " + " c.isnullable, " + " CAST(c.status AS INT) AS status " + "FROM " + " sysObjects o " + " JOIN sysColumns c ON c.id = o.id " + " JOIN systypes t on c.xtype = t.xtype AND t.status = 0 " + "WHERE o.name = @tableName " + "ORDER BY c.colid "; using (SqlCommand cmd = new SqlCommand(getDataForTable, conn)) { cmd.Parameters.AddWithValue("@tableName", tableName); conn.Open(); reader = cmd.ExecuteReader(); } return reader; } /// <summary> /// Adds the current field to the /// </summary> private static void AddToParameterSb(StringBuilder sb, string colName, string typeName, int length, int isNullable, int status) { if (sb.Length > 0) sb.Append(",\n"); sb.AppendFormat("\t@{0} ", colName); switch (typeName.ToLower()) { case "varchar": case "nvarchar": string lenString = length.ToString(); if (length == -1) lenString = "MAX"; sb.AppendFormat("{0}({1})", typeName, lenString); break; default: sb.Append(typeName); break; } if (status == 0x80) sb.Append(" OUT"); else if (isNullable != 0) sb.Append(" = NULL"); } /// <summary> /// Adds the current field to the VALUES list for an INSERT query /// </summary> private static void AddInsertValueListToSb(StringBuilder sb, string colName, int lineNumber) { AddListSeperatorToSb(sb, lineNumber); sb.AppendFormat("@{0}", colName); } /// <summary> /// Adds the current field to the Field list for an INSERT query /// </summary> private static void AddInsertFieldListToSb(StringBuilder sb, string colName, int lineNumber) { AddListSeperatorToSb(sb, lineNumber); sb.AppendFormat("[{0}]", colName); } /// <summary> /// Adds the comma and or new line to the list StringBuilder /// </summary> /// <remarks> /// Shouldn't have a comma before the first item in the list.<br /> /// Should only insert new ine after each ITEMS_PER_LINE items /// </remarks> private static void AddListSeperatorToSb(StringBuilder sb, int lineNumber) { if (sb.Length > 0) sb.Append(", "); if ((lineNumber % ITEMS_PER_LINE) == 0) sb.Append("\n\t"); } } }
I adapted this code for T4 Templates here
No comments:
Post a Comment