Click here to Skip to main content
15,887,214 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.5K   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.Collections.ObjectModel;
using System.ComponentModel;
using BrainTechLLC.ThreadSafeObjects;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Threading;
using Hyper.ComponentModel;

namespace BrainTechLLC.DAL
{
	public enum Cachability
	{
		Unknown = -1,
		NotCachable = 0,
		Cacheable = 1
	}

	public enum CacheType
	{
		Unknown = -1,
		Prepopulate = 0,
		GrowingCache = 1
	}

	public class DBLayerCache<T> : ICache<T> where T : DBLayer<T>, IDBLayer<T>, new()
	{
		public event EventHandler CacheSetInvalidated;

		internal Lockable _lock = new Lockable();

		public void LockCache()
		{
			_lock.AquireLock();
		}

		public void UnlockCache()
		{
			_lock.ReleaseLock();
		}
		#region "Fields and Properties"
		/// <summary>
		/// true indicates that all database rows were read from the table to cache (at which point we can
		/// perform complex queries in-memory and avoid database calls).  False indicates that not all rows
		/// are in memory, or we're not sure if all rows have been read into memory.  Everything except duplicate
		/// queries must go to the database (except in the case that a query is made on an identity column and
		/// we are able to retrieve the single result from cache)
		/// </summary>
		internal bool _retrievedAllRows;

		public bool RetrievedAllRows { get { return _retrievedAllRows; } set { _retrievedAllRows = value; } }

		/// <summary>
		/// If cache invalidation is suspended, then no repopulation of the cache occurs if items are inserted/deleted/updated, etc
		/// </summary>
		internal static bool _cacheInvalidationSuspended = false;

		/// <summary>
		/// Stores the number of (con)currently executing cache queries
		/// </summary>
		internal ThreadSafeCounter _queryingCacheCount = new ThreadSafeCounter();

		/// <summary>
		/// -1 = unknown, 0 = not growing, 1 = growing
		/// </summary>
		internal CacheType _isGrowingCache = CacheType.Unknown;

		public CacheType CacheType
		{
			get
			{
				if (_isGrowingCache == CacheType.Unknown)
				{
					_isGrowingCache = (DBLayer<T>.CachedAttributes.WantFullPrepopulateCache) ? CacheType.Prepopulate : CacheType.GrowingCache;
				}
				return _isGrowingCache;
			}
		}

		/// <summary>
		/// -1 = unknown, 0 = not cacheable, 1 = cacheable
		/// </summary>
		internal Cachability _classIsCacheable = Cachability.Unknown;

		/// <summary>
		/// A cache of which properties NOT to cache... go figure :)
		/// </summary>
		internal List<PropertyDescriptor> _doNotCachePropertiesLookup = null;

		/// <summary>
		/// For classes marked with the DontSearchAgainAfterItemNotFound attribute, this variable
		/// keeps track of which queries returned nothing
		/// </summary>
		internal ThreadSafeLookup<int, ThreadSafeList<DBWhere>> _cacheAndDatabaseMissLookup;

		public ThreadSafeLookup<int, ThreadSafeList<DBWhere>> CacheAndDatabaseMissLookup { get { return _cacheAndDatabaseMissLookup; } }

		/// <summary>
		/// The cache lock object is used to synchronize multiple threads that might try to work with the cache 
		/// at the same time.  The cache object should be locked only BRIEFLY, and preferable after doing an initial
		/// read-only check that what you are looking for may be in the cache
		/// </summary>
		// internal object _cacheLockObject = new object();

		/// <summary>
		/// Actual result set that holds the growing or prepopulated cached objects
		/// </summary>
		internal ResultSet<T> _cacheObjects = null;

		public ResultSet<T> CacheObjects
		{
			get { return _cacheObjects; }
		}
		/// <summary>
		/// Indices for the objects (i.e. Object T is stored as ResultRow 42 in rgCache)
		/// Provides reverse-lookup functionality
		/// </summary>
		internal ThreadSafeLookupNonRef<long, int> _cacheIndices = null;

		/// <summary>
		/// When does the cache expire (NYI)
		/// </summary>
		internal DateTime _cacheExpires = DateTime.MaxValue;

		/// <summary>
		/// Support for quick lookup attribute (permanent indexing of a widely-used, mostly-read-only property like UserID)
		/// </summary>
		internal LookupCollection<T> _find = new LookupCollection<T>();

		/// <summary>
		/// Is the cache dirty?
		/// </summary>
		internal bool _cacheIsDirty = false;

		/// <summary>
		/// Stores (caches) the attribute signifying whether or not the business object type is cacheable
		/// </summary>
		internal CollectionCacheable _collectionCacheable = null;

		/// <summary>
		/// Caches may be retrieved in a sorted order to optimize lookups and to eliminate the need to resort results
		/// </summary>
		internal OrderBy<T> _cacheSortedBy = null;

		/// <summary>
		/// Internal variable that marks that the cache is currently being invalidated
		/// </summary>
		internal bool _invalidatingCache = false;

		internal bool _checkedCacheable = false;
		#endregion

		public LookupCollection<T> Find { get { return _find; } }

		#region "Cache invalidation and pre-population"
		/// <summary>
		/// Invalidates the global/application level cache for a business object
		/// </summary>
		public void CacheSetInvalidate()
		{
			if (_cacheInvalidationSuspended) return;
			CacheSetInvalidateInternal();
		}

		/// <summary>
		/// Immediately prepopulates the global/application level cache for a business object
		/// </summary>			
		public void PrepopulateCacheNow(OrderBy<T> orderBy)
		{
			if (_cacheObjects == null)
			{
				LockCache();
				try
				{
					if (_cacheObjects == null) { PopulateCache(DateTime.MaxValue, orderBy); }
				}
				finally
				{
					UnlockCache();
				}
			}
		}

		/// <summary>
		/// Removes an item from cache
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		protected bool CacheRemoveItem(T item)
		{
			if (ClassIsCacheable == Cachability.Cacheable)
			{
				LockCache();
				try
				{
					PropertyDescriptor piQuickLookup = DBLayer<T>.CachedAttributes.QuickLookupProperty;
					return RemoveFromCache(item, piQuickLookup);
				}
				finally
				{
					UnlockCache();
				}
			}
			return false;
		}

		internal void CacheSetInvalidateInternal()
		{
			if (_invalidatingCache) return;

			LockCache();
			try
			{
				_invalidatingCache = true;
				_retrievedAllRows = false;

				if (DBLayer<T>.CacheAndPerformance._trackConcurrentDBQueries)
				{
					DBLayer<T>.CacheAndPerformance._databaseHitCount.ZeroCounter();
					DBLayer<T>.CacheAndPerformance._cacheHitCount.ZeroCounter();
				}

				DBLayer<T>.WhereCache.MarkDirty();
				_cacheIsDirty = true;
				if (_cacheObjects != null) _cacheObjects.InvalidateCachedProperties();
				if (_find._quickLookup != null) { _find._quickLookup.Clear(); }
				// CrossObjectMaps.InvalidateCache();

				if (CacheSetInvalidated != null)
				{
					CacheSetInvalidated(DBLayer<T>.ClassType, new EventArgs());
				}

#if LoggingOn
				DALQueryInfo.AddMessage("CACHE FOR " + DBLayer<T>.ClassType.Name + " SET TO DIRTY/INVALID.");
#endif

				_invalidatingCache = false;
			}
			finally
			{
				UnlockCache();
			}
		}

		internal void PopulateCache()
		{
			PopulateCache(DateTime.MaxValue, _cacheSortedBy);
		}
		#endregion

		internal bool RemoveFromCache(T item, PropertyDescriptor quickLookupPropertyDescriptor)
		{
			bool fRet = false;
			long key = DBLayer<T>.GetUniqueValue(item);
			LockCache();
			try
			{
				int index;
				if (_cacheIndices.TryGetValue(key, out index))
				{
					if (quickLookupPropertyDescriptor != null)
					{
						// class has a quick lookup property - remove the lookup from _quickLookup
						object o = quickLookupPropertyDescriptor.GetValue(item);
						if (o != null && _find._quickLookup != null)
						{
							_find._quickLookup.Remove(o.GetHashCode());
						}
					}

					try { _cacheObjects._cachedResultSet.RemoveAt(index); }
					catch (Exception ex) { DALQueryInfo.AddMessage(ex.ToString()); CacheSetInvalidate(); }

					_cacheObjects.InvalidateCachedProperties();
					_cacheIndices.Remove(key);
					fRet = true;
				}
			}
			finally { UnlockCache(); }

			return fRet;
		}

		/// <summary>
		/// If true, cache will not be invalidated until the value is set back to false.
		/// This is useful if you are searching for and then modifying multiple objects in a prepopulating cache and do not want the entire cache
		/// to be refetched when you search for the next object to modify.
		/// </summary>
		public bool SuspendCacheInvalidation
		{
			get { return _cacheInvalidationSuspended; }
			set
			{
				if (value == false)
				{
					// When we set back to false, invalidate the cache
					_cacheInvalidationSuspended = false;
					CacheSetInvalidate();
				}
				else { _cacheInvalidationSuspended = true; }
			}
		}

		public bool IsEmpty { get { return (_cacheObjects == null); } }

		/// <summary>
		/// Records that the specific WHERE statement returned no results.
		/// Used with the DontSearchAgainAfterItemNotFound attribute so that
		/// classes marked with the attribute don't subsequently requery the database.
		/// </summary>
		/// <param name="whereClause"></param>
		public void MarkNoResultsFound(DBWhere whereClause)
		{
			if (_cacheAndDatabaseMissLookup == null) return;

			try
			{
				// if marked as DontSearchAgainAfterItemNotFound and there is a specified where clause (not selecting all), 
				// then mark this query's logic as "not found" so the query won't be executed again
				int hash = whereClause.GetDBWhereHashCode();
				ThreadSafeList<DBWhere> whereBucket = null;
				if (_cacheAndDatabaseMissLookup.TryGetValue(hash, out whereBucket))
				{
					bool fSame = false;
					whereBucket.AquireLock();
					{
						for (int m = 0; m < whereBucket._list.Count; m++)
						{
							DBWhere where = whereBucket._list[m];
							// Overloaded to compare logic used in the where clause, just in case where clauses differ in order, etc
							if (whereClause.Equals(where)) { fSame = true; break; }
						}
					}
					whereBucket.ReleaseLock();

					if (!fSame)
					{
						if (whereBucket.Count > 100) { whereBucket.Clear(); } // HEREHERE: Change into setting
						whereBucket.Add(whereClause);
					}
				}
				else
				{
					Interlocked.Exchange<ThreadSafeList<DBWhere>>(ref whereBucket, new ThreadSafeList<DBWhere>());
					whereBucket.Add(whereClause);
					_cacheAndDatabaseMissLookup.AddOrSet(hash, whereBucket);
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
			}
		}

		/// <summary>
		/// Used with the DontSearchAgainAfterItemNotFound attribute.
		/// Determines if the same where clause has previously returned no results
		/// </summary>
		/// <param name="whereClause"></param>
		/// <returns></returns>
		public bool CheckMissBucket(DBWhere whereClause)
		{
			if (_cacheAndDatabaseMissLookup != null && whereClause != null)
			{
				int nHash = whereClause.GetDBWhereHashCode();
				ThreadSafeList<DBWhere> whereBucket = null;
				if (_cacheAndDatabaseMissLookup.TryGetValue(nHash, out whereBucket))
				{
					whereBucket.AquireLock();
					try
					{
						for (int m = 0; m < whereBucket._list.Count; m++)
						{
							DBWhere where = whereBucket._list[m];
							if (whereClause.CompareTo(where) != 0)
							{
#if LoggingOn
								List<SqlParameter> sqlParameterList = null;
								DALQueryInfo.AddMessage("AVOIDED QUERY: " + DBLayer<T>.ConstructSqlWhereStatement(whereClause, out sqlParameterList));
#endif
								return true;
							}
						}
					}
					finally
					{
						whereBucket.ReleaseLock();
					}
				}
			}
			return false;
		}

		/// <summary>
		/// Quick, cached check if the business object is Cache-able.  If so, checks the cache status
		/// </summary>
		public void CheckCache()
		{
			CollectionCacheable c = CollectionCacheableAttribute;
			if (c != null) { CheckCache(c, _cacheSortedBy); }
		}

		/// <summary>
		/// Checks that the cache is set up properly and not dirty/modified.  Takes appropriate action (writes
		/// changes to database - currently unused), invalidates the cache, or sets it up for the first time
		/// </summary>
		/// <param name="cache"></param>
		/// <param name="orderBy"></param>
		internal void CheckCache(CollectionCacheable cache, OrderBy<T> orderBy)
		{
			if (_queryingCacheCount > 0) return;
			bool setDirty = false;

			if (_cacheIsDirty || _cacheObjects == null)
			{
				LockCache();
				try
				{
					if (_queryingCacheCount > 0) return;

					if (_cacheIsDirty || _cacheObjects == null)
					{
						if (_cacheObjects != null) _cacheObjects._cachedResultSet.Clear();
						_cacheObjects = null;
						_cacheAndDatabaseMissLookup = null;
						setDirty = true;
					}
				}
				finally { UnlockCache(); }
			}

			if (setDirty)
			{
				PopulateCache(DateTime.Now.Add(cache.ExpirationTimespan), orderBy);
			}
		}

		/// <summary>
		/// Adds an item to the cache only (not to the database)
		/// </summary>      
		internal bool AddToCacheOnly(ResultRow<T> item, PropertyDescriptor quickLookupPropertyInfo, int index)
		{
			bool returnValue = false;
			long key = item._result.UniqueValue;

			if (!_lock.Locked)
				throw new Exception("Cache not locked going into AddToCacheOnly");

			if (!_cacheIndices.ContainsKey(key))
			{
				if (quickLookupPropertyInfo != null)
				{
					object o = quickLookupPropertyInfo.GetValue(item._result);
					if (o != null) { _find._quickLookup.AddOrSet(o.GetHashCode(), item._result); }
				}
				if (index < 0) { UnsafeAddItemToCache(item, key, false); }
				else { _cacheIndices.AddOrSet(key, index); }
				returnValue = true;
			}
			else
			{
				// Already in the cache
				try
				{
					_cacheObjects.Results._list[_cacheIndices[key]] = item;
					_cacheObjects.InvalidateCachedProperties();
					returnValue = false;
				}
				catch { CacheSetInvalidate(); }
			}

			return returnValue;
		}

		/// <summary>
		/// Adds an item to cache without performance-reducing sanity checks
		/// </summary>
		/// <param name="item"></param>
		/// <param name="isNewItem"></param>
		internal void UnsafeAddItemToCache(ResultRow<T> item, long key, bool isNewItem)
		{
			if (!_lock.Locked)
				throw new Exception("Cache not locked going into AddToCacheOnly");

			if (_doNotCachePropertiesLookup == null)
			{
				PropertyDescriptorCollection propertyDescriptorInfo = TypeDescriptor.GetProperties(DBLayer<T>.ClassType);
				_doNotCachePropertiesLookup = new List<PropertyDescriptor>(2);
				for (int n = 0; n < propertyDescriptorInfo.Count; n++) { if (DBLayer<T>.CachedAttributes.GetDoNotCache(propertyDescriptorInfo[n])) { if (!string.IsNullOrEmpty(DBLayer<T>.CachedAttributes.GetColumnNameForProperty(propertyDescriptorInfo[n]))) { _doNotCachePropertiesLookup.Add(propertyDescriptorInfo[n]); } } }
			}

			if (_cacheIndices.ContainsKey(key))
			{
				_cacheObjects._cachedResultSet._list[_cacheIndices[key]] = item;
				_cacheObjects.InvalidateCachedProperties();
			}
			else
			{
				_cacheObjects._cachedResultSet.Add(item);
				_cacheObjects.InvalidateCachedProperties();
				_cacheIndices.AddOrSet(key, _cacheObjects._cachedResultSet.Count - 1);
			}
		}

		internal Cachability ClassIsCacheable
		{
			get
			{
				if (_classIsCacheable == Cachability.Unknown)
				{
					CollectionCacheable cc = DBLayer<T>.GetCustomClassAttribute<CollectionCacheable>();
					if (cc == null)
					{
						_classIsCacheable = Cachability.NotCachable;
						Debug.Assert(false, "Collection not cacheable");
						return Cachability.NotCachable;
					}
					else
					{
						_classIsCacheable = Cachability.Cacheable;
					}
				}
				return _classIsCacheable;
			}
		}

		/// <summary>
		/// PopulateCache does the work of setting up cache pre-population, ordering, cache expiration, etc
		/// This is thread-safe -- the methods that call it lock the cache first
		/// </summary>
		/// <param name="dtExpire"></param>
		/// <param name="orderBy"></param> 
		internal void PopulateCache(DateTime dtExpire, OrderBy<T> orderBy)
		{
			if (ClassIsCacheable == Cachability.Cacheable)
			{
				if (_cacheAndDatabaseMissLookup == null && DBLayer<T>.CachedAttributes.TrackQueriesThatReturnNothing)
				{
					Interlocked.CompareExchange<ThreadSafeLookup<int, ThreadSafeList<DBWhere>>>(ref _cacheAndDatabaseMissLookup, new ThreadSafeLookup<int, ThreadSafeList<DBWhere>>(), null);
				}

				if (_isGrowingCache == CacheType.Unknown)
				{
					_isGrowingCache = (DBLayer<T>.CachedAttributes.WantFullPrepopulateCache) ? CacheType.Prepopulate : CacheType.GrowingCache;
				}

				if (_isGrowingCache == CacheType.Prepopulate)
				{
					LockCache();
					try
					{
						if (_cacheObjects != null && _cacheObjects.Results != null) { _cacheObjects.Results.Clear(); _cacheObjects = null; }
						bool fetchedFromQueryCache;
						string prebuiltSqlStatement = null;
						_cacheObjects = DBLayer<T>.GetRowsFromDB(SelectionSet.All, null, orderBy, out fetchedFromQueryCache, prebuiltSqlStatement, true);
						_cacheIsDirty = false;
						_cacheExpires = dtExpire;
						PropertyDescriptor piQuickLookup = DBLayer<T>.CachedAttributes.QuickLookupProperty;
						if (piQuickLookup != null && _find._quickLookup == null)
						{
							Interlocked.CompareExchange<ThreadSafeLookup<long, T>>(ref _find._quickLookup, new ThreadSafeLookup<long, T>(), null);
						}
						int nCount = _cacheObjects.RowCount;
						_cacheIndices = new ThreadSafeLookupNonRef<long, int>();
						for (int n = 0; n < nCount; n++)
						{
							ResultRow<T> item = _cacheObjects._cachedResultSet._list[n];
							AddToCacheOnly(item, piQuickLookup, n);
						}
					}
					finally { UnlockCache(); }
				}
				else
				{
					LockCache();
					try
					{
						// Set up a growing cache
						_cacheIsDirty = false;
						_cacheExpires = dtExpire;
						_cacheObjects = new ResultSet<T>();
						PropertyDescriptor piQuickLookup = DBLayer<T>.CachedAttributes.QuickLookupProperty;
						if (piQuickLookup != null && _find._quickLookup == null)
						{
							Interlocked.CompareExchange<ThreadSafeLookup<long, T>>(ref _find._quickLookup, new ThreadSafeLookup<long, T>(), null);
						}
						_cacheIndices = new ThreadSafeLookupNonRef<long, int>();
						_isGrowingCache = CacheType.GrowingCache;
					}
					finally { UnlockCache(); }
				}

#if LoggingOn
				DALQueryInfo.AddMessage("CACHE FOR " + DBLayer<T>.ClassType.Name + " POPULATED.");
#endif
			}
		}

		public void HandleGrowingCache(ResultSet<T> results, DBWhere whereClause)
		{
			if (_isGrowingCache == CacheType.GrowingCache && _classIsCacheable == Cachability.Cacheable && _cacheObjects != null)
			{
				HandleGrowingCacheInternal(results, whereClause);
			}
		}

		internal void HandleGrowingCacheInternal(ResultSet<T> results, DBWhere whereClause)
		{
			// If we're growing the cache, then add any in new items, but only if they are full row selects 
			if (_isGrowingCache == CacheType.GrowingCache && _classIsCacheable == Cachability.Cacheable && !_retrievedAllRows)
			{
				if (!_lock.Locked)
					throw new Exception("Cache not locked going into AddToCacheOnly");
				
				if (_cacheObjects != null)
				{
					PropertyDescriptor piQuickLookup = DBLayer<T>.CachedAttributes.QuickLookupProperty;
					if (piQuickLookup != null && _find._quickLookup == null)
					{
						Interlocked.CompareExchange<ThreadSafeLookup<long, T>>(ref _find._quickLookup, new ThreadSafeLookup<long, T>(), null);
					}
					if (!_cacheIsDirty && _cacheObjects != null)
					{
						for (int ii = 0; ii < results.RowCount; ii++)
						{
							ResultRow<T> resrow = results[ii];
							AddToCacheOnly(resrow, piQuickLookup, -1);
						}
					}
					if (whereClause == null || (whereClause._combinedWithLogic.Count == 0 && whereClause._matchRequirements._propertyNameAndValuePairs.Count == 0)) _retrievedAllRows = true;
				}
			}
		}

		/// <summary>
		/// Gets a snapshot of the entire contents of cache, returned as a ReadOnlyCollection(Of T)
		/// </summary>
		/// <returns></returns>
		public ReadOnlyCollection<T> GetCacheCopySnapshot()
		{
			ReadOnlyCollection<T> returnedList = null;
			if (ClassIsCacheable == Cachability.Cacheable && (_isGrowingCache == CacheType.Prepopulate))
			{
				CheckCache();
				LockCache();
				try
				{
					if (_cacheObjects == null) { _classIsCacheable = 0; return null; }
					returnedList = new ReadOnlyCollection<T>(_cacheObjects.BusinessObjectList.AsReadOnly());
				}
				finally { UnlockCache(); }
			}
			else
			{
				return DBLayer<T>.GetListOfAllRows().AsReadOnly();
			}
			return returnedList;
		}

		/// <summary>
		/// Retrieves rows from cache
		/// </summary>
		/// <param name="whereClause"></param>
		/// <param name="wantSingleResult"></param>
		/// <returns></returns>
		public ResultSet<T> CacheGetRows(DBWhere whereClause, bool wantSingleResult)
		{
			ResultSet<T> results = null;
			if (ClassIsCacheable == Cachability.Cacheable)
			{
				CheckCache();

				LockCache();
				try
				{
					// If it's a growing cache, we can't be sure our result set includes all matches
					if (_isGrowingCache == CacheType.GrowingCache && !_retrievedAllRows)
					{
						if ((DBLayer<T>.CacheAndPerformance._trackConcurrentDBQueries &&
							!DBLayer<T>.Cache.RetrievedAllRows &&
							DBLayer<T>.CacheAndPerformance._cacheHitCount == 0 &&
							DBLayer<T>.CacheAndPerformance._databaseHitCount > DBLayer<T>.CacheAndPerformance._dbHitsBeforeFullPopulate))
						{
							ResultSet<T> rgAll = DBLayer<T>.DALQuery(null);

							DBLayer<T>.Cache.LockCache();
							try { DBLayer<T>.Cache.HandleGrowingCache(rgAll, null); }
							finally { DBLayer<T>.Cache.UnlockCache(); }							
						}
					}

					_queryingCacheCount++;
					if (_cacheObjects._sortDescending && _cacheObjects.RowCount < 50000)
					{
						_cacheObjects = DBLayer<T>.Sorting.SortResultSetByUntypedProperty(_cacheObjects, _cacheObjects._sortedBy, false);
						_cacheObjects._sortDescending = false;
						//_findAllLookup.InvalidateCache();
						//_findFirstLookup.InvalidateCache();
					}
				}
				finally { UnlockCache(); }

				string recommendedSort = null;
				try
				{
					if (whereClause == null)
					{
						results = new ResultSet<T>();
						results._cachedResultSet.Add(_cacheObjects._cachedResultSet._list);
						results._sortedBy = _cacheObjects._sortedBy;
						results._sortDescending = _cacheObjects._sortDescending;
						results._fromCache = true;
						return results;
					}
					else
					{
						results = new ResultSet<T>();
						results._cachedResultSet.Add(_cacheObjects._cachedResultSet._list);
						results._sortedBy = _cacheObjects._sortedBy;
						results._sortDescending = _cacheObjects._sortDescending;
					}
					results = DBLayer<T>.QueryResultSet(results, whereClause, wantSingleResult, ref recommendedSort);
					if (results.RowCount == 0 && _isGrowingCache == CacheType.GrowingCache && !_retrievedAllRows) return null;
					results._fromCache = true;
				}
				finally
				{
					_queryingCacheCount--;
				}

				if (recommendedSort != null)
				{
					LockCache();
					try
					{
						// HEREHERE - 50,000 row limit?
						if (_cacheObjects.RowCount < 50000 && !_cacheObjects._sortedBy.Equals(recommendedSort, StringComparison.OrdinalIgnoreCase))
						{
							while (_queryingCacheCount > 0) Thread.Sleep(5);
							_cacheObjects = DBLayer<T>.Sorting.SortResultSetByUntypedProperty(_cacheObjects, recommendedSort, false);
							_cacheObjects._sortedBy = recommendedSort;
							_cacheObjects._sortDescending = false;
							DBLayer<T>.CacheAndPerformance._bestSortPropertyCount = 16;
							//_findAllLookup.InvalidateCache();
							//_findFirstLookup.InvalidateCache();
						}
					}
					finally { UnlockCache(); }
				}
			}
			return results;
		}

		public void CreateDynamicIndex(PropNameAndVal pair, ResultSet<T> foundResults, long numericValue, string stringValue)
		{
			LockCache();
			try
			{
				// integer index
				if (pair._comparisonCategoryType == ComparisonForType.IntShortByte)
				{
					Dictionary<long, ResultSet<T>> resultDictionary;
					if (DBLayer<T>._integerIndices.TryGetValue(pair._propertyName, out resultDictionary) == false)
					{
						resultDictionary = new Dictionary<long, ResultSet<T>>(16);
						resultDictionary.Add(numericValue, foundResults);

						try { DBLayer<T>._integerIndices.Add(pair._propertyName, resultDictionary); }
						catch { }
					}
					else
					{
						try { resultDictionary.Add(numericValue, foundResults); }
						catch { }
					}
				}
				else if (pair._comparisonCategoryType == ComparisonForType.String)
				{
					// string index
					Dictionary<string, ResultSet<T>> resultDictionary;
					if (DBLayer<T>._stringIndices.TryGetValue(pair._propertyName, out resultDictionary) == false)
					{
						resultDictionary = new Dictionary<string, ResultSet<T>>(16, StringComparer.OrdinalIgnoreCase);
						resultDictionary.Add(stringValue, foundResults);

						try { DBLayer<T>._stringIndices.Add(pair._propertyName, resultDictionary); }
						catch { }
					}
					else
					{
						try { resultDictionary.Add(stringValue, foundResults); }
						catch { }
					}
				}
			}
			finally { UnlockCache(); }
		}

		public CollectionCacheable CollectionCacheableAttribute
		{
			get
			{
				if (_checkedCacheable) return _collectionCacheable;

				DALExtensions.AtomicExecuteIfTrue(
				delegate() { return (!_checkedCacheable); },
				delegate()
				{
					CollectionCacheable cc = DBLayer<T>.GetCustomClassAttribute<CollectionCacheable>();
					if (cc != null) { Interlocked.Exchange<CollectionCacheable>(ref _collectionCacheable, cc); }
				},
				delegate() { _checkedCacheable = true; });

				return _collectionCacheable;
			}
		}
	}

	// removed implementation of IDataSourceViewSchema to avoid adding an extra (Design) dependency
	[TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
	public partial class DBLayer<T> where T : DBLayer<T>, IDBLayer<T>, new()
	{
		public static ICache<T> Cache;

		/// <summary>
		/// Invalidates the global/application level cache for a business object
		/// </summary>
		public static void CacheSetInvalidate()
		{
			if (Cache == null) EnsureInitialized();
			Cache.CacheSetInvalidate();
		}

	}
}

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