I have a Signalr Hub to notify user when have a new data inserted to Database
hub
public class NotifyHub : Hub
{
public void NotifyAllClients(string source, string msg)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotifyHub>();
context.Clients.All.displayNotification(source, msg);
}
}
Client
$(function () {
var notify = $.connection.notifyHub;
notify.client.displayNotification = function (source,msg) {
new PNotify({
title: source,
text: msg,
type: 'success',
styling: 'bootstrap3'
});
};
$.connection.hub.logging = true;
$.connection.hub.start();
});
client default.aspx codebehind
protected void Page_Load(object sender, EventArgs e)
{
SendNotifications();
}
public void SendNotifications()
{
string message = string.Empty;
string source = string.Empty;
string conStr = ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString;
using (SqlConnection connection = new SqlConnection(conStr)) {
string query = "SELECT [Source], [MSG] FROM [dbo].[LOG_EVENT] ORDER BY LOG_DATE DESC, LOG_TIME DESC";
using (SqlCommand command = new SqlCommand(query, connection)) {
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += dependency_OnChange;
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows) {
reader.Read();
source = reader(0).ToString();
message = reader(1).ToString();
}
}
}
NotifyHub nHub = new NotifyHub();
nHub.NotifyAllClients(source, message);
}
but this notify to all client when user connected to hub It caused multiple notify pop up to all user that use webpage.
I know that I can use caller property to notify to just caller, but this property not working with IHubContext I can't find any solution to solve this
Related
I'm new to the concept of query notifications with SQL Server and it's going to take some time for me to wrap my head around it.
My objective is to create a Windows service application that is notified when a change has been made to a SQL Server table. I followed this guide which was helpful in getting me started.
However I'm not able to get the expected result. The OnStart() method in my windows service app looks like so:
protected override void OnStart(string[] args)
{
eventLog1.WriteEntry("Service Started");
serviceRun = false;
SqlClientPermission perm = new SqlClientPermission(System.Security.Permissions.PermissionState.Unrestricted);
try
{
perm.Demand();
eventLog1.WriteEntry("permission granted");
}
catch (System.Exception)
{
eventLog1.WriteEntry("permission denied");
}
try
{
connstr = "Data Source=THSSERVER-LOCAL;Initial Catalog=ET;User ID=mujtaba;Password=ths123";
connection = new SqlConnection(connstr);
SqlCommand command = new SqlCommand("select * from dbo.Customer_FileUploads", connection);
// Create a dependency and associate it with the SqlCommand.
SqlDependency dependency = new SqlDependency(command);
// Maintain the reference in a class member.
// Subscribe to the SqlDependency event.
dependency.OnChange += Dependency_OnChange;
SqlDependency.Start(connstr);
connection.Open();
// Execute the command.
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
//eventLog1.WriteEntry("reading data");
}
}
else
{
eventLog1.WriteEntry("No rows found.");
}
reader.Close();
}
}
catch (Exception e)
{
eventLog1.WriteEntry("Error Message: " + e.Message);
}
}
The event SqlDependency is subscribed to, looks like so:
private void Dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// Handle the event.
eventLog1.WriteEntry("data changed");
}
The OnStop() method looks like so:
protected override void OnStop()
{
SqlDependency.Stop(connstr);
connection.Close();
eventLog1.WriteEntry("In onStop.");
}
I have ENABLE_BROKER set to true in my database. The end result is, The service runs and the followings logs are created:
"Service Started"
"permission granted"
"data changed"
However when I insert new data into the table, the OnChange() event doesn't fire and no new log is created. Also when I stop and start the service again, the OnChange() is triggered even though there was no new data inserted.
Can anyone help me understand the process?
The SqlDependency is removed after the event fires so you need to execute the command again with the dependency. Below is a console app example that will subscribe again unless the notification was due to an error.
using System;
using System.Data;
using System.Data.SqlClient;
namespace SqlDependencyExample
{
class Program
{
static string connectionString = #"Data Source=.;Initial Catalog=YourDatabase;Application Name=SqlDependencyExample;Integrated Security=SSPI";
static void Main(string[] args)
{
SqlDependency.Start(connectionString);
getDataWithSqlDependency();
Console.WriteLine("Waiting for data changes");
Console.WriteLine("Press enter to quit");
Console.ReadLine();
SqlDependency.Stop(connectionString);
}
static DataTable getDataWithSqlDependency()
{
using (var connection = new SqlConnection(connectionString))
using (var cmd = new SqlCommand("SELECT Col1, Col2, Col3 FROM dbo.MyTable;", connection))
{
var dt = new DataTable();
// Create dependency for this command and add event handler
var dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(onDependencyChange);
// execute command to get data
connection.Open();
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection));
return dt;
}
}
// Handler method
static void onDependencyChange(object sender,
SqlNotificationEventArgs e)
{
Console.WriteLine($"OnChange Event fired. SqlNotificationEventArgs: Info={e.Info}, Source={e.Source}, Type={e.Type}.");
if ((e.Info != SqlNotificationInfo.Invalid)
&& (e.Type != SqlNotificationType.Subscribe))
{
//resubscribe
var dt = getDataWithSqlDependency();
Console.WriteLine($"Data changed. {dt.Rows.Count} rows returned.");
}
else
{
Console.WriteLine("SqlDependency not restarted");
}
}
}
}
I have below code for SqlDependency. Below call back function is get called every time when data reader is executed. Rather than this call back function not get executing when i change some value in Table.
public IEnumerable<Messages> GetAllMessages()
{
var messages = new List<Messages>();
using (var connection = new SqlConnection(_connString))
{
connection.Open();
using (var command = new SqlCommand("SELECT SUM(MessageID) FROM [dbo].[Messages] group by Message", connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
// messages.Add(item: new Messages { MessageID = (int)reader["MessageID"], Message = (string)reader["Message"], EmptyMessage = reader["EmptyMessage"] != DBNull.Value ? (string)reader["EmptyMessage"] : "", MessageDate = Convert.ToDateTime(reader["Date"]) });
}
}
}
return messages;
}
CallBack:
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
MessagesHub.SendMessages();
}
}
Any Help please!
Hi I have some method which send me data from sql table and I want that - when I click button in datagridview - that send parameter to the method and parameter will have ID value(like on picture 107 or 106). Below on the picture is datagridview with 2 buttons and ID column.
public ObservableCollection<MyClass> ReadUpdate(int id_update)
{
ObservableCollection<MyClass> result = new ObservableCollection<MyClass>();
string nwConn = System.Configuration.ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
SqlDataReader dr;
SqlConnection conn = new SqlConnection(nwConn);
try
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
cmd.CommandText = "Insert_Update";
cmd.Parameters.AddWithValue("#id_update", id_update);
conn.Open();
dr = cmd.ExecuteReader();
while (dr.Read())
{
MyClass lin = new MyClass();
lin.id = dr.GetInt32(1);
if (!dr.IsDBNull(2)) lin.other = dr.GetString(2);
if (!dr.IsDBNull(3)) lin.barkod = dr.GetString(3);
if (!dr.IsDBNull(4)) lin.pw = dr.GetInt32(4);
result.Add(lin);
}
dr.Close();
return result;
}
catch (SqlException e)
{
MyClass lin = new MyClass();
lin.other = e.Message;
result.Add(lin);
return result;
}
finally
{
conn.Close();
};
}
My class:
public class PIS
{
public int ID { get; set; }
}
And my button:
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
pis_update = (PIS)((Button)sender).DataContext;
ChildWindow_Update childWindow_update = new ChildWindow_Update();
childWindow_update.DataContext = ((Pismo)((Button)sender).DataContext).Id_Pismo;
childWindow_update.Closed += ChildWindow_Update_Closed;
childWindow_update.Show();
}
public ChildWindow_Update()
{
InitializeComponent();
ServiceReference1.Service1Client webService = new ServiceReference1.Service1Client();
webService.ReadUpdateAsync((int)this.DataContext);
webService.ReadUpdateCompleted += WebService_ReadUpdateCompleted;
private void WebService_ReadUpdateCompleted(object sender, ServiceReference1.ReadUpdateCompletedEventArgs e)
{
if (e.Result != null)
{
//do something
}
}
I have error in webService.ReadUpdateAsync((int)this.DataContext);
"Null REference Exception".
At the time of your constructor call, your DataContext has not been set yet. You should make the id a constructor parameter if you need it in the constructor.
Generally speaking, you should use MVVM and the command pattern, where you can specify your id data as the command parameter in your XAML.
Hello and thanks for reading this.
This is my NotificationHub.cs
public class NotificationHub : Hub
{
private static readonly ConcurrentDictionary<string, User> Users = new ConcurrentDictionary<string, User>(StringComparer.InvariantCultureIgnoreCase);
private static List<User> UserList = new List<User>();
Int16 totalNewMessages = 0;
string UserID;
[HubMethodName("check")]
public Task Check(string id)
{
string profileId = id; //Context.QueryString["id"];
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(profileId, _ => new User
{
ProfileId = profileId,
ConnectionIds = id
});
lock (user.ConnectionIds)
{
Groups.Add(connectionId, user.ProfileId);
}
return base.OnConnected();
}
[HubMethodName("sendNotifications")]
public Task SendNotifications(string id)
{
UserID = id;
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
string query = "SELECT NotificationNumber FROM [dbo].[NotificationStatus] WHERE UserID=" + UserID;
connection.Open();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Notification = null;
DataTable dt = new DataTable();
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
dt.Load(reader);
if (dt.Rows.Count > 0)
{
totalNewMessages = Int16.Parse(dt.Rows[0]["NotificationNumber"].ToString());
}
}
}
User CurrentUser = UserList.FirstOrDefault(i => i.ProfileId == UserID);
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
//return context.Clients.All.RecieveNotification(totalNewMessages);
return context.Clients.Client(Users.Values.FirstOrDefault(i => i.ProfileId == UserID).ConnectionIds).RecieveNotification(totalNewMessages);
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
NotificationHub nHub = new NotificationHub();
nHub.SendNotifications(UserID);
}
}
}
These 2 lines is what I want to focus on.
//return context.Clients.All.RecieveNotification(totalNewMessages);
return context.Clients.Client(Users.Values.FirstOrDefault(i => i.ProfileId == UserID).ConnectionIds).RecieveNotification(totalNewMessages);
If i use the first line it returns the totalNewMessages back to ALL people on the website, but i only want to to return back to the user that requested it.
The second line is my attempt to return the totalNewMessages back tot he specific user, but it dont work.
How can i return the totalNewMessages Back to only the specific user?
I came across this problem a while back. I got it working by returning the messages depending on the user who's logged in
string userName = HttpContext.Current.User.Identity.Name;
return context.Clients.User(userName).RecieveNotification(totalNewMessages);
Also look into parameterized queries to prevent sql injection. There's a handy documentation for what you're trying to achieve here
I have written this class to automate SQLDependencys
public class DatabaseChangeAlert
{
private String connectionString;
private SqlConnection sc;
public event EventHandler<DatabaseChangedEvent> DatabaseChangeEvent;
private String tabelle;
private String query;
public DatabaseChangeAlert(String tabelle, String conString)
{
try
{
this.tabelle = tabelle;
this.sc = new SqlConnection(conString); ;//new SqlConnection(GlobalResources.ConnectionString);
this.connectionString = conString;
String sel = "";
using (SqlConnection con = new SqlConnection(conString))
{
con.Open();
SqlCommand cmd = con.CreateCommand();
cmd.CommandText = "SELECT dbo.syscolumns.name AS Spaltenname " +
" FROM dbo.syscolumns INNER JOIN " +
" dbo.sysobjects ON dbo.syscolumns.id = dbo.sysobjects.id " +
" WHERE (dbo.sysobjects.xtype = 'U') and dbo.sysobjects.name='" + tabelle + "' ";
using (SqlDataReader sr = cmd.ExecuteReader())
{
while (sr.Read())
{
sel += "["+sr.GetString(0)+"],";
}
}
sel = sel.Substring(0, sel.Length - 1);
}
query = "SELECT " + sel + " from [dbo].[" + tabelle+"]";
}
catch (Exception ex) { }
}
~DatabaseChangeAlert()
{
if(connectionString!=null)
SqlDependency.Stop(connectionString);
}
public void start()
{
try
{
SqlDependency.Start(connectionString);
}
catch (Exception ex)
{
// log(ex)
}
try
{
startDependency();
}
catch (Exception ex)
{
// log(ex)
}
}
private void startDependency()
{
// Assume connection is an open SqlConnection.
// Create a new SqlCommand object.
using (SqlCommand command = new SqlCommand(query, sc))
{
sc.Open();
// Create a dependency and associate it with the SqlCommand.
SqlDependency dependency = new SqlDependency(command);
// Maintain the refence in a class member.
// Subscribe to the SqlDependency event.
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
// Execute the command.
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
}
}
sc.Close();
}
}
void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Info != SqlNotificationInfo.Invalid)
{
startDependency();
if (DatabaseChangeEvent != null)
{
DatabaseChangeEvent(this, new DatabaseChangedEvent(tabelle,e.Info));
}
}
}
public class DatabaseChangedEvent : EventArgs
{
public readonly String Tablename;
public readonly SqlNotificationInfo info;
public DatabaseChangedEvent(String tablename,SqlNotificationInfo info)
{
this.Tablename = tablename;
this.info = info;
}
}
}
in my program I use this Class like that:
DatabaseChangeAlert alerttz = new DatabaseChangeAlert("table1", GlobalResources.ConnectionString);
alerttz.DatabaseChangeEvent += (e,d)=>{
MessageBox.Show("table1 changed");
};
alerttz.start();
DatabaseChangeAlert alert = new DatabaseChangeAlert("table2", GlobalResources.ConnectionString);
alert.DatabaseChangeEvent += (e, d) =>
{
MessageBox.Show("table2 changed");
};
alert.start();
Now my Problem is that when I change anything in table2 I do not get a notification, but when I do the same for table 1 I get a notification!
any ideas what the problem could be?
I also tried to only suscribe for table2...but it remain the same.
I've repeated your situation and wrote unit test (TwoTablesNotificationTest), but didn't find anything. It works fine for me. In this situation you could stop receive notifications from SqlDependency in case of destructor call of some DatabaseChangeAlert entity, because it has SqlDependency.Stop(connectionString) instruction. Thus, you have to call SqlDependency.Start after every SqlDependency.Stop to continue receiving events, but destructor is called automatically by garbage collector from parallel thread and it may cause this effect.
And I want to warn you - be careful using SqlDependency class because it has memory leak problem. For my project I used open source realization of SqlDependency - SqlDependencyEx. It is very easy to use:
int changesReceived = 0;
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
TEST_CONNECTION_STRING, TEST_DATABASE_NAME, TEST_TABLE_NAME))
{
sqlDependency.TableChanged += (o, e) => changesReceived++;
sqlDependency.Start();
// Make table changes.
MakeTableInsertDeleteChanges(changesCount);
// Wait a little bit to receive all changes.
Thread.Sleep(1000);
}
Assert.AreEqual(changesCount, changesReceived);
Hope this helps.