11using Enyim . Caching ;
22using Enyim . Caching . Memcached ;
3+ using Newtonsoft . Json . Linq ;
34using System ;
45using System . Collections . Generic ;
56using System . Configuration ;
@@ -18,6 +19,11 @@ public static class MemCacheD
1819 /// </summary>
1920 internal static bool API_MEMCACHED_ENABLED = Convert . ToBoolean ( ConfigurationManager . AppSettings [ "API_MEMCACHED_ENABLED" ] ) ;
2021
22+ /// <summary>
23+ /// Max size in MB before splitting a string record in sub-cache entries
24+ /// </summary>
25+ internal static uint API_MEMCACHED_MAX_SIZE = Convert . ToUInt32 ( ConfigurationManager . AppSettings [ "API_MEMCACHED_MAX_SIZE" ] ) ;
26+
2127 /// <summary>
2228 /// Maximum validity in number of seconds that MemCacheD can handle (30 days = 2592000)
2329 /// </summary>
@@ -38,6 +44,11 @@ public static class MemCacheD
3844 /// </summary>
3945 internal static MemcachedClient MemcachedClient = API_MEMCACHED_ENABLED ? new MemcachedClient ( ) : null ;
4046
47+ /// <summary>
48+ /// SubKey prefix
49+ /// </summary>
50+ internal static String SubKeyPrefix = "subKey_" ;
51+
4152 #endregion
4253
4354 #region Methods
@@ -99,7 +110,7 @@ private static string GenerateKey_ADO(string nameSpace, string procedureName, Li
99110 }
100111
101112 /// <summary>
102- /// Generate the Key for ADO
113+ /// Generate the Key for BSO
103114 /// </summary>
104115 /// <typeparam name="T"></typeparam>
105116 /// <param name="nameSpace"></param>
@@ -142,6 +153,35 @@ private static string GenerateKey_BSO<T>(string nameSpace, string className, str
142153 }
143154 }
144155
156+ /// <summary>
157+ /// Get a SubKey for a Key
158+ /// </summary>
159+ /// <param name="key"></param>
160+ /// <returns></returns>
161+ private static string GetSubKey ( string key )
162+ {
163+ return SubKeyPrefix + key ;
164+ }
165+
166+ /// <summary>
167+ /// Check if a subKey exists
168+ /// </summary>
169+ /// <param name="key"></param>
170+ /// <returns></returns>
171+ private static bool IsSubKey ( dynamic data , string key )
172+ {
173+ // A key must be either a String or a JValue returned from deserialisation
174+ if ( data . GetType ( ) == typeof ( String ) || data . GetType ( ) == typeof ( JValue ) )
175+ {
176+ // Check the explicit casting to String against the subKey
177+ if ( ( String ) data == GetSubKey ( key ) )
178+ {
179+ return true ;
180+ }
181+ }
182+ return false ;
183+ }
184+
145185 /// <summary>
146186 /// Generate the Hash
147187 /// </summary>
@@ -326,9 +366,33 @@ public static bool Store_BSO<T>(string nameSpace, string className, string metho
326366 /// <param name="key"></param>
327367 /// <param name="value"></param>
328368 /// <param name="validFor"></param>
369+ /// <param name="repository"></param>
329370 /// <returns></returns>
330371 private static bool Store ( string key , MemCachedD_Value value , TimeSpan validFor , string repository )
331372 {
373+ // Check if data is of type String or JValue
374+ if ( value . data . GetType ( ) == typeof ( String ) || value . data . GetType ( ) == typeof ( JValue ) )
375+ {
376+ // Cast data to String and check if oversized
377+ string sData = ( String ) value . data ;
378+ if ( sData . Length * sizeof ( Char ) > API_MEMCACHED_MAX_SIZE * 1024 * 1024 )
379+ {
380+ // Get a subKey
381+ string subKey = GetSubKey ( key ) ;
382+
383+ // SubStore the data by subKey
384+ if ( SubStore ( subKey , sData , validFor , repository ) )
385+ {
386+ // Override data with the subKey to fish it out later
387+ value . data = subKey ;
388+ }
389+ else
390+ {
391+ return false ;
392+ }
393+ }
394+ }
395+
332396 try
333397 {
334398 // The value must be serialised
@@ -371,6 +435,57 @@ private static bool Store(string key, MemCachedD_Value value, TimeSpan validFor,
371435 }
372436 }
373437
438+ /// <summary>
439+ /// SubStore the data string by subKey
440+ /// </summary>
441+ /// <param name="subKey"></param>
442+ /// <param name="data"></param>
443+ /// <param name="validFor"></param>
444+ /// <param name="repository"></param>
445+ /// <returns></returns>
446+ private static bool SubStore ( string subKey , string data , TimeSpan validFor , string repository )
447+ {
448+ try
449+ {
450+ // The data is a string, no need to serialize
451+ Log . Instance . Info ( "SubCache Size Uncompressed (Byte): " + data . Length * sizeof ( Char ) ) ;
452+
453+ // The data must be compressed
454+ string subCacheCompressed = Utility . GZipCompress ( data ) ;
455+ Log . Instance . Info ( "SubCache Size Compressed (Byte): " + subCacheCompressed . Length * sizeof ( Char ) ) ;
456+
457+ bool isStored = false ;
458+
459+ // Fix the MemCacheD issue with bad Windows' compiled version: use validFor instead of expiresAt
460+ // validFor and expiresAt match each other
461+ isStored = MemcachedClient . Store ( StoreMode . Set , subKey , subCacheCompressed , validFor ) ;
462+
463+ // Store Value by Key
464+ if ( isStored )
465+ {
466+ Log . Instance . Info ( "SubStore succesfull: " + subKey ) ;
467+
468+ // Add the cached record into a Repository
469+ if ( ! String . IsNullOrEmpty ( repository ) )
470+ {
471+ CasRepositoryStore ( subKey , repository ) ;
472+ }
473+
474+ return true ;
475+ }
476+ else
477+ {
478+ Log . Instance . Fatal ( "SubStore failed: " + subKey ) ;
479+ return false ;
480+ }
481+ }
482+ catch ( Exception e )
483+ {
484+ Log . Instance . Fatal ( e ) ;
485+ return false ;
486+ }
487+ }
488+
374489 /// <summary>
375490 /// Add a Key into a Cas Repository
376491 /// N.B. Cas records DO NOT exipire, see Enyim inline documentation
@@ -578,12 +693,10 @@ private static MemCachedD_Value Get(string key)
578693 Log . Instance . Info ( "Cache Valid For (s): " + cacheValidFor . TotalSeconds . ToString ( ) ) ;
579694 Log . Instance . Info ( "Cache Has Data: " + cache . hasData . ToString ( ) ) ;
580695
581- if ( ! Convert . ToBoolean ( cache . hasData ) )
582- {
583- Log . Instance . Info ( "Force removal of cache without data" ) ;
584- // Remove the record with no data
585- Remove ( key ) ;
586- }
696+ // Init subKey
697+ string subKey = GetSubKey ( key ) ;
698+ // Init isSubKey
699+ bool isSubKey = IsSubKey ( cache . data , key ) ;
587700
588701 // double check the cache record is still valid if not cleared by the garbage collector
589702 if ( cacheExpiresAt > DateTime . Now
@@ -594,22 +707,45 @@ private static MemCachedD_Value Get(string key)
594707 value . expiresAt = cacheExpiresAt ;
595708 value . validFor = cacheValidFor ;
596709 value . hasData = Convert . ToBoolean ( cache . hasData ) ;
597- value . data = cache . data ;
710+ value . data = isSubKey ? null : cache . data ;
711+
712+ // Check for data in the subKey
713+ if ( isSubKey )
714+ {
715+ // Get subCacheCompressed from the subKey
716+ string subCacheCompressed = MemcachedClient . Get < string > ( subKey ) ;
717+
718+ if ( ! String . IsNullOrEmpty ( subCacheCompressed ) )
719+ {
720+ Log . Instance . Info ( "SubCache found: " + subKey ) ;
721+ Log . Instance . Info ( "SubCache Size Compressed (Byte): " + subCacheCompressed . Length * sizeof ( Char ) ) ;
722+
723+ // Decompress the cache
724+ string subCache = Utility . GZipDecompress ( subCacheCompressed ) ;
725+ Log . Instance . Info ( "SubCache Size Decompressed (Byte): " + subCache . Length * sizeof ( Char ) ) ;
726+
727+ value . data = subCache ;
728+ }
729+ else
730+ {
731+ Log . Instance . Info ( "SubCache not found: " + key ) ;
732+ }
733+ }
598734
599735 return value ;
600736 }
601737 else
602738 {
603- Log . Instance . Info ( "Force removal of expired cache" ) ;
739+ Log . Instance . Info ( "Forced removal of expired cache" ) ;
604740 // Remove the expired record
605741 Remove ( key ) ;
742+
606743 }
607744 }
608745 else
609746 {
610747 Log . Instance . Info ( "Cache not found: " + key ) ;
611748 }
612-
613749 }
614750 catch ( Exception e )
615751 {
@@ -666,7 +802,15 @@ private static bool Remove(string key)
666802
667803 try
668804 {
669- // Remove the record by the Key
805+ string subKey = GetSubKey ( key ) ;
806+
807+ // Remove the (optional) subKey
808+ if ( MemcachedClient . Remove ( subKey ) )
809+ {
810+ Log . Instance . Info ( "SubRemoval succesfull: " + subKey ) ;
811+ }
812+
813+ // Remove the Key
670814 if ( MemcachedClient . Remove ( key ) )
671815 {
672816 Log . Instance . Info ( "Removal succesfull: " + key ) ;
0 commit comments