SqlDependency Start() 的多个实例与 SignalR

Posted

技术标签:

【中文标题】SqlDependency Start() 的多个实例与 SignalR【英文标题】:SqlDependency multiple instances of Start() with SignalR 【发布时间】:2016-06-13 15:57:40 【问题描述】:

我有一个 MVC 4 站点,它使用 SqlDependency 来检测对数据库的更改,然后通过 SignalR 通知订阅的客户端更改。所有机制都已到位,应用程序知道发生的变化,但有趣的是……我看到越来越多的客户端通知基于在 Application_Start() 和 @ 之间发生的任一浏览器刷新次数987654324@,或者连接的客户端数量。

也许我对这些技术的理解不正确,但我认为 SignalR 变成了单例,这导致所有流量都通过客户端和服务器之间的单个“管道”发生,而不管连接的客户端数量如何.

当然,这并不能解释为什么刷新似乎会实例化一个全新的 SqlDependency。

我看到 this answer 显示了关闭 SqlDependency 的想法(模拟 Application_End()),但所做的只是增加了页面的执行时间,并没有解决问题。

我很难过,非常感谢一些建议,以使其按预期工作。

这是我正在使用的代码...

In my Global.asax.cs file:

using System.Configuration;
using System.Data.SqlClient;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace SmartAppV1

    public class MvcApplication : System.Web.HttpApplication
    
        protected void Application_Start()
        
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // SignalR Wireup
            SqlDependency.Start(ConfigurationManager.ConnectionStrings["SmartAppSignalR"].ConnectionString);
        

        protected void Application_End()
        
            // Shut down SignalR Dependencies
            SqlDependency.Stop(ConfigurationManager.ConnectionStrings["SmartAppSignalR"].ConnectionString);
        
    

My SignalR Hub:

using System.Collections.Generic;
using System.Configuration;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SmartAppData.Entities;
using SmartAppV1.Models;

namespace SmartAppV1

    [HubName("smartAppHub")]
    public class SmartAppHub : Hub
    
        private readonly string _connection = ConfigurationManager.ConnectionStrings["SmartAppSignalR"].ConnectionString;

        public void MonitorGrid4DataChanges()
        
            var setGrid4 = new SmartAppSignalR
            
                ConnectionString = _connection,
                Query =
                    @"SELECT [ID], [OrdHeaderId], [LoadId], [NewStatus] FROM [dbo].[CTLoadStatusChangeLog] WHERE [NewStatus] = 'Delivered' ORDER BY [ID] DESC"
            ;
            setGrid4.DispatchBoardStatusChange();
        
    

My SignalR class:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.AspNet.SignalR;
using SmartAppData;
using SmartAppData.Entities;
using SmartAppData.Services;

namespace SmartAppV1.Models

    public class SmartAppSignalR
    
        public string ConnectionString  get; set; 
        public string Query  get; set; 

        public IEnumerable<DeliveredGridItem> ReadGrid4Data()
        
            var service = new LoadService();
            var result = service.GetLoadsByBookedByIdByTmsStatus(110, LoadTmsStatus.Delivered.ToString()).ToList();
            var deliveredList = new List<DeliveredGridItem>();
            foreach (var obj in result)
            
                var deliveredItem = new DeliveredGridItem(obj.LoadId)  LoadTmsStatus = obj.DataValue_LoadTmsStatus ;
                deliveredList.Add(deliveredItem);
            

            return deliveredList;
        

        public void DispatchBoardStatusChange()
        
            using (var conn = new SqlConnection(ConnectionString))
            
                using (var cmd = new SqlCommand(Query, conn))
                
                    cmd.Notification = null;
                    var dependency = new SqlDependency(cmd);
                    // make sure the OnChange doesn't exist
                    // trying to remove redundant calls
                    dependency.OnChange -= dispatchBoard_OnChange;
                    dependency.OnChange += dispatchBoard_OnChange;
                    if (conn.State == ConnectionState.Closed)
                        conn.Open();
                    var reader = cmd.ExecuteReader();
                    while (reader.Read())
                    
                    
                
            
        

        private void dispatchBoard_OnChange(object sender, SqlNotificationEventArgs e)
        
            if (e.Type != SqlNotificationType.Change) return;

            // dump the original OnChange event handler...we're going to re-register it
            var sqlDependency = (SqlDependency) sender;
            sqlDependency.OnChange -= dispatchBoard_OnChange;

            var retVal = new StatusChangedObject();

            using (var conn = new SqlConnection(ConnectionString))
            
                using (var cmd = new SqlCommand(Query, conn))
                
                    if (conn.State == ConnectionState.Closed)
                        conn.Open();
                    var reader = cmd.ExecuteReader();

                    if (reader.Read())
                    
                        retVal.Id = Convert.ToInt32(reader["ID"]);
                        retVal.OrdHeaderId = Convert.ToInt32(reader["OrdHeaderId"]);
                        retVal.LoadId = Convert.ToInt32(reader["LoadId"]);
                        retVal.NewStatus = reader["NewStatus"].ToString();

                        var clients = GlobalHost.ConnectionManager.GetHubContext<SmartAppHub>().Clients;
                        clients.All.gridUpdate(retVal);
                    
                
            

            // Re-register the SqlDependency
            DispatchBoardStatusChange();
        
    

    public class StatusChangedObject
    
        public int Id  get; set; 
        public int OrdHeaderId  get; set; 
        public int LoadId  get; set; 
        public string NewStatus  get; set; 
    

And, finally my .js code for the SignalR: 注意:我使用的是 Telerik 网格,因此我必须在 $(document).ready() 之外使用 SignalR 接线,这是我看到的所有其他教程/连接 SignalR 示例的地方。

// SignalR plumbing
var hub, hubStart;
hub = $.connection.smartAppHub;
hubStart = $.connection.hub.start().done(function () 
    if ($.connection.hub.state === $.signalR.connectionState.connected) 
        console.log('SignalR is connected.');
        console.log('Call the server method to monitor changes to Grid4');
        hub.server.monitorGrid4DataChanges();
    ;
);

hub.client.grid4Read = function () 
    console.log('Called grid4Read()');
;

hub.client.iDidSomething = function (response) 
    console.log('I was told to do something\r\n' + response);
;

hub.client.gridUpdate = function (response) 
    console.log("Entered hub.client.gridUpdate");
    // Will show alerts when something moves.
    // Plan on adjusting this via the 'Settings' menu.
    var showNotification = true;

    // Go get the order/load from the database
    var ordHeaderId = response['OrdHeaderId'];
    var loadId = response['LoadId'];
    var newStatus = response['NewStatus'];

    if (showNotification) 
        //setToastrOptions();
        //toastr["info"]("Grid update to loadId: " + loadId);
    

    console.log('Client-side fromSqlDependency:\r\nOrdHeaderId: ' + ordHeaderId + '\r\nLoadId: ' + loadId + '\r\nNewStatus: ' + newStatus);
;

【问题讨论】:

你可以点击这个链接,它可能会解决你的问题。***.com/questions/21448204/… 【参考方案1】:

好的,所以我认为这里发生了一些事情,您正在添加和删除用于处理 sqldependency 更改的事件过于频繁。您应该使用保护逻辑来查看事件处理程序是否存在,删除不存在的处理程序有时会产生奇怪的影响。至于多次启动等,每次重新加载或离开客户端页面时,它都会断开连接,并在加载时重新连接。除非您以这种方式编码,否则信号器集线器不是单例实例,默认情况下它不会以这种方式完成。这是您想要做的简单版本。

try
    
        using (
            var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
        
            connection.Open();
            using (SqlCommand command = new SqlCommand(@"SELECT [Id]
                                                                    ,[FName]
                                                                    ,[LName]
                                                                    ,[DOB]
                                                                    ,[Notes]
                                                                    ,[PendingReview] 
                                                   FROM [dbo].[Users]",
                connection))
            
                // Make sure the command object does not already have
                // a notification object associated with it.
                command.Notification = null;

                SqlDependency dependency = new SqlDependency(command);

                dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

                if (connection.State == ConnectionState.Closed)
                    connection.Open();

                command.ExecuteReader();
            
        
    
    catch (Exception e)
    
        throw;
    


private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
   
    SqlDependency dependency = sender as SqlDependency;
    if (dependency != null) dependency.OnChange -= dependency_OnChange;
    //Recall your SQLDependency setup method here.
    SetupDependency();
    JobHub.Show();

如果你想创建一个单例类型的集线器,你需要像这样设置它,你有一个静态集线器和一个集线器跟踪器类,这是在跟踪器类中。查看教程链接了解更多详情:

 private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

 private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();

查看本教程以将集线器用作单例: http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr

【讨论】:

感谢您的回复,凯尔索。自从发布这个问题以来,我一直在不停地关注这个问题。我怀疑问题实际上出在数据库本身,因为 SQL Profiler 显示正在发生的表更新,然后执行与 SqlDependency n 次关联的命令作为响应。我会看看使用你提供的链接中的信息是否会改变事情,但我也在调查数据库方面。 我有一种感觉,这与你为同一个任务有多少事件处理程序有关,如果你有多个处理程序订阅了你的触发器,那么这很难触发,这可能就是您看到它执行这么多次命令的原因。

以上是关于SqlDependency Start() 的多个实例与 SignalR的主要内容,如果未能解决你的问题,请参考以下文章

SqlDependency通知sql server 2005不会在多个插入上触发

SqlDependency数据库监控类的使用方法

SqlDependency类的使用

SqlDependency/Query 通知 - SQL Server 重新启动

使用SqlDependency与表的定期轮询(对性能的影响)

SQLDependency 超时