dyatchenko / servicebrokerlistener Goto Github PK
View Code? Open in Web Editor NEWComponent which receives SQL Server table changes into your .net code.
License: MIT License
Component which receives SQL Server table changes into your .net code.
License: MIT License
i had no issues with the code on my local computer but the following exception was thrown on the production server: Current user doesn''t have CONTROL SERVER permission to enable service broker. Grant sufficient permissions to current user or execute ALTER DATABASE [] SET ENABLE_BROKER with admin rights.
sql server installation adds by default CONTROL SERVER permissions to user installing the database service, but this is actually not required to enable the broker service, ENABLE_BROKER will run just fine without CONTROL SERVER permissions on some configurations. so i had to do this...
private const string SQL_FORMAT_INSTALL_SEVICE_BROKER_NOTIFICATION = @ -- Setup Service Broker IF EXISTS (SELECT * FROM sys.databases WHERE name = {0} AND (is_broker_enabled = 0 )) BEGIN ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE ALTER DATABASE [{0}] SET ENABLE_BROKER; ALTER DATABASE [{0}] SET MULTI_USER WITH ROLLBACK IMMEDIATE END -- Create a queue which will hold the tracked information IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = {1}) CREATE QUEUE {3}.[{1}] -- Create a service on which tracked information will be sent IF NOT EXISTS(SELECT * FROM sys.services WHERE name = {2}) CREATE SERVICE [{2}] ON QUEUE {3}.[{1}] ([DEFAULT]) ";
I have implemented the SqlDependencyEx class in windows service on windows 10. It works fine, as long as I make those changes in SQL Server Studio. When I do the changes with a program, on the same database I get no notification.
The Service is not on the same node as the database.
As the user in the service and the Program making the changes are different it might be a problem with access rights.
Any hints?
SQLDependency and Service broker works perfect in an architecture that has all the application tiers hosted on the same server, but presents a problem once the architecture is spread across multiple servers.
I have implemented both the standard .NET SqlDependency and the service broker listener (I prefer the later) and they both work perfectly when the connection string is pointing to the same server that the client (subscriber/consumer) is running on, but as soon as the connection string points to a different server no notification callbacks gets raised.
This may not be a very common problem but affects applications that have to be scaled out or that need the different tiers to be logically segregated due to standards or policies .i.e. PCI.
My last resort would be to help the service broker cross tiers by having the subscriber sitting in the same server as the DB then connecting to the intended subscriber through a keep alive connection of some sort .i.e a socket connection. But before going through this direction, I need to know if anyone has been successful at having the database running at server @server1 and the service broker running @server2 and events still being able to be received?
Hi,
I have a little problem when I try to start the listener on SQL Server 2012, when i try to execute [sp_InstallListenerNotification_1]
I have this error :
Msg 156, Niveau 15, État 1, Procédure tr_Listener_1, Ligne 21
Syntaxe incorrecte vers le mot clé 'Not'.
You can try :
USE [MyDatabase]
GO
DECLARE @return_value int
EXEC @return_value = [dbo].[sp_InstallListenerNotification_1]
SELECT 'Return Value' = @return_value
GO
DEBUG :
private void InstallNotification()
{
string execInstallationProcedureScript = string.Format(
SQL_FORMAT_EXECUTE_PROCEDURE,
this.DatabaseName,
this.InstallListenerProcedureName,
this.SchemaName);
ExecuteNonQuery(GetInstallNotificationProcedureScript(), this.ConnectionString); **#OK**
ExecuteNonQuery(GetUninstallNotificationProcedureScript(), this.ConnectionString); **#OK**
ExecuteNonQuery(execInstallationProcedureScript, this.ConnectionString); **#FAILED**
}
Are you an idea ?
Adw.
Hi,
I am looking forward to implement this cool SQLDependencyEx module in one of my project.
I created the POC with the sample code and it works (I mean debug the values are returned correctly -event fires every time) fine-but one thing, I can not write the received values on aspx response in a DIV or with the plain text (I guess because the DOM would already be built). Can I use your code with AJAX/SignalR for some real-time monitoring capabilities?
I might need the sample code to do that. OR
OR
you can suggest the remedy for the below code:
public partial class Default : System.Web.UI.Page, IDisposable
{
SqlDependencyEx sqlDependency;
public Default()
{
string DBConn = ConfigurationManager.ConnectionStrings["DBConn"].ToString();
sqlDependency = new SqlDependencyEx(DBConn, "TestDB", "tbl_MessageDetail", "dbo");
}
protected void Page_Load(object sender, EventArgs e)
{
RegisterNotification();
/*outputResult is a DIV - this is shown*/
outputResult.InnerHtml += ("Registered Notifier: " + DateTime.Now.ToShortTimeString() + "<br/>");
}
private void RegisterNotification()
{
sqlDependency.Stop();
sqlDependency.TableChanged += OnDataChange;
Thread.Sleep(2000);
sqlDependency.Start();
/*outputResult is a DIV - this is shown*/
outputResult.InnerHtml += ("SQL Dependency Started: " + DateTime.Now.ToShortTimeString() + "<br/>");
}
private void UnregisterNotification()
{
sqlDependency.Stop();
sqlDependency.TableChanged -= OnDataChange;
}
private void OnDataChange(object sender, SqlDependencyEx.TableChangedEventArgs e)
{
string result = e.Data.FirstNode.ToString();
/*outputResult is a DIV - where the result is NOT showing on change*/
outputResult.InnerHtml += result + "<br/>";
}
public override void Dispose()
{
// Clean up the resources!
UnregisterNotification();
}
}
Please suggest the work-around
Hi,
I'm using the following code to react to the NotificationProcessStopped
event:
// Listener contains the SqlDependencyEx instance
Listener.NotificationProcessStopped += (sender, args) =>
{
Task.Run(async () =>
{
await Task.Delay(5000);
AttemptReconnect();
});
};
private void AttemptReconnect()
{
try
{
Listener.Stop();
Listener.Start();
}
catch
{
Task.Run(async () =>
{
await Task.Delay(5000);
AttemptReconnect();
});
}
}
If I run this code and stop the SQL Server to simulate a disconnect, I am receiving an ObjectDisposedException: The CancellationTokenSource has been disposed
in the Stop()
method when the _threadSource.Token.IsCancellationRequested
property is read in line 567 on the second reconnect attempt (and every reconnect attempt thereafter).
When I change this to _threadSource.IsCancellationRequested
everything works fine.
Hello,
Would appreciate any guidance on this.
My scenario:
I have a lot of databases, each one it's a exact replica but for a different customer. Each DB can be on multiple servers. I can have more than one instance of my webapp.
Currently I have a job, that finds all my databases, servers, and connection strings and runs parallel certain tasks every 2 minutes in all DBs. I would like to change it to 30 min to reduce stress in some of the DBs and then add Service Broker Listener that run those tasks on only on those inserts.
Whats the best approach here? D:
Thanks
I noticed the sql permissions used to setup the listener are a little too broad. I would recommend following the answer provided here. I have verified that these permissions do in fact work with sqldependency so I assume they work with your code as well.
I have got triggers, service, queues, storedprocedures added as it has to be, but second trigger not firing,
iam using latest code. thanks
static void Main(string[] args)
{
try
{
SqlDependencyExNew sqlDependency = new SqlDependencyExNew(
connectionString: conx, databaseName: "db", tableName: "table", listenerType: NotificationTypes.Update, identity: 1);
sqlDependency.TableChanged += (o, e) => OnChange(o, e);
SqlDependencyExNew sqlDependency2 = new SqlDependencyExNew(
connectionString: conx, databaseName: "db", tableName: "table", listenerType: NotificationTypes.Update, identity: 2);
sqlDependency.TableChanged += (o, e) => OnChangeA(o, e);
sqlDependency.Start();
sqlDependency2.Start();
Console.WriteLine("______________Escuchando_______________");
Console.ReadKey();
sqlDependency.Stop();
sqlDependency2.Stop();
}
catch (Exception exp)
{
if (listener.Active == true)
{
listener.Stop();
}
if (listener2.Active == true)
{
listener2.Stop();
}
throw;
}
}
private static async void OnChangeA(object o, SqlDependencyExNew.TableChangedEventArgs e)
{
await SOMETHING(o, e);
}
private static Task<XElement> SOMETHING(object o, TableChangedEventArgs e)
{
var xxx = e.Data;
return Task.Run(() =>
{
return xxx;
});
}
private static async void OnChange(object o, SqlDependencyExNew.TableChangedEventArgs e)
{
await SOMETHING(o, e);
}
}
I have an exception when the code try to execute this :
"\r\n USE [<database>]\r\n IF OBJECT_ID ('dbo.sp_InstallListenerNotification_1', 'P') IS NOT NULL\r\n EXEC dbo.sp_InstallListenerNotification_1\r\n "
System.Data.SqlClient.SqlException was unhandled
Class=16
ErrorCode=-2146232060
HResult=-2146232060
LineNumber=29
Message=User does not have permission to perform this action.
Changed database context to '<database>'.
Number=15247
Procedure=sp_InstallListenerNotification_1
Server=<serverip>
Source=.Net SqlClient Data Provider
State=1
StackTrace:
à System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
à System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
à System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
à System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
à System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
à System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
à System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
à ServiceBrokerListener.Domain.SqlDependencyEx.ExecuteNonQuery(String commandText, String connectionString) dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\SqlDependencyEx.cs:ligne 632
à ServiceBrokerListener.Domain.SqlDependencyEx.InstallNotification() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\SqlDependencyEx.cs:ligne 751
à ServiceBrokerListener.Domain.SqlDependencyEx.Start() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\SqlDependencyEx.cs:ligne 532
à WindowsFormsApplication1.YourClass.RegisterNotification() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\YouClass.cs:ligne 33
à WindowsFormsApplication1.YourClass.Go() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\YouClass.cs:ligne 27
à WindowsFormsApplication1.Form1..ctor() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:ligne 85
à WindowsFormsApplication1.Program.Main() dans C:\Users\bbigras\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\Program.cs:ligne 28
à System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
à System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
à Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()
InnerException:
I did run the GRANTs I saw in the code :
GRANT CREATE PROCEDURE TO <user>;
GRANT CREATE SERVICE TO <user>;
GRANT CREATE QUEUE TO <user>;
GRANT REFERENCES ON CONTRACT::[DEFAULT] TO <user>;
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO <user>;
GRANT CONTROL ON SCHEMA::[dbo] TO <user>;
GRANT EXEC ON SCHEMA::[dbo] TO <user>;
I am trying to use this in an "off-label" application to notify my application when new rows are written so I can process the rows. During the processing more rows may be inserted, and I am not clear how to prevent those events from firing.
I can call Stop() and then call Start() although I did try that and it does not seem to re-start notifications. So I think I'm not using it correctly, but I'm not sure what the correct way to handle this is.
i am interested to know how to use your library instead of MS provided. it would be nice for all if you come with a tutorial like how to use this in any application and where it is different from built-in sql dependency.
thanks
Hi
SqlDependencyEx in multitable error "Incorrect syntax near the keyword 'Desc'."
error in sp_InstallListenerNotification_ID
Is it possible for a flag to be passed into the Stop method that is used to control the UninstallNotification logic? The reason being is that, in a clustered environment, if Stop is invoked from Server 1 then the SQL listeners are dropped and this causes Server 2 to start failing. Ideally it would be possible to control whether the objects get dropped.
Hello,
I am trying to use and library and it seems unless I have separate listeners for each event (Insert, Update and Delete) it does not fire up the change event. In the below sample code I have 2 listeners one for Insert and one for update this fires up but if I have only one listener only the Insert event fires up the e update and delete does not fire up any event. I am inserting the records directly using SSMS
string connectionString = ConfigurationManager.ConnectionStrings["EmployeeEntityModel"].ConnectionString;
// See constructor optional parameters to configure it according to your needs
var listener = new SqlDependencyEx(connectionString, "SignalRDemo", "Employee", listenerType: SqlDependencyEx.NotificationTypes.Update, receiveDetails: true, identity: 1);
// e.Data contains actual changed data in the XML format
listener.TableChanged += Listener_TableChanged;
// After you call the Start method you will receive table notifications with
// the actual changed data in the XML format
listener.Start();
var listener1 = new SqlDependencyEx(connectionString, "SignalRDemo", "Employee", listenerType: SqlDependencyEx.NotificationTypes.Insert, receiveDetails: true, identity: 2);
// e.Data contains actual changed data in the XML format
listener1.TableChanged += Listener1_TableChanged;
// After you call the Start method you will receive table notifications with
// the actual changed data in the XML format
listener1.Start();
}
private static void Listener1_TableChanged(object sender, SqlDependencyEx.TableChangedEventArgs e)
{
throw new NotImplementedException();
}
private static void Listener_TableChanged(object sender, SqlDependencyEx.TableChangedEventArgs e)
{
throw new NotImplementedException();
}
Hi,
I have a couple of issues here, some of which may be my own stupidity. I'm aware that the whole purpose of the library is to replicate SqlDependency callback in non SQL 2005 versions, but is there anything that would prevent your SqlDependencyEx class from working on a SQL 2005 database?
When running against SQL 2005 I receive the following error with the unit tests:
System.Data.SqlClient.SqlException: Cannot drop the service 'ListenerService_1', because it does not exist or you do not have permission.
Changed database context to 'TestDatabase'.
Errors: System.Data.SqlClient.SqlErrorCollection
This appears to be triggered by this statement:
USE [TestDatabase]
IF OBJECT_ID ('temp.sp_UninstallListenerNotification_1', 'P') IS NOT NULL
EXEC temp.sp_UninstallListenerNotification_1
If I change my connection string to use SQL 2012 then this issue goes away but the unit test doesn't pass because the expected table changes don't seem to materialize.
I have looked at SqlDependencyTest and SqlDependencyExTest but the former I believe is for the legacy native implementation of Sql Dependency callbacks and the latter cannot be run from within Visual Studio because it's configured to use NUnit.
Forgive the lengthy post, but here is the test case I'm using (which is mostly the same as what is in your project):
[TestFixture]
public class SqlDependencyExTest
{
private const string MASTER_CONNECTION_STRING =
"Data Source=localhost\\sql2005;Initial Catalog=master;Integrated Security=True";
private const string TEST_CONNECTION_STRING =
"Data Source=localhost\\sql2005;Initial Catalog=TestDatabase;User Id=TempLogin;Password=8fdKJl3$nlNv3049jsKK;";
private const string ADMIN_TEST_CONNECTION_STRING =
"Data Source=localhost\\sql2005;Initial Catalog=TestDatabase;Integrated Security=True";
private const string INSERT_FORMAT =
"USE [TestDatabase] INSERT INTO temp.[TestTable] (TestField) VALUES({0})";
private const string REMOVE_FORMAT =
"USE [TestDatabase] DELETE FROM temp.[TestTable] WHERE TestField = {0}";
private const string TEST_DATABASE_NAME = "TestDatabase";
private const string TEST_TABLE_NAME = "TestTable";
private const string TEST_TABLE_1_FULL_NAME = "temp.TestTable";
private const string TEST_TABLE_2_FULL_NAME = "temp2.TestTable";
[SetUp]
public void TestSetup()
{
const string CreateDatabaseScript = @"
CREATE DATABASE TestDatabase;
ALTER DATABASE [TestDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE [TestDatabase] SET ENABLE_BROKER;
ALTER DATABASE [TestDatabase] SET MULTI_USER WITH ROLLBACK IMMEDIATE
-- FOR SQL Express
ALTER AUTHORIZATION ON DATABASE::[TestDatabase] TO [sa]
ALTER DATABASE [TestDatabase] SET TRUSTWORTHY ON; ";
const string CreateUserScript = @"
CREATE LOGIN TempLogin
WITH PASSWORD = '8fdKJl3$nlNv3049jsKK', DEFAULT_DATABASE=TestDatabase;
USE [TestDatabase];
CREATE USER TempUser FOR LOGIN TempLogin;
GRANT CREATE PROCEDURE TO [TempUser];
GRANT CREATE SERVICE TO [TempUser];
GRANT CREATE QUEUE TO [TempUser];
GRANT REFERENCES ON CONTRACT::[DEFAULT] TO [TempUser]
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [TempUser];
GRANT CONTROL ON SCHEMA::[temp] TO [TempUser]
GRANT CONTROL ON SCHEMA::[temp2] TO [TempUser];
";
const string CreateTable1Script = @"
CREATE SCHEMA Temp
CREATE TABLE TestTable (TestField int, StrField NVARCHAR(MAX));
";
const string CreateTable2Script = @"
CREATE SCHEMA Temp2
CREATE TABLE TestTable (TestField int, StrField NVARCHAR(MAX));";
TestCleanup();
ExecuteNonQuery(CreateDatabaseScript, MASTER_CONNECTION_STRING);
ExecuteNonQuery(CreateTable1Script, ADMIN_TEST_CONNECTION_STRING);
ExecuteNonQuery(CreateTable2Script, ADMIN_TEST_CONNECTION_STRING);
ExecuteNonQuery(CreateUserScript, MASTER_CONNECTION_STRING);
}
[TearDown]
public void TestCleanup()
{
const string DropTestDatabaseScript = @"
IF (EXISTS(select * from sys.databases where name='TestDatabase'))
BEGIN
ALTER DATABASE [TestDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [TestDatabase]
END
IF (EXISTS(select * from master.dbo.syslogins where name = 'TempLogin'))
BEGIN
DECLARE @loginNameToDrop sysname
SET @loginNameToDrop = 'TempLogin';
DECLARE sessionsToKill CURSOR FAST_FORWARD FOR
SELECT session_id
FROM sys.dm_exec_sessions
WHERE login_name = @loginNameToDrop
OPEN sessionsToKill
DECLARE @sessionId INT
DECLARE @statement NVARCHAR(200)
FETCH NEXT FROM sessionsToKill INTO @sessionId
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Killing session ' + CAST(@sessionId AS NVARCHAR(20)) + ' for login ' + @loginNameToDrop
SET @statement = 'KILL ' + CAST(@sessionId AS NVARCHAR(20))
EXEC sp_executesql @statement
FETCH NEXT FROM sessionsToKill INTO @sessionId
END
CLOSE sessionsToKill
DEALLOCATE sessionsToKill
PRINT 'Dropping login ' + @loginNameToDrop
SET @statement = 'DROP LOGIN [' + @loginNameToDrop + ']'
EXEC sp_executesql @statement
END
";
ExecuteNonQuery(DropTestDatabaseScript, MASTER_CONNECTION_STRING);
}
[Test]
public void NotificationTestWith10ChangesAnd60SecDelay()
{
NotificationTest(10, 60);
}
[Test]
public void NotificationTestWith10Changes()
{
NotificationTest(10);
}
private void NotificationTest(
int changesCount,
int changesDelayInSec = 0,
string connStr = TEST_CONNECTION_STRING)
{
int changesReceived = 0;
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
connStr,
TEST_DATABASE_NAME,
TEST_TABLE_NAME, "temp"))
{
sqlDependency.TableChanged += (o, e) => changesReceived++;
sqlDependency.Start();
Thread.Sleep(changesDelayInSec * 1000);
MakeTableInsertDeleteChanges(changesCount);
// Wait a little bit to receive all changes.
Thread.Sleep(10000);
}
Assert.AreEqual(changesCount, changesReceived);
}
private static void MakeTableInsertDeleteChanges(int changesCount)
{
for (int i = 0; i < changesCount / 2; i++)
{
ExecuteNonQuery(string.Format(INSERT_FORMAT, i), MASTER_CONNECTION_STRING);
ExecuteNonQuery(string.Format(REMOVE_FORMAT, i), MASTER_CONNECTION_STRING);
}
}
private static void ExecuteNonQuery(string commandText, string connectionString)
{
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(commandText, conn))
{
conn.Open();
command.CommandType = CommandType.Text;
command.CommandTimeout = 60000;
command.ExecuteNonQuery();
}
}
}
If in a client i use SqlDependencyEx
to monitor multiple tables, and then have multiple instances of that client on different computers using the same database - do i need to make sure that the Identity
parameter of the SqlDependencyEx
constructor is unique in the database context, or just for application instance?
I.e. Is it a problem if two programs track changes in the same database, and use the same Identity
numbers?
Line 560ish (at least as of 6f98948) queues a worker item on the ThreadPool which has a 'while true' loop (canceled on stop). This effectively assigns a thread pool thread indefinitely, which is considered bad form.
The errors I received when running the program include instruction telling me to run this script to grant the needed permissions:
GRANT CREATE PROCEDURE TO [MyUser];
GRANT CREATE SERVICE TO [MyUser];
GRANT CREATE QUEUE TO [MyUser];
GRANT REFERENCES ON CONTRACT::[DEFAULT] TO [MyUser];
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [MyUser];
GRANT CONTROL ON SCHEMA::[dbo] TO [MyUser];
This script has been run and the permissions have been granted but I’m still getting an error when I run the program:
System.Data.SqlClient.SqlException
HResult=0x80131904
Message=Cannot drop the service 'ListenerService_1', because it does not exist or you do not have permission.
It would be great if you can help on this.
Regards.
i have 2 or more user in a database. The tablechanged event is only updated by the account that created it. How does the event be fired when any account changes the table.
Hi,
when you try to trigger a table with text, ntext or image datatype, you get an exception, because it is not possible to trigger this datatypes.
Solution is, to change this const to:
( WHERE DATA_TYPE NOT IN (''text'',''ntext'',''image'') AND ...)
private const string SQL_FORMAT_CREATE_INSTALLATION_PROCEDURE = @"
USE [{0}]
" + SQL_PERMISSIONS_INFO + @"
IF OBJECT_ID ('{6}.{1}', 'P') IS NULL
BEGIN
EXEC ('
CREATE PROCEDURE {6}.{1}
AS
BEGIN
-- Service Broker configuration statement.
{2}
-- Notification Trigger check statement.
{4}
-- Notification Trigger configuration statement.
DECLARE @triggerStatement NVARCHAR(MAX)
DECLARE @select NVARCHAR(MAX)
DECLARE @sqlInserted NVARCHAR(MAX)
DECLARE @sqlDeleted NVARCHAR(MAX)
SET @triggerStatement = N''{3}''
SET @select = STUFF((SELECT '','' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE NOT IN (''text'',''ntext'',''image'') AND TABLE_NAME = ''{5}'' AND TABLE_CATALOG = ''{0}''
FOR XML PATH ('''')
), 1, 1, '''')
SET @sqlInserted =
N''SET @retvalOUT = (SELECT '' + @select + N''
FROM INSERTED
FOR XML PATH(''''row''''), ROOT (''''inserted''''))''
SET @sqlDeleted =
N''SET @retvalOUT = (SELECT '' + @select + N''
FROM DELETED
FOR XML PATH(''''row''''), ROOT (''''deleted''''))''
SET @triggerStatement = REPLACE(@triggerStatement
, ''%inserted_select_statement%'', @sqlInserted)
SET @triggerStatement = REPLACE(@triggerStatement
, ''%deleted_select_statement%'', @sqlDeleted)
EXEC sp_executeSql @triggerStatement
END
')
END
";
Hi ,
I am using signalr to push notifications to user .Whenever any changes in table happens i am pushing notifications to frontend.But the sqldependencyex is not firing the onchange event.Please let me know if i made any mistakes.
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(ConfigurationManager.ConnectionStrings["dbConnectionstring"].ConnectionString, "EIS_BMT", "EIS_Notifications"))
{
sqlDependency.TableChanged += dependency_OnChange;
// Make table changes.
sqlDependency.Start();
// Wait a little bit to receive all changes.
Thread.Sleep(1000);
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
return context.Clients.All.RecieveNotification(totalNewMessages, Messages);
}
private void dependency_OnChange(object sender, SqlDependencyEx.TableChangedEventArgs e)
{
NotificationHub nhub = new NotificationHub();
nhub.SendNotifications();
}
Hi great library,
Does this work in an asp.net environment?
This is an example of how the Microsoft one works in ASP.NET environment
http://venkatbaggu.com/signalr-database-update-notifications-asp-net-mvc-usiing-sql-dependency/
Do you have an equivalent example?
Hi Devs,
I´m trying to use SqlDependencyEx the problem is that the OnDataChange event is not getting fired inside the controller, I already enabled Service Broker. Here´s is the code:
`
private const string CONNECTION_STRING = "Server=LFTCMCPTP83;Database=Database;Trusted_Connection=True;MultipleActiveResultSets=true; Integrated Security=false;User ID=used_id;Password=password";
private const string DATABASE_NAME = "db_name";
private const string TABLE_NAME = "table_name";
private const string SCHEMA_NAME = "dbo";
private SqlDependencyEx sqlDependency = new SqlDependencyEx(CONNECTION_STRING, DATABASE_NAME,
TABLE_NAME, SCHEMA_NAME);
private void RegisterNotification()
{
sqlDependency.TableChanged += OnDataChange;
sqlDependency.Start();
}
private void OnDataChange(object sender, SqlDependencyEx.TableChangedEventArgs e)
{
//Code
}
public IActionResult Create(){
RegisterNotification();
}`
Best Regards
Hi @dyatchenko
First of all this library is great !! :)
I am to use your library in a multithreaded environment. During performance optimization I came across a problem that seems to belong to the type of memory leaks. Using the diagnostic tool of visual studio I am to have this situation (after causing an intense dependency from sql server db):
in the picture the application is in waiting mode. As you can see there is a reference to the List of ActiveEntities.
Inserting using ConcurrentDictionary(.net 4.0) it appears that the behavior has improved and the reference is no longer present.
This is the list of the changes I've made to the code:
private static readonly List<int> ActiveEntities = new List<int>();
to
private static readonly ConcurrentDictionary<int, byte> ActiveEntities = new ConcurrentDictionary<int, byte>();
lock (ActiveEntities)
{
if (ActiveEntities.Contains(this.Identity))
throw new InvalidOperationException("An object with the same identity has already been started.");
ActiveEntities.Add(this.Identity);
}
to
ActiveEntities.TryAdd(this.Identity, byte.MinValue);
lock (ActiveEntities)
if (ActiveEntities.Contains(Identity)) ActiveEntities.Remove(Identity);
to
byte itemRemoved;
ActiveEntities.TryRemove(Identity, out itemRemoved);
that's all
Any plans on making this into a nuget package?
suppose some new records has been added to customer table or existing few changes then how could get those rows with more information like is it added or update?
share some code snippet to achieve it. thanks
I have been using SqlDependencyEx with a console App successfully. I get the notifications, the actual data changes etc.
I have to move this executable to an Azure WebJob, and need to work with an on-prem SQL server database for now. Would SqlDependencyEx still work in this scenario?
Awesome project, thanks.
Swanand
If you perform update/delete sql query over the table and no records are affected, the message that we recieve is and NotificationType property has value None.
Maybe is a good idea if user is not specify None notification type to skip sending such messages from trigger or rising the event.
Hello, your library is quit useful but I want to try to integrate it to an existing asp.net mvc application.
I want to call the start and stop methods in my Global.aspx file in the Application_Start() and Application_End() respectively. However, the anonymous function listener.TableChanged += (o, e) => Console.WriteLine("Your table was changed!")
to be in another class which is not in the Global.aspx file.
Please can you elaborate with code snippets on how to do this? Additionally, can you direct me to a more elaborate documentation of your library?
Thank you.
Hi there. Great plugin so far :-) Works like a charm right now, thank you!
I just had some struggle because in our environment, for some reason the SQL Server is set to look for matching case in SQL statements. (more info here: http://stackoverflow.com/questions/3296428/changing-sql-server-collation-to-case-insensitive-from-case-sensitive - but please don't ask me why! i don't know either and for 99% of time i'm happy to let other people deal with the sql machine itself, but I needed the event broker mechanism ;) )
So now the "Issue" is a very minor one. But it has cost me a few hours to find out. So...:
You are using sp_executeSql
in your SQL_FORMAT_CREATE_INSTALLATION_PROCEDURE
which made the created procedure fail "inside the sql-server" here. So I'd suggest you just change it to sp_executesql
which worked out to be the solution for me. I'd do it myself, but no time at the moment ;)
Hello,
I had a problem turning off the XML details only on production server on the hosting service for which i do not have much control. I got a malformed XML exception with the default XML message:
SET @message = N'<root/>'
So the shorter valid message i could send on the trigger was this one:
SET @message = N'<root><inserted><row></row></inserted><deleted><row></row></deleted></root>'
Great component, very well done, thank you very much!!!
Hi,
In SqlDependencyEx.cs at lines 53, 54, and 57 there is a strange notation "Data?" that breaks the compiler.
return Data?.Element(INSERTED_TAG) != null
? Data?.Element(DELETED_TAG) != null
? NotificationTypes.Update
: NotificationTypes.Insert
: Data?.Element(DELETED_TAG) != null
? NotificationTypes.Delete
: NotificationTypes.None;
I guess this is a typo.
Hi Dmitrii,
If I have more than 1 server listening for the event notification, I am finding that the first server "wins" and other servers do not receive the notification event.
Is this because of:
BEGIN TRY END CONVERSATION @ConvHandle; END TRY BEGIN CATCH END CATCH
Should that line be removed so that all servers receive the message or is there a setting I am missing?
Currently I'm trying to use this solution on two DBs. The first one is MS SQL Standard and another is Express. The solution works with Standard version like a charm, but seems to be not compatible with Express version, since I receive no notifications at all.
Am I doing something wrong, or it is supposed to work that way?
I have a table called dbo.Order but this appears to cause problems during the set up.
SET @select = STUFF((SELECT '','' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''{5}'' AND TABLE_CATALOG = ''{0}''
FOR XML PATH ('''')
), 1, 1, '''')
I believe that square brackets are required around {5}.
Hello, I have been working with sqldependencyEx for a time in a Console Application and now I have to port it in a Windows Service. While debugging in Windows Service, throws me an exception that's an error of logging into the database. In the console Application I didn't get this problem and I copied the files from that project.
This is the error message that displays me:
An exception of type 'System.Data.SqlClient.SqlException' occurred in System.Data.dll but was not handled in user code
Additional information: No se puede abrir la base de datos "LINX" solicitada por el inicio de sesión. Error de inicio de sesión.
Error de inicio de sesión del usuario 'NT AUTHORITY\SYSTEM'.*
*I added after the last line because I didn't see it.
I'm using windows authentication in Microsoft Sql Server 2014, so I don't have user and password, any clue about this error? I'm working with sql express if it helps.
XElement.Parse
throws an XmlException if it encounters invalid charactes in the notificationMessage
string here
I encountered this on a table that had a char
column that contained a null character.
Patched my local copy by wrapping the Parse
in a try-catch and discarding results on error since I don't really need it.
Exception message:
Additional information: '.', hexadecimal value 0x00, is an invalid character.
After a couple of days experimenting with ServiceBrokerListener I have found out that I have about 100-200 ListenerServices and ListenerQueues in my DB.
I'll try to find a solution a bit later, right now I am too busy cleaning the db. Deleting a service takes a lot of time. =)
There is a query:
private const string SQL_FORMAT_DROP_PROCEDURE = @"
USE [{0}]
IF OBJECT_ID ('{2}.{1}', 'P') IS NOT NULL
DROP PROCEDURE temp.[{1}]
";
That should probably look like this:
private const string SQL_FORMAT_DROP_PROCEDURE = @"
USE [{0}]
IF OBJECT_ID ('{2}.{1}', 'P') IS NOT NULL
DROP PROCEDURE {2}.[{1}]
";
If there is more than 1 database on the same server, the code seems to fail during the installation process with the error:
The Service Broker in database "XXX" cannot be enabled because there is already an enabled Service Broker with the same ID.
The problem seems to be in the SQL_FORMAT_INSTALL_SEVICE_BROKER_NOTIFICATION template:
ALTER DATABASE [{0}] SET ENABLE_BROKER;
Changing to the following will ensure a new GUID is used for each database's service broker.
ALTER DATABASE [{0}] SET NEW_BROKER;
I need to monitor 5 tables in total...
hi i tried the dll on default schema dbo and it works perfectly, but when i specify a different schema seems its not working
Message=Cannot drop the service 'ListenerService_1', because it does not exist or you do not have permissi
I have already checked out the library itself and it works like a charm on SQL Server Express 2014, with obviously the fact of enabling the Service Broker feature on the target database which I initially missed, but I have the express necessity to track updates on multiple tables. How can I manage them? What I have seen is like Service Broker only supports to track your initial subscription until you ask yourself for an immediate rollback. Any help would be appreciated. Thank you.
I am using concurrently two instances of SqlDependencyEx with two different callback. It seems however that
This is not what I am expecting from the code below. Where am I wrong?
NOTE: I have forced the two dependencies to have different Identity-es
this.sqlDependencyMailSent = new SqlDependencyEx(
ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString,
"costerdb",
"PLAN_AlarmRelayQueue",
"dbo",
SqlDependencyEx.NotificationTypes.Delete,true,0);
this.sqlDependencyMailSent.TableChanged += onMailSent;
this.enqueuedId = -1;
if(!this.sqlDependencyMailSent.Active)
this.sqlDependencyMailSent.Start();
this.sqlDependencyMailAcked = new SqlDependencyEx(
ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString,
"costerdb",
"PLAN_AlarmRelayAck",
"dbo",
SqlDependencyEx.NotificationTypes.Insert,true,1);
this.sqlDependencyMailAcked.TableChanged += onMailNotified;
if (!this.sqlDependencyMailAcked.Active)
`this.sqlDependencyMailAcked.Start();
Hi Dmitriy,
Firstly, nice work, this could help us out a lot!
We are just running it in a console application to do some testing and so far seems to work really well however on stopping we get an unhandled exception during cleanup. What looks to be happening is that the listener thread is attempting to access the Queue from the ReceiveEvent method after the Queue has already been deleted by the main thread and obviuosly before the listener thread is aborted.
So Error occurs while the object is disposing on line:
using (var reader = command.ExecuteReader())
when passing the command:
DECLARE @ConvHandle UNIQUEIDENTIFIER
DECLARE @message VARBINARY(MAX)
USE [Assetic_Local]
WAITFOR (RECEIVE TOP(1) @ConvHandle=Conversation_Handle
, @message=message_body FROM dbo.[ListenerQueue_1]), TIMEOUT 30000;
BEGIN TRY END CONVERSATION @ConvHandle; END TRY BEGIN CATCH END CATCH
SELECT CAST(@message AS NVARCHAR(MAX))
We can work around it, just wanted to see if you had come across it or had any insight on it?
Also, just as a side question, not an issue, just wondering the reason behind the 30sec timeout in the above procedure? It removes and starts a new conversation every 30secs?
Thanks!
HI, I tried both your "version" and also the one with async and await through a simple console program. just for watch "How To Use".
Sure, everything works, but when the test program stops (ie after a simple Console.ReadLine) and performs the
//sqlDependency.TableChanged -= OnDataChangeAsync;
sqlDependency.Stop();
in the output window VS2015 I find these errors.
The generated exception: 'System.Data.SqlClient.SqlException' in System.Data.dll
The generated exception: 'System.Data.SqlClient.SqlException' in System.Data.dll
The generated exception: 'System.Data.SqlClient.SqlException' in System.Data.dll
The generated exception: 'System.Data.SqlClient.SqlException' in mscorlib.dll
My environment W8.1x64, VS2015 SQL2014 Express.
Any thoughts?
Thanks in advance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.