Передача изменений в базу данных при помощи хранимых процедур. Объект CommandBuilder
Проблемы, связанные с обновлением базы данных
Представим себе, что некоторый пользователь при помощи своего приложения подключился к базе и получил данные. Объект 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.21):
Сразу замечаем, что мастер генерирует различные SQL-конструкции для команд UPDATE и DELETE. Теперь можно расположить эти фрагменты рядом для детального рассмотрения (таблица 13.1).
Приложение "UseOptimConcur" |
---|
// // 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)); |
Приложение "NoUseOptimConcur" |
// // 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).