Опубликован: 05.08.2007 | Доступ: свободный | Студентов: 2201 / 83 | Оценка: 4.47 / 4.09 | Длительность: 20:11:00
ISBN: 978-5-9556-0097-0
Лекция 13:

Передача изменений в базу данных при помощи хранимых процедур. Объект 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):

 Окно "Advanced SQL Generation Options" мастера настройки объекта DataAdapter

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

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

 Окно "Сравнить и объединить документы"

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

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

 Различия документов "UseOptimConcur.doc" и "NoUseOptimConcur.doc"

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

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

Таблица 13.1. Фрагменты листингов приложений "UseOptimConcur" и "NoUseOptimConcur"
Приложение "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).