Apologize for duplicate question. I have googled and everywhere I found only the question but no successful solution. I am facing exactly the same issue described here and here and struggling from past week,but I did not find any success, so finally I decided to ask a question here. I have tried to solve it myself by following the comments suggested in these question, but to success. I want SqlDependency onchange event to fire exactly once for any number for instance or users logged in.
This method is called once whenever a new user login into the application
public List<TransactionMaster> GetUnregisteredTransactions()
{
List<TransactionMaster> ltrans = new List<TransactionMaster>();
TransactionMaster trans = new TransactionMaster();
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
string query = "SELECT TransId, ReceivedFrom,ReceivedOn,Mask FROM [dbo].TransactionMaster WHERE StageId =1";
SqlDependency.Start(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
using (SqlCommand cmd = new SqlCommand(query, connection))
{
cmd.Notification = null;
DataTable dt = new DataTable();
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
ltrans = dal.ConvertDataTable<TransactionMaster>(dt);
}
}
return ltrans;
}
And then again this method is called from onchange event
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
TransactionHub.GetUnregTransactions(GetUnregisteredTransactions());
}
For my case each time event fired I added events to the dependency
dependency.OnChange += new OnChangeEventHandler(SqlNotification.dependency_OnChange);
after I found this solution, i changed my code to :
dependency.OnChange -= new OnChangeEventHandler(SqlNotification.dependency_OnChange);
dependency.OnChange += new OnChangeEventHandler(SqlNotification.dependency_OnChange);
and this solved my problem.
Every time you call GetUnregisteredTransactions you are calling dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);, This is causing a new event to registered, so now you have two events that will fire. when the two events fire it will call dependency.OnChange += new OnChangeEventHandler(dependency_OnChange); two more times making it 4 events that will fire. This number will double with every call till the object dependency is pointing at is garbage collected.
You need to either not re-register the dependency on calls from the event version (for example pass a bool to the function to know if it should do the registration or not) or you need to unregister the old notification before you create a new one.
Here is a example of unregistering.
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
if (e.Type == SqlNotificationType.Change)
TransactionHub.GetUnregTransactions(GetUnregisteredTransactions());
}
I know this is a late answer but I wanted to share my very fun experience with the SqlDependecy firing OnChange event multiple times.
So I ran into this problem as some people did and started looking for answers and quickly realized that there were no code examples that painted the whole picture of how to go about this problem.
This DatabaseWatcher class that I have snatched from
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/detecting-changes-with-sqldependency, I had to modify it to prevent multiple subscriptions to OnChange events that in the end cause multiple OnChange events to be fired due to what I suspect, multiple instances of SqlDepency floating around with those OnChange subscriptions still attached.
The way I go about is by applying the solution from Scott Chamberlain and then catching the current instance of the SqlDepency when subscribing to the OnChange event to make sure to unsubscribe from the OnChange event before liquidating the DatabaseWatcher instance.
Unrelated to the subject, I also forward the OnChange event to hide it in my business class.
public class DatabaseWatcher : IDisposable
{
/// <summary>
/// Forwards the original OnChangeEventHandler event with the database table notification data
/// </summary>
public event EventHandler<SqlNotificationEventArgs> OnTableChange;
public SqlDependency CurrentSqlDependencyInstance { get; set; }
public string ConnectionString { get; set; }
public string Query { get; set; }
public DatabaseWatcher(string connectionString, string query)
{
ConnectionString = connectionString;
Query = query;
}
/// <summary>
/// Starts the database notification listener. Needs to be started before subscribing to the database notifications.
/// </summary>
public void StartDatabaseNotificationsListener()
{
// Create a dependency connection.
SqlDependency.Start(ConnectionString);
}
/// <summary>
/// Subscribes to the database table change notifications. After each notification, a new re-subscription is needed to receive the next database/table notification.
/// </summary>
public void SubscribeToDatabaseNotifications()
{
//create database connection
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
//open database conneciton
connection.Open();
// Create a new SqlCommand object.
using (SqlCommand command = new SqlCommand(Query, connection))
{
// Create a dependency and associate it with the SqlCommand.
SqlDependency dependency = new SqlDependency(command);
//catch current SqlDependency instance
CurrentSqlDependencyInstance = dependency;
// Subscribe to databas/table notifications
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
// Execute the command for the initial table snapshot to later compare with when changes arise
var affectedRowsCount = command.ExecuteNonQuery();
}
}
}
private void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
var sqlDependency = (SqlDependency)sender;
//don't unsubscribe after the subscription event
if (e.Type == SqlNotificationType.Subscribe)
return;
//unsubscribe from the OnChange event from the current instance of the SqlDepency object
sqlDependency.OnChange -= new OnChangeEventHandler(OnDependencyChange);
//don't fire the event if no changes were done to the table records
if (e.Type != SqlNotificationType.Change)
return;
if (e.Info != SqlNotificationInfo.Insert && e.Info != SqlNotificationInfo.Delete)
return;
OnTableChange = new EventHandler<SqlNotificationEventArgs>(OnTableChange);
OnTableChange?.Invoke(sender, e);
}
/// <summary>
/// Stops listening to database notifications, method is called on Dispose
/// </summary>
public void StopDatabaseNotificationsListener()
{
//unsubscribe from the OnChange event from the current instance of the SqlDepency object
CurrentSqlDependencyInstance.OnChange -= new OnChangeEventHandler(OnDependencyChange);
SqlDependency.Stop(ConnectionString);
}
public void Dispose()
{
StopDatabaseNotificationsListener();
}
}
So there it is, please leave comments, thoughts, etc,
Cheers.
Related
I know there is a similar question on SO that has an answer, and after reading that and other articles, I'm still not sure why I can't get this wired up properly. I've tried quite a bit of examples and have been trying different things and simply can't get it to work. I know there are refactoring opportunities but I'm trying to keep it simple right now just to get it working first.
I have permissions to the db, and I can see that a temporary service and queue gets created. One thing I noticed though was that if I run a query, the queue does not display any records and my dependency_OnChange event does not get fired. I'm assuming it's not getting fired because nothing is entering the queue...
UPDATE: I'm getting it to fire now, however, the only event I get is Subscribe. The SqlNotificationEventArgs show Invalid for the Info, but my query and table match all the SELECT rules for sql dependency.
Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SqlDependency.Start(_entityConnString);
}
protected void Application_End()
{
SqlDependency.Stop(_entityConnString);
}
InfectionPreventionHub.cs: (This is a simple SignalR Hub that starts up with a certain area)
/// <summary>
/// Sends the notifications.
/// </summary>
[HubMethodName("sendUpdates")]
private void SendUpdates()
{
var infectionPreventionItems = InfectionPreventionOperations.GetInfectionPrevention();
var vm = new InfectionPreventionViewModel
{
InfectionPrevention = infectionPreventionItems
};
var result = JsonConvert.SerializeObject(vm);
GetUpdates();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<InfectionPreventionHub>();
context.Clients.All.RecieveUpdates(result);
}
/// <summary>
/// Gets the updates.
/// </summary>
private void GetUpdates()
{
const string commandText = #"SELECT [Id], [FirstName] FROM [TestDb].[dbo].[Association]";
using (var connection = new SqlConnection(EntityConnString))
{
connection.Open();
using (var command = new SqlCommand(commandText, connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(sqlDependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
// NOTE: You have to execute the command, or the notification will never fire.
using (var reader = command.ExecuteReader())
{
}
}
}
}
/// <summary>
/// Handles the OnChange event of the sqlDependency control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SqlNotificationEventArgs"/> instance containing the event data.</param>
private void sqlDependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// TODO: Check for the type of event
GetUpdates();
SendUpdates();
}
It was real easy to wire this up to RavenDB, but SqlServer is giving me a hard time.
GetUpdates only fires once when the hub connection happens, which I thought would start up the dependency. However, when updating or adding something to the Association table, no events get raised. I'm required to use SignalR for the UI's realtime updates and data will be entered via 3rd party tools. Any help is appreciated.
Can any body tell me what is the reason behind SQL dependeny OnChange event calls multiple times after page refresh. What could be the possible reason behind this? Before page refresh it is called only one time per change in the database.
Problem: When I refresh page each time a new SQL Dependency variable was created and also a new Change_Event_Handler associated with that new dependency variable, and when SQL dependency is called then it has to unsubscribe from all of the existing change events which made multiple calls to my function.
Solution: Define these both variable as static in class:
internal static SqlCommand command = null;
internal static SqlDependency dependency = null;
Then use the function like this, and in application start first stop the Dependency and then start again and then do other stuff like this.
Check if dependency is started already then don't create new dependency connection and similarly new ChangeEvent,
using (EmailController.command = new SqlCommand(SQL.emailmessagesbyaccount_sql(), conn.getDbConnection()))
{
defaultemailid = emailid;
EmailController.command.Parameters.Add(new SqlParameter("#emailaccountid", emailid));
EmailController.command.Notification = null;
if (EmailController.dependency == null)
{
EmailController.dependency = new SqlDependency(EmailController.command);
EmailController.dependency.OnChange += new OnChangeEventHandler(emailMessages_OnChange);
}
var reader = EmailController.command.ExecuteReader();
}
and finally you have to implement the onchange_event like this:
private void emailMessages_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
//if not null then unsubscribe the calling event
if (EmailController.dependency != null)
{
EmailController.dependency.OnChange -= emailMessages_OnChange;
}
//do my email updates
NotificationHub.EmailUpdateRecords();
// here again subscribe for the new event call re initialize the
// exising dependecy variable the one which we defined as static
SingletonDbConnect conn = SingletonDbConnect.getDbInstance();
using (EmailController.command = new SqlCommand(SQL.emailmessagesbyaccount_sql(), conn.getDbConnection()))
{
EmailController.command.Parameters.Add(new SqlParameter("#emailaccountid", defaultemailid));
EmailController.command.Notification = null;
EmailController.dependency = new SqlDependency(EmailController.command);
EmailController.dependency.OnChange += new OnChangeEventHandler(emailMessages_OnChange);
var reader = EmailController.command.ExecuteReader();
}
}
}
Actually this was my code logic but hope you will get pretty much good idea from this implementation how to handle this kind of issue which made me stumbling my head for a week.
I was having the same problem and #usman's answer helped me a lot. I change the logic of dependency_OnChange method a little bit.
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (dependency != null)
{
dependency.OnChange -= dependency_OnChange;
dependency = null;
}
if (e.Type == SqlNotificationType.Change)
{
MessagesHub.SendMessages();
}
}
I set the dependency to null if it is not null. If we don't set it to null it fires, on every page refresh and if the page is opened multiple times or opened from multiple browsers. As #usman did define the dependency as internal static and set dependency to null in the onChange method. This made my day. Hope it helps another who are facing the same problem.
I used Detecting Changes with SqlDependency as example for the code that I'm writing. I've also looked at other links with similar code, but none of them work.
Essentially, I simply want to change label1.Text when a change has been made to table [ErrorLog]. For some reason, OnDependencyChange is not firing.
I've enabled Service Broker in the database:
ALTER DATABASE TestDB
SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE
Now, here's my complete code. It's very short:
public partial class Form1 : Form
{
private string GetConnectionString()
{
return #"Data Source=USER-PC\SQLEXPRESS;Initial Catalog=TestDB;Persist Security Info=True;User ID=TestUser;Password=12345;";
}
SqlConnection connection;
public Form1()
{
InitializeComponent();
connection = new SqlConnection(GetConnectionString());
connection.Open();
SqlDependency.Start(GetConnectionString());
i = 0;
}
int i;
void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
i++;
label1.Text = "Changed: " + i.ToString();
// Handle the event (for example, invalidate this cache entry).
}
void SomeMethod()
{
// Assume connection is an open SqlConnection.
// Create a new SqlCommand object.
using (SqlCommand command =
new SqlCommand("SELECT [ErrorLog].[ID],[ErrorLog].[Project],[ErrorLog].[Form],[ErrorLog].[Message],[ErrorLog].[Exception],[ErrorLog].[InsertDate] " +
"FROM [dbo].[ErrorLog]", 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 += new OnChangeEventHandler(OnDependencyChange);
// Execute the command.
using (SqlDataReader reader = command.ExecuteReader())
{
// Process the DataReader.
}
}
}
}
I checked if service broker is enabled and it is; the following returns 1:
SELECT is_broker_enabled
FROM sys.databases
WHERE name = 'TestDB';
Any help is appreciated.
Thanks.
You are doing everything correctly except one thing. Call the method SomeMethod() once in your Form1 constructor.
All subsequent changes to your table data will trigger the dependency change.
I have used NUGET to install the Sqlite Core package into my c# project using:
>Install-Package System.Data.SQLite.Core
I create a database connection as follows:
var data = new SQLiteConnection(connectionString);
I then hook an event handler to the update event which fires every time that an update statement occurs (for the purposes of a last write date field for a particular piece of business logic)
data.Update += DataOnUpdate;
This is all awesome. However the SqliteConnection class also exposes an event called Trace The documentation says the following about this event:
"This event is raised whenever SQLite Statement First begins executing on this connection. It only applies for the given connection"
I read this to mean that it performs a similar function to the Update event whereby it should fire whenever an SQL statement is being executed.
HOWEVER
When I hook this event up as follows:
data.Trace += DataOnTrace;
It never fires. I have tried SELECT, UPDATE, DELETE, CREATE TABLE, TRANSACTIONS and basically every bit of Sql logic that I can think of and it refuses to fire.
What is this event there for if not to fire? or is there something I need to do to get the connection to fire this event?
I downloaded the System.Data.SQLite package and wrote the following code. The trace event seems to fire OK for me.
Given a SQLite database containing a table called "tbl1" (schema unimportant)
static void Main(string[] args)
{
using (SQLiteConnection conn = new SQLiteConnection(#"Data Source=C:\dev\Sandbox\Sandbox.Console\test.db;Version=3;"))
{
conn.Open();
conn.Trace += conn_Trace;
using(SQLiteCommand cmd = new SQLiteCommand("Select * from tbl1", conn))
{
using (SQLiteDataAdapter da = new SQLiteDataAdapter(cmd))
{
DataSet ds = new DataSet();
da.Fill(ds);
}
}
conn.Trace -= conn_Trace;
conn.Close();
}
}
static void conn_Trace(object sender, TraceEventArgs e)
{
System.Console.WriteLine(e.Statement);
}
I have a table in a SQL Server database that represents a log file of some actions inserted from a running windows service. Everything is working well.
But, I have a Windows application that gets the latest rows that have been inserted in the log table and views it in a DataGridView. While developing this application I depended on Using SqlDependency in a Windows Application from MSDN. It is working well, but when the log table receives a large number of log details, the Windows app hangs up and the main thread pool becomes too busy.
I want to run the same code referenced in the previous link in a separated thread pool by using Thread class or BackgroundWorker control. This means a thread for using UI controls and another one for listening to the database changes and get it into the DataGridView.
You can see the UI screenshot from this link "UI"
No. (1): This GroupBox represents the UI tools which users can use it while monitoring.
No. (2): The Start button is responsible for beginning to listen and receive updates from the database and refill the DataGridView.
No. (3): This grid represents the new logs that have been inserted in the database.
No. (4): This number (38 changes) represents the count of listening of sql dependency to the database changes.
My code:
public partial class frmMain : Form
{
SqlConnection conn;
const string tableName = "OutgoingLog";
const string statusMessage = "{0} changes have occurred.";
int changeCount = 0;
private static DataSet dataToWatch = null;
private static SqlConnection connection = null;
private static SqlCommand command = null;
public frmMain()
{
InitializeComponent();
}
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 dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(tempDelegate, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
// At this point, the code is executing on the
// UI thread, so it is safe to update the UI.
++changeCount;
lblChanges.Text = String.Format(statusMessage, changeCount);
this.Refresh();
// Reload the dataset that is bound to the grid.
GetData();
}
private void GetData()
{
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
}
}
private void btnStart_Click(object sender, EventArgs e)
{
changeCount = 0;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop("<my connection string>");
SqlDependency.Start("<my connection string>");
if (connection == null)
{
connection = new SqlConnection("<my connection string>");
}
if (command == null)
{
command = new SqlCommand("select * from OutgoingLog", connection);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private void frmMain_Load(object sender, EventArgs e)
{
btnStart.Enabled = CanRequestNotifications();
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop("<my connection string>");
}
}
What I want exactly: when user click the Start button, the application run the code in a separated thread pool.
First if I understand well the change notification is already executed on another thread so using one more threading layer should be useless.
Indeed what causes the application to hang is the update of the UI, on the UI thread.
Could you show the code responsible for this update please?
If there is a lot of notifications visual update will be longer and you can't do much:
update the grid by chunks to smooth the update: instead of inserting 1000 new records you run 10 updates of 100 records, but you take the risk of being overwhelmed by data if you don't process them fast enough
using a collection that handles notification natively like a BindingList could help
Moreover what you can do to enhance the user experience, avoiding the unpleasant "it hangs" effect, is displaying a progress bar or a simple spinner.
UPDATE:
So if the first part of the GetData function is the bottleneck then indeed you could use another thread, e.g. from the thread-pool:
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
// Update the UI
dgv.Invoke(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
});
}
});
}
So the only part that will run on the UI thread is the update of the datagrid.
Not tested but hope this helps...
LAST? UPDATE:
With some synchronization to avoid concurrent execution:
AutoResetEvent running = new AutoResetEvent(true);
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
running.WaitOne();
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
running.Set();
// Update the UI
dgv.Invoke(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
});
}
});
}