2

我正在使用以下技术开发一个项目:

  • 实体框架版本:5
  • 实体框架迁移
  • SignalR 版本 1.1.2
  • 国际奥委会:温莎城堡
  • 点网框架 4.5
  • 网络API
  • 视觉工作室 2012
  • SQL Server 速成版 2012

我收到错误

The operation cannot be completed because the DbContext has been disposed

在 ServerHub 类中,我放了以下内容:

// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed

有人知道我为什么会这样吗?我已经阅读了很多答案,但到目前为止我没有尝试过解决它。

EF 通用存储库 (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);
    }
}

集线器安装程序

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(工作单位)

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
    }
}

从 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);
        }

    }

更新 我已经添加了德鲁建议的这个......仍然没有运气

    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);
        }
    }

和新班级

集线器处理程序

    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);
        }
    }
}

更新 2

现在我将计时器从 Hub 移到 HubHandler 中。我还安装了 Nuget 包以将 LifeStyle.HybridPerWebRequestTransient 用于 GdpUow 和 RepositoryProvider 仍然是同样的问题。

服务器集线器

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);
        }
    }
}

集线器安装程序

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());
    }
}

集线器处理程序

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("");
    }
}

注册中心.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();
    }
}

更新 3

我从温莎那里收到了一个错误...

如果我将此作为 IoC.config 中的最新行(在 Application_Start 中调用)

webApicontainer.Resolve<IHubHandler>().StartTimer();

我得到:

No component for supporting the service GdpSoftware.Server.Ui.Web.Hubs.IHubHandler was found

如果我从 IoC.config 中删除它并尝试将其用作 Application_Start 中的最新行,同样的事情

Container.Resolve<IHubHandler>().StartTimer();

如果我添加

container.Register(Component
.For<IHubHandler>()
.ImplementedBy<HubHandler>()
.LifestyleSingleton());

到 ControllersInstaller 我得到

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)

我在哪里/如何使用

Container.Resolve<IHubHandler>().StartTimer();

这是我当前的 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();
    }
}

这是我的 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);
    }

我什至不明白为什么它应该在 ControllersInstaller 而不是 HubInstaller 中......

4

2 回答 2

3

在我看来,您将 UoW 注入集线器,但希望在计时器回调中使用该实例。问题是集线器实例及其依赖项在处理它收到的最后一条消息后将在逻辑上被清理(例如释放),因此当计时器回调触发时,它会找到一个已经释放的实例。

最好将这种基于计时器的逻辑从集线器移出到一个单独的类中,该类解析集线器上下文以在需要时触发消息。因此,这里有一个示例,说明在该类中获取消息并将消息发送到集线器的逻辑可能如下所示:

   // 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);

对您来说,诀窍是使用容器为计时器触发时创建一个生命周期范围,以便您拥有特定于事件触发的依赖项的实例。在 StackOverflow 和整个网络上有很多关于如何做这类事情的例子,所以我不会在这里详细说明这些细节。

更新

我对 Windsor 做了一些研究(我通常使用 Autofac/Ninject),所以我现在可以为您提供更好的示例代码。让我们从你所谓的HubHandler类开始。它应该设计为一个单例,您在安装程序中向容器注册,然后在启动时解析并稍后解析以启动计时器。所以在你的这样的东西HubInstaller::Install

container.Register(Component.For<IHubHandler>()
    .ImplementedBy<HubHandler>()
    .LifestyleSingleton());

然后,在安装容器后在应用程序启动范围内的某个位置(例如,如果是 ASP.NET,则为 Application_Start),您希望在已注册的位置上启动计时器IHubHandler

container.Resolve<IHubHandler>().StartTimer();

接下来,您将 HubHandler 类更改为如下所示:

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("");
       }
    }
}

然后只需更新您的ServerHub课程,使其不再了解/对以下内容执行任何操作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);
    }
}

因此,首先,作为免责声明,这是一个快速而肮脏的示例,只是试图让您了解如何将这些东西连接在一起。一般来说,拥有一个类实际上依赖于 IoC 框架(IKernel在这种情况下)并不是一个好的设计。但是,由于此类确实需要在回调中管理生命周期范围,因此它确实需要与它正在使用的容器密切相关。你可能想稍微清理一下。

其次,您可能不想直接在回调中使用 GlobalHost.ConnectionManager ,而是实际上只是IConnectionManager通过容器进行解析。您显然必须在容器中注册默认ConnectionManager实例,IConnectionManager然后 SignalR 会看到/使用该实例,而不是回退并创建自己的实例。这种解耦将允许您使用可能需要的模拟/假实现来测试HubHandler类。IConnectionManager

于 2013-07-05T23:26:11.440 回答
1

正如我看到的那样,您正在做很多抽象……这使整个事情变得比应有的复杂得多。

从问题描述看来,它确实与 Windsor 中配置的生活方式有关。

我可以建议你的是:试试这个NuGet 包HybridPerWebRequestTransient中提供的生活方式。它解决了我在尝试实例化实现类的派生类内部时遇到的这个错误。DbContextSignalR.Hub

请确保您仔细阅读这些帖子:

温莎的混合生活方式

SignalR OnDisconnected Task and Dependency Injection with Castle Windsor - 混合生活方式来拯救。

于 2013-07-09T04:11:39.370 回答