**SignalR** SQLDependency Onchanged fired multiple times - c#

I am having a ASP.Net MVC Monitoring App with VS2013 and .Net Framework 4.5.1 which should automatically refresh the Real-time Chart everytime there are changes in database. Everything works fine when I have a single instance of the browser open, but when i open another tab or browser either on the same machine or on the different machine it fires the SQLDependency Event multiple times.
Here's my Repository.cs
public class Repository
{
private static string conString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString();
public List<GetTakeInCount> UpdateTRTakeInData()
{
try
{
var lst = new List<GetTakeInCount>();
using (SqlConnection con = new SqlConnection(conString))
{
using (var cmd = new SqlCommand(#"SELECT [Date],[TotalPlan],[TakeInQty],[OrderQty],[DelayedQty] FROM [dbo].[ProductTakeInData] Where [Production] = 'TR'", con))
{
cmd.Notification = null;
if (con.State == ConnectionState.Closed)
con.Open();
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
var rdr = cmd.ExecuteReader();
while (rdr.Read())
{
GetTakeInCount rowData = new GetTakeInCount();
rowData.TakeInQuantity = Convert.ToDouble(rdr["TakeInQty"].ToString());
rowData.Date = rdr["Date"].ToString();
rowData.PlanQuantity = rdr["TotalPlan"].ToString();
rowData.AccPlanQty = Convert.ToDouble(rdr["TotalPlan"].ToString());
rowData.OrderQty = Convert.ToDouble(rdr["OrderQty"].ToString());
rowData.DelayedQty = Convert.ToDouble(rdr["DelayedQty"].ToString());
lst.Add(rowData);
}
rdr.Dispose();
}
}
return lst;
}
catch (Exception e)
{
throw e;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e) //this will be called when any changes occur in db table.
{
if (e.Type == SqlNotificationType.Change)
{
//Call SignalR
MyHub mh = new MyHub();
mh.UpdateChart();
}
SqlDependency dependency = sender as SqlDependency;
if (dependency != null) dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
}
}
Here's my Hub
[HubName("MyHub")]
public class MyHub : Hub
{
public void UpdateChart()
{
List<GetTakeInCount> data = new List<GetTakeInCount>();
Repository rep = new Repository();
data = rep.UpdateTRTakeInData();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.All.updateChartData(data);
}
}
Here's my js stuff:
$(function () {
// Declare a proxy to reference the hub.
var chartConnection = $.connection.MyHub;
$.connection.hub.logging = true;
//debugger;
// Create a function that the hub can call to broadcast messages.
chartConnection.client.updateChartData = function () {
updateChart();
console.log('hub started')
};
// Start the connection.
$.connection.hub.start();
updateChart();
});
function updateChart()
{
-- Update Chart Code ---
}
Anyone can help?

Ryan, You can re-look into the event deregister & register part.
"SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);"
I think it's better to have singleton object of SqlDependency and then do the deregistration & registration of that.
To do this You can create a singleton class that will give you an instance of SqlDependency and instead of doing "new SqlDependency(cmd)" you can call the that getInstance(cmd) method of singletonclass and do the deregister & Register on that.

Related

SqlDependency and Console app monitoring changes

I need your help. I'm trying to implement SqlDependency in a console app to constantly monitor a database table (ultimately a service broker queue). Here is my code and the problem is that after one execution application exits.
class Program
{
static void Main(string[] args)
{
var listener = new Listener();
listener.Listening();
Console.ReadKey();
}
}
public class Listener
{
const string notificationQuery = "SELECT [ID],[Name] FROM [dbo].[tblUsers]";
const string sampleConnectionString = #"Server = xxx; Database = yyy; Integrated Security = SSPI;";
public void Listening()
{
try
{
SqlClientPermission permission = new SqlClientPermission(PermissionState.Unrestricted);
permission.Demand();
}
catch (Exception ex)
{
}
using (var connection = new SqlConnection(sampleConnectionString))
{
connection.Open();
using (var command = new SqlCommand(notificationQuery, connection))
{
SqlDependency.Start(sampleConnectionString);
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
var hasChanges = dependency.HasChanges;
command.ExecuteReader();
}
}
}
void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine(sender);
Listening();
}
What I found:
how to keep sql dependency doing the its purpose
but adding Listening(); to EventHandler doesn't help. When I'm debugging program and I add some value to the table event is triggered but this adds only one more execution of the Listening method while I want the app to keep working and monitoring the table all the time. Can someone help?

SqlDependency with SignalR user specific

Please follow code:
_Layout:
$(function () {
var connection = $.connection.notificationHub;
//signalr method for push server message to client
connection.client.addNotification = function (who) {
//send notification here
console.info("Send Notification")
};
// Start hub
$.connection.hub.start().done(function () {
console.log("SignalR Started")
});
});
Global.asax.cs:
public class Global : HttpApplication
{
string con = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//here in Application Start we will start Sql Dependency
SqlDependency.Start(con);
}
protected void Session_Start(object sender, EventArgs e)
{
NotificationComponent NC = new NotificationComponent();
var currentTime = DateTime.Now;
HttpContext.Current.Session["LastUpdated"] = currentTime;
NC.RegisterNotification(currentTime);
}
protected void Application_End()
{
//here we will stop Sql Dependency
SqlDependency.Stop(con);
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyAction(string parameter)
{
//Database Notification (Table - Contacts) -Add or Update
ctx.SaveChanges();
}
NotificationHub : Hub
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
public static void SendNotification(string who)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
foreach (var connectionId in _connections.GetConnections(who))
{
var result = context.Clients.Client(connectionId);
if (result != null)
{
result.addNotification(who);
}
}
}
Components.cs:
public void RegisterNotification(DateTime currentTime)
{
string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
string sqlCommand = #"SELECT [ContactID],[ContactName],[ContactNo] from [dbo].[Contacts] where [AddedOn] > #AddedOn";
//you can notice here I have added table name like this [dbo].[Contacts] with [dbo], its mendatory when you use Sql Dependency
using (SqlConnection con = new SqlConnection(conStr))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
cmd.Parameters.AddWithValue("#AddedOn", currentTime);
if (con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency sqlDep = new SqlDependency(cmd);
sqlDep.OnChange += sqlDep_OnChange;
//we must have to execute the command here
using (SqlDataReader reader = cmd.ExecuteReader())
{
// nothing need to add here now
}
}
}
//After code `ctx.SaveChanges()`, call the code below (50 times):
void SqlDep_OnChange(object sender, SqlNotificationEventArgs e) //<-- Here Problem
{
//from here we will send notification message to client
NotificationHub.SendNotification("User1586");
//...
//re-register notification
RegisterNotification(DateTime.Now);
//HERE -After this line "RegisterNotification(DateTime.Now);", it returns again to the line: "void SqlDep_OnChange(object sender, SqlNotificationEventArgs e)"
}
User "User1586" is receiving multiple notifications. 
The line "void SqlDep_OnChange(object sender, SqlNotificationEventArgs e)" repeats several times.
If you have 50 online users, do 50 times on this line:  
void SqlDep_OnChange(object sender, SqlNotificationEventArgs e)
If you have 1000 online users, do 1000 times on this line:  
void SqlDep_OnChange(object sender, SqlNotificationEventArgs e).
 In other words, user "User1586" receives several notifications.
I followed the example here: http://www.dotnetawesome.com/2016/05/push-notification-system-with-signalr.html
Idea is to send notification to a specific user after database update.
Any solution ?
First of all, Sql dependency is not aware what data has changed. So you should query inside the event handler. If you want to send data corresponding the user id and specifically just for one user, I would recommend you to do like this;
Event Handler
private void SqlDependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Info == SqlNotificationInfo.Insert)
{
RecordInfo info = GetLastInsertedRecord(); //Just a custom entity
if(info.UserId > 0)
NotificationHub.SendNotification(info.UserId);
}
RegisterNotification(DateTime.Now);
}
Hub
public static List<UserConnection> ListUser { get; set; }
public static void SendNotification(string who)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
// Get specific user from connected ones.
string Id = ListUser.Find(x => x.UserId == who).ConnectionId;
context.Clients.Client(Id).addNotification(who); // or another data
}
//Add every connected users to the list
public override Task OnConnected()
{
ListUser = new List<UserConnection>();
var us = new UserConnection();
us.UserId = Context.QueryString["UserId"];
us.ConnectionId = Context.ConnectionId;
ListUser.Add(us);
return base.OnConnected();
}
UPDATE Client
$(function () {
var connection = $.connection.notificationHub;
//Pass the userId here as querystring
$.connection.hub.qs = "UserId=" + $("#labelHoldsUserId").val();
//signalr method for push server message to client
connection.client.addNotification = function (who) {
//send notification here
console.log(who + " sends message");
};
// Start hub
$.connection.hub.start().done(function () {
console.log("SignalR Started")
});
})
It appears that the both the OnChange handler and the SqlDependency instance are only good for one event. After the event is fired and you unsubscribe the handler, you need to register your handler to a NEW SqlDependency object.
Please see the link here for full details: http://msdn.microsoft.com/en-us/library/a52dhwx7(v=vs.80).aspx

SqlDependency.OnChange dependency_OnChange did not fire

I have just started usingSignalR and setup configurations according to different articles and multiple questions here at SO. I have followed every step. I am unable to figure out why dependency OnChange is not firing ?
[HubName("broadcastHub")]
public class BroadcastHub : Hub
{
[HubMethodName("sendNotifications")]
public Task<object> SendNotifications()
{
DataTable dt = new DataTable();
using (var connection = new SqlConnection(strConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
var reader = command.ExecuteReader();
dt.Load(reader);
connection.Close();
}
}
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<BroadcastHub>();
var json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
return (context.Clients.All.RecieveNotification(json));
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SendNotifications();
}
}
}
It works fine for the first time and I get expected data. But when any change is made in Table it does not fire dependency_OnChange
I have also identified that Broker Service is enabled by using the following queries:-
select is_broker_enabled from sys.databases where name='msdb'
select is_broker_enabled from sys.databases where name='mydb'
Both are enabled and value is 1.
Query which I am using in SendNotifications is:-
select Id,OXEName,OXEIP IP,ConnectionStatus Status, Case WHEN ConnectedOxeIP IS NULL OR ConnectedOxeIP = '' THEN OXEIP ELSE ConnectedOxeIP END as ConnectedOxeIP from PBXDetail
Java Script
$(function () {
var notifications = $.connection.broadcastHub;
notifications.client.recieveNotification = function (response) {
};
$.connection.hub.start().done(function () {
notifications.server.sendNotifications();
}).fail(function (e) {
});
});
Startup.cs
[assembly: OwinStartup(typeof(myNameSpace.Startup))]
namespace myNameSpace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration() { EnableJSONP = true });
}
}
}
Global.asax
protected void Application_Start(object sender, EventArgs e)
{
System.Data.SqlClient.SqlDependency.Start(strConnectionString);
}
protected void Application_End(object sender, EventArgs e)
{
System.Data.SqlClient.SqlDependency.Stop(strConnectionString);
}
I have figured it out and posting it as an answer so that any future reader who will be facing this type of issue would be able to figure it out.
I debugged piece of code and found that I was getting following Parameters with Values in SqlNotificationEventArgs at dependency_OnChange and those were:
Info => Invalid
Type => Subscribe
Source => Statement
If info is invalid this lead me to know that there was a problem with my query. Then I changed my query syntax like following and it worked fine.
select [Id],[OXEName],[OXEIP] as [IP],[ConnectionStatus] as [Status], Case WHEN [ConnectedOxeIP] IS NULL OR [ConnectedOxeIP] = '' THEN [OXEIP] ELSE [ConnectedOxeIP] END as [ConnectedOxeIP] from dbo.PBXDetail
Following are the query statuses which I found:
select * from table // did not work
select ID from table // did not work
select [ID] from table // did not work
select [ID] from dbo.table // Worked
After doing this I have found that at every page refresh dependency_OnChange was firing as many times the page was refresh. For instance if page is refreshed 10 times it will fire 10 times. So I have made the following changes:
[HubMethodName("sendNotifications")]
public Task<object> SendNotifications()
{
DataTable dt = new DataTable();
using (var connection = new SqlConnection(strConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Notification = null;
if (ServiceController.dependency == null)
{
ServiceController.dependency = new SqlDependency(command);
ServiceController.dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
}
var reader = command.ExecuteReader();
dt.Load(reader);
connection.Close();
}
}
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<BroadcastHub>();
var json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
return (context.Clients.All.RecieveNotification(json));
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
if (ServiceController.dependency != null)
{
ServiceController.dependency.OnChange -= dependency_OnChange;
ServiceController.dependency = null;
}
SendNotifications();
}
}
ServiceController
public static class ServiceController
{
internal static SqlCommand command = null;
internal static SqlDependency dependency = null;
internal static bool isCachingEnabled = false;
}
Global.asax
protected void Application_Start(object sender, EventArgs e)
{
if (!ServiceController.isCachingEnabled)
{
SqlDependency.Stop(strConnectionString);
SqlDependency.Start(strConnectionString);
}
}

Notify winforms application by Sql Server database changes

I created a Sql server database, in which I added a table named user. Then I executed this script
ALTER DATABASE [TestNotification] SET ENABLE_BROKER
I'd like to use SqlDependency class to notify a winforms application when the user table were changed.
namespace Watcher
{
public partial class Form1 : Form
{
private int changeCount = 0;
private const string statusMessage = "{0} changes have occurred.";
private DataSet dataToWatch = null;
private SqlConnection connection = null;
private SqlCommand command = null;
public Form1()
{
InitializeComponent();
button1.Enabled = CanRequestNotifications();
this.FormClosed += Form1_FormClosed;
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
SqlDependency.Stop(GetConnectionString());
if (connection != null)
{
connection.Close();
}
}
private bool CanRequestNotifications()
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm =
new SqlClientPermission(
PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void button1_Click(object sender, EventArgs e)
{
changeCount = 0;
label1.Text = String.Format(statusMessage, changeCount);
//SqlDependency.Stop(GetConnectionString());
SqlDependency.Start(GetConnectionString());
if (connection == null)
{
connection = new SqlConnection(GetConnectionString());
}
if (command == null)
{
command = new SqlCommand(GetSQL(), connection);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private string GetConnectionString()
{
return #"Data Source=BILOG-PRT-12\SQLEXPRESS; Initial Catalog=TestNotification;Integrated Security=True";
}
private string GetSQL()
{
return "Select [id],[nom],[prenom],[age] from [dbo].[user]";
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
MessageBox.Show("modification Occurred");
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
if (i.InvokeRequired)
{
OnChangeEventHandler tempDelegate =new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
i.BeginInvoke(tempDelegate, args);
return;
}
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
++changeCount;
label1.Text = String.Format(statusMessage, changeCount);
GetData();
}
private void GetData()
{
//dataToWatch.Clear();
//command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
if (connection.State != ConnectionState.Open) connection.Open();
using (var dr = command.ExecuteReader())
{
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
}
}
}
}
All the broker service are running :
I launched the application, Then I clicked into the button and finally I go the Sql Server management studio And I inserted a new row. The problem is that the message box in the application is not shown so the c# application is not notified by SQL Server!!!
So I need to know :
Why this happens?
Which step I forget ?
How can I resolve this issue?
There's quite a few limitations with SqlDependency. To quote one relevant issue:
The projected columns in the SELECT statement must be explicitly stated, and table names must be qualified with two-part names.Notice that this means that all tables referenced in the statement must be in the same database.
(see https://msdn.microsoft.com/library/ms181122.aspx for the full list)
You have to explicitly use two-part name (e.g. dbo.user instead of just user).
Also, you need to execute the command. It doesn't just start working automagically :) Adding a simple using (var dr = command.ExecuteReader()) {} should be enough.

how to start window services automatically while it is stopped by c#

I had created a window services and it will stop when it detect there is changes on my database.
Problem : how to start the window services again after it is stop in maybe 5 or 10 second by coding in C# ?
private static string connectString = ConfigurationManager.ConnectionStrings['ConnStr'].ToString();
int sql_depend = 0;//stop
private delegate void GridDelegate(DataTable table);
private SqlDependency dep;
public Watcher()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
SqlDependency.Start(connectString);
sql_depend = 1;
UpdateGrid();
string file = #"E:\WatcherLogFile\sds.txt";
TextWriter writer = new StreamWriter(file, true);
writer.WriteLine("Window services started");
writer.Close();
}
protected override void OnStop()
{
string file = #"E:\WatcherLogFile\sds.txt";
TextWriter writer = new StreamWriter(file, true);
writer.WriteLine("Window services stopped");
writer.Close();
}
//sql dependency check
private void UpdateGrid()
{
string sql = "select [Name], [ClientName] from [Account]";
DataTable dt = new DataTable();
using (SqlConnection cn = new SqlConnection(connectString))
{
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cn.Open();
dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(dep_OnChange);
using (SqlDataReader rdr = cmd.ExecuteReader())
{
dt.Load(rdr);
}
}
}
}
//sql dependency detect changes
void dep_OnChange(object sender, SqlNotificationEventArgs e)
{
ServiceController myService = new ServiceController();
myService.ServiceName = "Watcher";
myService.Stop();
SqlDependency.Stop(connectString);
sql_depend = 0;
}
can it be loop function in onStop() ? I want it manually start it once on everyday then it start / stop by itself after that.
You can try this.
var sc = new ServiceController(YouServiceNameString);
// stop service
// sc.Stop();
//start service
sc.Start();
// or restart
/*
if (sc.Status.Equals(ServiceControllerStatus.Running))
{
sc.Stop();
sc.WaitForStatus(ServiceControllerStatus.Stopped);
}
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running);
*/
If you want pause, use Thread.Sleep or this example
var _stopToken = new CancellationTokenSource();
// call this when you want stop
// _stopToken.Cancel();
// there is you can set timeout
_stopToken.Token.WaitHandle.WaitOne(YourWaitTime);
// if you want circle
// while (!_stopToken.IsCancellationRequested)
// { /*...do somthing...*/ }
You have various options to solve your problem.
Easiest solution would be configuring recovery action for a service failure.
Check this link for detailed instructions.
Another option I can think of writing your custom script/program to restart it when you notice the failure.

Categories

Resources