目錄
介紹
SqlTableDependency是一個類,用於在指定查詢的結果集由於對數據庫表執行的任何insert,update或者delete操作而更改時接收通知。
但是,此類不會發送回已更改記錄的值。
因此,假設我們要在網頁上顯示股票值,則對於收到的每個通知,我們都必須執行一個新的完整查詢以刷新緩存,然後刷新瀏覽器。
但是,如果我們願意的話,一旦某一股票值發生變化,瀏覽器便會立即顯示新的值,而無需刷新?理想情況下,我們想要的是直接從Web服務器接收通知,而沒有來自瀏覽器的任何輪詢系統,也沒有拉到數據庫表。
解決方案是將SignalR與SqlTableDependency:SqlTableDependency結合使用從表中獲取通知,然後SignalR將消息發送到網頁。
增強功能
SqlTableDependency是通用C#組件,用於在指定表的內容更改時發送事件。此事件報告操作類型(INSERT/ UPDATE/ DELETE)以及已刪除、已插入或已修改的值。該組件的實現是:
- SqlTableDependency 對於SQL Server
OracleTableDependency對於Oracle
怎麼運行的
實例化後,此組件將動態生成用於監視表內容的所有數據庫對象。對於SqlTableDependency,我們有:
- 消息類型
- 消息契約
- 隊列
- Service Broker
- 表觸發器
- 儲存程序
一旦SqlTableDependency被釋放,所有這些對象都被釋放。
Watch Dog
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<Stock> GetAllStocks()
{
return _stockTicker.GetAllStocks();
}
}
我們將使用SignalR Hub API處理服務器到客戶端的交互。從SignalR Hub類派生的StockTickerHub類將處理從客戶端接收連接和方法調用。我們不能將這些函數放在Hub類中,因爲Hub實例是瞬時的。Hub將爲集線器上的每個操作創建一個類實例,例如從客戶端到服務器的連接和調用。因此,該機制可以保存庫存數據,更新值並廣播必須在單獨的類中運行的值更新,您將其命名爲StockTicker:
public class StockTicker
{
// Singleton instance
private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
() => new StockTicker
(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
private static SqlTableDependency<Stock> _tableDependency;
private StockTicker(IHubConnectionContext<dynamic> clients)
{
Clients = clients;
var mapper = new ModelToTableMapper<Stock>();
mapper.AddMapping(s => s.Symbol, "Code");
_tableDependency = new SqlTableDependency<Stock>(
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<dynamic> Clients
{
get;
set;
}
public IEnumerable<Stock> GetAllStocks()
{
var stockModel = new List<Stock>();
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;
}
/// <summary>
/// Broadcast New Stock Price
/// </summary>
void SqlTableDependency_Changed(object sender, RecordChangedEventArgs<Stock> 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頁面了:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>SqlTableDependencly with SignalR</title>
</head>
<body>
<h1>SqlTableDependencly with SignalR</h1>
<div id="stockTable">
<table border="1">
<thead style="background-color:silver">
<tr><th>Symbol</th><th>Name</th><th>Price</th></tr>
</thead>
<tbody>
<tr class="loading"><td colspan="3">loading...</td></tr>
</tbody>
</table>
</div>
<script src="jquery-1.10.2.min.js"></script>
<script src="jquery.color-2.1.2.min.js"></script>
<script src="../Scripts/jquery.signalR-2.2.0.js"></script>
<script src="../signalr/hubs"></script>
<script src="SignalR.StockTicker.js"></script>
</body>
</html>
以及我們如何管理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 = '<tr data-symbol="{Symbol}"><td>
{Symbol}</td><td>{Name}</td><td>{Price}</td></tr>';
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)
- 用一些數據填充表。
- 運行Web應用程序,然後瀏覽/SignalR.Sample/StockTicker.html頁面。
- 修改表中的任何數據以在HTML頁面上立即獲得通知。