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 显示正在发生的表更新,然后执行与 SqlDependencyn
次关联的命令作为响应。我会看看使用你提供的链接中的信息是否会改变事情,但我也在调查数据库方面。
我有一种感觉,这与你为同一个任务有多少事件处理程序有关,如果你有多个处理程序订阅了你的触发器,那么这很难触发,这可能就是您看到它执行这么多次命令的原因。以上是关于SqlDependency Start() 的多个实例与 SignalR的主要内容,如果未能解决你的问题,请参考以下文章
SqlDependency通知sql server 2005不会在多个插入上触发
SqlDependency/Query 通知 - SQL Server 重新启动