I've got a table and a SqlDependency that is waiting for new inserts.
OnChange fires as I need, but I don't understand if it's possible to get the row which cause the databse change.
SqlDependency sql command:
SqlCommand cmd = new SqlCommand("SELECT id FROM dbo.DataRequests", m_sqlConn);
OnChange code:
private void OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -= OnChange;
Console.WriteLine("Info: " + e.Info.ToString());
Console.WriteLine("Source: " + e.Source.ToString());
Console.WriteLine("Type: " + e.Type.ToString());
Console.WriteLine(DateTime.Now);
GetMessages();
}
No information is available about the rows that caused the dependency to be fired.
I guess as a workaround you could always put a timestamp on your records and track when the event was last fired.
Take a look at this component:
SqlTableDependency
For every change done on a SQL Server database table, the C# code receive an event containing a list of RECORDs changed.
Find a very ingenious solution here
According to this post, you can't: http://social.msdn.microsoft.com/Forums/en-US/sqlservicebroker/thread/07234067-73e1-4db5-a4e6-0f9f0bae22ae/
You can only narrow down the reason for the notification by using the properties
Source
Type
Info
of the provided SqlNotificationEventArgs
Just use a cross-platform .NET 3.5 compatible and open-source SqlDependencyEx. It uses a database trigger and native Service Broker notification to receive events about the table changes. This is a usage example:
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);
You can get change notifications as well as information which was changed. Please follow the link for details.
I hope this helps you:
string commandString = string.Format("SELECT [Id] FROM [dbo].[Tech]");
command = new SqlCommand(commandString, connection);
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
this.Dispatcher.Invoke((System.Action)(() =>
{
if (e.Info.ToString().ToLower().Trim() == "insert")
{
GetData();
int NewTechID = TechIDs.Last();
}
}));
}
private void GetData()
{
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
command.Connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
TechIDs.add(int.Parse(reader.GetValue(0).ToString()));
}
reader.Close();
}
command.Connection.Close();
}
You can use Temp tables.
first of all you need to create a temp table with all the fields you need to keep under investigation. something like:
CREATE TABLE ##TempTab(
[field1] [varchar](50) NULL,
[field2] [varchar](50) NULL
}
Please note that kind of tables created within external cose are automatically dropped since the creator program quits so you don't need to drop it on formClosing...
Now, after setting up sqlDepency stuffs you have to fill up you temp table, it's something like a snapshot of the starting scenario.
Then, every time the onChange event is fired you just need to compare your temp table with updated situation. it could be something like:
select * from ##temptable left outer join mytable
ON ##temptable.field1=myTable.field1 AND ##temptable.field2=myTable.field2
WHERE myTable.field2 is null
this will give you all rows has just been deleted (or chagend with old values).
On the other side:
select * from mytable left outer join ##temptable
ON ##temptable.field1=myTable.field1 AND ##temptable.field2=myTable.field2
WHERE ##temptable.field2 is null
will give you all rows has just been added (or changed with new values).
After this compare you just need to update your temp table with new values (faster way is to delete everything and insert all values)
Of course, if your programm will be run simultaneously by different users, you'll need to handle userid within temp table.
Related
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;
});
}
});
}
nice people! I am stuck at one problem here and really need your help. I have a Winforms application, which has a few tabs. It connects to MS SQL Server 2008 DB and shows us a DataGridView on the first tab where we
connect to the DB:
private void button_ConnectDB_Click(object sender, EventArgs e)
{
if (sqlConnection == null || sqlConnection.State != ConnectionState.Open)
sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
try
{
dataAdapter = new SqlDataAdapter(queryStringUsers, sqlConnection);
SetConnectDBObjects();
SetConnectDBButtons();
}
catch
{
ShowUnableToConnect();
return;
}
}
and have some info from DB:
private void button_SpecAcc_Click(object sender, EventArgs e)
{
dataGridView1.DataSource = this.usersBindingSource;
LoadData(ref dataAdapter, ref clientDataSet, TAB_USERS);
dataGridView1.ReadOnly = false;
},
where LoadData is actually:
private void LoadData(ref SqlDataAdapter dataAdapter, ref clientDataSet dataset, string table)
{ dataAdapter.Fill(dataset, table); }
I have the dataAdapter connected straight to SQL Server, so its configured automatically (all operations, like INSERT, UPDATE, DELETE also). And everything worked just fine (could connect to DB, make any operations, save it (via Apply button, which uses adapter"s Update method)), until Ive decided to make another tab.
Another tab should get me the info (several rows) from other place (some ext device I have connected with), get it into DataTable and MERGE with other rows from the DB. Did it all on this tab following this tutorial: website.
When I try to MERGE some row I can see in my DB nice and good UPDATE, BUT in my first tab (on the form) I can see TWO different rows (one, as if it wasn"t touched and second - as if it was updated). I supposed it''s something with Refreshing (tried Refreshing of GridView, didn"t help), or some other element"s Refresh (but which one?...), and I still can not find it.
Would appreciate any help... Thanks
Dealt with it through creating temp dataset, editing it (via DataRows) and getting it into a database. Thanks all the watchers :)
I have two tables, employees and project, in listbox2, I show all the employees and in listbox1 all the projects, now obviously one employee can be involved in many projects and one project could have many employee. So I have this EmployeeProject that maps the many to many relation that exists. What I want is, if user click a project name in first listbox, then all employees in that project should be selected in listbox2. Also, when a user clicks a item in listbox2, (an employee) all project of which that employee is a part should be selected in listbox1
But If I use ListBox.SelectedIndexChanged event for this process, and select even a single value in listbox2 then it would trigger the SelectedIndexChagned for listbox2, and that would start working by selecting all items in listbox1 that current employee is a part of, but again, as soon as even one item in listbox1 is selected, it would fire up its SelectedIndexChanged event, and it would go on forever like this. So what's the solution of this? So far, I've done this..
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// Load the list of employees
cmd.CommandText =
"SELECT EmpName FROM Employee WHERE EmpID IN(SELECT EmpID FROM EmployeeProject WHERE ProjectID =(SELECT ProjectID FROM Project WHERE ProjectName = '" +
listBox1.SelectedItem.ToString() + "')) ORDER BY EmpId";
var rdr = cmd.ExecuteReader();
listBox2.Items.Clear();
// right now, I am doing this to escape this recursive loop, but thats not what I want
while (rdr.Read())
{
listBox2.Items.Add(rdr.GetString(0));
}
rdr.Close();
this.AutoScroll = true;
}
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
// Load the list of projects
cmd.CommandText =
"SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '" +
listBox2.SelectedItem.ToString() + "')) ORDER BY ProjectID";
var rdr = cmd.ExecuteReader();
listBox1.Items.Clear();
// again, I don't want to clear, but select all those employee in listbox1 that are involved in this selected project, but can't do it because it would cause infinite recursion of these
while (rdr.Read())
{
listBox2.Items.Add(rdr.GetString(0));
}
rdr.Close();
this.AutoScroll = true;
}
So? What should I do to achieve what I want to achieve? And how would I avoid that recursion? I know this way also works what I just showed, but I don't want to clear up and show up again, (this might confuse a simple user). I want for each selection, values corresponding to that selection be selected in other listbox (without causing recursion of course!). How do I do it?
EDIT I don't know how can I select multiple items in listbox programmatically, so if you could tell that, it would be great!
There is a design pattern called Balking, I think it applies here.
http://en.wikipedia.org/wiki/Balking_pattern
The idea is to introduce an auxiliary state variable to control the operation:
private bool doesProcessing { get; set; }
private void listBox1_SelectedIndexChanging( ... )
{
// signal the beginning of processing
if ( doesProcessing )
return;
else
doesProcessing = true;
try
{
// your logic goes here
}
finally
{
// signal the end of processing
doesProcessing = false;
}
}
and the same for listBox2.
When you change the selected items of one listbox, you could temporarily disable the events on the other. For example:
// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged);
private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
// Remove your event so that updating lbEmployees doesn't cause
// lbEmployees_SelectedIndexChanged to get fired.
lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;
// other event handler logic
}
finally
{
// Ensure that the handler on lbEmployees is re-added,
// even if an exception was encountered.
lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
}
}
Note: I have renamed your listBoxes (and the associated events) to be more readable. As per your description, listBox1 is now lbEmployees and listBox2 is now lbProjects.
As for programatically selecting multiple items, if you know the index for each one, you could use the ListBox.SetSelected Method. For example:
listBox1.SetSelected(1, true);
listBox1.SetSelected(3, true);
causes listBox1 to select the items at 1 and 3.
In your case, I would recomend only using the queries to get what values to select (not clearing your listboxes and then just adding back the values that should be selected). Below is my suggestion for how you would rewrite one of your handlers:
// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged);
private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
// Declare this outside of try so that we can close it in finally
DbDataReader reader = null;
try
{
// Remove your event so that updating lbEmployees doesn't cause
// lbEmployees_SelectedIndexChanged to get fired.
lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;
// Deselect all items in lbEmployees
lbEmployees.ClearSelected();
cmd.CommandText = "SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '#EmpName')) ORDER BY ProjectID";
cmd.Parameters.AddWithValue("#EmpName", lbProjects.SelectedItem.ToString());
reader = cmd.ExecuteReader();
// For each row returned, find the index of the matching value
// in lbEmployees and select it.
while (rdr.Read())
{
int index = lbEmployees.FindStringExact(rdr.GetString(0));
if(index != ListBox.NoMatches)
{
lbEmployees.SetSelected(index, true);
}
}
this.AutoScroll = true;
}
finally
{
// Ensure that the reader gets closed, even if an exception ocurred
if(reader != null)
{
reader.Close();
}
// Ensure that the handler on lbEmployees is re-added,
// even if an exception was encountered.
lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
}
}
As a side note, you may want to look into using a JOIN operator in your query string, rather than multiple nested SELECTS.
You can remove the attached event to one of the controls when you are in the other, before doing any modifications.
then reattach at the end.
Have one eventMask integer variable and use the pattern below to protect your event each other.
Using a bool as suggested in another answer is enough for this scenario. However, I prefer to use an integer rather than a bool : it can handle nested and stacked events calls correctly.
It is particularly useful when I have to deal with selection/deselection events in treeviews and listviews for example.
int eventMask = 0;
void items_PropertyChanged(object sender, PropertyChangedEventArgs e) // for example...
{
if (eventMask > 0)
return;
try
{
eventMask++;
// processing
// it happens some other events can be called. Including this one.
}
finally
{
// put this in finally, so there is no lock risk
eventMask--;
}
}
The logic of the OP code (if working as OP expect) contains a problem.
Suppose you fill the two lists with all possible values.
Then, when the user clicks on one item, the second list is emptied and filled only with the item linked to the other list. But clicking between the two list could remove forever one or more item from the other list. It is better to leave the two list filled and trying to highlight the line of the corresponding items
For example (supposing ListBox.SelectionMode = SelectionMode.MultiSimple or MultiExtended)
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
........
try
{
......
// Remove the event handler, do your selection, No event on the listbox2 will be fired....
listBox2.SelectedIndexChanged -= new System.EventHandler(this.listBox2_SelectedIndexChanged);
// Do not remove the items , instead clear previous selection
listBox2.ClearSelected();
while (rdr.Read())
{
int index = listBox2.FindString(rdr.GetString(0), -1);
if(index != -1) listBox2.SetSelected(index, true));
}
....
}
finally
{
// before exit, reapply the event handler
listBox2.SelectedIndexChanged += new System.EventHandler(this.listBox2_SelectedIndexChanged);
}
}
of course you need to be sure to reapply the event, so use a try/finally block
The same approach will be valid to stop the event firing for listBox1 when you fill it in the listBox2.SelectedIndexChanged event,
I was trying to make an application which can popup a notify message whenever a new row is added to the database.i was using mysql and changed the default engine to csv engine so that i can use the FileSystemWatcher to detect any changes.The filesystemwatcher is triggering whenever a row is deleted but the problem is its not triggering changed event when a new row is added to the database.I also observed that when a row is deleted the "Date Modified" is changing but when I add a new row its not updating. Please help me.
private void button1_Click(object sender, EventArgs e)
{
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = "C:\\xampp\\mysql\\data\\doubts\\";
fsw.EnableRaisingEvents = true;
fsw.Changed += new FileSystemEventHandler(func);
}
private void func(Object obj,FileSystemEventArgs e)
{
notifyIcon1.Icon = SystemIcons.Application;
notifyIcon1.BalloonTipText =
"Addition of new row to the database detected...";
notifyIcon1.ShowBalloonTip(4000);
}
I would recommend that you poll the database periodically to check for changed records.
If your query is expensive, you could create an AFTER INSERT or AFTER UPDATE trigger which inserts into another table, then poll that table.
If this still is not sufficient, you can do this: From your application, run a command like
SELECT "WaitForChanges", SLEEP(999);
Create a trigger which kills this select via the KILL QUERY command: How to do this from a stored procedure.