1

In our application, we need to update certain fields of a building (ie whether or not it's occupied already) in real time when any other user using that same application changes those fields for a specific building.

However, this is only necessary if both users are currently viewing the same building. A building is selected in a grid, so when the selected row is changed, I create a new SqlDependency object with the correct SqlCommand.

RefreshDependency method:

private void RefreshDependency()
    {
        if (//Check if a row is selected at all and if it's a valid building)
        {
            if (_dependency != null)
            {
                _dependency.OnChange -= OnDependencyChange;
            }

            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                if (connection.State == ConnectionState.Closed)
                {
                    connection.Open();
                }

                using (SqlCommand command = new SqlCommand($"SELECT [MyFields] FROM [dbo.MyTable] WHERE [BuildingID] = '{selectedBuilding.ID}'", connection))
                {
                    SqlDependency dependency = new SqlDependency(command);
                    _dependency = dependency;
                    dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);

                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        reader.Read();
                    }
                }

                connection.Close();
            }
        }
    }

OnDependencyChange event:

private void OnDependencyChange(object sender, SqlNotificationEventArgs e)
    {
        RefreshDependency();

        //Code to update fields
    }

Since the OnChange event is not always called, I remove the OnChange event whenever RefreshDependency is called, rather than inside the OnChange event itself, before reassigning it.

The issue now is that while the code works perfectly fine and the application updates correctly whenever a change happens in the database, I noticed while looking into the memory leak issue caused by SqlDependency that every time a new SqlDependency is made, it creates a new conversation in sys.conversation_endpoints. That's alright so far, but if the user happens to keep their overview open for a long time and selects say, 100 buildings over the course of a few hours, then 100 new conversations are added to sys.conversation_endpoints. However, only those that receive a change will ever actually be set to CLOSED, the rest will remain on STARTED_OUTBOUND for an extremely long lifespan.

Now, I can clean out the CLOSED ones no issue, but I don't think I can simply do the same for STARTED_OUTBOUND, lest I remove conversations that actually need to still be open for other users, right? Since everyone naturally shares 1 database.

I'm not sure this is entirely an issue with my code either since even if only a single Dependency is ever made, if no one ever changes anything for that building and the user simply closes the app or overview (causing SqlDependency.Stop() to be called), that will leave that one conversation stuck on STARTED_OUTBOUND as well.

I've noticed that even if a field is changed much, much later, then the database will put all the related conversations to closed as well, no matter how long ago they were created, but considering multiple buildings may never receive a change, I'm a bit worried about leaving these conversations unchecked - I know CLOSED ones are considered a memory leak, and have already implemented a fix for those.

If this is by design for SqlDependency, should I look into using alternatives such as SqlDependencyEx or SqlTableDependency instead?

Senne
  • 21
  • 3
  • Related: https://stackoverflow.com/q/17600537 | https://github.com/dotnet/SqlClient/issues/148 – Pang Mar 18 '20 at 00:47

0 Answers0