Технология Microsoft ADO.NET

Проблемы, связанные с обновлением базы данных


Представим себе, что некоторый пользователь при помощи своего приложения подключился к базе и получил данные. Объект DataSet способен хранить полученные данные достаточно долго. За это время другой пользователь или группа пользователей могут загрузить эти же данные в свои объекты DataSet. При редактировании локальных данных различными пользователями и последующей их синхронизации возникает проблема: какие именно изменения база данных должна записывать? Ответ на этот вопрос зависит от структуры обновляющих запросов. По умолчанию при создании запросов с помощью мастера Data Adapter Configuration Wizard исключается возможность перезаписи первым пользователем изменений, внесенных в базу другими пользователями за время его автономной работы. Это реализация так называемого оптимистического параллелизма (optimistic concurrency). Создайте новое Windows-приложение и назовите его "UseOptimConcur". Переходим на вкладку Data панели инструментов Toolbox, добавляем на форму объект SqlDataAdapter. В появившемся мастере настраиваем подключение к базе данных Microsoft SQL "BDTur_firm2" для извлечения всего двух полей - "Код туриста" и "Фамилия" из таблицы "Туристы". Завершив работу мастера, создайте новое приложение и назовите его "NoUseOptimConcur". Проделываем те же самые действия, только на этот раз в окне Generate the SQL statements нажимаем на кнопку "Advanced Options_". В появившемся окне Advanced SQL Generation Options снимаем галочку "Use optimistic concurrency" (рис. 13.19):


увеличить изображение
Рис. 13.19.  Окно "Advanced SQL Generation Options" мастера настройки объекта DataAdapter

Закрываем окна настройки и завершаем работу мастера. Итак, у нас получилось два приложения, и для того чтобы разобраться с оптимистическим параллелизмом, нам нужно сравнить их листинги. Скопируем весь код приложений5) в два отдельных документа Microsoft Word, которые затем сохраним, называя так же, как проекты.
Открываем документ UseOptimConcur.doc в главном меню переходим "Сервис \ Сравнить и объединить исправления", в появившемся окне выбираем документ NoUseOptimConcur.doc, отмечаем галочку "Черные строки" (рис. 13.20):


увеличить изображение
Рис. 13.20.  Окно "Сравнить и объединить документы"

Различия появляются в новом документе, выделенные цветом и сопровожденные комментариями (рис. 13.21):


увеличить изображение
Рис. 13.21.  Различия документов "UseOptimConcur.doc" и "NoUseOptimConcur.doc"

Сразу замечаем, что мастер генерирует различные SQL-конструкции для команд UPDATE и DELETE. Теперь можно расположить эти фрагменты рядом для детального рассмотрения (таблица 13.1).

Таблица 13.1. Фрагменты листингов приложений "UseOptimConcur" и "NoUseOptimConcur"Приложение "UseOptimConcur"Приложение "NoUseOptimConcur"


// // sqlUpdateCommand1 // this.sqlUpdateCommand1.CommandText = @"UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия WHERE (Кодтуриста = @Original_Кодтуриста) AND (Фамилия = @Original_Фамилия OR @Original_Фамилия IS NULL AND Фамилия IS NULL); SELECT Кодтуриста, Фамилия FROM Туристы WHERE (Кодтуриста = @Кодтуриста)"; this.sqlUpdateCommand1.Connection = this.sqlConnection1; this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Кодтуриста", System.Data.SqlDbType.Int, 4, "Кодтуриста")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Фамилия", System.Data.SqlDbType.NVarChar, 50, "Фамилия")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Фамилия", System.Data.SqlDbType.NVarChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Фамилия", System.Data.DataRowVersion.Original, null));
// // sqlDeleteCommand1 // this.sqlDeleteCommand1.CommandText = "DELETE FROM Туристы WHERE (Кодтуриста = @Original_Кодтуриста) AND (Фамилия = @Ori" + "ginal_Фамилия OR @Original_Фамилия IS NULL AND Фамилия IS NULL)"; this.sqlDeleteCommand1.Connection = this.sqlConnection1; this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Фамилия", System.Data.SqlDbType.NVarChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Фамилия", System.Data.DataRowVersion.Original, null));


// // sqlUpdateCommand1 // this.sqlUpdateCommand1.CommandText = "UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия WHERE (Кодтуриста" + " = @Original_Кодтуриста); SELECT Кодтуриста, Фамилия FROM Туристы WHERE (Кодтури" + "ста = @Кодтуриста)"; this.sqlUpdateCommand1.Connection = this.sqlConnection1; this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Кодтуриста", System.Data.SqlDbType.Int, 4, "Кодтуриста")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Фамилия", System.Data.SqlDbType.NVarChar, 50, "Фамилия")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));


// // sqlDeleteCommand1 // this.sqlDeleteCommand1.CommandText = "DELETE FROM Туристы WHERE (Кодтуриста = @Original_Кодтуриста)"; this.sqlDeleteCommand1.Connection = this.sqlConnection1; this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));
<


Внимательно изучив рис. 13.21 и таблицу 13.1, делаем вывод, что по умолчанию мастер Data Adapter Configuration Wizard включает в раздел WHERE все поля и добавляет соответствующие параметры. Такая логика исключает перезапись изменений, сделанных другими пользователями в интервал времени между выборкой данных и последующей через некоторое время отправкой изменений. Действительно, если за время работы в отсоединенном режиме запись будет изменена, значения ее полей уже не будут соответствовать условиям раздела WHERE. Следовательно, объект Data Adapter, обратившись к базе и не обнаружив нужной записи, не будет вносить изменения.

Если вам, наоборот, нужно обновлять данные, "затирая" внесенные изменения, в SQL-запросы UPDATE и DELETE следует включать только поля первичного ключа. Это пример деструктивного параллелизма (destructive concurrency), модель которого характеризуют фразой "побеждает пришедший последним".

В программном обеспечении к курсу вы найдете папку "Concurrency" с приложениями UseOptimConcur, NoUseOptimConcur, а также с соответствующими документами Microsoft Word (Code\Glava6\Concurrency).


Содержание раздела