Click here to Skip to main content
15,896,912 members
Articles / Programming Languages / C#

Design and Implementation of an Attribute-Driven, Caching Data Abstraction Layer

Rate me:
Please Sign up or sign in to vote.
4.98/5 (25 votes)
21 Jul 2008CPOL30 min read 68.8K   595   103  
An easy-to-use, attribute-driven data abstraction layer with multi-database support, intelligent caching, transparent encryption, multi-property sorting, property change tracking, etc.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace BrainTechLLC.DAL
{
	/// <summary>
	/// Support for Ole DB connection to back-end database - uses OleDbDataReader
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class DALSqlOleDb<T> : IDALDatabase<T> where T : DBLayer<T>, IDBLayer<T>, new()
	{
		public int SQLExecute(string sqlStatement, string parameterName, object parameterValue, out Exception exOut)
		{
			OleDbParameter[] sp = new OleDbParameter[1];
			sp[0] = new OleDbParameter(parameterName, parameterValue);
			sp[0].Direction = ParameterDirection.Input;
			return SQLExecute(sqlStatement, sp, out exOut);
		}

		public int SQLExecute(string sqlStatement, string parameterName, string stringParameterValue, out Exception exOut)
		{
			OleDbParameter[] sp = new OleDbParameter[1];
			sp[0] = new OleDbParameter(parameterName, stringParameterValue);
			sp[0].Direction = ParameterDirection.Input;
			return SQLExecute(sqlStatement, sp, out exOut);
		}

		public int SQLExecute(string sqlStatement, out Exception exOut) { return SQLExecute(sqlStatement, (SqlParameter[])null, out exOut); }

		/// <summary>
		/// Direct access to execute SQL commands (on any table) with a set of OleDb Parameters 
		/// (or null for no parameters) passed in via parameter "sp"
		/// </summary>
		/// <param name="sqlStatement">The Command to execute.  Can include ? as long as there is a corresponding OleDbParameter passed in "sp"</param>
		/// <param name="sp">An array of Sql Parameters that correspond with the ?'s in sqlStatement statement.</param>
		/// <param name="exOut"></param>
		/// <returns>Number of rows affected by the Execute statement.  If 0, check exOut for any exceptions that might have occurred</returns>
		public int SQLExecute(string sqlStatement, IDbDataParameter[] sqlParameters, out Exception exOut)
		{
			OleDbParameter[] oleDBParams = null;
			if (sqlParameters != null)
			{
				oleDBParams = new OleDbParameter[sqlParameters.Length];
				for (int i = 0; i < sqlParameters.Length; i++)
				{
					oleDBParams[i] = new OleDbParameter(sqlParameters[i].ParameterName, sqlParameters[i].Value);
				}
			}

			exOut = null; int nRetVal = 0;
			using (OleDbConnection connection = new OleDbConnection(DBLayer<T>._connectionString))
			{
				if (sqlStatement.Contains(" * ")) sqlStatement = sqlStatement.Replace(" * ", " ");
				OleDbCommand objCmd = new OleDbCommand(sqlStatement, connection);
				if (oleDBParams != null) { objCmd.Parameters.AddRange(oleDBParams); }
				try
				{
					connection.Open();
				}
				catch (Exception ex)
				{
#if LoggingOn
					DALQueryInfo.AddMessage("SQLOLEDbExecute: " + sqlStatement + ", Error: " + ex.ToString());
#endif
					exOut = ex; Debug.WriteLine("SQL Query: " + sqlStatement + ", Error: " + ex.ToString());
					return nRetVal;
				}
				try
				{
#if LoggingOn
					DALQueryInfo.AddMessage("DB EXECUTE NONQUERY: " + sqlStatement);
#endif
					//Debug.WriteLine(sqlStatement);
					nRetVal = objCmd.ExecuteNonQuery();
				}
				catch (OleDbException exs) { exOut = exs; DALQueryInfo.AddMessage("SQLOLEDbExecute Query: " + sqlStatement + ", Error: " + exs.ToString()); Debug.WriteLine("SQLOLEDbExecute Query: " + sqlStatement + ", Error: " + exs.ToString()); }
				catch (Exception ex) { exOut = ex; DALQueryInfo.AddMessage("SQLOLEDbExecute Query: " + sqlStatement + ", Error: " + ex.ToString()); Debug.WriteLine("SQLOLEDbExecute Query: " + sqlStatement + ", Error: " + ex.ToString()); }
				finally
				{
					connection.Close();
				}

			}
			return nRetVal;
		}

		public List<T> SQLLoadList(string sqlStatement)
		{
			Exception exOut;
			string message;

			ResultSet<T> res = SQLLoadRows(sqlStatement, null, null, out exOut, out message);
			return (res == null) ? null : new List<T>(res.BusinessObjectList);
		}

		public DataSet SQLLoadDataSet(string sqlStatement)
		{
			Exception exOut;
			string message;

			ResultSet<T> res = SQLLoadRows(sqlStatement, null, null, out exOut, out message);
			return (res == null) ? null : res;
		}


		public DataSet SQLLoadList(string sqlStatement, out Exception exOut, out string message)
		{
			ResultSet<T> res = SQLLoadRows(sqlStatement, null, null, out exOut, out message);
			return (res == null) ? null : res;
		}

		public ResultSet<T> SQLLoadRows(string sqlStatement, SelectionSet selectionSet, out Exception exOut, out string message)
		{
			return SQLLoadRows(sqlStatement, selectionSet, (IDbDataParameter[])null, out exOut, out message);
		}

		public ResultSet<T> SQLLoadResultSet(string sqlStatement, out Exception exOut, out string message)
		{
			ResultSet<T> res = SQLLoadRows(sqlStatement, null, null, out exOut, out message);
			return res;
		}

		/// <summary>
		/// Heart of the OleDb Query functionality
		/// </summary>
		/// <param name="sqlStatement">SQL statement to execute</param>
		/// <param name="selectionSet">The set of selected columns (can be defined - i.e. COUNT(*) AS CNT, MAX(ID) as MAXID</param>
		/// <param name="sqlParameters">Support for OleDb parameters</param>
		/// <param name="exOut"></param>
		/// <param name="message"></param>
		/// <returns></returns>
		public ResultSet<T> SQLLoadRows(string sqlStatement, SelectionSet selectionSet, IDbDataParameter[] sqlParameters, out Exception exOut, out string message)
		{
			OleDbParameter[] oleDBParams = null;
			if (sqlParameters != null)
			{
				oleDBParams = new OleDbParameter[sqlParameters.Length];
				for (int i = 0; i < sqlParameters.Length; i++)
				{
					oleDBParams[i] = new OleDbParameter(sqlParameters[i].ParameterName, sqlParameters[i].Value);
				}
			}

			message = ""; exOut = null;
			int nPrefix = sqlStatement.IndexOf("WHERE", StringComparison.OrdinalIgnoreCase);
			string sPrefix;
			if (nPrefix < 0)
			{
				sPrefix = sqlStatement;
			}
			else
			{
				sPrefix = sqlStatement.Substring(0, nPrefix - 1);
			}

			ResultSet<T> ret = new ResultSet<T>();
			DBLayer<T>.CheckColNames();
			Dictionary<string, PropertyDescriptor> columnNamesAndProperties;
			StringBuilder sb = new StringBuilder(256);
			bool wantAddToCache = false;
			if (!DBLayer<T>._propertyLookup.TryGetValue(sPrefix, out columnNamesAndProperties))
			{
				columnNamesAndProperties = new Dictionary<string, PropertyDescriptor>(DBLayer<T>._propertyCount * 2);
				wantAddToCache = true;
			}
			if (wantAddToCache)
			{
				for (int i = 0; i < DBLayer<T>._propertyCount; i++)
				{
					string databaseColumnName = DBLayer<T>._columnNames[i];
					if (!string.IsNullOrEmpty(databaseColumnName))
					{
						columnNamesAndProperties.Add(databaseColumnName, DBLayer<T>._propertyDescriptors[i]);
					}
				}
				try { DBLayer<T>._propertyLookup.Add(sPrefix, columnNamesAndProperties); }
				catch { }
			}

			if (DBLayer<T>.CacheAndPerformance._trackConcurrentDBQueries)
				DBLayer<T>.CacheAndPerformance._currentSimultaneousDBQueries++;

			PropertyDescriptor pi;

			// using ensures disposal of the connection object after exiting the using() scope
			using (OleDbConnection connection = new OleDbConnection(DBLayer<T>._connectionString))
			{
				OleDbCommand objCmd = new OleDbCommand(sqlStatement, connection);
				try
				{
					// support for OleDb parameters (uses ? to denote placeholder)
					if (oleDBParams != null)
					{
						objCmd.Parameters.AddRange(oleDBParams);
					}
					try
					{
						connection.Open();
						OleDbDataReader sqlread = objCmd.ExecuteReader();

						if (sqlread.HasRows)
						{
#if LoggingOn
							DALQueryInfo.AddMessage("FROM DB: " + sqlStatement);
#endif
							// Read in the first row and gather field types, field names, and match up with
							// class properties (cached in the future)
							sqlread.Read();
							int nFields = sqlread.FieldCount;
							bool fNew = false;

							string[] fieldNames;
							PropertyDescriptor[] propertiesForDatabaseColumns;
							object[] readValues = new object[nFields];
							Type[] propertyTypesForColumns;
							Type[] fieldTypes;

							if (!DBLayer<T>._columnNameLookup.TryGetValue(sPrefix, out ret._columnNames))
							{
								ret.ColumnNames = new string[nFields];
								fNew = true;
							}

							if (!DBLayer<T>._propertyFieldNameLookup.TryGetValue(sPrefix, out ret._retrieved))
							{
								fNew = true;
							}
							if (!DBLayer<T>._propertyTypesForDBColsLookup.TryGetValue(sPrefix, out propertyTypesForColumns))
							{
								propertyTypesForColumns = new Type[nFields];
								fNew = true;
							}
							if (!DBLayer<T>._fieldNameLookup.TryGetValue(sPrefix, out fieldNames))
							{
								fieldNames = new string[nFields];
								fNew = true;
							}
							if (!DBLayer<T>._propsForDBColsLookup.TryGetValue(sPrefix, out propertiesForDatabaseColumns))
							{
								propertiesForDatabaseColumns = new PropertyDescriptor[nFields];
								fNew = true;
							}
							if (!DBLayer<T>._fieldTypeLookup.TryGetValue(sPrefix, out fieldTypes))
							{
								fieldTypes = new Type[nFields];
								fNew = true;
							}

							if (fNew)
							{
								for (int n = 0; n < nFields; n++)
								{
									fieldNames[n] = sqlread.GetName(n);
									fieldTypes[n] = sqlread.GetFieldType(n);
									if (columnNamesAndProperties.TryGetValue(fieldNames[n], out pi))
									{
										propertiesForDatabaseColumns[n] = pi;
										ret.ColumnNames[n] = pi.Name;
										ret.Retrieved[pi.Name] = fieldNames[n];
										propertyTypesForColumns[n] = pi.PropertyType;
									}
									else
									{
										pi = DBLayer<T>.CachedAttributes.CheckExtraColumns(fieldNames[n]);
										if (pi != null)
										{
											ret.Retrieved[pi.Name] = fieldNames[n];
											ret.ColumnNames[n] = pi.Name;
											propertiesForDatabaseColumns[n] = pi;
											propertyTypesForColumns[n] = pi.PropertyType;
										}
									}
								}
								try { DBLayer<T>._columnNameLookup.Add(sPrefix, ret.ColumnNames); }
								catch { }
								try { DBLayer<T>._propertyFieldNameLookup.Add(sPrefix, ret.Retrieved); }
								catch { }
								try { DBLayer<T>._propertyTypesForDBColsLookup.Add(sPrefix, propertyTypesForColumns); }
								catch { }
								try { DBLayer<T>._fieldNameLookup.Add(sPrefix, fieldNames); }
								catch { }
								try { DBLayer<T>._propsForDBColsLookup.Add(sPrefix, propertiesForDatabaseColumns); }
								catch { }
								try { DBLayer<T>._fieldTypeLookup.Add(sPrefix, fieldTypes); }
								catch { }
							}

							int nPath = -1;

							do
							{
								// Create a new instance of class T
								T newClass = new T();
								newClass._isNewInstance = false;
								newClass._modified = true;
								newClass._doNotRaisePropChanged = true;

								AdditionalResultRow<T> additionalResultRow = null;

								// Read values from the database
								sqlread.GetValues(readValues);

								if (nPath == 1)
								{
									for (int n = 0; n < nFields; n++)
									{
										DBLayer<T>.FillInValWithResult(newClass, readValues[n], fieldTypes[n], propertiesForDatabaseColumns[n], propertyTypesForColumns[n], sb);
									}
								}
								else if (nPath == 0)
								{

									for (int n = 0; n < nFields; n++)
									{
										// If the returned value matches a Property, fill in the returned value
										if (propertiesForDatabaseColumns[n] != null) { DBLayer<T>.FillInValWithResult(newClass, readValues[n], fieldTypes[n], propertiesForDatabaseColumns[n], propertyTypesForColumns[n], sb); }
										else
										{
											// otherwise, return the value in the Additional Results structure                  
											if (additionalResultRow == null) { additionalResultRow = new AdditionalResultRow<T>(additionalResultRow); }
											AdditionalResult additionalResult = new AdditionalResult();
											additionalResult.ResultName = fieldNames[n];
											additionalResult.ResultType = fieldTypes[n];
											additionalResult.ResultObject = readValues[n];
											additionalResultRow.Results.Add(additionalResult);
										}
									}
								}
								else
								{

									for (int n = 0; n < nFields; n++)
									{
										// If the returned value matches a Property, fill in the returned value
										if (propertiesForDatabaseColumns[n] != null)
										{
											if (nPath == -1) { nPath = 1; }
											DBLayer<T>.FillInValWithResult(newClass, readValues[n], fieldTypes[n], propertiesForDatabaseColumns[n], propertyTypesForColumns[n], sb);
										}
										else
										{
											nPath = 0;
											// otherwise, return the value in the Additional Results structure                  
											if (additionalResultRow == null) { additionalResultRow = new AdditionalResultRow<T>(additionalResultRow); }
											AdditionalResult additionalResult = new AdditionalResult();
											additionalResult.ResultName = fieldNames[n];
											additionalResult.ResultType = fieldTypes[n];
											additionalResult.ResultObject = readValues[n];
											additionalResultRow.Results.Add(additionalResult);
										}
									}
								}

								newClass._modified = false;
								if (DBLayer<T>.CacheAndPerformance._enableObjectConservation)
								{
									long unique = DBLayer<T>.GetUniqueValue(newClass);
									T foundClass = null;

									lock (DBLayer<T>.CacheAndPerformance._objectConservationLock)
									{
										if (DBLayer<T>.CacheAndPerformance._objectConservationLookup.TryGetValue(unique, out foundClass))
										{
											newClass = foundClass;
										}
										else
										{
											if (DBLayer<T>.CacheAndPerformance._objectConservationLookup.Count > DBLayer<T>.CacheAndPerformance._maxUniqueObjectCountForConservation)
											{
												DBLayer<T>.CacheAndPerformance._objectConservationLookup.Clear();
											}
											DBLayer<T>.CacheAndPerformance._objectConservationLookup.Add(unique, newClass);
										}
									}
								}
								ResultRow<T> result = new ResultRow<T>(ret, (T)newClass);
								newClass._doNotRaisePropChanged = DBLayer<T>._noPropertyChangeTracking;
								if (additionalResultRow != null) { result.AdditionalResults = additionalResultRow; }
								ret._cachedResultSet.Add(result);
							}
							while (sqlread.Read()); // per row

							ret._selectionSet = selectionSet;
						}
					}
					catch (Exception ex)
					{
						Debug.WriteLine(ex.ToString());
#if LoggingOn
						DALQueryInfo.AddMessage("OleDB Query failed: " + sqlStatement + " : " + ex.ToString());
#endif
					}
					finally
					{
						connection.Close();
						if (DBLayer<T>.CacheAndPerformance._trackConcurrentDBQueries)
							DBLayer<T>.CacheAndPerformance._currentSimultaneousDBQueries--;
					}
				}
				catch (OleDbException ex) { exOut = ex; DALQueryInfo.AddMessage("OleDB Exception - Query failed: " + sqlStatement + " : " + ex.ToString()); Debug.Assert(false, ex.ToString()); }
				catch (Exception ex) { exOut = ex; DALQueryInfo.AddMessage("OleDB Query failed: " + sqlStatement + " : " + ex.ToString()); Debug.Assert(false, ex.ToString()); }
			}
			return ret;
		}
	}
}

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) Troppus Software
United States United States
Currently working as a Senior Silverlight Developer with Troppus Software in Superior, CO. I enjoy statistics, programming, new technology, playing the cello, and reading codeproject articles. Smile | :)

Comments and Discussions