0

I have a console app that monitors a database using SqlDependency. The app is to monitor the database table and send an email once a new record is added in the table.

Everything works fine, just that each time a new record is added the whole query is executed again and all the records in the table are returned. What I actually want is to return only the newly added record in the table.

Here is my code below:

SQLWatcher class:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestApp
{
    public enum SqlWatcherNotificationType
    {
        Blocking,
        Threaded // Launch in another thread so SqlWatcher can immediately start monitoring again.
    }

    public class SqlWatcher : IDisposable
    {
        private string ConnectionString;
        private SqlConnection Connection;
        private SqlCommand Command;
        private SqlDataAdapter Adapter;
        private DataSet Result;
        private SqlWatcherNotificationType NotificationType;

        public SqlWatcher(string ConnectionString, SqlCommand Command, SqlWatcherNotificationType NotificationType)
        {
            this.NotificationType = NotificationType;
            this.ConnectionString = ConnectionString;

            SqlDependency.Start(this.ConnectionString);

            this.Connection = new SqlConnection(this.ConnectionString);
            this.Connection.Open();
            this.Command = Command;
            this.Command.Connection = this.Connection;

            Adapter = new SqlDataAdapter(this.Command);
        }

        public void Start()
        {
            RegisterForChanges();
        }

        public void Stop()
        {
            SqlDependency.Stop(this.ConnectionString);
        }

        public delegate void SqlWatcherEventHandler(DataSet Result);

        public event SqlWatcherEventHandler OnChange;

        public DataSet DataSet
        {
            get { return Result; }
        }

        private void RegisterForChanges()
        {
            // Remove old dependency object
            this.Command.Notification = null;

            // Create new dependency object
            SqlDependency dep = new SqlDependency(this.Command);
            dep.OnChange += new OnChangeEventHandler(Handle_OnChange);

            // Save data
            Result = new DataSet();
            Adapter.Fill(Result);

            // Notify client of change to DataSet
            switch (NotificationType)
            {
                case SqlWatcherNotificationType.Blocking:
                    OnChange(Result);
                    break;

                case SqlWatcherNotificationType.Threaded:
                    ThreadPool.QueueUserWorkItem(ChangeEventWrapper, Result);
                    break;
            }
        }

        public void ChangeEventWrapper(object state)
        {
            DataSet Result = (DataSet)state;
            OnChange(Result);
        }

        private void Handle_OnChange(object sender, SqlNotificationEventArgs e)
        {
            if (e.Type != SqlNotificationType.Change)
                throw new ApplicationException("Failed to create queue notification subscription!");

            //Clean up the old notification
            SqlDependency dep = (SqlDependency)sender;
            dep.OnChange -= Handle_OnChange;

            //Register for the new notification
            RegisterForChanges();
        }

        public void Dispose()
        {
            Stop();
        }
    }
}

Implementation:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;

namespace TestApp
{
    class Program
    {
        private static SqlWatcher SqlQueueWatcher;

        // string pin = string.Empty;
        string siteURL = "http://docapp/sites/nlpc";

        public string benefitCheck()
        {
            DataContext db = new DataContext("Data Source=;Initial Catalog=ServiceTest;User ID=;password =  ;Integrated Security=true");
            BenefitDocModel RSAName = new BenefitDocModel();
            CustomerCareContextDataContext LandingDb = new CustomerCareContextDataContext(siteURL);
            IEnumerable<BenefitDocModel> r = db.GetTable<BenefitDocModel>().ToList<BenefitDocModel>();

            var query = r.ToList();

            var rsa = from rr in query
                      select ( new { rr.PIN , rr.Document_Name, rr.Firstname, rr.Surname, rr.URL});

            foreach (var rsapin in rsa)
            {
                Console.WriteLine(rsapin);
                // sendEmail(rsapin.PIN);
            }
                /*pin = RSAName.PIN;
                RSAsLibraryDocument test = new RSAsLibraryDocument();
                BenefitDocModel RSAName1 = db.GetTable<BenefitDocModel>().FirstOrDefault(x => x.PIN == pin);
            if (pin == RSAName1.PIN)
                {
                    test.PIN = RSAName.PIN;
                    test.UserID = RSAName.UserID;
                    test.Firstname = RSAName.Firstname;
                    test.Surname = RSAName.Surname;
                    test.Document_Name = RSAName.Document_Name;
                    test.Document_URL = RSAName.URL;
                    test.UserType = RSAName.UserType;
                    test.Name = RSAName.PIN + RSAName.Document_Name;
                }*/
            return "success";

            }//

        public void SQLServiceStartForLog()
        {
            //BenefitDocModel rsapin = db.GetTable<BenefitDocModel>().FirstOrDefault(x => x.PIN == pin);
            //cmd.Notification = null;
            string connS = "Data Source=;Initial Catalog=ServiceTest;User ID=;password =  ;Integrated Security=true";
            SqlCommand cmd = new SqlCommand();
            //cmd.Notification = null;
            cmd = new SqlCommand("SELECT UserID, Surname, Firstname, PIN, URL, Document_Name FROM dbo.BenefitDoc");
            cmd.CommandType = CommandType.Text;
            SqlQueueWatcher = new SqlWatcher(connS, cmd, SqlWatcherNotificationType.Blocking);
            SqlQueueWatcher.OnChange += new SqlWatcher.SqlWatcherEventHandler(QueueSQLWatcher_OnChangeForLog);
            cmd.Notification = null;
            SqlQueueWatcher.Start();   
        }
        private void QueueSQLWatcher_OnChangeForLog(DataSet Result)
        {
            try
            {
                Console.WriteLine("Database monitoring is starting....");
                benefitCheck();
                Console.WriteLine("Database monitoring completed.");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
        public void sendEmail(string pin)
        {
            MailMessage mail = new MailMessage("", "");
            SmtpClient client = new SmtpClient();
            client.Port = 25;
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.UseDefaultCredentials = false;
            client.Host = "smtp.gmail.com";
            client.EnableSsl = true;
            client.Credentials = new System.Net.NetworkCredential("", "");
            mail.Subject = "New Record added for RSA with " + pin;
            mail.Body = "The benefit application for user has been created.";
            client.Send(mail);
        }

        public static void Stop()
        {
            SqlQueueWatcher.Dispose();
        }
        static void Main(string[] args)
        {
            Program n = new Program();
            n.SQLServiceStartForLog();
            Console.ReadLine();
        }
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Tolu Ajayi
  • 31
  • 2
  • 9
  • Is this a "demo" use case, or is your requirement really to send an email when the table is modified? If the later, please read this post: http://dba.stackexchange.com/questions/15613/elegant-way-of-sending-e-mails-with-service-broker – Luc Morin Jan 01 '17 at 22:47
  • Thanks Luc, well the send an email is not the problem. The problem is I want it to return only the newly added record and not all the record as it currently does. – Tolu Ajayi Jan 01 '17 at 23:05
  • My point is that there is "built-in" functionality already in SQL server to accomplish what you're trying to accomplish, so why re-invent the wheel? I also found this: http://stackoverflow.com/a/10755518/172769 – Luc Morin Jan 01 '17 at 23:07
  • SqlTableDependency which CodingYoshi introduced can do what you want. This is also what I'm using with good perfomance. This notifies you after every Insert, Update, Delete command and returns only the newly added values of the row and you can do something after being notified. It's somehow advanced version of SqlDependency. You'd better to try. – Kay Lee Jan 02 '17 at 05:48
  • Thanks Kay Lee, it worked – Tolu Ajayi Jan 05 '17 at 10:20

1 Answers1

0

That is not how SqlDependency works. Basically the way it works is you give it a query and if the query results change, it triggers a notification. But when you execute the datareader, it will not only return the changes. It returns data as if it was a regular query.

If you want to get only the changes, then you need to have a column in your table with something like LastModifiedDateTime and then query those records where LastModifiedDateTime is bigger than when you retrieved records the last time.

Alternatively, there is this NuGet package that will do this sort of thing for you if you are interested in using it.

CodingYoshi
  • 25,467
  • 4
  • 62
  • 64