A White Paper by Don Tebbet
A White Paper by Don Tebbet
627 Ridgeview Rd.
Brightwood, Virginia 22715
(540)543-2109 http://www.tebbet.com
Date
Contents
Introduction
This paper describes a code generator, CSWinCodeGen , for C#.NET Windows Applications. (The source for CSWinCodeGen is available for download here .)
Problem Statement
Clients often wish to see, as soon as possible in the software development life cycle (SDLC), some evidence that their hard-spent consulting budget is returning some tangible benefit. In order to satisfy their demand to ‘see some screens’ considerable effort may be spent prematurely in developing foundation components of a running system which, as analysis and design progress, must be continuously changed to accommodate new discoveries in the requirements gathering process.
Previous Options
Devoting development resources to construction of incomplete design has been a typical method of keeping the customer happy with some visible evidence that progress is indeed being made. Unfortunately, this immediately front-end loads the development process with much tedious work which may become almost as immediately obsolete.
Alternatively, code generation tools based upon logical design may be employed to create foundation components of systems. In my experience, these tools lack the practical visibility of presenting actual data that is meaningful to the client.
A Solution
CSWinCodeGen is a code generation tool which builds class components for a system from an existing database schema. The benefits of the tool include…
Automated generation of control classes
Automated generation of the data access layer
Automated generation of CRUD forms
Automated generated of grid forms
Automated generation of control classes
Based upon the tables present in a given database, CSWinCodeGen produces .CS files for classes for each table to represent the control class for the object represented by the table.
Automated generation of data access layer (DAL)
Based upon the tables present in a given database, CSWinCodeGen produces .CS files for classes for each table to represent the entity or data access class for the object represented by the table.
Automated generation of CRUD forms
Based upon the tables present in a given database, CSWinCodeGen produces forms classes supporting the basic features of change, report, update and delete (CRUD) for each table.
Automated generation of grid forms
Based upon the tables present in a given database, CSWinCodeGen produces forms classes providing data grid views of each table.
Implementation
CSWinCodeGen Main Form
In the main form CSWinCodeGen above I have provided a name of the SQL Server where the database, ERP, resides. I have provided a namespace, ERPWin, to be used in code generation. Note that leaving the optional ‘Table’ textbox empty implies that code will be generated for all tables present in the ERP database. The checkboxes indicate which code components are to be generated. The ‘Target
Folder’ textbox provides the path to which generated files will be written.
The following sections examine what happens when the ‘Generate’ button is clicked.
Generating app.config
Note that one of the members of formMain is the following StringBuilder to hold a database connection string.
/// <summary>
/// Database connection string
/// </summary> public StringBuilder sbConnStr;
Here’s the event handler for the click of the ‘Generate’ button. private void buttonOk_Click( object sender, System.EventArgs e)
{ this .textBoxEvents.Text = "";
GenCode(); textBoxEvents.Text += "\r\nDone\r\n";
}
In the GenCode() method note the call to BuildConnectionString().
public void GenCode()
{ this .Cursor = Cursors.WaitCursor; alCurrentProperties.Clear();
CopyBaseFiles();
BuildConnectionString();
.
.
.
And BuildConnectionString() lookslike this.
/// <summary>
/// Builds the database connection string
/// </summary> public void BuildConnectionString()
{ sbConnStr = new StringBuilder(); sbConnStr.Append("Data Source="); sbConnStr.Append(textBoxSQLServer.Text); sbConnStr.Append(";"); sbConnStr.Append("Initial Catalog="); sbConnStr.Append(textBoxDatabase.Text); sbConnStr.Append(";"); sbConnStr.Append("Integrated Security=SSPI;Persist Security Info=False;");
}
This default build of the connection string assumes integrated authentication is sufficient. Of course, this can be modified here or in the resultant app.config.
The CodeGen() method (above), may subsequently call the following WriteAppConfig().
}
/// <summary>
/// Create an app.config for the target code base with a key for the SQL connection string
/// </summary>
/// <param name="connStr"></param> private void WriteAppConfig()
{
FileStream fsAppConfig = System.IO.File.Create(textBoxTargetFolder.Text + "\\" + "app.config");
StreamWriter swAppConfig = new StreamWriter(fsAppConfig); swAppConfig.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); swAppConfig.WriteLine("<configuration>"); swAppConfig.WriteLine("\t<appSettings>"); swAppConfig.WriteLine("\t\t<add key=\"SqlConnectStr\" value=\"" + sbConnStr.ToString() + "\"/>"); swAppConfig.WriteLine("\t</appSettings>"); swAppConfig.WriteLine("</configuration>"); swAppConfig.Close(); fsAppConfig.Close();
The WriteAppConfig() code above creates an app.config file as follows.
<?
xml version ="1.0" encoding ="utf-8" ?>
< configuration >
< appSettings >
< add key ="SqlConnectStr" value ="Data Source=DRTTOSHIBA2;Initial Catalog=ERP;Integrated Security=SSPI;Persist Security
Info=Fal_u101 ?;"/>
</ appSettings >
</ configuration >
Identifying Classes and Members to Generate in the CodeGen() Method
Continuing our examination of the CodeGen() method we see the creation of a database connection and a query to get table and column names.
.
.
.
// Create a database connection
System.Data.SqlClient.SqlConnection conn = new SqlConnection(sbConnStr.ToString()); conn.Open();
// Create SQL Cmd to query for the tables and columns in the database
StringBuilder sbSqlCmd = new StringBuilder(); sbSqlCmd.Append("SELECT * FROM INFORMATION_SCHEMA.TABLES JOIN INFORMATION_SCHEMA.COLUMNS ON
INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = INFORMATION_SCHEMA.TABLES.TABLE_NAME WHERE INFORMATION_SCHEMA.TABLES.TABLE_NAME <>
'sysdiagrams'");
// If a specific table is present, qualify the query if (textBoxTable.Text.Length > 0)
{ sbSqlCmd.Append("AND INFORMATION_SCHEMA.TABLES.TABLE_NAME='"); sbSqlCmd.Append(textBoxTable.Text); sbSqlCmd.Append("'");
}
// Execute SQL Cmd to read the table(s) and columns
System.Data.SqlClient.SqlCommand sqlCmd = new SqlCommand(sbSqlCmd.ToString(), conn); sqlCmd.CommandType = CommandType.Text;
.
.
.
We can now read the results of the query and load our own Array List of properties for classes, alProperties…
.
.
.
.
. using ( SqlDataReader reader = sqlCmd.ExecuteReader ( ) )
{ textBoxEvents.Text += "Parsing tables & columns\r\n\r\n";
// Iterate through the tables and columns - load our arraylist with what we'll need while ( reader.Read ( ) )
{ structProperty property = new structProperty();
} property.TableName = reader.GetString(2); property.Name = reader.GetString(7); property.SqlDataType = reader.GetString(11); property.CsDataType = SqlDataType2CSharp(property.SqlDataType); property.ControlType = MapCol2ControlType(property.Name, property.CsDataType); property.ControlName = property.ControlType.ToLower() + property.Name; alProperties.Add(property); textBoxEvents.Text += property.TableName + ", " + property.Name + ", " + property.SqlDataType + ", " + property.CsDataType + ", " + property.ControlType + ", " + property.ControlName + "\r\n";
.
… and then use alProperties to drive the creation of .CS files using a little bit of classic control break processing on a new array list of properties containing only members of the ‘current’ class.
.
.
.
}
// Now that we know the tables and columns, we can create some files for classes string currentTableName = ""; textBoxEvents.Text += "Beginning code generation\r\n\r\n"; foreach (structProperty p in alProperties)
{ if (p.TableName != currentTableName)
{ if (currentTableName.Length > 0)
{
}
// Control break on new class
GenCSFiles(); alCurrentProperties.Clear();
}
} currentTableName = p.TableName; alCurrentProperties.Add(p);
// Control break on new class
GenCSFiles();
At each control break in the for each loop above we have an array list, alCurrentProperties, loaded with only members of a single class and call the GenCSFiles() method to process that array list to create the selected source files.
{
/// <summary>
/// Generate .cs files
/// </summary> public void GenCSFiles() if (checkBoxGenCtrl.Checked)
GenCtrlCSFile(); if (checkBoxGenDAL.Checked)
GenDALCSFile(); if (checkBoxGenCRUD.Checked)
GenCRUDForm();
} if (checkBoxGenGridForm.Checked)
GenGridForm();
Generating the Control Classes
For each table in the database a control class is generated. The .CS file is named as the table prefixed with ‘ctrl’. Given a table in the database named ‘Location’
CSWinCodeGen will create a control class file named ‘ctrlLocation.cs’. Ideally, the generated control classes will be included in a solution project for an assembly representing the control objects through which other layers of the solution architecture will access the system objects.
The GenCtrlCSFile() method of CSWinCodeGen produces a control class for the Location table as follows.
Note the namespace inclusion of ERPWin.DAL
which provides access to the generated data access layer.
Within the Private Properties region we see the following code corresponding to the columns in the Location table.
.
.
{
.
.
. namespace ERPWin.Control
///<summary>
/// Location class associated with table ERP.Location
/// This code was generated by CSWinCodeGen
///</summary> public class Location
{
#region
///
///
///
Private Properties
<summary>
Private Properties for this class
</summary> private int _Id; private int _LocationTypeId; private string _Description;
#endregion
.
In the Public Properties region we see the public property methods corresponding to the private members.
.
.
.
.
.
.
}
/// The Id public Property of Location
///</summary> public int Id
{ get
{ return this ._Id;
} set
{
} this ._Id = value ;
}
///<summary>
/// The LocationTypeId public Property of Location
///</summary> public int LocationTypeId
{ get
{ return this ._LocationTypeId;
} set
{
} this ._LocationTypeId = value ;
Finally, we see the Read(), Write() and Delete() methods which work through the data access layer.
.
.
.
.
.
.
}
///<summary>
/// Read a record into the control class
///</summary>
/// <param name="Id"></param> public void Read( int Id)
{ dalLocation dal = new dalLocation(); dal.Read(Id); this .LocationTypeId = dal.LocationTypeId; this .Description = dal.Description;
.
.
.
.
.
.
}
///<summary>
/// Write the control class to the database
///</summary> public void Write()
{ dalLocation dal = new dalLocation(); dal.Id = this .Id; dal.LocationTypeId = this .LocationTypeId; dal.Description = this .Description; dal.Write();
.
.
.
.
.
.
{
}
///<summary>
/// Delete a record
///</summary>
/// <param name="Id"></param> public void Delete( int Id) dalLocation dal = dal.Delete(Id); new dalLocation();
Generating the Data Access Layer
For each table in the database a data access layer (DAL) class is generated. The .CS file is named as the table prefixed with ‘dal’.
Given a table in the database named ‘Location’ CSWinCodeGen will create a control class file named ‘dalLocation.cs’. Ideally, the generated control classes will be included in a solution project for an assembly representing the DAL through which other layers of the solution architecture will access the data in the database.
The GenDALCSFile() method of CSWinCodeGen produces a data access class for the Location table as follows.
Generating the CRUD Forms
For each table in the database a rudimentary Windows form class is generated. The .CS file is named as the table prefixed with ‘crud’.
Given a table in the database named ‘Location’ CSWinCodeGen will create a control class file named ‘crudLocation.cs’. Ideally, the generated control classes will be included in a solution project for a user interface to the solution.
The GenCRUDForm() method of CSWinCodeGen produces a Windows form class for the Location table. The form design appears as follows.
Generating Grid Forms
For each table in the database a Windows grid form class is generated. The .CS file is named as the table prefixed with ‘gridForm’.
Given a table in the database named ‘Location’ CSWinCodeGen will create a form class file named ‘gridFormLocation.cs’. Ideally, the generated control classes will be included in a solution project for a user interface to the solution.
The GenGridForm() method of CSWinCodeGen produces a Windows grid form class for the Location table. The form design appears as follows.
When executed within the ERPWin solution with code to present the form it appears as follows.
Including Generated Code in a Solution
In the solution shown below we see three projects in a solution called ‘ ERPWin
’ for which we have used
CSWinCodeGen to generate classes.
ERPControl
ERPDAL
ERPWin
The ERPControl project is a class library composed of the control classes generated by CSWinCodeGen . The ERPDAL project is a class library composed of the data access layer classes generated by CSWinCodeGen.
The ERPWin project is the Windows application project providing user interface to the solution including the CRUD and grid form classes generated by CSWinCodeGen .
The expanded solution view of the ERPControl project shows the inclusion of our generated control classes.
The expanded solution view of the ERPDAL project shows the inclusion of our generated DAL classes.
The expanded solution view below shows the solution view including the generated forms classes.
Summary
Using CSWinCodeGen we can quickly generate the basic code required for an n-tier solution for a given a database.