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();
}
}
}