Reputation: 716
I have a Web Api controller. The controller has a constructor which expects a given interface. The Interface is injected via Dependency Injection (DI).
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
}
The injected interface (IMyManager) implements methods that consume an Entity Framework repository. There are a few methods that constantly access a table that never changes. Since the controller is under heavy use I noticed that a lot of memory traffic it's being generated due to these continuous calls to the DB and also the cost of materializing/disposing the objects. The context (ISomeContext) is also created via DI.
I created static private members that are instantiated only once and this approach works fine, however, since it's a multi-thread process I need to lock objects to make sure the instance is generated only once. With this approach I am getting rid of those DB and materializing/disposing costs with the penalty of lock wait times.
public class MyManager : IMyManager
{
protected ISomeContext _someContext;
private static IList<MyPOCO> _myTableList;
private static readonly object MyTableListLock = new object();
public FileUploadManager(ISomeContext someContext)
{
_someContext = someContext;
//I want to avoid using this lock... A lazy implementation perhaps?
lock (MyTableListLock)
{
if (_myTableList == null)
{
_myTableList = _someContext.MyPOCO.ToList();
}
}
}
}
Do you have any ideas how to achieve the same result of the previous code without locks? I was thinking about a lazy implementation, however I am kind of lost since the repository is non-static.
Thanks in advance,
Carlos
Upvotes: 1
Views: 9880
Reputation: 716
I followed Matt's suggestion of delegating the responsibility to the DI Container(Autofac). I am sharing pieces of the code I used for solving the Lock issue.
1) I created a separate manager that handles static data and that is referenced internally by other managers and it looks like this:
public sealed class DataManager : IDataManager
{
static DataManager()
{
using (var context = new MyDataContext())
{
_queryableTable1 = context.StaticTable1.AsNoTracking()
.ToList()
.AsQueryable();
}
}
private static readonly IQueryable<StaticTable1> _queryableTable1;
public IQueryable<StaticTable1> QueryableTable1
{
get { return _queryableTable1; }
}
}
The interface looks like this:
public interface IDataManager
{
IQueryable<StaticTable1> QueryableTable1 { get; }
}
2) There are two options, Injecting the class via constructor or property. In my case I used property injection with properties autowired for the controller that references the main manager. Both approaches work. The code looks like this:
var builder = new ContainerBuilder();
builder.RegisterType<DataManager>()
.As<IDataManager>()
.SingleInstance();
//The rest of the container registration go here...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
3) The main manager looks like this:
public class MainManager : IMainManager
{
//Option A: Property injection with public property
public IDataManager DataManager { get; set; }
//Option B: Constructor injection with private member
private IDataManager _dataManager;
public MainManager (IDataManager dataManager)
{
_dataManager = dataManager;
}
//This method shows how to consume the DataManager.
public void MyMethod()
{
var row = DataManager.QueryableTable1.FirstOrDefault();
//Some more logic....
}
}
4) In the case of property injection, the property can be optionally added to the interface as shown below:
public interface IMyManager
{
//This is optional.
IDataManager DataManager { get; set; }
//This method shows how to consume the DataManager.
public void MyMethod();
}
The DataManager is public, however it has a static constructor which instantiates the static types only the first time. The creation of the instance is delegated to Autofac so it is guaranteed that the instance is created only once and that is thread safe by specifying "Singleton". In my case the controller creates instances per request/per lifetime scope, so for every controller there's an instance of the DataManager which will be shared between instances.
NOTE: This is not the full code but I tested it and it works fine.
Upvotes: 1
Reputation: 56909
Lighter-weight Locking
You may get better multi-thread throughput by using the ReaderWriterLockSlim to differentiate a read lock from a write lock to your cache. I based one of my designs on the design from this article which combines ReaderWriterLockSlim
with the lazy lock pattern.
private ReaderWriterLockSlim synclock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
public T GetOrAdd(string key, Func<T> loadFunction)
{
LazyLock lazy;
bool success;
synclock.EnterReadLock();
try
{
success = this.cacheProvider.TryGetValue(key, out lazy);
}
finally
{
synclock.ExitReadLock();
}
if (!success)
{
synclock.EnterWriteLock();
try
{
if (!this.cacheProvider.TryGetValue(key, out lazy))
{
lazy = new LazyLock();
this.cacheProvider.Add(key, lazy);
}
}
finally
{
synclock.ExitWriteLock();
}
}
return lazy.Get(loadFunction);
}
private sealed class LazyLock
{
private volatile bool got;
private object value;
public TValue Get<TValue>(Func<TValue> activator)
{
if (!got)
{
if (activator == null)
{
return default(TValue);
}
lock (this)
{
if (!got)
{
value = activator();
got = true;
}
}
}
return (TValue)value;
}
}
Disposing Context
Since you are not reporting any performance issues other than memory consumption, it is likely that you are not cleaning up your Entity Framework context and doing so will release the memory. WebApi has a mechanism for this. Override the Dispose()
method in the controller.
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_myManager.Dispose();
}
base.Dispose(disposing);
}
}
Of course, you will need to make sure your manager is set up to properly release the Entity Framework context by calling Dispose()
on it. Do note that a controller is always instantiated and disposed within the scope of a single request.
Alternatively, you could always ensure that your Entity Framework context is used inside of a using statement (inside your IMyManager).
using (var context = new MyEFContext())
{
// Run your db action here
}
See this answer for some additional possible alternatives.
Upvotes: 1