DBQueryCache.cs
1 //#define PoCv2
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Runtime.Caching;
6 using System.Diagnostics;
7 using System.Windows.Forms;
8 
9 namespace sage.ew.db
10 {
11 
15  public static class QueryCache
16  {
17  #region Variables
18 
22  private static readonly int MAX_LEGHT_TRACE = 200;
23 
27  private static List<string> _cUpdateTableWords = new List<string>() { " UPDATE ", "INSERT", "DELETE", "DROP", "TRUNCATE" };
28 
32  private static List<string> lisTablasSistema = new List<string>() { "[LOG_ANALISIS]", ".LOG_ANALISIS", "[CONNECT], [LOG_ERROR]" };
33 
37  private static List<string> lisIdentificadoresTablas = new List<string>() { ".DBO.", ".INFORMATION_SCHEMA.", ".SYS." };
38 
39  private static ulong TotalPeticiones = 0;
40  private static ulong RespuestasDeCache = 0;
41  private static ulong InsercionesEnCache = 0;
42 #if PoCv2
43  private static int MaximoDeTablas = 0;
44  private static ulong EliminacionesItemsDeCache = 0;
45  private static ulong EliminacionesTablasDeCache = 0;
46  private static List<string> ListaClavesFallidas = new List<string>();
47 
48  static Dictionary<string, List<string>> _IndiceTablas = new Dictionary<string, List<string>>();
49  static Dictionary<string, long> _CosteConsulta = new Dictionary<string, long>();
50  private static long AhorroCoste = 0;
51 #endif
52 
53  #endregion Variables
54 
63  public static T GetObjectFromCache<T>(string tcNomItem, int tnTiempoEnCache, Func<T> toFuncionObtencionDatos)
64  {
65  TotalPeticiones++;
66 
67  ObjectCache loCache = MemoryCache.Default;
68  var objetoDeCache = (T)loCache[tcNomItem];
69  if (objetoDeCache == null)
70  {
71  //Trace.WriteLineIf(Debugger.IsAttached, tcNomItem);
72 
73  CacheItemPolicy policy = new CacheItemPolicy
74  {
75  AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(tnTiempoEnCache)
76  };
77 #if PoCv2
78  Stopwatch timer = new Stopwatch();
79  timer.Start();
80 #endif
81  objetoDeCache = toFuncionObtencionDatos();
82 #if PoCv2
83  timer.Stop();
84  var coste = timer.ElapsedMilliseconds;
85 #endif
86  loCache.Set(tcNomItem, objetoDeCache, policy);
87 
88  InsercionesEnCache++;
89 #if PoCv2
90  var tablasAfectadas = ObtenerTablasDelQuery(tcNomItem);
91  tablasAfectadas.ForEach(t => RegistrarQuerys(t, tcNomItem));
92  RegistrarCoste(tcNomItem, coste);
93 #endif
94  }
95  else
96  {
97  RespuestasDeCache++;
98 #if PoCv2
99  AhorroCoste += ObtenerCoste(tcNomItem);
100 #endif
101  }
102 
103 
104  //Si se modifican datos hago saltar la cache
105  ComprobarSiHayModificacionDeDatos(tcNomItem);
106 
107  return objetoDeCache;
108  }
109 
110  private static bool elQueryModificaDatos(string tcQuery)
111  {
112  if (!string.IsNullOrWhiteSpace(tcQuery) && _cUpdateTableWords.Any(lcWord => tcQuery.ToUpper().Contains(lcWord.ToUpper())))
113  return true;
114  else
115  return false;
116  }
117 
118 #if PoCv2
119  private static bool elQueryModificaDatos(string tcQuery, out List<string> tablasAfectadas)
120  {
121  tablasAfectadas = new List<string>();
122  if (elQueryModificaDatos(tcQuery))
123  {
124  tablasAfectadas = ObtenerTablasDelQuery(tcQuery);
125  return true;
126  }
127  else
128  {
129  return false;
130  }
131  }
132 
133  private static List<string> ObtenerTablasDelQuery(string tcQuery)
134  {
135  //Los querys que modifican datos suelen afecta únicamente a una tabla pe. insert, update
136  var partesDelQuery = tcQuery.ToUpper().Split(' ');
137 
138  var listaTablas = partesDelQuery.Where(p => lisIdentificadoresTablas.Any(ident => p.ToUpper().Contains(ident))).ToList();
139  if (listaTablas.Count > MaximoDeTablas)
140  MaximoDeTablas = listaTablas.Count;
141 
142  if (!listaTablas.Any())
143  ListaClavesFallidas.Add(tcQuery);
144 
145  return listaTablas;
146  }
147 #endif
148 
153  public static void ComprobarSiHayModificacionDeDatos(string tcQuery)
154  {
155 #if PoCv2 //Si se modifican datos hago saltar la cache
156  if (elQueryModificaDatos(tcQuery, out List<string> tablasAfectadas))
157  {
158  if (lisTablasSistema.Any(lcWord => tcQuery.ToUpper().Contains(lcWord.ToUpper())))
159  {
160  Trace.WriteLineIf(Debugger.IsAttached, " => Evitar que el query haga saltar la cache => " + queryToTrace(tcQuery), "Cache"); //Operación de sistema
161  }
162  else
163  {
164  tablasAfectadas.ForEach(t => BorrarQuerysCacheTabla(t));
165  Trace.WriteLineIf(Debugger.IsAttached, "Query que modifica datos => " + queryToTrace(tcQuery), "Cache");
166  }
167  }
168 #else
169  //Si se modifican datos hago saltar la cache
170  if (elQueryModificaDatos(tcQuery))
171  {
172  if (lisTablasSistema.Any(lcWord => tcQuery.ToUpper().Contains(lcWord.ToUpper())))
173  Trace.WriteLineIf(Debugger.IsAttached, " => Evitar que el query haga saltar la cache => " + queryToTrace(tcQuery), "Cache");
174  else
175  {
176  ResetCache();
177  Trace.WriteLineIf(Debugger.IsAttached, "Query que hace saltar la cache => " + queryToTrace(tcQuery), "Cache");
178  }
179  }
180 #endif
181  }
182 
183 #if PoCv2
184  private static void BorrarQuerysCacheTabla(string tabla)
185  {
186  if (_IndiceTablas.ContainsKey(tabla))
187  {
188  EliminacionesTablasDeCache++;
189 
190  ObjectCache loCache = MemoryCache.Default;
191 
192  foreach (string key in _IndiceTablas[tabla])
193  {
194  EliminacionesItemsDeCache++;
195  loCache.Remove(key);
196  }
197 
198  Trace.WriteLineIf(Debugger.IsAttached, $"Eliminar tabla de la cache {tabla} con {_IndiceTablas[tabla].Count} items registrados", "Cache");
199 
200  _IndiceTablas[tabla].Clear();
201  _IndiceTablas.Remove(tabla);
202  }
203  }
204 
205  private static void RegistrarQuerys(string tabla, string key)
206  {
207  if (_IndiceTablas.ContainsKey(tabla))
208  {
209  if (!_IndiceTablas[tabla].Contains(key))
210  {
211  _IndiceTablas[tabla].Add(key);
212  }
213  }
214  else
215  {
216  _IndiceTablas.Add(tabla, new List<string>() { key });
217  }
218  }
219 
220  private static void RegistrarCoste(string key, long coste)
221  {
222  if (_CosteConsulta.ContainsKey(key))
223  _CosteConsulta[key] = coste;
224  else
225  _CosteConsulta.Add(key, coste);
226  }
227 
228  private static long ObtenerCoste(string key)
229  {
230  if (_CosteConsulta.ContainsKey(key))
231  {
232  return _CosteConsulta[key];
233  }
234 
235  return 0;
236  }
237 #endif
238 
239  private static string queryToTrace(string tcQuery)
240  {
241  if (tcQuery.Length > MAX_LEGHT_TRACE)
242  return tcQuery.Substring(0, MAX_LEGHT_TRACE) + "*** TRUNCADO ***";
243  else
244  return tcQuery;
245  }
246 
252  public static bool ContaninsKey(string tcKey)
253  {
254  ObjectCache cache = MemoryCache.Default;
255  return cache.Contains(tcKey);
256  }
257 
258  internal static void VaciarCache()
259  {
260 #if PoCv2
261  EstadoDeLaCache("Vaciado final");
262  _IndiceTablas.Clear();
263  ReiniciarEstadisticas();
264 #else
265  Trace.WriteLineIf(Debugger.IsAttached, "*********** Vaciado final de cache *************", "Cache");
266 #endif
267 
268  ObjectCache loCache = MemoryCache.Default;
269  loCache.__Clear();
270  GC.Collect();
271  }
272 
273  internal static void ResetCache()
274  {
275 #if PoCv2
276  EstadoDeLaCache("Reset de cache");
277  _IndiceTablas.Clear();
278  ReiniciarEstadisticas();
279 #else
280  Trace.WriteLineIf(Debugger.IsAttached, "*********** Reset de cache *************", "Cache");
281 #endif
282 
283 
284  ObjectCache loCache = MemoryCache.Default;
285  loCache.__Clear();
286  }
287 
288 #if PoCv2
289  private static void ReiniciarEstadisticas()
290  {
291  TotalPeticiones = 0;
292  RespuestasDeCache = 0;
293  AhorroCoste = 0;
294  EliminacionesTablasDeCache = 0;
295  EliminacionesItemsDeCache = 0;
296  InsercionesEnCache = 0;
297  MaximoDeTablas = 0;
298  ListaClavesFallidas.Clear();
299  }
300 
301  public static void EstadoDeLaCache(string momento = "sin especificar")
302  {
303  ObjectCache loCache = MemoryCache.Default;
304  var items = loCache.Count();
305 
306  var tablas = _IndiceTablas.Count;
307  var listaTablas = new List<string>();
308  var claves = 0;
309  foreach (var tabla in _IndiceTablas.Keys)
310  {
311  claves += _IndiceTablas[tabla].Count;
312  listaTablas.Add(tabla);
313  }
314 
315  //Debugger.Launch();
316 
317  if (Debugger.IsAttached)
318  {
319  Trace.WriteLineIf(Debugger.IsAttached, $"*********** ESTADO DE LA CACHE ({momento}) *************", "Cache");
320  Trace.WriteLineIf(Debugger.IsAttached, $"Items {items}", "Cache");
321  Trace.WriteLineIf(Debugger.IsAttached, $"Total peticiones al motor SQL {TotalPeticiones}", "Cache");
322  Trace.WriteLineIf(Debugger.IsAttached, $"Peticiones atendidas con elementos de cache {RespuestasDeCache}", "Cache");
323  Trace.WriteLineIf(Debugger.IsAttached, $" * Estimación de coste ahorrado acumulado con cache {AhorroCoste} ms.", "Cache");
324  Trace.WriteLineIf(Debugger.IsAttached, $"Máximo de tablas de un elemento de cache {MaximoDeTablas}", "Cache");
325  Trace.WriteLineIf(Debugger.IsAttached, $"Diccionario con {tablas} tablas y {claves} claves", "Cache");
326  Trace.WriteLineIf(Debugger.IsAttached, $"Inserciones en cache {InsercionesEnCache}", "Cache");
327  Trace.WriteLineIf(Debugger.IsAttached, $"Eliminaciones de indices {EliminacionesTablasDeCache}", "Cache");
328  Trace.WriteLineIf(Debugger.IsAttached, $"Eliminaciones de elementos {EliminacionesItemsDeCache}", "Cache");
329  Trace.WriteLineIf(Debugger.IsAttached, $"Memoria (del proceso, no de la cache): ", "Cache");
330  traceUsoDeMemoria();
331 
333  //var detalleTablas = string.Join(" || ", listaTablas);
334  //Trace.WriteLineIf(Debugger.IsAttached, $"Tablas: ", "Cache");
335  //Trace.WriteLineIf(Debugger.IsAttached, $"{detalleTablas} ", "Cache");
336 
338  //Trace.WriteLineIf(Debugger.IsAttached, $"Claves fallidas: ", "Cache");
339  //ListaClavesFallidas.ForEach(cf => Trace.WriteLineIf(Debugger.IsAttached, $"Fallo de clave con: {cf}", "Cache"));
340  }
341  //else
342  //{
343  // var msg = $"*********** ESTADO DE LA CACHE ({momento}) *************" + Environment.NewLine +
344  // $"Items {items}" + Environment.NewLine +
345  // $"Total peticiones al motor SQL {TotalPeticiones}" + Environment.NewLine +
346  // $"Peticiones atendidas con elementos de cache {RespuestasDeCache}" + Environment.NewLine +
347  // $" * Estimación de coste ahorrado acumulado con cache {AhorroCoste} ms." + Environment.NewLine +
348  // $"Máximo de tablas de un elemento de cache {MaximoDeTablas}" + Environment.NewLine +
349  // $"Diccionario con {tablas} tablas y {claves} claves" + Environment.NewLine +
350  // $"Inserciones en cache {InsercionesEnCache}" + Environment.NewLine +
351  // $"Eliminaciones de indices {EliminacionesTablasDeCache}" + Environment.NewLine +
352  // $"Eliminaciones de elementos {EliminacionesItemsDeCache}" + Environment.NewLine +
353  // $"Memoria (del proceso, no de la cache): " + Environment.NewLine;
354 
355  // MessageBox.Show(msg, "Uso de cache");
356  //}
357 
358  }
359 #endif
360 
361  private static void traceUsoDeMemoria()
362  {
363  Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
364  long totalBytesOfMemoryUsed = currentProcess.WorkingSet64;
365  Trace.WriteLineIf(Debugger.IsAttached, "Total: " + totalBytesOfMemoryUsed + " bytes - " + (totalBytesOfMemoryUsed * 0.000001) + " Mb." , "Cache");
366  }
367  }
368 
372  public static class QueryCacheExtensions
373  {
378  public static void __Clear(this ObjectCache cache)
379  {
380  List<string> cacheKeys = cache.Select(kvp => kvp.Key).ToList();
381  foreach (string cacheKey in cacheKeys)
382  {
383  cache.Remove(cacheKey);
384  }
385  }
386  }
387 }