I'm fairly new at using dependency injection and I think I must be overlooking something really simple.
I have a Web API project where I'm registering generic repositories. The repositories take a dbContext as a parameter in their constructor.
The behavior I find strange is that I can make one successfull call to the service but any subsequent calls tell me that the dbcontext has been disposed. I do have a using statement in there but that shouldn't be a problem since DI is supposed to be creating new instances of my dependencies for each web request(although I could be wrong).
Here is my generic repository:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
internal DbContext _context;
internal DbSet<T> _dbSet;
private bool disposed;
public GenericRepository(DbContext context)
{
_context = context;
_dbSet = _context.Set<T>();
}
/// <summary>
/// This constructor will set the database of the repository
/// to the one indicated by the "database" parameter
/// </summary>
/// <param name="context"></param>
/// <param name="database"></param>
public GenericRepository(string database = null)
{
SetDatabase(database);
}
public void SetDatabase(string database)
{
var dbConnection = _context.Database.Connection;
if (string.IsNullOrEmpty(database) || dbConnection.Database == database)
return;
if (dbConnection.State == ConnectionState.Closed)
dbConnection.Open();
_context.Database.Connection.ChangeDatabase(database);
}
public virtual IQueryable<T> Get()
{
return _dbSet;
}
public virtual T GetById(object id)
{
return _dbSet.Find(id);
}
public virtual void Insert(T entity)
{
_dbSet.Add(entity);
}
public virtual void Delete(object id)
{
T entityToDelete = _dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(T entityToDelete)
{
if (_context.Entry(entityToDelete).State == EntityState.Detached)
{
_dbSet.Attach(entityToDelete);
}
_dbSet.Remove(entityToDelete);
}
public virtual void Update(T entityToUpdate)
{
_dbSet.Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void Save()
{
_context.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
//free managed objects here
_context.Dispose();
}
//free any unmanaged objects here
disposed = true;
}
~GenericRepository()
{
Dispose(false);
}
}
Here is my generic repository interface:
public interface IGenericRepository<T> : IDisposable where T : class
{
void SetDatabase(string database);
IQueryable<T> Get();
T GetById(object id);
void Insert(T entity);
void Delete(object id);
void Delete(T entityToDelete);
void Update(T entityToUpdate);
void Save();
}
This is my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var container = new UnityContainer();
container.RegisterType<IGenericRepository<Cat>, GenericRepository<Cat>>(new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities()));
container.RegisterType<IGenericRepository<Dog>, GenericRepository<Dog>>(new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities()));
config.DependencyResolver = new UnityResolver(container);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
This is my DependencyResolver(which is pretty standard):
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer container;
public UnityResolver(IUnityContainer container)
{
this.container = container ?? throw new ArgumentNullException(nameof(container));
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
container.Dispose();
}
}
And finally this is part of the controller that's giving me trouble:
public class AnimalController : ApiController
{
private readonly IGenericRepository<Cat> _catRepo;
private readonly IGenericRepository<Dog> _dogPackRepo;
public AnimalController(IGenericRepository<Cat> catRepository,
IGenericRepository<Dog> dogRepository)
{
_catRepo = catRepository;
_dogRepo = dogRepository;
}
[HttpGet]
public AnimalDetails GetAnimalDetails(int tagId)
{
var animalDetails = new animalDetails();
try
{
var dbName = getAnimalName(tagId);
if (dbName == null)
{
animalDetails.ErrorMessage = $"Could not find animal name for tag Id {tagId}";
return animalDetails;
}
}
catch (Exception ex)
{
//todo: add logging
Console.WriteLine(ex.Message);
animalDetails.ErrorMessage = ex.Message;
return animalDetails;
}
return animalDetails;
}
private string getAnimalName(int tagId)
{
try
{
//todo: fix DI so dbcontext is created on each call to the controller
using (_catRepo)
{
return _catRepo.Get().Where(s => s.TagId == tagId.ToString()).SingleOrDefault();
}
}
catch (Exception e)
{
//todo: add logging
Console.WriteLine(e);
throw;
}
}
}
The using statement around the _catRepo object is not behaving as expected. After I make the first service call the _catRepo is disposed of. On a subsequent call I'm expecting to have a new _catRepo instantiated. However, this is not the case since the error I'm getting talks about the dbcontext being disposed.
I've tried changing the LifeTimeManager to some of the other ones available but that didn't help.
I also started going down a different route where the generic repository would take a second generic class and instantiate its own dbcontext from it. However, when I did that Unity couldn't find my controller's single-parameter constructor.
I guess what I really need, per the comments below, is a way to instantiate the DbContext
on a per-request basis. I don't know how to go about doing that though.
Any hints would be greatly appreciated.