I'm working on a project with the following technologies:
I'm getting the error
The operation cannot be completed because the DbContext has been disposed
in the class ServerHub where I put the following:
// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
Anyone has any clue of why I'm getting that? I've read a lot of answers but nothing I've tried to so far seems to fix it.
EF Generic Repository (EF5)
public class EFRepository<T> : IRepository<T> where T : class
{
public EFRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("dbContext");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
protected DbContext DbContext { get; set; }
protected DbSet<T> DbSet { get; set; }
public virtual IQueryable<T> GetAll()
{
return DbSet;
}
public virtual IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = DbContext.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public virtual T GetById(long id)
{
return DbSet.Find(id);
}
public virtual IQueryable<T> GetByPredicate(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = DbContext.Set<T>().Where(predicate);
return query;
}
public virtual IQueryable<T> GetByPredicateIncluding(System.Linq.Expressions.Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = DbContext.Set<T>().Where(predicate);
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public virtual void Upsert(T entity, Func<T, bool> insertExpression)
{
if (insertExpression.Invoke(entity))
{
Add(entity);
}
else
{
Update(entity);
}
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
public virtual void Delete(int id)
{
var entity = GetById(id);
if (entity == null) return; // not found; assume already deleted.
Delete(entity);
}
}
HubsInstaller
using Microsoft.AspNet.SignalR;
public class HubsInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component
.For<RepositoryFactories>()
.ImplementedBy<RepositoryFactories>()
.LifestyleSingleton());
container.Register(Component
.For<IRepositoryProvider>()
.ImplementedBy<RepositoryProvider>()
.LifestylePerWebRequest());
container.Register(Component
.For<IGdpUow>()
.ImplementedBy<GdpUow>()
.LifestylePerWebRequest());
container.Register(Classes.FromThisAssembly()
.BasedOn<Hub>()
.LifestyleTransient());
}
}
IocConfig.cs
using System.Web.Routing;
using Microsoft.AspNet.SignalR;
namespace CompanyGdpSoftware.Server.Ui.Web
{
using System.Web.Http;
using System.Web.Mvc;
using Castle.Windsor;
using CommonServiceLocator.WindsorAdapter;
using Infrastructure;
using Microsoft.Practices.ServiceLocation;
public static class IocConfig
{
public static IWindsorContainer Container { get; private set; }
public static void RegisterIoc(HttpConfiguration config)
{
var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
GlobalHost.DependencyResolver = signalrDependency;
//RouteTable.Routes.MapHubs(signalrDependency); // Needed to remove the parameter when moved from SignalR RC to 1.1.2
RouteTable.Routes.MapHubs(); // Used this one when moving to SignalR release update.
// Set the dependency resolver for Web API.
var webApicontainer = new WindsorContainer().Install(new WebWindsorInstaller());
GlobalConfiguration.Configuration.DependencyResolver = new WebApiWindsorDependencyResolver(webApicontainer);
// Set the dependency resolver for Mvc Controllers
Container = new WindsorContainer().Install(new ControllersInstaller());
DependencyResolver.SetResolver(new MvcWindsorDependencyResolver(Container));
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(Container));
var controllerFactory = new WindsorControllerFactory(Container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
}
}
UoW (Unit of Work)
public class GdpUow : IGdpUow, IDisposable
{
public GdpUow(IRepositoryProvider repositoryProvider)
{
CreateDbContext();
repositoryProvider.DbContext = DbContext;
RepositoryProvider = repositoryProvider;
}
public IRepository<Branch> Branches { get { return GetStandardRepo<Branch>(); } }
public void Commit()
{
DbContext.SaveChanges();
}
protected void CreateDbContext()
{
DbContext = new GdpSoftwareDbContext();
// Do NOT enable proxied entities, else serialization fails
DbContext.Configuration.ProxyCreationEnabled = false;
// Load navigation properties explicitly (avoid serialization trouble)
DbContext.Configuration.LazyLoadingEnabled = false;
// Because Web API will perform validation, I don't need/want EF to do so
DbContext.Configuration.ValidateOnSaveEnabled = false;
}
protected IRepositoryProvider RepositoryProvider { get; set; }
private IRepository<T> GetStandardRepo<T>() where T : class
{
return RepositoryProvider.GetRepositoryForEntityType<T>();
}
private T GetRepo<T>() where T : class
{
return RepositoryProvider.GetRepository<T>();
}
private GdpSoftwareDbContext DbContext { get; set; }
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
if (DbContext != null)
{
DbContext.Dispose();
DbContext = null;
}
}
#endregion
}
}
Extract from ServerHub
using System;
using System.Linq;
using System.Timers;
using Data.Contracts;
using Data.Model;
using Microsoft.AspNet.SignalR;
public class ServerHub : Hub
{
private static System.Timers.Timer aTimer;
public IGdpUow Uow { get; set; }
DateTime lastDate = DateTime.UtcNow;
public ServerHub()
{
// Create a timer with a ten second interval.
aTimer = new System.Timers.Timer(10000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Enabled = true;
// If the timer is declared in a long-running method, use
// KeepAlive to prevent garbage collection from occurring
// before the method ends.
GC.KeepAlive(aTimer);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
SendNewMessage(e.SignalTime);
}
public void SendNewMessage(DateTime SignalTime)
{
// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
var configurationsRecord = this.Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeCheck")).SingleOrDefault();
if (configurationsRecord == null)
{
throw new ApplicationException("Please set the LastDateTimeCheck value");
}
}
// Called from the client
public void GetAllMessages()
{
var MessagesList = Uow.Messages.GetAll().Select(
newMessage => new MessageDto
{
Country = newMessage.Country,
CountryId = newMessage.CountryId ?? 0,
MessageId = newMessage.MessageId
});
Clients.All.handleGetAll(MessagesList);
}
}
UPDATE I've added this that Drew suggested...still no luck
using System;
using System.Linq;
using System.Timers;
using Data.Contracts;
using Data.Model;
using Microsoft.AspNet.SignalR;
public class ServerHub : Hub
{
private static System.Timers.Timer aTimer;
DateTime lastDate = DateTime.UtcNow;
public IHubHandler hubHandler { get; set; }
public ServerHub()
{
// Create a timer with a ten second interval.
aTimer = new System.Timers.Timer(10000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Enabled = true;
// If the timer is declared in a long-running method, use
// KeepAlive to prevent garbage collection from occurring
// before the method ends.
GC.KeepAlive(aTimer);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
HubHandler.SendNewMessage(e.SignalTime);
}
// Called from the client
public void GetAllMessages()
{
var MessagesList = Uow.Messages.GetAll().Select(
newMessage => new MessageDto
{
Country = newMessage.Country,
CountryId = newMessage.CountryId ?? 0,
MessageId = newMessage.MessageId
});
Clients.All.handleGetAll(MessagesList);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
hubHandler.SendNewMessage(e.SignalTime);
}
}
and the new class
HubHandler
using System;
using System.Linq;
using Data.Contracts;
using Data.Model;
using Microsoft.AspNet.SignalR;
public class HubHandler : IHubHandler
{
public IGdpUow Uow { get; set; }
DateTime lastDate = DateTime.UtcNow;
public void SendNewMessage(DateTime signalTime)
{
// Get a hub context for ServerHub
var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
var gdpConfigurationRecord = Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
if (gdpConfigurationRecord == null)
{
throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
}
var lastMessagesDateTimeCheck = gdpConfigurationRecord.DateTimeValue;
// Send a message to all the clients
serverHub.Clients.All.handleNewMessages("message");
gdpConfigurationRecord.DateTimeValue = signalTime.ToUniversalTime();
Uow.GdpConfigurations.Update(gdpConfigurationRecord);
}
}
}
UPDATE 2
Now I moved the timer out of the Hub to the HubHandler. Also I installed Nuget package to use LifeStyle.HybridPerWebRequestTransient for GdpUow and RepositoryProvider Still the same issue.
ServerHub
namespace GdpSoftware.App.Ui.Web.Hubs
{
using System;
using System.Linq;
using Data.Contracts;
using Data.Model;
using Microsoft.AspNet.SignalR;
public class ServerHub : Hub
{
public IGdpUow Uow { get; set; }
public IHubHandler hubHandler { get; set; }
public void GetAllMessages()
{
var messagesList = Uow.Messages.GetAll().Select(
newMessage => new MessageDto
{
MessageId = newMessage.MessageId,
Messagestatus = newMessage.MessageStatus.Description
});
hubHandler.SetClients(Clients);
hubHandler.StartTimer();
Clients.All.handleMessages(messagesList);
}
}
}
HubInstaller
public class HubsInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component
.For<RepositoryFactories>()
.ImplementedBy<RepositoryFactories>()
.LifestyleSingleton());
container.Register(Component
.For<IRepositoryProvider>()
.ImplementedBy<RepositoryProvider>()
.LifeStyle.HybridPerWebRequestTransient());
container.Register(Component
.For<IGdpUow>()
.ImplementedBy<GdpUow>()
.LifeStyle.HybridPerWebRequestTransient());
container.Register(Component
.For<IHubHandler>()
.ImplementedBy<HubHandler>()
.LifestyleSingleton());
container.Register(Classes.FromThisAssembly()
.BasedOn<Hub>()
.LifestyleTransient());
}
}
HubHandler
public class HubHandler : IHubHandler
{
private static System.Timers.Timer aTimer;
private IHubConnectionContext Clients { get; set; }
public IGdpUow Uow { get; set; }
DateTime lastDate = DateTime.UtcNow;
public void SetClients(IHubConnectionContext clients)
{
Clients = clients;
}
public void StartTimer()
{
aTimer = new System.Timers.Timer(10000);
aTimer.Elapsed += new ElapsedEventHandler(SendNewMessage);
aTimer.Enabled = true;
//If the timer is declared in a long-running method, use KeepAlive to prevent garbage collection from occurring before the method ends.
GC.KeepAlive(aTimer);
}
public void SendNewMessage(object state, ElapsedEventArgs elapsedEventArgs)
{
// Get a hub context for ServerHub
var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
var gdpConfigurationsRecord = Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
if (gdpConfigurationsRecord == null)
{
throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
}
// Send a message to all the clients
serverHub.Clients.All.handleSendNewMessages("");
}
}
RegisterHubs.cs
public static class RegisterHubs
{
public static void Start()
{
// Register the default hubs route: ~/signalr
var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
GlobalHost.DependencyResolver = signalrDependency;
RouteTable.Routes.MapHubs();
}
}
UPDATE 3
I'm getting an error from Windsor...
If I put this as the latest line in IoC.config (which is called in Application_Start)
webApicontainer.Resolve<IHubHandler>().StartTimer();
I get:
No component for supporting the service GdpSoftware.Server.Ui.Web.Hubs.IHubHandler was found
Same thing if I remove it from IoC.config and try to use this in as the latest line in Application_Start
Container.Resolve<IHubHandler>().StartTimer();
If I add
container.Register(Component
.For<IHubHandler>()
.ImplementedBy<HubHandler>()
.LifestyleSingleton());
to ControllersInstaller I get
Can't create component 'GdpSoftware.Server.Ui.Web.Hubs.HubHandler' as it has dependencies to be satisfied. (Service 'Castle.Windsor.IWindsorContainer' which was not registered)
Where/How can I use the
Container.Resolve<IHubHandler>().StartTimer();
This is my current IoC.config
public static class IocConfig
{
public static IWindsorContainer Container { get; private set; }
public static void RegisterIoc(HttpConfiguration config)
{
var webApicontainer = new WindsorContainer().Install(new WebWindsorInstaller());
GlobalConfiguration.Configuration.DependencyResolver = new WebApiWindsorDependencyResolver(webApicontainer);
Container = new WindsorContainer().Install(new ControllersInstaller());
DependencyResolver.SetResolver(new MvcWindsorDependencyResolver(Container));
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(Container));
var controllerFactory = new WindsorControllerFactory(Container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
webApicontainer.Resolve<IHubHandler>().StartTimer();
}
}
and this is my Application_Start
protected void Application_Start()
{
RegisterHubs.Start();
IocConfig.RegisterIoc(GlobalConfiguration.Configuration);
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
GlobalConfig.CustomizeConfig(GlobalConfiguration.Configuration);
}
I don't even understand why it should be in ControllersInstaller and not in HubInstaller...
Looks to me like you're injecting your UoW into the Hub, but expecting to use the instance in a timer callback. The problem is that the hub instance and its dependencies will have been logically cleaned up (e.g. disposed) after it processes the last message it received, so when the timer callback fires it's finding an already disposed instance.
It's better to move this kind of timer based logic out of the hub into a separate class that resolves the hub context to fire messages when it needs to. So, here's an example of what the logic to get and send a message to the hub might look like in that class:
// Get a hub context for ServerHub
IHubContext serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
// Send a message to all the clients
serverHub.Clients.All.SendNewMessage(e.SignalTime);
The trick for you will be working with the container to create a lifetime scope for when the timer fires so that you have instances of the dependencies you need specific to that one firing of the event. There are many examples of how to do this kind of stuff here on StackOverflow and the web at large, so I won't bother detailing the specifics of that here.
UPDATE
I did a little research into Windsor (I usually use Autofac/Ninject) so I can provide you with better sample code now. Let's start with what you've called your HubHandler
class. It should be designed as a singleton which you register with the container in your installer and then resolve and later resolve at startup to start the timer. So something like this in your HubInstaller::Install
:
container.Register(Component.For<IHubHandler>()
.ImplementedBy<HubHandler>()
.LifestyleSingleton());
Then, somewhere in the scope of your application startup after the container is installed (Application_Start if ASP.NET for example) you want to startup the timer on the registered IHubHandler
:
container.Resolve<IHubHandler>().StartTimer();
Next, you would change your HubHandler class to look something like this:
public class HubHandler : IHubHandler
{
System.Timers.Timer aTimer;
DateTime lastDate = DateTime.UtcNow;
IKernel kernel;
public HubHandler(IKernel kernel)
{
this.kernel = kernel;
}
public void StartTimer()
{
aTimer = new System.Timers.Timer(10000);
aTimer.Elapsed += new ElapsedEventHandler(SendNewMessage);
aTimer.Enabled = true;
}
public void SendNewMessage(object state, ElapsedEventArgs elapsedEventArgs)
{
// Create a container specific to the scope of this timer callback to
// resolve dependencies from
// NOTE: instances resolved from this container will be cleaned up when
// the container itself is disposed at the end of the using block
// NOTE: you must make sure to register the types you will use here with
// LifestyleScoped() as well so they will be disposed of when the scope ends
using(kernel.BeginScope())
{
// Resolve our IGdpUow dependency from the scoped container
IGdpUow gdpUow = kernel.Resolve<IGdpUow>();
var gdpConfigurationsRecord = gdpUow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
if (gdpConfigurationsRecord == null)
{
throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
}
// Get a hub context for ServerHub
var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
// Send a message to all the clients
serverHub.Clients.All.handleSendNewMessages("");
}
}
}
Then just update your ServerHub
class so it no longer knows about/does anything with the IHubHandler
:
public class ServerHub : Hub
{
public IGdpUow Uow { get; set; }
public void GetAllMessages()
{
var messagesList = Uow.Messages.GetAll().Select(
newMessage => new MessageDto
{
MessageId = newMessage.MessageId,
Messagestatus = newMessage.MessageStatus.Description
});
Clients.All.handleMessages(messagesList);
}
}
So first, as a disclaimer, this is a quick and dirty example just trying to get you to understand how you need to wire this stuff together. Generally having a class actually have a dependency on the IoC framework (IKernel
in this case) isn't a great design. However, since this class does need to manage the lifetime scope in the callback it does need to be rather intimate with the container it's working with. You might want to look into cleaning this up a little bit.
Second, instead of using GlobalHost.ConnectionManager directly in the callback, you might want to look into actually just resolving IConnectionManager
through the container. You would obviously have to register the default ConnectionManager
instance as IConnectionManager
in your container and SignalR would then see/use that instance rather than falling back and creating its own. This decoupling would allow you to test the HubHandler
class with a mock/fake IConnectionManager
implementation which is probably desirable.