Событие SqlDependency OnChange срабатывает много раз для каждого отдельного события в базе данных.

Я разрабатываю систему уведомлений с использованием SqlDependency и signalR. Проблема, с которой я не могу справиться, заключается в том, что при изменении значения атрибута "IsOnline" в БД на "Истина" или "Ложь" в зависимости от состояния участника событие OnChange запускается много раз, впервые новый пользователь войти в систему, я получаю два уведомления, тогда во второй раз я получаю больше, как 4, то больше, чем больше. Количество уведомлений увеличивается с каждым новым входом или выходом. Я уверен, что проблема в SqlDependency не в SignalR, я собираюсь поделиться с вами частью моего кода.

Заранее спасибо.

  [System.Web.Services.WebMethod]

    public static IEnumerable<AttendeeList> GetAllUsers()
    {
        var AttendeeList = new List<AttendeeList>();

        try
        {
            using (var connection = new SqlConnection(_connString))
            {
                connection.Open();
                string str = "";
                str += "SELECT [AttendeeID], ";
                str += "       [IsAllowToUploadDocuments],";
                str += "       [IsOnline], ";
                str += "       [AttendeeTypeName],";
                str += "       [UserName] ";
                str += "       FROM [dbo].[Meeting_Attendees]   ";
                str += "       INNER JOIN [dbo].[aspnet_Users]  ON [aspnet_Users].[UserId] = [Meeting_Attendees].[AttendeeID] ";
                str += "       INNER JOIN   [dbo].[AttendeeType] ON [dbo].[AttendeeType].[AttendeeTypeID] = [dbo].[Meeting_Attendees].[AttendeeTypeID] ";
                str += "       WHERE [MeetingID]=@MeetingID ORDER BY [IsOnline] DESC";

                using (var command = new SqlCommand(@str, connection))
                {
                    SqlParameter prm = new SqlParameter("@MeetingID", SqlDbType.Int);
                    prm.Direction = ParameterDirection.Input;
                    prm.DbType = DbType.Int32;
                    prm.Value = Convert.ToInt32(Properties.Settings.Default.MeetingID);
                    command.Parameters.Add(prm);
                    command.Notification = null;

                    var dependency = new SqlDependency(command);
                    dependency.OnChange += new OnChangeEventHandler(dependencyUsers_OnChange);

                    if (connection.State == ConnectionState.Closed)
                        connection.Open();

                    var reader = command.ExecuteReader();

                    while (reader.Read())
                    {
                        AttendeeList.Add(item: new AttendeeList { UserName = (string)reader["UserName"], UserType = (string)reader["AttendeeTypeName"], IsOnline = (bool)reader["IsOnline"], IsAllowToUploadDocuments = (bool)reader["IsAllowToUploadDocuments"], IsCurrentUser = true ? (Guid)reader["AttendeeID"] == new Guid(Properties.Settings.Default.UserID.ToString()) : false });
                    }
                }
            }
        }
        catch { }
        return AttendeeList;
    }

    private static void dependencyUsers_OnChange(object sender, SqlNotificationEventArgs e)
    {
        if (e.Type == SqlNotificationType.Change && e.Info == SqlNotificationInfo.Update)
        {
            //Call SignalR  
            MessagesHub.UpdateUsers();
        }
    }

3 ответа

Чтобы обработчик событий был зарегистрирован один раз, поставьте "-=" перед "+=":

oDependency.OnChange -= new OnChangeEventHandler(DBUpdateNotificationReeived);
oDependency.OnChange += new OnChangeEventHandler(DBUpdateNotificationReeived);

Проверьте, нет ли в таблице ur SQL триггеров (кроме sqltabledependency), которые "обновляют" обновляемую запись.

У меня тоже была такая же проблема с несколькими звонками на OnChange событие в моем проекте, но я исправил это с помощью переменной counter. Следуя этому примеру, в моем случае dependencyUsers_OnChange Функция из примера стреляла дважды.

Я инициализировал переменную counter как глобальную. После "сканирования" состояния ваших временных данных перед любым изменением я также установил значение счетчика на 0.

Следуя вашему примеру, после этого шага, изменение было сделано в dependencyUsers_OnChange в if заявление:

    private static void dependencyUsers_OnChange(object sender, SqlNotificationEventArgs e)
    {
            if (e.Type == SqlNotificationType.Change && e.Info == SqlNotificationInfo.Update && counter == 0)
            {
                //Call SignalR  
                MessagesHub.UpdateUsers();
                counter++; //The update is done once
            }
            else 
            {
                counter = 0; //if the update is needed in the same iteration, please don't update and set the counter to 0
            }
     }

В вашем случае решение будет примерно таким:

int counter = 0; //initialization of help counter
[System.Web.Services.WebMethod]

    public static IEnumerable<AttendeeList> GetAllUsers()
    {
        var AttendeeList = new List<AttendeeList>();

        try
        {
            using (var connection = new SqlConnection(_connString))
            {
                connection.Open();
                string str = "";
                str += "SELECT [AttendeeID], ";
                str += "       [IsAllowToUploadDocuments],";
                str += "       [IsOnline], ";
                str += "       [AttendeeTypeName],";
                str += "       [UserName] ";
                str += "       FROM [dbo].[Meeting_Attendees]   ";
                str += "       INNER JOIN [dbo].[aspnet_Users]  ON [aspnet_Users].[UserId] = [Meeting_Attendees].[AttendeeID] ";
                str += "       INNER JOIN   [dbo].[AttendeeType] ON [dbo].[AttendeeType].[AttendeeTypeID] = [dbo].[Meeting_Attendees].[AttendeeTypeID] ";
                str += "       WHERE [MeetingID]=@MeetingID ORDER BY [IsOnline] DESC";

                using (var command = new SqlCommand(@str, connection))
                {
                    SqlParameter prm = new SqlParameter("@MeetingID", SqlDbType.Int);
                    prm.Direction = ParameterDirection.Input;
                    prm.DbType = DbType.Int32;
                    prm.Value = Convert.ToInt32(Properties.Settings.Default.MeetingID);
                    command.Parameters.Add(prm);
                    command.Notification = null;

                    var dependency = new SqlDependency(command);
                    counter = 0; //Whenewer the web method is called, set te counter to 0
                    dependency.OnChange += new OnChangeEventHandler(dependencyUsers_OnChange);

                    if (connection.State == ConnectionState.Closed)
                        connection.Open();

                    var reader = command.ExecuteReader();

                    while (reader.Read())
                    {
                        AttendeeList.Add(item: new AttendeeList { UserName = (string)reader["UserName"], UserType = (string)reader["AttendeeTypeName"], IsOnline = (bool)reader["IsOnline"], IsAllowToUploadDocuments = (bool)reader["IsAllowToUploadDocuments"], IsCurrentUser = true ? (Guid)reader["AttendeeID"] == new Guid(Properties.Settings.Default.UserID.ToString()) : false });
                    }
                }
            }
        }
        catch { }
        return AttendeeList;
    }

    private static void dependencyUsers_OnChange(object sender, SqlNotificationEventArgs e)
    {
            if (e.Type == SqlNotificationType.Change && e.Info == SqlNotificationInfo.Update && counter == 0)
            {
                //Call SignalR  
                MessagesHub.UpdateUsers();
                counter++; //The update is done once
            }
            else 
            {
                counter = 0; //if the update is needed in the same iteration, please don't update and set the counter to 0
            }
     }

Я надеюсь, что эта идея будет полезна для кого-то, я решил проблему в своем проекте с этим решением.

Я попадаю в ту же проблему при использовании SignalR и SQL Dependency

Строка исполнялась не раз. На событие следует подписаться только один раз. oDependency.OnChange += new OnChangeEventHandler(DBUpdateNotificationReeived);

Уведомления о запросах запускаются при изменении отслеживаемого набора результатов, см. Раздел "Понимание того, когда появляются уведомления о запросах". Как правило, вы можете получить больше уведомлений, чем фактических изменений данных:

Обратите внимание, что SQL Server может выдавать уведомление о запросе в ответ на события, которые не изменяют данные, или в ответ на изменение, которое фактически не влияет на результаты запроса. Например, когда инструкция UPDATE изменяет одну из строк, возвращаемых запросом, уведомление может сработать, даже если обновление строки не изменило столбцы в результатах запроса. Уведомления о запросах предназначены для поддержки общей цели улучшения производительность для приложений, которые кэшируют данные. Когда сервер сильно загружен, SQL Server может создать сообщение с уведомлением о запросе для подписки, а не выполнять работу по определению, изменились ли результаты запроса.

О том, вызывает ли это проблемы в вашем случае, невозможно сказать из вашего поста, в частности, неясно, как вы обрабатываете обновления, так что это приводит к еще 4 уведомлениям. Получение 4 уведомлений подразумевает, что вы отправили 4 запроса на уведомление, поэтому, скорее всего, у вас также есть проблема с кодом и вы переподписались.

Прочтите Использование трассировки SQL для устранения неполадок уведомлений о запросах и попробуйте выяснить, что происходит, где создаются и аннулируются уведомления.

Другие вопросы по тегам