【问题标题】:SignalR OnDisconnected event not persisting data to DBSignalR OnDisconnected 事件未将数据持久保存到数据库
【发布时间】:2014-08-26 13:54:01
【问题描述】:

我有一个 SignalR 集线器,我在其中注入服务类,这些服务类通过 Castle Windsor 将数据持久保存到本地 SQL Server 实例。

集线器的样子:

[Authorize]
public class MyHub : Hub 
{
    private readonly IHubService _hubService;
    private readonly IHubUserService _hubUserService;
    private readonly IUserService _userService;

    public MyHub(IHubService hubService, IHubUserService hubUserService, IUserService userService)
    {
        _hubService = hubService;
        _hubUserService = hubUserService;
        _userService = userService;
    }

    public async Task JoinHub(Guid hubId) 
    {
        var hub = _hubService.GetHubById(hubId);

        if (hub == null)
            throw new NotFoundException(String.Format("Hub ({0}) was not found.", hubId.ToString()));

        var userName = Context.User.Identity.Name;
        var user = _userService.GetUserByUserName(userName);

        if (user == null)
            throw new NotFoundException(String.Format("User ({0}) was not found.", userName));

        var hubUser = new HubUser
        {
            User = user,
            Hub = hub,
            ConnectionId = Context.ConnectionId
        };

        // Persist a new HubUser to the DB
        hubUser = _hubUserService.InsertHubUser(hubUser);

        await Groups.Add(Context.ConnectionId, hub.Id.ToString());
        Clients.Group(hub.Id.ToString()).addChatMessage(userName + " has joined.");
    }

    public async Task LeaveHub()
    {
        var userName = Context.User.Identity.Name;

        var hubUser = _hubUserService.GetHubUserByUserName(userName);

        // Removes HubUser from the DB
        _hubUserService.RemoveHubUser(hubUser);

        await Groups.Remove(Context.ConnectionId, hubUser.Hub.Id.ToString());
        Clients.Group(hubUser.Hub.Id.ToString()).addChatMessage(userName + " has left.");
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        var userName = Context.User.Identity.Name;

        var hubUser = _hubUserService.GetHubUserByUserName(userName);

        // Removes HubUser from the DB
        _hubUserService.RemoveHubUser(hubUser); // This line executes but does not persist anything to DB

        Groups.Remove(Context.ConnectionId, hubUser.Hub.Id.ToString());
        Clients.Group(hubUser.Hub.Id.ToString()).addChatMessage(userName + " has left.");
        return base.OnDisconnected(stopCalled);
    }
}

从客户端调用 JoinHub 和 LeaveHub 方法时,一切正常。但是,当 OnDisconnected 方法触发时,不会从数据库中删除任何内容。我可以看到代码确实执行了,但是记录保留在数据库中并且没有被删除。

我想知道我的休眠会话是否由于城堡 Windsor 的依赖生命周期或其他原因而没有将事务提交到数据库,但是,奇怪的是 LeaveHub 按预期执行但相同的代码没有在 OnDisconnected 方法中。

根据this 博客文章,我的依赖项使用以下配置注册。

Kernel.Register(
    //Nhibernate session factory
    Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,

    //Nhibernate session
    Component.For<ISession>().UsingFactoryMethod(kernel => kernel.Resolve<ISessionFactory>().OpenSession()).LifeStyle.HybridPerWebRequestTransient()
);

我还注册了一个拦截器来实现工作单元模式:

// Unitofwork interceptor
Component.For<NhUnitOfWorkInterceptor>().LifeStyle.HybridPerWebRequestTransient()

如果有人可以就 LeaveHub 方法为何正确工作以及为什么它无法在 OnDisconnected 方法中保留任何内容提供任何意见,我们将不胜感激。

【问题讨论】:

  • 我只是在猜测,因为我没有足够的上下文,但由于您使用的是任务,您确定 OnDisconnected 在 UnitOfWork 发布之前完成。
  • 我不相信 OnDisconnected 在 UnitOfWork 发布之前完成。拦截器拦截存储库级别的所有方法,因此 UnitOfWork 在我的服务调用 _hubUserService.RemoveHubUser(hubUser); 中的 OnDisconnected 内启动和完成
  • 我相信在你的拦截器实现中你有类似提交当前截断的东西?你可以在那里放置断点吗?也许看看 Sql profiler 中发生了什么?
  • 我可以看到事务在我期望的时候提交。我将尝试使用 NHibernate Profiler 进行更深入的研究。在日志中 NHibernate 在 OnDisconnected 方法期间没有执行任何查询。我相信这可能与我管理会话的方式有关。

标签: nhibernate fluent-nhibernate asp.net-mvc-5 signalr castle-windsor


【解决方案1】:

只是一个仅供参考的 Nhibernate Sessions 使用 async 做得不好,因为它们根本不是线程安全的。尝试同步运行,看看你会得到什么。

Nhibernate 是否设置为在事务提交时刷新?我无法发表评论,因为我是新手,但我前段时间遇到了这个问题。我没有使用 FluentNhibernate,但我确信有一个配置选项可以在事务提交时设置刷新。这是假设您将所有打开的会话调用包装在事务中。我在会话中使用这样的东西。也去拿 Nhibernate Profiler 这是天赐之物。

 public class SessionManager : ISessionManager
{
    private readonly ISessionFactory _sessionFactory;
    private ISession _currentSession;
    private ITransaction _currentTransaction;

    public SessionManager(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
    }

    public ISession OpenSession()
    {
        if (CurrentSessionContext.HasBind(_sessionFactory))
        {
            _currentSession = _sessionFactory.GetCurrentSession();
        }
        else
        {
            _currentSession = _sessionFactory.OpenSession();
            CurrentSessionContext.Bind(_currentSession);
        }
        CurrentSessionContext.Bind(_currentSession);
        _currentTransaction = _currentSession.BeginTransaction();
        return _currentSession;
    }

    public void Dispose()
    {
        try
        {
            if (_currentTransaction != null && _currentTransaction.IsActive)
                _currentTransaction.Commit();
        }
        catch (Exception)
        {
            if (_currentTransaction != null) _currentTransaction.Rollback();
            throw;
        }
        finally
        {
            if (_currentSession != null)
            {
                if (_currentTransaction != null) _currentTransaction.Dispose();
                _currentSession.Close();
            }
        }
    }
}

这是我的配置,我在几个应用程序上使用它。另一方面,我不使用 FluentNhibernate 是有原因的,内置代码的映射非常棒且灵活。让我知道我可以给您发送一些示例映射。

  public class SessionFactoryBuilder
{
    public static ISessionFactory BuildSessionFactory(string connectionString)
    {
        var cfg = new Configuration();
        cfg.DataBaseIntegration(db =>
        {
            db.Dialect<MsSql2012Dialect>();
            db.Driver<Sql2008ClientDriver>();
            db.ConnectionString = connectionString;
            db.BatchSize = 1500;
            db.LogSqlInConsole = false;
            db.PrepareCommands = true;
            db.ConnectionReleaseMode = ConnectionReleaseMode.AfterTransaction;
            db.IsolationLevel = IsolationLevel.ReadCommitted;
        })
            .SetProperty(Environment.CurrentSessionContextClass, "web")
            .SetProperty(Environment.UseSecondLevelCache, "true")
            .SetProperty(Environment.ShowSql, "true")
            .SetProperty(Environment.PrepareSql, "true")
            .Cache(c =>
            {
                c.UseQueryCache = true;
                c.Provider<RtMemoryCacheProvider>();
                c.DefaultExpiration = 1440;
            }).SessionFactory().GenerateStatistics();

        HbmMapping mapping = GetMappings();
        cfg.AddDeserializedMapping(mapping, "AppName");
        SchemaMetadataUpdater.QuoteTableAndColumns(cfg);
        return cfg.BuildSessionFactory();
    }

    private static HbmMapping GetMappings()
    {
        var mapper = new ModelMapper();

        mapper.AddMappings(typeof (UserMap).Assembly.GetTypes());
        HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
        return mapping;
    }
}

这里有一个关于使用 Castle 管理 SignalR 依赖项的简洁说明。您可能想尝试一下,只是为了咯咯笑。

  public class SignalRDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver
{
    private readonly IWindsorContainer _container;

    public SignalRDependencyResolver(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        return TryGet(serviceType) ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return TryGetAll(serviceType).Concat(base.GetServices(serviceType));
    }

    [DebuggerStepThrough]
    private object TryGet(Type serviceType)
    {
        try
        {
            return _container.Resolve(serviceType);
        }
        catch (Exception)
        {
            return null;
        }
    }

    private IEnumerable<object> TryGetAll(Type serviceType)
    {
        try
        {
            Array array = _container.ResolveAll(serviceType);
            return array.Cast<object>().ToList();
        }
        catch (Exception)
        {
            return null;
        }
    }
}

在设置控制器工厂之前将其放入全局 asax

 // SignalR
        _container.Register(Classes.FromThisAssembly().BasedOn(typeof(IHub)).LifestyleTransient());
        SignalRDependencyResolver signalRDependencyResolver = new SignalRDependencyResolver(_container);
        Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver = signalRDependencyResolver;

【讨论】:

  • 我会尝试这种方法来管理会话,也许它会解决问题。所以在我的存储库类中,我应该注入 ISessionManager 并在我的构造函数中调用 _sessionManager.OpenSession()?
  • 实施您的方法效果很好,但在 OnDisconnected 方法期间仍然遇到同样的问题。我在这一行收到一个空对象错误:if (CurrentSessionContext.HasBind(_sessionFactory))。调试显示我的 _sessionFactory 不为空,所以我不确定问题仍然存在。
  • 在你的 nhibernate 配置中将你的上下文设置为 web Environment.CurrentSessionContextClass, "web"。
  • 你现在要让我使用 SignalR 创建一些东西,而不是你。哈哈
  • 我有点好奇 LifeStyle.HybridPerWebRequestTransient() 为什么 LifeStylePerWebRequest 不起作用?
猜你喜欢
  • 1970-01-01
  • 2015-12-07
  • 2014-05-16
  • 2022-11-24
  • 2015-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多