使用SignalR和SQLTableDependency進行記錄更改的SQL Server通知

目錄

介紹

增強功能

怎麼運行的

Watch Dog

代碼

測試方法

參考文獻


介紹

SqlTableDependency是一個類,用於在指定查詢的結果集由於對數據庫表執行的任何insertupdate或者delete操作而更改時接收通知。

但是,此類不會發送回已更改記錄的值。

因此,假設我們要在網頁上顯示股票值,則對於收到的每個通知,我們都必須執行一個新的完整查詢以刷新緩存,然後刷新瀏覽器。

但是,如果我們願意的話,一旦某一股票值發生變化,瀏覽器便會立即顯示新的值,而無需刷新?理想情況下,我們想要的是直接從Web服務器接收通知,而沒有來自瀏覽器的任何輪詢系統,也沒有拉到數據庫表。

解決方案是將SignalRSqlTableDependencySqlTableDependency結合使用從表中獲取通知,然後SignalR將消息發送到網頁。

增強功能

SqlTableDependency是通用C#組件,用於在指定表的內容更改時發送事件。此事件報告操作類型(INSERTUPDATEDELETE)以及已刪除、已插入或已修改的值。該組件的實現是:

  • 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頁面上立即獲得通知。

參考文獻

發佈了69 篇原創文章 · 獲贊 139 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章