Today I started learning about SqlDependency and all of it's features and I have slowly been testing it on my WCF Service. I've got everything working so far by following a couple of tutorials, but I got stuck at a very crucial point.
My issue is (and I know that I DO NOT understand the whole SqlDependency concept yet), that for the life of me cannot figure out how to send my client-side WPF application a callback from my WCF service after the OnChange event has been triggered. I might be wording everything wrong in my question, so please excuse me.
Here I start my SqlDependency:
private string dependencyResult = "";
public void StartListener()
{
SqlDependency.Stop(ConfigurationManager.ConnectionStrings["TruckDbWcf"].ConnectionString);
SqlDependency.Start(ConfigurationManager.ConnectionStrings["TruckDbWcf"].ConnectionString);
RegisterDependency();
}
Here is my RegisterDependency method:
public void RegisterDependency()
{
SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["TruckDbWcf"].ConnectionString);
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.Connection.Open();
command.CommandType = CommandType.Text;
command.CommandText = "SELECT [Id], [Title] FROM [dbo].[Feeds];";
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += SqlDependencyOnChange;
command.ExecuteReader();
command.Connection.Close();
}
And my OnChange event:
private void SqlDependencyOnChange(object sender, SqlNotificationEventArgs eventArgs)
{
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -= SqlDependencyOnChange;
StartListener();
}
I want to call my StartListener() method from my WPF app, so I use an
[OperationContract]
void StartListener();
And I call the method from my WPF application like so from a button click event:
service.StartListenerAsync();
So my question is: How would I be possible to get a notification/message/callback from my service to let me know that data in my dbo.Feeds table has changed? I have no clue how to do this and I feel a bit stupid that I cannot figure this out at the moment. Can someone please help me?
I want to show a message in a label in my WPF application that there has been a change in data.
Edit - I also have enabled the Service Broker and set the required permissions in my database.
Related
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 data grid view that its data source is a data table in form in c#,
how can i make it read table from database continuously
i mean if my program running in many computers in same network and connected with same database , if computer 1 add row to the database its appears automatically in computer 2 without clicking any button to refresh.
void load()
{
c.connect("sel_dep");
c.com.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da=new SqlDataAdapter (c.com);
DataTable dt = new DataTable();
c.open();
int last = 0;
while (true)
{
if (dt.Rows.Count > 0)
dt.Rows.Clear();
da.Fill(dt);
dd = dt;
if (dt.Rows.Count != last)
{
last = dt.Rows.Count;
this.Invoke((MethodInvoker)delegate { dataGridView1.DataSource = dt; dataGridView1.SelectedRows[0].Selected = true; label1.Text = dataGridView1.RowCount.ToString(); });
}
}
c.close();
}
private void Form3_Load(object sender, EventArgs e)
{
aa = new Thread(() => { load(); });
aa.Start();
}
this is my tray
If you are using Winforms to create the desktop application, you would not need timer.
Simply drag and drop Timer control from the Toolbox on your form, in design mode.
' The form load should be altered as below
private void Form3_Load(object sender, EventArgs e)
{
' Assume the Timer control is named as 'oTimer'
oTimer.Enabled = true;
oTimer.Interval = 60000; ' In Milliseconds, equates to 1 minute
oTimer.Start();
}
Create the Tick event for the Timer. This event would be fired each time the interval elapses.
private void oTimer_Tick(object sender, EventArgs e)
{
<Call your function to initiate/refresh the DataGrid.DataSource within this event>
}
In order to further understand how the Timer class works, refer Timer Class (System.Windows.Forms).
Also refer to Stackoverlow question Winforms Timer for Dummies with plenty of resources and tips to master the Timer control.
If you're using Sql Server you can have a look at Query Notifications, more specifically at SqlDependency class. It can be used to get notifications from sql server, when data changes, into your desktop application
Built upon the Service Broker infrastructure, query notifications
allow applications to be notified when data has changed. This feature
is particularly useful for applications that provide a cache of
information from a database, such as a Web application, and need to be
notified when the source data is changed.
This is the first time I've ever needed to use an SqlDependency so I am hoping that its a stupid mistake I've made.
The problem I'm having is that the OnChanged event doesn't fire when the sql table changes. No errors or anything just it doesn't fire.
Here is the code
public class SqlWatcher
{
private const string SqlConnectionString = "Data Source = CN-PC08\\DEV; Initial Catalog=DEP; User = sa; Password=******";
public SqlWatcher()
{
SqlClientPermission perm = new SqlClientPermission(System.Security.Permissions.PermissionState.Unrestricted);
perm.Demand();
SqlCommand cmd = new SqlCommand("SELECT [DataAvaliable], [RowNumber] FROM [dbo].[Trigger]", new SqlConnection(SqlConnectionString));
SqlDependency sqlDependency = new SqlDependency(cmd);
sqlDependency.OnChange += On_SqlBitChanged;
}
private void On_SqlBitChanged(object sender, SqlNotificationEventArgs sqlNotificationEventArgs)
{
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= On_SqlBitChanged;
// Fire the event
if (NewMessage != null)
{
NewMessage(this, new EventArgs());
}
}
public void Start()
{
SqlDependency.Start(SqlConnectionString);
}
public void Stop()
{
SqlDependency.Stop(SqlConnectionString);
}
public event EventHandler NewMessage;
And in my main window I have this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
SqlWatcher sqlWatcher = new SqlWatcher();
sqlWatcher.Start();
sqlWatcher.NewMessage += On_NewMessage;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void On_NewMessage(object sender, EventArgs eventArgs)
{
MessageBox.Show("Message Received");
}
}
So the expected behaviour is that if I run the following sqlQuery a messageBox will be displayed saying "Message Received"
INSERT INTO [DEP].[dbo].[Trigger] Values(0,3)
Could anyone give me a hint on what to check/change?
I'm aware that only a subset of Sql features can be used in dependencies but I don't think I'm trying to do anything to fancy here.
I'm hoping that its a stupid mistake I've made.
Unfortunately (or fortunately?) you are making several mistakes.
First is you need to understand that Query Notifications will invalidate one query. So you will only be notified at most once and you have to re-subscribe again (re-submit the query) if you want to receive further notifications.
Next you need to understand that you will be notified for any reason, not only for changes. In your callback you must check the reason you're notified, which are passed in via the SqlNotificationEventArgs.
Next you need to understand asynchronous programming basic principles: if you subscribe for an event make sure you subscribe before the event can happen first time. Case in point: the On_SqlBitChanged can fire as soon as you submit the query. This should happen in the SqlWatcher.SqlWatcher constructor, but you subscribe to the sqlWatcher.NewMessage after the constructor runs. On_SqlBitChanged can be invoked between the constructor finishes before you hook up the NewMessage event callback in which case the notification is silently ignored.
If you want to use a service make sure you start it before you use it. You are using SqlDependency in SqlWatcher.SqlWatcher but you start it after that when you call SqlWatcher.Start().
Finally, if you want to be notified of changes on a query you have to submit the query. You are constructing the SqlCommand object, set up the notification and then... discard the object. Unless you actually submit the query, you did not yet subscribed to anything.
Suggestions for fix:
Make Start and Stop statics, call Start in application start up.
Make sure you subscribe to NewMessage before you submit the query
Actually submit the query (call SqlComamnd.ExecuteQuery())
Inspect the Info, Type and Source in the On_SqlBitChanged callback, if your submission contains an error this is the only way to learn (the SqlComamnd.ExecuteQuery() will succeed even if the notification request is invalid)
You must re-subscribe once you're notified of a change, execute the query again.
One more thing: don't invoke UI code in background callbacks. You cannot call MessageBox.Show("Message Received"); from a callback, you must route through the form main thread via Form.Invoke. YEs, I know that strictly speaking MessageBox.Show does work on a non-UI thread but you will soon move away from alert boxes to actually form interaction and then things will break.
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.