I haven't done much .NET development in several years. Now I'm enjoying some of the new stuff with modern .NET. I just want to make sure I'm using things correctly. I'm a solo developer with no one else to bounce ideas off of.
I have a new MVC web application. I'm using the Massive micro-ORM. I'm a big believer in caching database requests. I've also created an EnumWrapper class.
Does anything look terrible in this code?
My caching class:
using System;
using System.Runtime.Caching;
namespace ULH.Common
{
public static class Caching
{
private static readonly object _cacheLock = new object();
public static dynamic LoadFromCache(string cacheKey, int secondsToCache, Func<object> query)
{
object result;
if (secondsToCache == 0)
{
// if we're not caching, clear the cache and return result directly
MemoryCache.Default.Remove(cacheKey);
result = query.Invoke();
}
else
{
// otherwise do the caching, but use a double-check lock
result = MemoryCache.Default[cacheKey];
if (result == null)
{
lock (_cacheLock)
{
result = MemoryCache.Default[cacheKey];
if (result == null)
{
result = query.Invoke();
MemoryCache.Default.Add(cacheKey, result, DateTime.UtcNow.AddSeconds(secondsToCache));
}
}
}
}
return result;
}
}
}
An example of one of my data model objects:
using System;
using ULH.Common;
namespace ULH.Intranet.Chaplaincy
{
public class EncounterTypes : BaseModel
{
private EncounterTypes()
{
this.TableName = "EncounterTypes";
this.PrimaryKeyField = "Id";
}
public static dynamic GetActive()
{
return Caching.LoadFromCache("EncounterTypes_GetActive", 120,
() =>
{
dynamic tbl = new EncounterTypes();
return tbl.Find(Enabled: 1);
});
}
public static dynamic GetById(int id)
{
string cacheKey = "EncounterTypes_GetById_" + id.ToString();
return Caching.LoadFromCache(cacheKey, 600,
() =>
{
dynamic tbl = new EncounterTypes();
return tbl.First(ID: id);
});
}
}
}
Here is my EnumWrapper class, where I also cache the List that is created:
using System;
using System.Collections;
using System.Collections.Generic;
namespace ULH.Common
{
public sealed class EnumWrapper : ReadOnlyCollectionBase
{
Type _enumType;
Type _basetype;
public static EnumWrapper Wrap(Type type)
{
if (type == null || !type.IsEnum)
throw new ArgumentException("Type must be an enum", "type");
return Caching.LoadFromCache("EnumWrapper:" + type.ToString() , 600,
() =>
{
return new EnumWrapper(type);
});
}
private EnumWrapper(Type type)
{
_enumType = type;
_basetype = Enum.GetUnderlyingType(type);
foreach (var item in Enum.GetValues(_enumType))
{
this.InnerList.Add(
new KeyValuePair<object, string>(
Convert.ChangeType(item, _basetype),
item.ToString()
)
);
}
}
}
}
I'm using dynamic
quite a bit due to the Massive ORM.
This won't be a huge system, so I don't want to deal with IOC containers. I'll have one common library (ULH.Common) with caching, the Massive ORM, EnumWrapper, and other common functions. There will then be several small websites using that library.
Suggestions? Does everything look okay?
Edit: George Howarth made some interesting points. I was unaware that MemoryCache was thread-safe. I've now modified my LoadFromCache() method to this:
public static dynamic LoadFromCache(string cacheKey, int secondsToCache, Func<object> query)
{
object tocache = null;
// result will always get the value here
// tocache will only have the value when it was pulled from our method
object result = MemoryCache.Default[cacheKey] ?? (tocache = query.Invoke());
if (secondsToCache > 0)
{
if (tocache != null) // only save to cache if it wasn't there
MemoryCache.Default.Add(cacheKey, tocache, DateTime.UtcNow.AddSeconds(secondsToCache));
}
else
{
// remove from cache only if secondsToCache was zero or less
MemoryCache.Default.Remove(cacheKey);
}
return result;
}
EnumWrapper
class is used to store an instance of the enum for populating drop-downs and other UI widgets. It's selected value is not saved. \$\endgroup\$