Thursday, August 21, 2008

CodeDom NHibernate Mapping File Builder

I am currently watching the 'Summer of NHibernate' videos and have found myself wanting a tool for generating the HBM Mapping file. This was a painful place to be looking at a white screen to hand-fill with XML, as if anybody wants to state again all of the columns and data types already typed into a class and a "Create Table" script.


I have code already in all my other tools for getting database schema into my "columns" typed dataset. I ran XSD.EXE over the HBM file which I built by hand while watching 'Summer of NHibernate' to get a Class for easily authoring the XML document. Populating this class and then Serializing it was a nice way to stay in C# for the whole operation.

So here is what I am starting out of the gate with.



public static string GetMappingFile(SqlConnection Connection, string TableName)
{
string fileName = TableName + "hbm.xml";
hibernatemapping newMapping = new hibernatemapping();
newMapping.@namespace = "Acme.DataLayer";
newMapping.@class = new hibernatemappingClass();

hibernatemappingClass newClass = newMapping.@class;
newClass.name = TableName;
newClass.table = TableName;

Columns cols = PopulateColumns(Connection, TableName, CommandType.TableDirect);
newClass.property = new hibernatemappingClassProperty[cols.Column.Count];

int index = 0;

foreach (Columns.ColumnRow cRow in cols.Column)
{
newClass.property[index] = new hibernatemappingClassProperty();
hibernatemappingClassProperty prop = newClass.property[index];
prop.column = cRow.Column_Name;
prop.name = cRow.Column_Name;
prop.type = cRow.Data_Type;

switch (cRow.Data_Type.ToUpper())
{
case "DECIMAL":
break;
case "INT":
prop.type = "Int32";
break;
case "SMALLINT":
prop.type = "Int32";
break;
case "BIT":
prop.type = "Int32";
break;
case "VARCHAR":
prop.type = "string";
break;
default:
prop.type = "string";
break;
}
index++;
}

System.Xml.Serialization.XmlSerializer xmlSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(hibernatemapping));

StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);

xmlSerializer.Serialize(sw, newMapping);

return sb.ToString();
}

Saturday, August 09, 2008

CodeDom LWDO Builder

Seeing alot of classes that are "Lightweight Data Objects", simple representations of table entries that can be manipulated locally and then sent to a database, or passed around in application code. So I built a small generator using the CodeDom Namespace. Not intended to compete with MyGenerate or any other tool, just wanted to have this quick one handy.
This is for SQL Server database tables. The main function below :


public static string GetClientClass(SqlConnection Connection, string TableName)
{
CodeTypeReference codeTypeRef;
CodeMemberField memberField;
CodeMemberProperty memberPropPublic;

Columns cols = PopulateColumns(Connection, TableName, CommandType.TableDirect);

CodeNamespace codeNS = new CodeNamespace("ACME.Data");

CodeTypeDeclaration ct = new CodeTypeDeclaration(TableName);
ct.IsClass = true;
codeNS.Types.Add(ct);

CodeExpression markDirty = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(),
"MarkDirty",
new CodeExpression[] { });

foreach (Columns.ColumnRow cRow in cols.Column)
{
switch (cRow.Data_Type.ToUpper())
{
case "DECIMAL":
codeTypeRef = new CodeTypeReference("System.Decimal");
break;
case "INT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "SMALLINT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "BIT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "VARCHAR":
codeTypeRef = new CodeTypeReference("System.String");
break;
default:
codeTypeRef = new CodeTypeReference("System.String");
break;
}

memberField = new CodeMemberField(
codeTypeRef,
"_" + cRow.Column_Name.ToLower());

memberField.Attributes = MemberAttributes.Private;
ct.Members.Add(memberField);
memberPropPublic = new CodeMemberProperty();
memberPropPublic.Name = MakeFieldName(cRow.Column_Name);
memberPropPublic.Attributes = MemberAttributes.Public;
memberPropPublic.Type = codeTypeRef;
ct.Members.Add(memberPropPublic);

// Get Statement
CodeVariableReferenceExpression cvrexp =
new CodeVariableReferenceExpression("_" + cRow.Column_Name.ToLower());
CodeMethodReturnStatement cmrstmnt = new CodeMethodReturnStatement(cvrexp);
memberPropPublic.GetStatements.Add(cmrstmnt);

// Set Statement
memberPropPublic.SetStatements.Add(
new CodeAssignStatement(cvrexp,
new CodePropertySetValueReferenceExpression()));

memberPropPublic.SetStatements.Add(markDirty);
}

CSharpCodeProvider cSharpProvider = new CSharpCodeProvider();
StringBuilder sb = new StringBuilder();
System.IO.TextWriter tw = new System.IO.StringWriter(sb);
cSharpProvider.GenerateCodeFromNamespace(codeNS,
tw,
new System.CodeDom.Compiler.CodeGeneratorOptions());
return sb.ToString();
}



My code uses a DataSet Class I call "Columns.xsd" just for being able to iterate over something that represents the database table, without making a bunch of DataReader calls.



private static Columns PopulateColumns(SqlConnection Connection,
string TableName,
CommandType TableOrSproc)
{
Columns cols = new Columns();

Connection.Open();
SqlCommand sqlCmd = new SqlCommand();

if(TableOrSproc == CommandType.StoredProcedure)
{
// Get Parameters for Command.
sqlCmd = new SqlCommand(
"Select Parameter_Name as Column_Name,Data_Type, Character_maximum_length" +
" from Information_Schema.Parameters Where Specific_Name = '"
+ TableName +
"'",
Connection);
}
else
{
// Get Columns for the Table.
sqlCmd = new SqlCommand(
"Select Column_Name,Data_Type,Character_maximum_length" +
" from Information_Schema.Columns Where Table_Name = '"
+ TableName +
"'",
Connection);
}

sqlCmd.CommandType = CommandType.Text;

SqlDataReader dr = sqlCmd.ExecuteReader();
while (dr.Read())
{
Columns.ColumnRow row = cols.Column.NewColumnRow();
row.Column_Name = dr["Column_Name"].ToString();
row.Data_Type = dr["Data_Type"].ToString();
row.Character_maximum_length = dr["Character_maximum_length"].ToString();
cols.Column.AddColumnRow(row);
}
dr.Close();
Connection.Close();

return cols;
}