Click here to Skip to main content
15,891,896 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.7K   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.Threading;
using System.Reflection;
using System.Collections.Generic;
using BrainTechLLC.ThreadSafeObjects;

namespace BrainTechLLC.DAL
{
	public class DALWhereCache<T> : IWhereCache<T> where T : DBLayer<T>, IDBLayer<T>, new()
	{
		/// <summary>
		/// If set to true, cached query results associated with specific WHERE logic will not be cached
		/// </summary>
		public bool _disableWhereCache;

		internal Lockable _lock = new Lockable();

		/// <summary>
		/// Limits the number of entries in the in-memory WHERE logic statement result cache.			
		/// </summary>
		public int _maxQueriesInWhereCache = 1000;
		
		/// <summary>
		/// Limits the number of rows/results contained in all of the in-memory WHERE logic statement result caches.
		/// For classes that a large number of lengthy columns, this number should be set fairly low.  For small/lean
		/// tables or in situations where memory usage is less of a concern, this number can remain fairly large.
		/// </summary>
		public int _maxObjectsInWhereCache = 400000;

		/// <summary>
		/// Invalidates the WHERE cache, which stores results from frequently-used WHERE clauses
		/// </summary>
		public void MarkDirty()
		{
			_cachedQueriesDirty = true;
		}

		public bool CheckCacheDirty()
		{
			if (_cachedQueriesDirty)
			{
				_lock.AquireLock();
				try
				{
					if (_cachedQueriesDirty)
					{
						_cachedQueries.Clear();
						_cachedQueriesDirty = false;
						return true;
					}
				}
				finally
				{
					_lock.ReleaseLock();
				}
			}
			return false;
		}

		/// <summary>
		/// Support for quick "level one" cache.  This cache is temporary (any update, insert, or delete
		/// from a inheriting class invalidates the entire cache for that class), and only works with exact 
		/// matching WHERE statements (which occur frequently due to the use of the DBWhere class to
		/// generate WHERE clauses).  In addition, queries containing any parameters (@Param) will not
		/// be cached at this level. 
		/// </summary>
		internal ThreadSafeLookup<string, ResultSet<T>> _cachedQueries = new ThreadSafeLookup<string, ResultSet<T>>();

		internal bool _cachedQueriesDirty;

		/// <summary>
		/// Stores the number of objects currently stored in all of the in-memory WHERE logic statement result caches.
		/// </summary>
		public int _currentObjectCountInWhereCache = 0;

		public bool IsStatementInCache(string sqlStatement)
		{
			return _cachedQueries.ContainsKey(sqlStatement);
		}

		/// <summary>
		/// Look in "level 1" cache for any results that match this SELECT query exactly
		/// Will not work with parameters - parameterized queries should not call this method
		/// </summary>
		public ResultSet<T> FindInWhereCache(string sqlStatement)
		{
			if (_disableWhereCache) return null;

			_lock.AquireLock();
			try
			{
				if (_cachedQueries.Count > _maxQueriesInWhereCache) { ClearCachedQueries(); }
				else
				{
					if (_cachedQueries.ContainsKey(sqlStatement))
					{
						ResultSet<T> results;
						// check and handle any cache invalidation that has occured
						if (_cachedQueriesDirty) { ClearCachedQueries(); }
						else { if (_cachedQueries.TryGetValue(sqlStatement, out results)) { results._fromCache = true; return results; } }
					}
				}
			}
			finally
			{
				_lock.ReleaseLock();
			}
			return null;
		}

		internal void ClearCachedQueries()
		{
			//_findAllLookup.InvalidateCache();
			//_findFirstLookup.InvalidateCache(); 
			_cachedQueries.Clear();
			DBLayer<T>._integerIndices.Clear();
			DBLayer<T>._stringIndices.Clear();
			_cachedQueriesDirty = false;
		}

		public void RecordInWhereCache(string sql, ResultSet<T> results)
		{
			RecordInWhereCache(sql, results, results.RowCount);
		}

		public void RecordInWhereCache(string sql, ResultSet<T> results, int nResultRows)
		{
			// Enforce the _maxObjectsInWhereCache cache limit
			if (_currentObjectCountInWhereCache >= _maxObjectsInWhereCache)
			{
				MarkDirty();
			}

			_lock.AquireLock();
			try
			{
				if (_cachedQueriesDirty) { ClearCachedQueries(); }
				if (!_cachedQueries.ContainsKey(sql))
				{
					if (!_cachedQueries.Add(sql, results))
					{
						// consider making this non-thread safe to improve performance slightly?  our exact count might be off, but we
						// would not need to call Interlocked.Add... hmmm
						Interlocked.Add(ref _currentObjectCountInWhereCache, nResultRows);
					}
				}
			}
			finally
			{
				_lock.ReleaseLock();
			}
		}
	}
}

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