SQLExpress, SQLDependency, ASP.NET und SignalR

Das könnte man nun als Prototyp bezeichnen. Zuerst wurde evaluiert wie Push aus einer SQL Datenbank funktioniert. Als nächstes wurde der PrePrototype auf SQLExpress portiert. Letztendlich wird aus einer Winforms Anwendung eine Web Anwendung auf Basis IIS.

Das Szenario ist relativ komplex, entsprechend ein einfaches Architektur Diagramm.

Der Benutzer erfasst per Webbrowser Daten und schickt im Schritt 1 diese per HTTP Post an eine ASP.NET Website die diese (Schritt 2) in eine dynamisch angefügte SQL Datenbank schreibt.

Da ASP.NET seinen Seiten und den darin enthaltenen Programmcode eine sehr kurze Lebensdauer gönnt, muss eine statische Klasse (c# static, vb.net shared) den Watcher übernehmen. Diese entspricht dem singleton Pattern.

Die Datenbank Engine löst nun das Event aus (Schritt 3), das die Hub Methode eines SignalR Hubs weckt. ( Schritt 4)

Der SignalR Hub benachrichtigt dann alle Abonnenten per Push über die Änderung (Schritt 5). Üblicherweise sind das Webbrowser in denen ein JavaScript Event darauf lauert. Das Javascript manipuliert dann das DOM im Browser und macht das Ergebnis für einen anderen Benutzer sichtbar.

image

Anwendungsfall ist ein Echtzeit Börsenticker. Sehr interessante Frage die sich stellt, an welchen Schritt übertrage ich welche Daten? Ich tendiere eher dazu nur ID’s zu senden, die unbedingt nötig sind und dann im logischen Schritt 7 den Client  entscheiden zu lassen, was er eigentlich Neues braucht (Stichwort Etag und Delta).

Der SignalR Hub ist in diesem Code Sample extrem einfach und sendet nur eine Zahl. Diese Zahl repräsentiert die ID des neuen Datensatzes.

   1:  Public Class votingReportHub
   2:      Inherits Hub
   3:      Public Sub hello(msg As String)
   4:          Dim ctx = GlobalHost.ConnectionManager.GetHubContext(Of votingReportHub)()
   5:          ctx.Clients.All.helloclient(msg)
   6:      End Sub

Um sich eine Einblick in SignalR zu verschaffen empfehle ich meinen Blog Eintrag dazu. Dort steht auch wie der HTML Part und das JavaScript zu erstellen ist.

Für mich spannender ist die statische PushHelper Klasse im ASP.NET Projekt. Das SQL Kommando ist eine Query, die nur die höchste ID liefert. Erhöht sich diese durch einen neuen Datensatz, wird die SQL Notification ausgelöst. Vorsicht Update, Delete Ereignisse auf den Daten werden ignoriert.

In Zeile 25 wird ein Brücke geschlagen zum SignalR Hub. Manche Blog Beispiele führen den SignalR Hub auch als statische Methode aus, was bei mir nicht klappt. (eventuell 2.0 Änderung)

   1:  Public Class PushHelper
   2:      Public Shared Property dp As SqlDependency
   3:      Public Shared lastid As Integer = 0
   4:      Public Shared Sub SetupDP()
   5:          Dim ef = New feedbackentities
   6:   
   7:          Dim cmd As SqlCommand =
   8:              New SqlCommand("select id from dbo.voting where id>" + 
lastid.ToString + " order by id desc",
   9:                             ef.Database.Connection)
  10:          If Not IsNothing(dp) Then
  11:              RemoveHandler dp.OnChange, AddressOf dataChanged
  12:          End If
  13:          dp = New SqlDependency(cmd)
  14:          AddHandler dp.OnChange, AddressOf dataChanged
  15:          ef.Database.Connection.Open()
  16:          Dim res = cmd.ExecuteReader()
  17:          If res.Read() Then
  18:              lastid = res(0)
  19:          Else
  20:              lastid = 0
  21:          End If
  22:   
  23:          ef.Database.Connection.Close()
  24:      
  25:          Dim ctx = GlobalHost.ConnectionManager.GetHubContext(Of votingReportHub)()
  26:          ctx.Clients.All.helloclient(lastid)
  27:      End Sub
  28:      Private Shared Sub dataChanged(sender As Object, e As SqlNotificationEventArgs)
  29:          If e.Info = SqlNotificationInfo.Error Then
  30:          Else
  31:   
  32:              SetupDP()
  33:          End If
  34:   
  35:      End Sub
  36:   
  37:  End Class

Die Magic für die Abhängigkeit wird im beim anlegen des Datensatzes (1-3) durchgeführt. Also die SQLDependency in der ASPX Seite erstellt, die auch den neuen Record anlegt. Aber eben nur beim ersten Laden. Wahrscheinlich wird man in der Praxis häufiger in das Application Start Event in der global.asax wechseln.

   1:  Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
   2:          If Page.IsPostBack = False Then
   3:              Dim ef = New feedbackentities
   4:              Label1.Text = ef.voting.Count()
   5:              SqlDependency.Stop(ef.Database.Connection.ConnectionString.Replace
("|DataDirectory|", Server.MapPath("app_data")))
   6:              SqlDependency.Start(ef.Database.Connection.ConnectionString.Replace
("|DataDirectory|", Server.MapPath("app_data")))
   7:              PushHelper.SetupDP()
   8:          End If
   9:   End Sub

Zeile 5 zeigt eine seltsame Art den Connection String zusammen zu setzen. In meinen Tests hat SQlDependency Start versucht die Datenbank Datei im Programmverzeichnis von IIS Express zu suchen (C:\Program Files (x86)\IIS Express). Ich halte das für einen Bug und meine Methode per Server.Mappath als Workaround.

pushsql

 

Hinweis: laut Doku soll unter Heavy Load das System (die Notification Queue des SQL Servers) nicht zuverlässig arbeiten und auch mal die Dependency Events auslassen. Das heißt manche Änderung erreicht den Subscriber nicht. Entsprechend muss die Programmlogik diesen Fall abdecken und beim nächsten Push eventuell mehr Daten laden.

Kommentare sind geschlossen