how to keep sql dependency doing the its purpose - c#

I have a console application.
I wanna keep watching the changes on a specific column in my database table.
I read through internet and I have found that sql dependency is good for my purpose. I started learning about it and I did the following:
create a class.
In the constructor, I called the static function start and I called a function that has all the sql dependency settings.
My problem
When I run the application using the start click on visual studio 2013, the apps works and then stops. However, what I need is that the apps starts working and keep watching for changes in my database's table.
Could you help me please?
Code:
This is a very very simple c# code.
public class MyListener
{
public MyListener()
{
SqlDependency.Start(getConnectionString());
this.listen();
}
private string getConnectionString()
{
return ConfigurationManager.ConnectionStrings["popup"].ConnectionString.ToString();
}
private void listen()
{
string query = "SELECT CallerID FROM TransferToSIP WHERE hasBeenRead = 0";
SqlConnection con = new SqlConnection(getConnectionString());
SqlCommand cmd = new SqlCommand(query, con);
con.Open();
using (cmd)
{
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new
OnChangeEventHandler(OnDependencyChange);
using (SqlDataReader reader = cmd.ExecuteReader())
{
}
}
}
void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine("Roma");
}
void Termination()
{
SqlDependency.Stop(getConnectionString());
Console.Read();
}

The problem is in absence of the resubscruption. You should call the listen method inside of OnDependencyChange. I know that it is weird, but it is the SqlDependency class.

Be careful using the SqlDependency class to monitor changes in the database tables - it has the problems with the memory leaks. However, you can use your own realization with DDL triggers and SQL Service Broker API or use one of an open source projects, e.g. SqlDependencyEx:
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.

Related

Is there any way to react on Azure SQL Database changes?

I use SignalR in my application and to react on database changes I've used Sql Dependency
SqlDependency.Start(con);
But I'm getting the following error:
Statement 'RECEIVE MSG' is not supported in this version of SQL Server
So as I understand Azure SQL Database doesn't support Service Broker.
Is there any solution, besides migrating to Azure VM?
Example of code with SQL Dependency:
public class NotificationComponent
{
public void RegisterNotification(DateTime currentTime)
{
string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
string sqlCommand = #"SELECT [ContactID],[ContactName],[ContactNo] from [dbo].[Contacts] where [AddedOn] > #AddedOn";
using (SqlConnection con = new SqlConnection(conStr))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
cmd.Parameters.AddWithValue("#AddedOn", currentTime);
if (con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency sqlDep = new SqlDependency(cmd);
sqlDep.OnChange += sqlDep_OnChange;
using (SqlDataReader reader = cmd.ExecuteReader())
{
}
}
}
void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency sqlDep = sender as SqlDependency;
sqlDep.OnChange -= sqlDep_OnChange;
var notificationHub = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
notificationHub.Clients.All.notify("added");
RegisterNotification(DateTime.Now);
}
}
public List<Contact> GetContacts(DateTime afterDate)
{
using (MyPushNotificationEntities dc = new MyPushNotificationEntities())
{
return dc.Contacts.Where(a => a.AddedOn > afterDate).OrderByDescending(a => a.AddedOn).ToList();
}
}
}
You can use the "when an item is created" and the "when an item is modified" triggers for SQL in Azure Logic App to react to data changes.
The SQL connector in Azure Logic Apps uses a polling mechanism to query a table for changes using a TIMESTAMP / ROWVERSION column. This data type is specifically designed for this kind of processing in SQL. The polling query essentially selects all rows where the rowversion is greater than the last polled value. The behavior is reliable since the column is controlled by SQL Server and the performance is extremely fast in the case where there is no new data. When there is new data, the performance is comparable to a simple row query.
For more information, please read this article.

How to get call trigger on c# Method using store procedure when i update table data without using sqldependency

i want call a C# method when i update table data or delete table data on every way like sql newquery 'Delete top(1) FROM [dbo].[Leaves]' like this and call c# method using store procedure. i perform it on sql dependencey but i don't need this i perform it using store procedure. you can see my code of sqldependecey. but i want another way to call this method using store procedure.
public class NotificationEvent
{
private delegate void RateChangeNotification(DataTable table);
private SqlDependency dependency;
string ConnectionString = #"Data Source=.;Initial Catalog=Message;Integrated Security=True";
string UserName = Environment.UserName;
public void RegisterForNotification()
{
var connectionString = ConnectionString;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var queryString = "SELECT [ID] FROM [dbo].[Leaves]";
using (var oCommand = new SqlCommand(queryString, connection))
{
// Starting the listener infrastructure...
SqlDependency.Start(connectionString);
var oDependency = new SqlDependency(oCommand);
oDependency.OnChange += OnNotificationChange;
// NOTE: You have to execute the command, or the notification will never fire.
oCommand.ExecuteReader();
}
}
}
private void OnNotificationChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine("Notification Info: " + e.Info);
//Re-register the SqlDependency.
//var oDependency = new SqlDependency();
//oDependency.OnChange += OnNotificationChange;
RegisterForNotification();
}
}
#Umar Asif If the problem you face is totally regarding DB-to-DB communication, I would recommend a concept called "Merge Replication" in SQL Server (using Publisher-Subscriber(s)) design between DBs:
https://learn.microsoft.com/en-us/sql/relational-databases/replication/merge/merge-replication?view=sql-server-2017
Otherwise, if your problem requires a solution ONLY through a call to C# method, please refer:
How to call C# function in stored procedure

SqlDependency.OnChange fires on one database but not another on the same server

I'm trying to make a C# Forms program that views data from a database. It needs to request data on startup, cache it, then update it if the DB is updated. I'm trying to do it using SqlDependency. Here's the code:
private const string connectionString = "Data Source=DESKTOP-VT1F04F\\MSSQLSERVER14;Initial Catalog=test1;Trusted_Connection=True";
private void button1_Click(object sender, EventArgs e)
{
SqlDependency.Stop(connectionString);
SqlDependency.Start(connectionString);
ExecuteWatchingQuery();
}
private void ExecuteWatchingQuery()
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
var command = new SqlCommand("select nbase, name from dbo.filial", connection);
var sqlDependency = new SqlDependency(command);
sqlDependency.OnChange += new OnChangeEventHandler(OnDatabaseChange);
command.ExecuteReader();
}
}
private void OnDatabaseChange(object sender, SqlNotificationEventArgs args)
{
//MessageBox.Show("?");
SqlNotificationInfo info = args.Info;
if (SqlNotificationInfo.Insert.Equals(info)
|| SqlNotificationInfo.Update.Equals(info)
|| SqlNotificationInfo.Delete.Equals(info))
{
MessageBox.Show("!");
//todo
}
ExecuteWatchingQuery();
}
It doesn't do anything. ExecuteWatchingQuery competes fully, but OnDatabaseChange never fires. However, if I replace database test1 with a freshly created database watcher_test with a freshly created table, it works as intended. I have tried the following SQL commands:
alter database test1 set enable_broker
CREATE QUEUE ContactChangeMessages;
CREATE SERVICE ContactChangeNotifications ON QUEUE ContactChangeMessages
Both databases have the same owner (as select name, suser_sname(owner_sid) from sys.databases shows), same permissions, they're on the same server, and I can't see any difference between them, in settings, or anywhere else. The program isn't able to access any table in the first database, but is able to access a copy of a table done with insert into from the first into the second table. The program behaves identically on a different computer that uses the same database restored from a backup.

Why OnChange event is not getting fired upon changing a cell value in database table?

I have been working on this code for the last 24 hours but it ain't working.
I want the OnChange event to get hit If I modify a cell in Services table but it doesn't, no matter what I do.
I have enabled the services broker on the database but still, it doesn't work.
Code:
class Program
{
static void Main(string[] args)
{
var cs = Utility.getConnectionString();
using (SqlConnection connection = new SqlConnection(cs))
{
connection.Open();
SqlCommand command = new SqlCommand(#"Select [Services].[ServiceName], [Services].[ServicePrice] from dbo.Services", connection);
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(OnChange);
SqlDependency.Start(cs);
command.ExecuteReader().Dispose();
}
Console.ReadKey();
}
private static void OnChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine(e.Info);
}
}
Making changes to the db using edit more or update query.
enter image description here
Below is a worked example based on your code that displays SqlNotificationEventArgs values when the event is fired and re-subscribes only when valid.
using System;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main(string[] args)
{
SqlDependency.Start(Utility.getConnectionString());
GetDataWithSqlDependency();
Console.WriteLine("Waiting for data changes");
Console.WriteLine("Press any key to quit");
Console.ReadKey();
SqlDependency.Stop(Utility.getConnectionString());
}
static void GetDataWithSqlDependency()
{
using (var connection = new SqlConnection(Utility.getConnectionString()))
using (var cmd = new SqlCommand(#"SELECT [Services].[ServiceName], [Services].[ServicePrice] from dbo.Services;", connection))
{
var dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
connection.Open();
cmd.ExecuteReader().Dispose();
}
}
static void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine($"OnDependencyChange Event fired. SqlNotificationEventArgs: Info={e.Info}, Source={e.Source}, Type={e.Type}");
if ((e.Info != SqlNotificationInfo.Invalid)
&& (e.Type != SqlNotificationType.Subscribe))
{
SqlDependency.Start(Utility.getConnectionString());
GetDataWithSqlDependency();
Console.WriteLine($"Data changed.");
}
else
{
Console.WriteLine("SqlDependency not restarted");
}
}
}
static class Utility
{
public static string getConnectionString()
{
return #"Data Source=.;Initial Catalog=YourDatabase;Application Name=SqlDependencyExample;Integrated Security=SSPI";
}
}
This is DDL for the test database:
CREATE DATABASE YourDatabase;
GO
ALTER DATABASE YourDatabase SET ENABLE_BROKER;
GO
USE YourDatabase;
GO
CREATE TABLE dbo.Services(
ServiceName varchar(100) NOT NULL
CONSTRAINT PK_Services PRIMARY KEY
, ServicePrice decimal(10,2) NOT NULL
);
GO
These queries fire the OnChanged event:
INSERT INTO dbo.Services VALUES('SomeService', 1.00);
GO
UPDATE dbo.Services SET ServicePrice = 2.00 WHERE ServiceName = 'SomeService';
GO
DELETE FROM dbo.Services WHERE ServiceName = 'SomeService';
GO
EDIT:
If you run your application using a minimally privileged account (a best practice), below is an example script to grant the minimum permissions needed for SqlDependency.
--create user for schema ownership
CREATE USER SqlDependencySchemaOwner WITHOUT LOGIN;
GO
--create schema for SqlDependency objects
CREATE SCHEMA SqlDependency AUTHORIZATION SqlDependencySchemaOwner;
GO
--add existing login as a minimally privileged database user with default schema SqlDependency
CREATE USER YourLogin WITH DEFAULT_SCHEMA = SqlDependency;
--grant user control permissions on SqlDependency schema
GRANT CONTROL ON SCHEMA::SqlDependency TO YourLogin;
--grant user impersonate permissions on SqlDependency schema owner
GRANT IMPERSONATE ON USER::SqlDependencySchemaOwner TO YourLogin;
GO
--grant database permissions needed to create and use SqlDependency objects
GRANT CREATE PROCEDURE TO YourLogin;
GRANT CREATE QUEUE TO YourLogin;
GRANT CREATE SERVICE TO YourLogin;
GRANT REFERENCES ON
CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification] TO YourLogin;
GRANT VIEW DEFINITION TO YourLogin;
GRANT SELECT to YourLogin;
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO YourLogin;
GRANT RECEIVE ON QueryNotificationErrorsQueue TO YourLogin;
GO
--grant permissions on user objects used by application
GRANT SELECT ON dbo.Services TO YourLogin;
GO
Done.
It was only because I haven't been using sa as a login. Only I changed my database to sa and used that in the connection string. It worked.
Discussion:
Question
it could be from the dbo being mapped to a Windows account that sometimes is invalid. 'dbo' really is the user that created the database. This problem happen for instance if I create the sample on the SQL instance on my laptop at work, then I go home and continue from home. At home I don't have access to the domain ActiveDirectory and the samples sudenly stop working because 'dbo' is realy my Windows account and is no longer available. Messages (like notifications) sent by 'dbo' sit in sys.transmissions_queue with a status complaining 'Could not retrieve info about user '...' from the domain controller'. Running EXECUTE AS USER='dbo' in the database also shows the same error happening. The fix in this case is to change 'dbo' to a login that is valid, e.g. by running ALTER AUTHORIZATION ON DATABASE::[dbname] TO [sa];
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlDependency.Start(connectionString);
string commandText = "select ID, status from dbo.VendorSetup";
SqlCommand cmd = new SqlCommand(commandText, conn);
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dbChangeNotification);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var employee = new VendorSetup
{
ID = Convert.ToInt32(reader["ID"]),
status = reader["status"].ToString(),
//Age = Convert.ToInt32(reader["Age"])
};
employees.Add(employee);
}
}
return employees;
}
private void dbChangeNotification(object sender, SqlNotificationEventArgs e)
{
_hubcontext.Clients.All.SendAsync("displayNotification");
}

SQL dependency and data refering in SQL

when we work with sql dependency then we need to always refer a sql like below one SELECT ActivityDate FROM [bba-reman].MyLog
i just like to know if i write the above sql this way then does it work
SELECT TOP 1 ActivityDate FROM [bba-reman].MyLog OR
SELECT TOP 5 ActivityDate FROM [bba-reman].MyLog
i am looking for suggestion and guidance.
private void RegisterNotification()
{
string tmpdata = "";
System.Data.SqlClient.SqlDependency.Stop(connectionString);
System.Data.SqlClient.SqlDependency.Start(connectionString);
try
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT ActivityDate FROM [bba-reman].MyLog";
dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(OnDataChange);
SqlDataReader dr = cmd.ExecuteReader();
{
while (dr.Read())
{
if (dr[0] != DBNull.Value)
{
tmpdata = dr[0].ToString();
}
}
}
dr.Dispose();
cmd.Dispose();
}
}
finally
{
//SqlDependency.Stop(connStr);
}
}
According to the SQL Server Books Online (https://msdn.microsoft.com/en-us/library/t9x04ed2.aspx), one of the restrictions for using QueryNotifications is that the statement must not use a TOP expression. SqlDependency is just a higher level implementation of QueryNotifications that takes care of the Service Broker plumbing.
The SqlDependency class has a lot of restrictions as well as the memory leak problems. An absence of the TOP instruction is the one of them. Hovewer, you can use an open source realization of the SqlDependency class - SqlDependencyEx. It uses a database trigger and native Service Broker notification to receive events about the table changes. This is an 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);
With SqlDependecyEx you are able to monitor INSERT, DELETE, UPDATE separately and receive actual changed data (xml) in the event args object. Filtering incoming messages helps you implement desirable behavior. Hope this help.

Categories

Resources