Click here to Skip to main content
15,892,737 members
Articles / Web Development / ASP.NET

Creating DAL Components Using Custom ASP.NET Build Providers And Compiler Techniques

Rate me:
Please Sign up or sign in to vote.
4.86/5 (28 votes)
24 Oct 2006CPOL9 min read 107.2K   886   114  
This article describes how to create Data Access Layer Components (DALC) using ASP.NET build providers and a self-defined description language in C#, including an easy scanner, parser, and CodeDOM generator.
//
// Parago Media GmbH & Co. KG, J�rgen B�urle (jbaurle@parago.de)
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
// 

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;

using Microsoft.CSharp;
using Microsoft.VisualBasic;

namespace Parago.DALComp.Compiler {

	/// <summary>
	/// The CodeGen class represents the last phase of the small compiler application
	/// and generates source code in C# or VB from an AST (Abstract Syntax Tree) that 
	/// is built up by a parser instance. Since the compiler is not compiling to any
	/// machine code (MSIL), this kind of compiler is often called a source-to-source 
	/// translator.
	/// </summary>
	public class CodeGen {

		AST dal;

		/// <summary>
		/// Initializes a new instance of the CodeGen class when given an AST instance. 
		/// </summary>
		public CodeGen(AST dal) {
			this.dal=dal;
		}

		/// <summary>
		/// Initializes a new instance of the CodeGen class when given a parser 
		/// instance that contains the AST (Abstract Syntax Tree). 
		/// </summary>
		public CodeGen(Parser parser) {
			dal=parser.DAL;
		}

		/// <summary>
		/// Generates C# source code from the AST (Abstract Syntax Tree) built up by 
		/// the parser.
		/// </summary>
		public string GenerateSourceCodeCSharp() {
			return GenerateSourceCode(new CSharpCodeProvider());
		}

		/// <summary>
		/// Generates VB source code from the AST (Abstract Syntax Tree) built up by 
		/// the parser.
		/// </summary>
		public string GenerateSourceCodeVB() {
			return GenerateSourceCode(new VBCodeProvider());
		}

		/// <summary>
		/// Generates source code based on the passed CodeDomProvider, for instance 
		/// the CSharpCodeProvider for C# code generation.
		/// </summary>
		public string GenerateSourceCode(CodeDomProvider codeProvider) {
			StringWriter writer=new StringWriter();
			codeProvider.GenerateCodeFromCompileUnit(Generate(), writer, new CodeGeneratorOptions());
			string source=writer.ToString();
			writer.Close();
			return source;
		}

		/// <summary>
		/// Generates source code by creating a container for a CodeDOM program graph
		/// and returns the CodeCompileUnit. The method will use the Abstract Syntax
		/// Tree (AST) which is built up by the parser and generate DAL code that
		/// targets the Microsoft SQL Server (Config:DatabaseType=MSSQL).
		/// </summary>
		public CodeCompileUnit Generate() {

			// Create container for CodeDOM program graph
			CodeCompileUnit compileUnit=new CodeCompileUnit();

			try {

				// If applicable replace the value '|BaseDirectory|' with the current 
				// directory of the running assembly (within the connection string) 
				// to allow database access in DALComp.Test.Console
				string connectionString=dal.Settings["CONNECTIONSTRING"]
					.Replace("|BaseDirectory|", Directory.GetCurrentDirectory());

				// Define new namespace (Config:Namespace)
				CodeNamespace namespaceUnit=new CodeNamespace(dal.Settings["NAMESPACE"]);
				compileUnit.Namespaces.Add(namespaceUnit);

				// Define necessary imports
				namespaceUnit.Imports.Add(new CodeNamespaceImport("System"));
				namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
				namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Data"));
				namespaceUnit.Imports.Add(new CodeNamespaceImport("System.Data.SqlClient"));

				// Generate private member fields (to save public property values) 
				// by analyzing the database table which is defined for the DALC
				SqlConnection connection=new SqlConnection(connectionString);
				connection.Open();

				// Generate a new public accessable class for each DALC definition 
				// with all defined methods
				foreach(DALC dalc in dal.DALCs) {

					// Generate new DALC class type and add to own namespace
					CodeTypeDeclaration typeUnit=new CodeTypeDeclaration(dalc.Name);
					namespaceUnit.Types.Add(typeUnit);

					// Generate public empty constructor method
					CodeConstructor constructor=new CodeConstructor();
					constructor.Attributes=MemberAttributes.Public;
					typeUnit.Members.Add(constructor);

					// Get schema table with column defintions for the current DALC table
					DataSet schema=new DataSet();
					new SqlDataAdapter(String.Format("SELECT * FROM {0}", dalc.Table), connection)
						.FillSchema(schema, SchemaType.Mapped, dalc.Table);

					// Generate for each column a private member field and a public 
					// accessable property to use
					foreach(DataColumn column in schema.Tables[0].Columns) {

						// Define names by checking DALC mapping definition
						string name=column.ColumnName;
						string nameMapped=dalc.Mapping.ContainsKey(name.ToUpper())?dalc.Mapping[name.ToUpper()]:name;

						// Generate private member field with underscore plus name; define 
						// member field type by checking if value type and create a 
						// nullable of that type accordingly
						CodeMemberField field=new CodeMemberField();
						field.Name=String.Format("_{0}", nameMapped);
						field.Type=GenerateFieldTypeReference(column.DataType);
						typeUnit.Members.Add(field);

						// Generate public accessable property for private member field, 
						// to use for instance in conjunction with ObjectDataSource
						CodeMemberProperty property=new CodeMemberProperty();
						property.Name=nameMapped;
						property.Type=GenerateFieldTypeReference(column.DataType);
						property.Attributes=MemberAttributes.Public;
						property.GetStatements.Add(
							new CodeMethodReturnStatement(
								new CodeFieldReferenceExpression(
									new CodeThisReferenceExpression(),
									field.Name
								)
							)
						);
						property.SetStatements.Add(
							new CodeAssignStatement(
								new CodeFieldReferenceExpression(
									new CodeThisReferenceExpression(),
									field.Name
								),
								new CodePropertySetValueReferenceExpression()
							)
						);
						typeUnit.Members.Add(property);

					}

					// The following code (language-independent) will be generated 
					// for SelectData: (CLASSNAME=typeUnit.Name)
					//    public static List<CLASSNAME> SelectData(string sql) {
					//			List<CLASSNAME> result;
					//       result=new List<CLASSNAME>();
					//			SqlConnection connection;
					//			SqlCommand command;
					//			SqlDataReader reader;
					//			connection=new SqlConnection("CONNECTIONSTRING")
					//			connection.Open();
					//			command=new SqlCommand(sql, connection);
					//			reader=command.ExecuteReader();
					//			while(reader.Read()) {
					// 			CLASSNAME o;
					// 			o=new CLASSNAME();
					// 			if(Convert.IsDBNull(reader["ID"]))
					// 				o.ID=null;
					// 			else 
					//		 		o.ID=(int?)reader["ID"];
					//				...
					// 			result.Add(o);
					// 		}
					//			reader.Close();
					// 		connection.Close();
					//			return result;
					//		}
					CodeMemberMethod selectDataMethod=new CodeMemberMethod();
					
					// public static List<CLASSNAME> SelectData(string sql) { ...
					selectDataMethod.Name="SelectData";
					selectDataMethod.Attributes=MemberAttributes.Public|MemberAttributes.Static;
					selectDataMethod.ReturnType=GenerateGenericListTypeReference(typeUnit.Name);
					selectDataMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sql"));
					
					// List<CLASSNAME> result;
					selectDataMethod.Statements.Add(
						new CodeVariableDeclarationStatement(
							GenerateGenericListTypeReference(typeUnit.Name), 
							"result"
						)
					);
					
					// result=new List<CLASSNAME>();							
					selectDataMethod.Statements.Add(
						new CodeAssignStatement(
							new CodeFieldReferenceExpression(null, "result"),
							new CodeObjectCreateExpression(GenerateGenericListTypeReference(typeUnit.Name))
						)
					);
					
					// SqlConnection connection;
					// SqlCommand command;
					// SqlDataReader reader;
					selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlConnection), "connection"));
					selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlCommand), "command"));
					selectDataMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(SqlDataReader),"reader"));
					
					// connection=new SqlConnection("CONNECTIONSTRING");
					selectDataMethod.Statements.Add(
						new CodeAssignStatement(
							new CodeVariableReferenceExpression("connection"),
							new CodeObjectCreateExpression(typeof(SqlConnection), new CodePrimitiveExpression(connectionString))
						)
					);
					
					// connection.Open();
					selectDataMethod.Statements.Add(
						new CodeMethodInvokeExpression(
							new CodeMethodReferenceExpression(
								new CodeVariableReferenceExpression("connection"),
								"Open"
							)
						)
					);
					
					// command=new SqlCommand(sql, connection);
					selectDataMethod.Statements.Add(
						new CodeAssignStatement(
							new CodeFieldReferenceExpression(null, "command"),
							new CodeObjectCreateExpression(
								typeof(SqlCommand),
								new CodeExpression[] {
										new CodeVariableReferenceExpression("sql"),
										new CodeVariableReferenceExpression("connection")
									}
							)
						)
					);
					
					// reader=command.ExecuteReader();
					selectDataMethod.Statements.Add(
						new CodeAssignStatement(
							new CodeVariableReferenceExpression("reader"),
							new CodeMethodInvokeExpression(
								new CodeMethodReferenceExpression(
									new CodeVariableReferenceExpression("command"),
									"ExecuteReader"
								)
							)
						)
					);
					
					CodeStatementCollection whileStatements=new CodeStatementCollection();
					
					// CLASSNAME o;
					// o=new CLASSNAME();
					whileStatements.Add(new CodeVariableDeclarationStatement(typeUnit.Name, "o"));
					whileStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("o"),new CodeObjectCreateExpression(typeUnit.Name)));

					// Generate code to populate object instance and add later to
					// CodeIterationStatement (while body)
					foreach(DataColumn column in schema.Tables[0].Columns) {

						// Define names by checking DALC mapping definition
						string name=column.ColumnName;
						string nameMapped=dalc.Mapping.ContainsKey(name.ToUpper())?dalc.Mapping[name.ToUpper()]:name;

						// if(Convert.IsDBNull(reader["ID"]))
						// 	o.ID=null;
						// else 
						//		o.ID=(int?)reader["ID"];
						whileStatements.Add(
							new CodeConditionStatement(
								new CodeMethodInvokeExpression(
									new CodeMethodReferenceExpression(
										new CodeSnippetExpression("Convert"),
										"IsDBNull"
									),
									new CodeArrayIndexerExpression(
										new CodeVariableReferenceExpression("reader"),
										new CodePrimitiveExpression(name)
									)
								),
								new CodeStatement[] {
									new CodeAssignStatement( 
										new CodeFieldReferenceExpression(
											new CodeVariableReferenceExpression("o"), 
											nameMapped
										),
										new CodePrimitiveExpression(null)
									) 
								},
								new CodeStatement[] {
									new CodeAssignStatement( 
										new CodeFieldReferenceExpression(
											new CodeVariableReferenceExpression("o"), 
											nameMapped
										),
										new CodeCastExpression(
											GenerateFieldTypeReference(column.DataType),		
											new CodeArrayIndexerExpression(
												new CodeVariableReferenceExpression("reader"),
												new CodePrimitiveExpression(name)
											)
										)
									) 
								}
							)
						);
					
					}

					// result.Add(o);
					whileStatements.Add(
						new CodeMethodInvokeExpression(
							new CodeMethodReferenceExpression(
								new CodeVariableReferenceExpression("result"),
								"Add"
							),
							new CodeVariableReferenceExpression("o")
						)
					);
					
					// Need array to add to CodeIterationStatement
					CodeStatement[] whileStatementsArray=new CodeStatement[whileStatements.Count];
					whileStatements.CopyTo(whileStatementsArray, 0);
					
					// while(reader.Read()) { ...
					selectDataMethod.Statements.Add(
						new CodeIterationStatement(
							new CodeSnippetStatement(),
							new CodeMethodInvokeExpression(
								new CodeMethodReferenceExpression(
									new CodeVariableReferenceExpression("reader"),
									"Read"
								)
							),
							new CodeSnippetStatement(),
							whileStatementsArray
						)
					);

					// reader.Close();
					// connection.Close();
					selectDataMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("reader"),"Close")));
					selectDataMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("connection"),"Close")));

					// return result;
					selectDataMethod.Statements.Add(
						new CodeMethodReturnStatement(
							new CodeFieldReferenceExpression(null, "result")
						)
					);

					// Add SelectData method to new class
					typeUnit.Members.Add(selectDataMethod);

					// Generate defined functions as static methods	for DALC class
					foreach(Function function in dalc.Functions) {

						// public static List<CLASSNAME> FUNCTIONNAME( ...
						CodeMemberMethod selectMethod=new CodeMemberMethod();
						selectMethod.Name=function.Name;
						selectMethod.Attributes=MemberAttributes.Public|MemberAttributes.Static;
						selectMethod.ReturnType=GenerateGenericListTypeReference(typeUnit.Name);

						// string internalSql;
						selectMethod.Statements.Add(new CodeVariableDeclarationStatement(typeof(string), "internalSql"));

						StringBuilder sb1=new StringBuilder();
						StringBuilder sb2=new StringBuilder();
						StringBuilder sql=new StringBuilder();

						sql.AppendFormat("SELECT * FROM {0}", dalc.Table);

						// Add method parameters to DALC defined function and generate code to
						// validate and convert input values
						if(function.Parameters.Count>0) {

							sql.Append(" WHERE ");
							int i=1;

							// int p1;
							// p1=PARAMETERNAME1;
							// string p2;
							// if((PARAMETERNAME2==null)) {
							// 	p2="-";
							// }
							// else {
							// 	p2=PARAMETERNAME2.Replace("\'", "\'\'");
							// }
							// ...
							// internalSql=String.Format("SELECT * FROM CLASSNAME WHERE FIELDNAME1=\'{0}\' AND FIELDNAME2=\'{1}\'", PARAMETERNAME1, PARAMETERNAME2);
							CodeExpressionCollection formatParameters=new CodeExpressionCollection();
							foreach(Parameter parameter in function.Parameters) {
								string p=String.Format("p{0}", i);
								selectMethod.Parameters.Add(new CodeParameterDeclarationExpression(parameter.Type, parameter.Name));
								selectMethod.Statements.Add(new CodeVariableDeclarationStatement(parameter.Type,p));
								if(parameter.Type=="System.String")
									selectMethod.Statements.Add(
										new CodeConditionStatement(
											new CodeBinaryOperatorExpression(
												new CodeVariableReferenceExpression(parameter.Name),
												CodeBinaryOperatorType.ValueEquality,
												new CodePrimitiveExpression()
											),
											new CodeStatement[] {
													new CodeAssignStatement( 
														new CodeVariableReferenceExpression(p), 
														new CodePrimitiveExpression("-")
													)
												},
											new CodeStatement[] {
													new CodeAssignStatement(
														new CodeVariableReferenceExpression(p),
														new CodeMethodInvokeExpression(
															new CodeMethodReferenceExpression(
																new CodeVariableReferenceExpression(parameter.Name),
																"Replace"
															),
															new CodeExpression[] {
																new CodePrimitiveExpression("'"),
																new CodePrimitiveExpression("''")
															}
														)
													)
												}
										)
									);
								else
									selectMethod.Statements.Add(
										new CodeAssignStatement(
											new CodeVariableReferenceExpression(p),
											new CodeVariableReferenceExpression(parameter.Name)
										)
									);
								sql.AppendFormat("{0}='{{{1}}}' AND ", parameter.Field, i-1);
								formatParameters.Add(new CodeVariableReferenceExpression(p));
								i++;
							}
							formatParameters.Insert(0, new CodePrimitiveExpression(sql.ToString().TrimEnd(" AND ".ToCharArray())));
							CodeExpression[] formatParametersArray=new CodeExpression[formatParameters.Count];
							formatParameters.CopyTo(formatParametersArray, 0);
							selectMethod.Statements.Add(
								new CodeAssignStatement(
									new CodeVariableReferenceExpression("internalSql"),
									new CodeMethodInvokeExpression(
										new CodeMethodReferenceExpression(
											new CodeSnippetExpression("String"),
											"Format"
										),
										formatParametersArray
									)
								)
							);
						}
						else
							//	internalSql="SELECT * FROM TABLENAME";
							selectMethod.Statements.Add(
								new CodeAssignStatement(
									new CodeVariableReferenceExpression("internalSql"),
									new CodePrimitiveExpression(sql.ToString())
								)
							);

						// return SelectData(internalSql);
						selectMethod.Statements.Add(
							new CodeMethodReturnStatement(
								new CodeMethodInvokeExpression(
									new CodeMethodReferenceExpression(
										null,
										"SelectData"
									),
									new CodeVariableReferenceExpression("internalSql")
								)
							)
						);

						// Add DALC defined function
						typeUnit.Members.Add(selectMethod);

					}

				}

				// Close database connection and all associated managed and 
				// unmanaged resources
				connection.Dispose();
			}
			catch(Exception e) {
				throw new Exception(String.Format("DALComp code generation error: {0}", e.Message));
			}
			return compileUnit;
		}

		/// <summary>
		/// Generates a nullable type if input type is a value type.
		/// </summary>
		CodeTypeReference GenerateFieldTypeReference(Type columnType) {
			if(!columnType.IsValueType)
				return new CodeTypeReference(columnType);
			Type nullableType=typeof(Nullable<>);
			return new CodeTypeReference(nullableType.MakeGenericType(new Type[] { columnType }));
		}

		/// <summary>
		/// Generates a generic type of List with items of type passed in className.
		/// </summary>
		CodeTypeReference GenerateGenericListTypeReference(string className) {
			return new CodeTypeReference("List", new CodeTypeReference[] { new CodeTypeReference(className) });
		}

	}

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Germany Germany
I’m a software developer based in Germany.

Homepage

Comments and Discussions