目录
介绍
增强功能
怎么运行的
Watch Dog
代码
测试方法
参考文献
SqlTableDependency是一个类,用于在指定查询的结果集由于对数据库表执行的任何insert,update或者delete操作而更改时接收通知。
但是,此类不会发送回已更改记录的值。
因此,假设我们要在网页上显示股票值,则对于收到的每个通知,我们都必须执行一个新的完整查询以刷新缓存,然后刷新浏览器。
但是,如果我们愿意的话,一旦某一股票值发生变化,浏览器便会立即显示新的值,而无需刷新?理想情况下,我们想要的是直接从Web服务器接收通知,而没有来自浏览器的任何轮询系统,也没有拉到数据库表。
解决方案是将SignalR与SqlTableDependency:SqlTableDependency结合使用从表中获取通知,然后SignalR将消息发送到网页。
SqlTableDependency是通用C#组件,用于在指定表的内容更改时发送事件。此事件报告操作类型(INSERT/ UPDATE/ DELETE)以及已删除、已插入或已修改的值。该组件的实现是:
实例化后,此组件将动态生成用于监视表内容的所有数据库对象。对于SqlTableDependency,我们有:
一旦SqlTableDependency被释放,所有这些对象都被释放。
SqlTableDependency具有watchDogTimeOut,可在应用程序突然断开连接的情况下删除那些对象。此超时设置为3分钟,但是在部署阶段可以增加该超时时间。
放置所有这些对象后,SqlTableDependency获取表内容更改的通知,并在包含记录值的C#事件中转换此通知。
假设一个包含股票值不断变化的SQL Server数据库表:
CREATE TABLE [dbo].[Stocks](
[Code] [nvarchar](50) NULL,
[Name] [nvarchar](50) NULL,
[Price] [decimal](18, 0) NULL
) ON [PRIMARY]
我们将使用以下模型映射这些表列:
public class Stock
{
public decimal Price { get; set; }
public string Symbol { get; set; }
public string Name { get; set; }
}
接下来,我们安装NuGet软件包:
PM> Install-Package SqlTableDependency
下一步是创建一个自定义hub类,用于SignalR基础架构:
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly StockTicker _stockTicker;
public StockTickerHub() :
this(StockTicker.Instance)
{
}
public StockTickerHub(StockTicker stockTicker)
{
_stockTicker = stockTicker;
}
public IEnumerable GetAllStocks()
{
return _stockTicker.GetAllStocks();
}
}
我们将使用SignalR Hub API处理服务器到客户端的交互。从SignalR Hub类派生的StockTickerHub类将处理从客户端接收连接和方法调用。我们不能将这些函数放在Hub类中,因为Hub实例是瞬时的。Hub将为集线器上的每个操作创建一个类实例,例如从客户端到服务器的连接和调用。因此,该机制可以保存库存数据,更新值并广播必须在单独的类中运行的值更新,您将其命名为StockTicker:
public class StockTicker
{
// Singleton instance
private readonly static Lazy _instance = new Lazy(
() => new StockTicker
(GlobalHost.ConnectionManager.GetHubContext().Clients));
private static SqlTableDependency _tableDependency;
private StockTicker(IHubConnectionContext clients)
{
Clients = clients;
var mapper = new ModelToTableMapper();
mapper.AddMapping(s => s.Symbol, "Code");
_tableDependency = new SqlTableDependency(
ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString,
"Stocks",
mapper);
_tableDependency.OnChanged += SqlTableDependency_Changed;
_tableDependency.OnError += SqlTableDependency_OnError;
_tableDependency.Start();
}
public static StockTicker Instance
{
get
{
return _instance.Value;
}
}
private IHubConnectionContext Clients
{
get;
set;
}
public IEnumerable GetAllStocks()
{
var stockModel = new List();
var connectionString = ConfigurationManager.ConnectionStrings
["connectionString"].ConnectionString;
using (var sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandText = "SELECT * FROM [Stocks]";
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
var code = sqlDataReader.GetString(sqlDataReader.GetOrdinal("Code"));
var name = sqlDataReader.GetString(sqlDataReader.GetOrdinal("Name"));
var price =
sqlDataReader.GetDecimal(sqlDataReader.GetOrdinal("Price"));
stockModel.Add
(new Stock { Symbol = code, Name = name, Price = price });
}
}
}
}
return stockModel;
}
void SqlTableDependency_OnError(object sender, ErrorEventArgs e)
{
throw e.Error;
}
///
/// Broadcast New Stock Price
///
void SqlTableDependency_Changed(object sender, RecordChangedEventArgs e)
{
if (e.ChangeType != ChangeType.None)
{
BroadcastStockPrice(e.Entity);
}
}
private void BroadcastStockPrice(Stock stock)
{
Clients.All.updateStockPrice(stock);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_tableDependency.Stop();
}
disposedValue = true;
}
}
~StockTicker()
{
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
现在是时候查看HTML页面了:
SqlTableDependencly with SignalR
SqlTableDependencly with SignalR
Symbol Name Price
loading...
以及我们如何管理JavaScript代码中从SignalR返回的数据:
// Crockford's supplant method
if (!String.prototype.supplant) {
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g,
function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
}
);
};
}
$(function () {
var ticker = $.connection.stockTicker; // the generated client-side hub proxy
var $stockTable = $('#stockTable');
var $stockTableBody = $stockTable.find('tbody');
var rowTemplate = '
{Symbol} {Name} {Price} ';
function formatStock(stock) {
return $.extend(stock, {
Price: stock.Price.toFixed(2)
});
}
function init() {
return ticker.server.getAllStocks().done(function (stocks) {
$stockTableBody.empty();
$.each(stocks, function () {
var stock = formatStock(this);
$stockTableBody.append(rowTemplate.supplant(stock));
});
});
}
// Add client-side hub methods that the server will call
$.extend(ticker.client, {
updateStockPrice: function (stock) {
var displayStock = formatStock(stock);
$row = $(rowTemplate.supplant(displayStock)),
$stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']').replaceWith($row);
}
});
// Start the connection
$.connection.hub.start().then(init);
});
最后,我们不必忘记注册SignalR路由:
[assembly: OwinStartup(typeof(Stocks.Startup))]
namespace Stocks
{
public static class Startup
{
public static void Configuration(IAppBuilder app)
{
// For more information on how to configure your application using OWIN startup,
// visit http://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR();
}
}
}
在附件中,有一个简单的Web应用程序,其中包含一个HTML页面,该页面在表格中报告股票值。
要测试,请按照下列步骤操作:
CREATE TABLE [dbo].[Stocks]([Code] [nvarchar](50) NOT NULL, _
[Name] [nvarchar](50) NOT NULL, [Price] [decimal](18, 0) NOT NULL)