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);
}
Related
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.
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.
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'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.
As you see, my code (it's in myform_load), when I run project, it continues to line 1. Just not run other lines after that. I got no error, but the code doesn't run completely. After it come to line one, myform is shown. Where is the problem?
SqlDataAdapter userSharj_history;
private void myform_Load(object sender, EventArgs e)
{
using (SqlConnection con = new SqlConnection(connectionString))
{
con.Open();
using (userSharj_history = new SqlDataAdapter(String.Format("SELECT * FROM users_sharj WHERE user_id = {0} AND datetime BETWEEN '{1}%' AND '{2}%'", user_id, az_tarikh_globalizationDateTimePicker1.Text, ta_tarikh_globalizationDateTimePicker1.Text), con))
{
1. userSharj_history.Fill(nan_DataSet, "sharjes");
2. gridControl1.DataSource = nan_DataSet.Tables["sharjes"];
}
con.Close();
}
view_btn.Focus();
}
The Query string is fine. I tested it in GUI Query Builder.
I use this dataset in my main form that call this form and there is no table in it with the name sharjes.
Either check the Output window or enable reporting of all exceptions. Visual Studio will not inform you about many exceptions by default.
Go to menu Debug --> Exceptions... and then tick Thrown for Common language runtime Exceptions and click OK. Run your code again and see if it throws an exception now.