WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 4 | 5 || 7 | 8 |

«David Sceppa Microsoft Press Дэвид Москва 2003 Р Г Г If Р F г V А Л А Ц л г УДК 004.45 32.973.26-018.2 С28 Д. ...»

-- [ Страница 6 ] --

как посредством этого запроса и объектной модели ав томатически получать значения автоинкремента после передачи обновлений.

Выборка данных при помощи пакетных запросов после передачи обновления В главе 5 рассказывалось о выборке результатов пакетного запроса, аналогично го следующему, с помощью объекта DataAdapter.

SELECT Phone FROM Customers;

SELECT OrderlD, OrderDate FROM Orders Кроме пакетные запросы позволяют выбирать данные после передачи обновлений. Пакетный запрос объединить запрос UPDATE и выбирающий новое значение timestamp. Задайте свойству коман ды объекта такой запрос:

UPDATE [Order Details] SET OrderlD = ?, ProductID = ?, Quantity = ?, UnitPrice = ?

WHERE OrderlD = ? AND ProductID = ? AND TSCol = ?;

SELECT TSCol [Order Details] WHERE OrderlD = ? AND ProductID = ?

Примечание Не все БД поддерживают пакетные запросы, возвращающие за писи. в отличие от Microsoft SQL Server, в БД Oracle и Microsoft Access поддержки таких запросов нет. Подробнее о поддержке данной — в документации БД.

Передавая отложенное изменение, объект DataAdapter также выполняет по следующий запрос SELECT и помещает результаты в измененный объект содержит новое значение поля timestamp, и запись раз решается снова изменить и успешно передать эти изменения в БД.

ГЛАВА 11 Сложные случаи обновления данных Свойство объекта Command Объект передает обновления в БД с использованием объектов Com mand, хранящихся в свойствах и Но как DataAdapter узнает о необходимости просмотреть результаты работы Insert Command или Все дело в свойстве объекта Command, Данное свойство принимает значения из перечисления (табл. По умолчанию объект Command выбирает для измененной записи новые данные, проверяя параметры вывода и первую возвращенную запросом запись.

Таблица Элементы перечисления UpdateRowSource Константа Значение Описание 3 Указывает объекту Command выбрать для записи но вые используя параметры вывода и первую возвращенную запись. Значение по умолчанию 2 Указывает объекту Command выбрать для записи вые данные с использованием первой возвращенной записи None 0 Указывает Command по завершении выпол нения не выбирать для новые данные 1 Указывает объекту Command выбрать для записи но вые данные с использованием параметров вывода Чтобы повысить производительность обновления, достаточно задать свойству UpdatedDataSource требуемое значение. Я провел для данного свойства малень кий неформальный тест, содержимое таблицы Order Details и вставляя его в новую таблицу с той же структурой. Для начала я не менял значение свой по умолчанию UpdatedDataSource, Both.

В тесте объект просто передавал новую запись. Он не включа ет пакетного запроса для изменения содержимого записи после передачи обнов ления, и поэтому задавать свойству UpdatedDataSource объекта InsertCommand отличное от None, не требуется. При таком значении производитель ность оказывалась на выше.

Если свойству UpdatedDataSource задано по умолчанию, объект Command проверяет параметры вывода и возвращенную им запись, даже если он вообще не возвращает данных. Задавайте свойству UpdatedDataSource объектов Command нужное значение, иначе случится небольшое, но совершенно неоправданное падение производительности.

Выборка новых данных с помощью параметров вывода Свойство UpdatedDataSource позволяет также указать, что новые данные требует ся выбрать с помощью параметров вывода.

Создадим хранимую которая средствами аналогичного запроса UPDATE изменяет запись таблицы Order Details и возвращает новое значение типа timestamp с использованием параметра вывода:

408 Часть III Автономная работа с данными: объект DataSet модели CREATE spUpdateDetail int, money, int, int, AS UPDATE [Order Details] SET = = Quantity = UnitPrice = WHERE = AND ProductlD = AND = ffTSCol;

IF = SELECT TSCol [Order Details] WHERE OrderlD = AND ProductlD = Все, что осталось сделать, — задать свойству объекта создать объекта задать свойству объекта Command значение Both.

Этот процесс более чем возврат средствами запроса SELECT.

Проверить значение параметра гораздо быстрее, чем выбрать результаты запро са. Кроме того, БД типа Oracle поддерживают параметры вывода, но не поддер живают пакетные запросы, записи.

Выборка данных с помощью события объекта DataAdapter после передачи обновления Некоторые БД, например Microsoft Access, не поддерживают пакетные запросы, возвращающие записи, и не поддерживают параметры вывода хранимых проце дур. При работе с такими БД два описанных выше способа выборки данных пос ле передачи обновления становятся недоступными. Тем не менее есть еще один способ, не связанный с переходом на БД, поддерживающую нужную функциональ ность.

У объекта есть два события, наступающих при передаче измене ний, в объекте — и RowUpdated. Как следует из их имен, первое событие наступает непосредственно перед передачей изме а последнее — сразу после передачи.

Если передаются изменения нескольких записей, события RowUpdating и dated наступают для каждой из них. Добавив код регистрации событий, вы уви дите в журнале следующие записи:

RowUpdating event fired for row # RowUpdated event fired for # RowUpdating event fired for RowUpdated event fired RowUpdating event fired for row RowUpdated event fired for row С помощью события RowUpdated удается выбрать новое значение, генерируе мое БД для обновленной записи. Следующий фрагмент кода демонстрирует ра с событием RowUpdated. Для краткости он ссылается на выдуманные ГЛАВА 11 Сложные случаи обновления данных объекты DataTable, и Command для выборки нового значения timestamp.

Заметьте: в обработчике события код проверяет успешность об новления и то, что изменение записи обновлением или вставкой. Понятно, что при удалении записи БД потребности в запросе для получения нового значе ния timestamp не возникнет. Кроме того, поскольку объект Command, выбираю щий новое значение timestamp, только одно значение, код получает его с помощью метода Visual Basic Dim da As OleDbDataAdapter = Dim As = AddressOf Dim As DataTable = Private Sub sender As Object, e As If = AndAlso = = Then Value = = = End If End Sub Visual OleDbDataAdapter da = OleDbCommand cmdGetNewTS = CreateGetNewTSCommandO;

da.RowUpdated += new DataTable tbl = private void sender, OleDbRowUpdatedEventArgs e) { if == || == = = Часть III Автономная работа с данными: объект DataSet модели Получив новое значение timestamp и задав его соответствующему полю объекта код с помощью метода подтверждает это изменение.

Без вызова метода объект кэшировал бы изменение, что бы его позже передать в БД. Это совсем не то, для чего мы получали новое значе ние timestamp. Вызывая мы указываем просто при нять хранящиеся в изменения.

Данный метод очень гибок, поскольку работает в любой БД;

тем не менее за гибкость приходится платить производительностью. В выполненных мной тестах производительность выборки timestamp с использованием событий оказалась на 35% ниже производительности пакетных запросов и на 50% ниже производительности параметров вывода хранимых процедур.

Приложение Timestamp На прилагаемом к книге компакт-диске записано приложение (на Visual Basic и на С* иллюстрирующее все три способа выборки дан ных после передачи обновления (рис.

Приложение демонстрирует исходную проблему: если не выбирать после пе редачи изменений в БД новые значения timestamp, последующие попытки обнов ления этих же записей завершатся неудачей.

update timestamp sample in a row alter submitting you click any the buttons the code will run a test adds a new a that a timestamp column.

The lest then the ol DataRow and to change to the the update then ID delete database In each lest, the relies on the key and values ID optimistic II the does not value that currently in the then the update attempl will fail Retrieving new stored procedures output В Update Рис. 11-1. Приложение Timestamp Выборка новых значений автоинкремента SQL Server, Access, Sybase и другие БД используют столбцы с также называемые columns). Вы можете вставить в таб ГЛАВА Сложные случаи обновления данных лицу новую запись, и БД сгенерирует для нее новое значение поля с автоинкре ментом. Многие таблицы БД например Employees. Orders и Products, используют столбцы с автоинкрементом в качестве первичного ключа.

Почему работа со столбцами с автоинкрементом считается сложным случаем обновления? Вы можете передать в БД новую запись, но БД сгенерирует для нее новое значение поля с автоинкрементом. Это означает, что после передачи но вой записи в БД значение ее поля с автоинкрементом вам неизвестно. Вообще говоря, вам требуется знать значение первичного ключа записей, Так как же средствами объектной модели ADO.NET получить для записи новое значение автоинкремента?

Работа с SQL Server Представьте на минуту, что вы передаете изменения в БД не с помощью объекта а разрабатываете для этой цели собственные запросы, При работе со сведениями о заказах из БД Northwind получить данные из таб лицы Orders можно средствами следующего запроса:

SELECT FROM Orders Для вставки новой записи в таблицу используйте такой запрос:

INSERT INTO Orders (CustomerlD, OrderDate) VALUES (?, ?, ?) Получить значение автоинкремента, сгенерированное БД для новой записи, можно с помощью показанного ниже запроса:

SELECT Примечание Почему «можно», а не «нужно»? Подробнее об этом — в разделе этой главы, посвященном сравнению функции и ключево го слова Этот запрос — ключ к получению значения автоинкремента. Данный запрос используется в объектной модели ADO.NET так же, как и запрос из предыдущего примера, возвращающий значение timestamp.

Можно изменить значение свойства команды объекта и выполнять запрос SELECT после каждой вставки:

INSERT INTO Orders (CustomerlD, EmployeelD, OrderDate) VALUES (?, ?, ?);

SELECT AS OrderlD Заметьте: SELECT запрос включает псевдоним, указывающий объек ту в какой столбец поместить результаты запроса.

Как и при выборке новых значений timestamp, вернуть новое значение авто инкремента удается средствами параметра вывода хранимой процедуры:

CREATE PROCEDURE int OUTPUT, int, AS INSERT INTO Orders (CustomerlD, EmployeelD, OrderDate) 412 Часть III Автономная работа с данными: объект DataSet модели VALUES SELECT = Наконец, можно воспользоваться событием RowUpdated объекта и выполнить запрос, выбирающий новое значение автоинкремента:

Visual Basic da As OleDbDataAdapter = Dim As Dim cmdGetldentity As New AddHandler AddressOf HandleRowUpdated Dim tbl As DataTable = Private Sub sender As Object, e As OleDbRowUpdatedEventArgs) If = = Then = Integer) e.

End If End Sub Visual C# OleDbDataAdapter da = OleDbConnection = cmdGetldentity = new += new OleDbRowUpdatedEventHandler(HandleRowUpdated);

DataTable tbl = private void OleDbRowUpdatedEventArgs e) if == == = (int) :

У этого фрагмента кода и фрагмента, выбиравшего новые значения timestamp с использованием события два отличия. Первое, самое очевидное — то, что для выборки данных выполняется другой запрос.

Второе отличие — в производительности. Какой самый быстрый способ рать новые значения автоинкремента? Полученные мной при тестах величины ГЛАВА Сложные случаи обновления данных производительности соответствовали значениям, полученным при тестах на вы борку значений timestamp. Параметры вывода хранимых процедур обеспечива ют пакетные запросы — средний, а использование события Row Updated — наименьший уровень производительности.

Функция IDENTITY и ключевое слово Запрос SELECT возвращает последнее значение IDENTITY, сгенери рованное на данном соединении. Это означает, что работа других пользователей па других соединениях не влияет на результаты вашего запроса. Однако из этого не следует, что вы получите ожидаемое значение.

Администраторы БД зачастую отслеживают изменения содержимого БД с по мощью собственных таблиц аудита. Для регистрации изменений обычно приме няются триггеры и хранимые процедуры (рис.

Рис. 11-2. Отслеживание изменений с помощью таблиц аудита Почему посреди дискуссии о выборке значений автоинкремента я углубился в обсуждение журналов аудита и триггеров? Предположим, что таблица на которую ссылается триггер на рис, включает столбец с автоинкрементом.

в таблицу Orders новую запись и выполнив запрос SELECT вы получите значение автоинкремента, сгенерированное триггером для новой записи таблицы аудита.

Помните: запрос SELECT возвращает последнее значение автоин кремента, сгенерированное на данном соединении.

решения этой проблемы в SQL Server 2000 реализован новый способ вы борки значений автоинкремента: ключевое слово Выполнив в описанной ситуации запрос SELECT вы получите значение автоинкремента, сгенерированное для новой записи таблицы Orders.

Если вы работаете с SQL Server или Microsoft Desktop Engine версии или более поздней, используйте вместо Однако из этого правила есть одно небольшое исключение. Если вы вставляете новую запись средствами хранимой процедуры и хотите после ее вызова получить значение автоинкремента, вернет Null. Как я и говорил, небольшое Часть III Автономная работа с данными: объект DataSet модели исключение. Если вы вставляете новые записи с помощью хранимой процедуры и вам требуется получить новое значение автоинкремента, пара метром вывода.

Подробнее о различиях и - в SQL Server Books Online.

Работа с Access При работе с БД Access новые значения автоинкремента также получают посред ством запроса SELECT Эта возможность реализована в 4 версии ставщика OLE DB Jet Provider и поддерживается только БД Access версии 2000 или более поздней. Как и в SQL Server, в Access запрос SELECT возвраща ет последнее значение автоинкремента, сгенерированное на данном соединении.

БД Access не поддерживают параметры вывода — хранимых запро сов, аналогичных представлениям и хранимым процедурам. Поставщик OLE DB Jet Provider не предоставляет поддержки пакетных запросов. Таким образом, един ственный способ выбрать новые значения автоинкремента — воспользоваться событием объекта как рассказывалось ранее.

Работа с последовательностями Oracle БД Oracle не поддерживают столбцы с автоинкрементом, зато предоставляют ана логичную конструкцию — В SQL Server вы поме чаете столбец как столбец с автоинкрементом, и SQL Server автоматически гене рирует новые значения соответствующего поля для вставляемых записей.

довательность Oracle ведет себя несколько иначе. Обычно последовательность создают, чтобы генерировать новые значения для столбца БД, однако прямой связи между последовательностью и таблицей ил столбцом нет. Последовательность Oracle — это объект, наподобие таблицы или хранимой процедуры.

Приведенный ниже запрос создает последовательность Oracle:

CREATE SEQUENCE При создании последовательности Oracle можно указать ряд параметров, в ча стности, ее минимальное и максимальное значения.

Примечание Я не считаю себя экспертом по последовательностям Oracle. Я умею создавать и использовать их в простых запросах INSERT и SELECT, но не более того. Подробнее о последовательностях — в документации Oracle.

Известно два способа использования последовательности. на нее можно в запросе INSERT INSERT INTO NyTable (ID, VALUES При каждом выполнении запроса последовательность будет возвращать новое значение.

После вставки новой записи выполняют запрос к последовательности и опре деляют последнее использованное значение:

ГЛАВА Сложные случаи обновления данных SELECT DUAL Как и в случае запроса SELECT в БД Access и SQL Server, работа других пользователей, вставляющих новые записи с применением этой же пос ледовательности, на результаты данного запроса не влияет.

Так как вставить новые значения последовательности в объекты Oracle не поддерживает пакетные возвращающие данные, и поэтому в свойстве команды нельзя использовать оператор Тем не менее разрешается выполнить в обработчике события RowUpdated объекта такой запрос:

Visual Basic Dim da As = Dim As = Dim As String = "SELECT FROM DUAL" Dim As New AddHandler Dim As DataTable = Sub sender As Object, e As If = AndAlso = Then = Integer) End If End Sub Visual C# OleDbDataAdapter da = OleDbConnection = StrSQL = "SELECT DUAL";

cmdGetSequence = new += new DataTable tbl = private void HandleRowUpdated(object sender, OleDbRowUpdatedEventArgs e) if == StatementType == { = (int) Часть Автономная работа с данными: объект DataSet модели } Вам не надо использовать в запросе INSERT INTO команду Обратиться к последовательности разрешается перед выполнением INSERT INTO. Следующий фрагмент кода создает новую процедуру, которая выполняет запрос к последовательности для получения нового значения и помещает это значение в переменную. Затем процедура использует это значение для вставки новой записи при помощи запроса INSERT INTO.

CREATE PROCEDURE IN VARCHAR2, OUT NUMBER) IS BEGIN SELECT INTO pID FROM DUAL;

INSERT INTO MyTable (ID, VALUES (pID, pOtherCol);

END;

Хранимая процедура возвращает новое значение последовательности с исполь зованием параметра вывода. Такую процедуру следует задать свойству объекта чтобы новые записи. Если связать параметр вывода с соответствующим полем объекта получит новое зна чение сразу же после передачи новой записи и БД.

Примечание Помните: прямой связи между последовательностью и столбцом таблицы нет. Использовать последовательность при вставке новой записи в таблицу не требуется. Если пользователи вставляют новые записи без ссылок на последовательность, та генерирует новые значения, уже име ющиеся в БД. Во избежание проблем убедитесь, что единственный спо соб вставить новую запись в таблицу — вызвать хранимую ссылающуюся на последовательность.

Генерирование фиктивных значений для объектов последовательности Последовательности не являются столбцами с но если задать свойству соответствующих объектов DataColumn значение True, можно заставить ADO.NET генерировать для новых записей фиктивные значения.

Задавать значение свойства придется вручную. Прямой связи между последо вательностью и таблицей нет. Если получить информацию схемы БД с помо щью метода та или средствами мастера Data Adapter Con figuration Wizard, ADO.NET не узнает, что столбец таблицы сопоставлен с по следовательностью.

Аналогичные проблемы возникают при генерации логики обновления для объектов DataAdapter. Объект и мастер Data Adapter Configuration Wizard не узнают, что в логике, заданной свойству нужно опус тить соответствующий столбец. Если вы собираетесь создавать логику обновле ГЛАВА Сложные случаи обновления данных ния при помощи указанных средств, вам потребуется внести в нее ные изменения.

Может показаться, что я критикую эти утилиты. На самом деле это не так. Ин формации схемы, которая связывала бы столбец таблицы и последовательность, не существует, и поэтому данные утилиты делают все чтобы создать подходящий код.

Тот факт, что поле, косвенно связанное с последовательностью Oracle, разре шено обрабатывать как поле с автоинкрементом и что можно управлять поряд ком выборки новых значений последовательности в объект — подтверж дение мощи, предоставляемой ADO.NET разработчикам. В предыдущих моделях доступа к данным Microsoft такая функциональность отсутствует.

Приложение, выбирающее значения автоинкремента Я описал несколько связанных с выборкой новых значений автоинк ремента и последовательности в объект DataSet. Для получения новых значений автоинкремента SQL Server используют пакетные запросы, параметры вывода хранимых процедур и события объекта для новых зна чений автоинкремента Access — события объекта DataAdapter, для новых значений последовательности Oracle — параметры вывода хранимых про цедур или события объекта DataAdapter. Выбор велик.

На прилагаемом к книге компакт-диске записано приложение (на Visual Basic и С* которое поможет вам разобраться во всем этом, демонст рируя на практике каждую ситуацию и каждого варианта выбор ки значений. На рис. показана одна из версий этого приложения, Кроме того, на компакт-диске есть примеры, выбирающие значения ав тоинкремента из БД Access, а также значения последовательности из БД Oracle.

* SQL Server of a SQL any of the buttons below. he run a test that adds new to a then those your database The lest will the of column for each row the and after submitting the new Each the contents of table your database inserts two lows As a new rows have of and 5.

new values output new rows to j new ID Second Third row new ID new row ID = Third new ID = Рис. 11-3. Приложение 41 8 Часть III Автономная работа с данными: объект DataSet модели Использование ключевого слова Server Многие администраторы БД добавляют в триггеры и/или хранимые процедуры логику для отслеживания выполняемых приложениями запросов. Хранимая про цедура, вставляющая новую может выглядеть так:

CREATE PROCEDURE int OUTPUT, int, datetime) AS INSERT INTO Orders OrderDate) VALUES SELECT = INSERT INTO OrdersLog VALUES + RETURN Ее задают свойству объекта чтобы успешно пере дать изменения. Тем не менее передача обновлений средствами такой же проце дуры вызовет проблемы. Что еще хуже, эти проблемы могут проявить себя толь ко после развертывания приложения.

Чтобы разобраться в этом, рассмотрим следующую процедуру включающую аналогичный код ведения журнала:

CREATE PROCEDURE spOrderUpdate int, datetime, int, datetime) AS Orders SET CustomerlD = EmployeelD = OrderDate = WHERE = AND CustomerlD = AND EmployeelD = AND OrderDate = IF = INSERT INTO OrdersLog (TypeOfChange, DateOfChange) VALUES order ' GetDateO) ELSE INSERT INTO OrdersLog (TypeOfChange, DateOfChange) VALUES (Tailed to modify order + GetDateO) RETURN ее в SQL Server Query Analyzer для обновления заказа, вы увидите такой вывод в окне Results:

(1 affected) (1 affected) ГЛАВА 11 Сложные случаи обновления данных Обработано две записи? Хранимая процедура обновила только один заказ, Использование первичного ключа в разделе WHERE запроса UPDATE гарантиру ет, что запрос изменит не более одной записи. Вторая запись, обработанная хра нимой процедурой, — это запись, вставленная в таблицу журнала.

Такие же результаты вы получите, создав объект Command ADO.NET для вызо ва хранимой процедуры и обновления заказа. Метод вернет 2.

Данная процедура добавляет запись в таблицу журнала независимо от успеха или неудачи обновления, Таким образом, если возникнет ошибка об новления, связанная с оптимистическим контролем параллелизма, хранимая про цедура сообщит, что обработала одну запись.

Бы, понимаете, что одна обработанная хранимой запись соответствует неудачной попытке обновления. Однако для ADO.NET это не так, DataAdapter проверяет число обработанных запросом записей, и только число, равное нулю, соответствует ошибке обновления. В противном случае предпола гается, что обновление прошло успешно.

Итак, если задать эту хранимую процедуру свойству объекта DataAdapter, последний всегда считает, что обновление завершилось успешно.

Ключевое слово NOCOUNT SQL Server позволяет управлять тем, сообщают ли запросы о результатах выполнения. Добавив в хранимой процедуре перед запро сом UPDATE ключевое слово SET NOCOUNT ON и вызвав ее в SQL Server Analyzer, мы получим следующий результат:

The completed successfully.

Он не обязательно соответствует успешному но означает лишь, что запрос не сгенерировал ошибки. БД, в отличие от вас, не считает запрос, не изменивший ни одной записи, ошибочным.

Подавление вывода сообщений о числе записей, обработанных выполняемы ми процедурой запросами, не решает проблему, Если задать свойству UpdateCom mand объекта DataAdapter новую версию хранимой процедуры, DataAdapter по прежнему считает, что все попытки обновления завершились успешно.

Нам требуется подавить сообщения affected» всех запросов, за исклю чением изменяющего нужную запись БД. Для этого в хранимой процедуре клю чевое слово SET NOCOUNT ON следует переместить за запрос UPDATE, как пока зано ниже:

CREATE PROCEDURE spOrderUpdate datetime, int, nchar(5), int, datetime) AS UPDATE Orders SET = = = WHERE = AND CustomerlD = AND EmployeelD = AND OrderDate = SET NOCOUNT ON 420 Часть III Автономная работа с данными: объект DataSet модели IF INSERT INTO VALUES order ' + ELSE INSERT INTO OrdersLog (TypeOfChange, DateOfChange) VALUES to modify order + RETURN Если процедура выполнением запроса UPDATE добавляет запись таб лицу можно воспользоваться таким кодом:

CREATE PROCEDURE MyUpdateProcedure AS SET NOCOUNT ON INSERT INTO HyLogTable SET NOCOUNT OFF SET RETURN Если вы не уверены в как интерпретирует результаты запро са, заданного свойству или про верьте возвращаемое значение метода Какое значение метод возвращает, если вы передали параметры, обеспечившие успешное выпол нение запроса? Какое значение возвращается, когда переданные параметры при водят к неудачному выполнению запроса?

Передача изменений Если вы изменяете данные на нескольких уровнях иерархичного объекта DataSet, при передаче этих изменений в БД возникают две проблемы. Рассмотрим их по подробнее.

Передача записей, ожидающих вставки и удаления вы имеете дело с иерархией, включающей о клиен тах и заказах. Созданное вами приложение — система приема заказов. Пользова тель внес ряд изменений в данные и теперь хочет эти изменения в БД.

Измененные данные в объекте DataSet теперь содержат новых клиентов и новые заказы. Кроме того, DataSet содержит записи о клиентах и заказах, помеченные на удаление.

Проблема в том, чтобы передать эти изменения в правильном порядке, соот ветствующем определенным в БД ссылочной целостности.

ничения ссылочной целостности БД требуют, чтобы записи о заказе соответствовала запись о клиенте.

Если DataSet включает новых клиентов и соответствующие им новые следует сначала передать записи о клиентах и только потом — записи о разме щенных ими заказах. Как новые записи следует передавать в нисходя щем порядке.

ГЛАВА Сложные случаи обновления данных Тем не менее для удаленных записей верно обратное. Удалить из БД клиентов, имеющих размещенные заказы, нельзя. Следует предварительно удалить соответствующие записи о заказах.

Примечание Данный пример — упрощение носящей общий харак тер. БД Northwind не позволит удалить заказ, которому записи таблицы Order Details.

Следующий фрагмент кода выполняется с ошибкой, поскольку пытается уда лить клиентов, имеющих размещенные заказы.

Visual Basic Visual C# Но если обратить порядок обновлений, перв'ая попытка обновления завершится неудачно, так как объект связанный с таблицей Orders, пытается пе редать заказы, размещенные клиентами, отсутствующими в БД.

Visual Basic Visual C# Что же делать бедному программисту? Нужен способ, который позволит орга низовать передачу изменений из иерархичного объекта в следующем порядке:

новые клиенты;

2. новые заказы;

3. измененные измененные заказы;

5. удаленные заказы;

6. удаленные клиенты.

Передача изменений с помощью метода Select объекта DataTabfe Как рассказывалось в главе 7, метод позволяет искать объекты удовлетворяющие заданным критериям. Так, следующая строка кода воз вращает массив объектов DataRow, ожидающих передачи в БД, значение 422 Часть III Автономная работа с данными: объект DataSet модели поля City которых — Seattle. Возвращенные объекты сортируются по полю Contact Name.

= Метод Select возвращает массив объектов а один из перегруженных методов DataAdapter.update принимает такой массив. Какое приятное совпадение!

Данный фрагмент кода с помощью метода Select изолирует нужные изменения и передает их в БД в требуемом порядке:

Visual Basic Dim ds As OataSet = Dim tblCustomers As DataTable = Dim As DataTable = Dim daCustomers As OleDbDataAdapter Dim As OleDbDataAdapter = новые записи о клиентах, затем - о заказах измененные записи о клиентах, затем - о заказах удаленные записи о затем - о клиентах Visual C# DataSet ds = CreateDataSetO;

DataTable tblCustomers = DataTable tblOrders = OleDbDataAdapter daCustomers = CreateCustomersAdapterO;

OleDbDataAdapter daOrders = передаем новые записи о - о заказах измененные записи о клиентах, затем - о заказах ГЛАВА Сложные случаи обновления данных удаленные о затем - о клиентах Передача изменений с помощью метода Управлять порядком передачи изменений удается также с помощью метода ges объектов и DataTable. Следующий фрагмент кода создает новый объект DataTable, включающий только ожидающие передачи в БД записи из оригиналь ного объекта DataTable:

= = По-моему, такой код удобнее писать и проще читать, чем код на основе да Select. И все же я не рекомендую вам использовать данный метод.

Вызывая метод GetChanges объектов DataSet и DataTable, вы создаете новый, отдельный объект. Показанный фрагмент кода передает новые записи в таблицы Customers и Orders БД. Таблица Orders БД включает поле с автоинкре ментом — OrderlD. Если объект с помощью которого передаются изменения в таблицу Orders, включает логику для выборки новых значений поля эти значения вставлены в объект DataTable, используемый мето дом Update, — tblNewOrders. Однако данный объект DataTable не связан с основ ным объектом DataTable tblOrders, и поэтому новые значения OrderlD в основ ном объекте не появятся.

Эта ситуация станет более понятной, когда я буду знакомить вас с изоляцией и повторной интеграцией изменений в следующих разделах главы.

Если вы передаете измененные записи с помощью метода Select, изменения.

возвращенные объектом DataAdapter, вносятся в основной объект DataTable, по скольку Select возвращает массив объектов Фактически элементы этого массива представляют собой указатели на объекты из состава DataTable.

Изменения содержимого массива отражаются на содержимом основного объек та DataTable.

Работа со значениями автоинкремента и реляционными данными Слегка сместим фокус нашего иерархичного объекта DataSet. Теперь он данные из таблиц БД Northwind Orders и Order Details. Приложение так и оста нется системой приема заказов. В данном примере пользователь введет два но вых заказа клиента, а также товары, входящие в состав этих заказов.

Как вы помните из главы 6, при работе со столбцами с автоинкрементом ствам и объекта задать значение -1. Если вы последуете этому совету и добавите в иерархию за писи о заказах и заказанных товарах, до передачи новых заказов в БД объект DataSet будет выглядеть аналогично показанному на рис Часть III Автономная работа с данными: объект DataSet модели Orders D 10268 8 7/30/ -1 7 1/14/ -2 GROSR 7 1/14/ Order Details 29 10 99, 10268 72 10785 10 10 31. 10785 75 10 7. -1 1 12 18. -1 67 24 14. -2 6 22. -2 65 Рис. 11-4. Объект DataSet с записями о заказах и заказанных товарах, ожидающими передачи в БД Для успешной передачи записей следует передать новые записи о заказах, получить для них новые значения добавить их в соответствую щие записи о заказанных товарах и затем передать новые записи о заказанных товарах в БД. На первый взгляд данный процесс кажется сложным, но в действи тельности он прост.

Вы уже умеете передавать в БД новые записи о заказах. Если требуется пере дать только их, вызовите метод Select объекта DataTable с информацией о зака зах, как описывалось ранее. Выбрать новые значения автоинкремента можно любым из известных вам способов.

Но как добавить эти значения в новые записи о заказанных чески делать вам что-либо не требуется. ADO.NET выполнит всю необходимую работу, используя объект DataRelation. По умолчанию объект DataRelation каска дирует изменения через объект DataSet. Если в объекте DataSet есть DataRelation, определяющий отношение между объектами DataTable Orders и Order Details, после передачи новых записей о заказах в БД объект сразу же каскадирует новое значение автоинкремента в объект DataTable с информацией о заказанных товарах (рис. 11-5).

ГЛАВА Сложные случаи обновления данных Orders ' 10268 GRQSR 8 7/30/ 12001 GROSR Order Details 10268 10 99. 10268 72 27. 10785 31, 10785 10 7. 12000 18. 12000 67 24 14. 12001 22. 12001 21. Рис. Каскадирование в иерархии значений автоинкремента в дочернюю Как только у новых записей объекта Order Details обновятся значе ния поля OrderlD, эти записи можно будет успешно передать в БД. Благодаря функциональности объекта каскадирование новых значений авто инкремента в иерархии — простейшая часть процесса передачи изменений.

Изоляция и повторная интеграция изменений Предположим, вы создаете многоуровневое приложение с пользовательским dows-интерфейсом, обращающееся к БД через Web-сервис. Web-сервис возвращает клиентам объекты с запрошенной ими информацией. Клиентское прило жение позволяет изменять содержимое DataSet. После того как пользователь внес необходимые изменения и щелкнул кнопку, клиент передаст изменения в БД через Web-сервис. Простейший способ сделать это — передать Web-сервису объект DataSet и заставить сервис с помощью объектов передать изменения БД.

426 Часть Ml Автономная работа с данными: объект DataSet модели Для максимальной следует оптимизировать использование полосы Чем меньше данных передается между кли ентским приложением и быстрее выполняется приложение.

объема возвращаемых Web-сервисом, — достаточно простая и интуитивно задача. Если ваши таблицы содержат тысячи и миллионы учтите это в архитектуре сервиса и не содержи мое таблиц полностью;

в случае приложения ощутимо снизится. Убедитесь также, что Web-сервис возвращает только необхо димые клиентскому приложению данные, А что если ограничить объем передаваемых клиентским приложени ем Web-сервису? Для передачи в БД можно передать Web-сервису объект Но если назначение сервиса — вносить изменения в БД, передавать ему все содержимое DataSet не нужно. Если DataSet включает несколько сотен запи и пользователь изменил только пару из передача всего содержимого DalaSel Web-сервису окажется весьма Как можно усовершенство вать данный процесс?

Экономное использование полосы пропускания при помощи метода GetChanges Объекты DataSet и DataTable предоставляют перегруженный метод GetChanges.

Метод новый по ствующий оригинальному объекту, но только измененные записи из оригинального DataSet.

Если для передачи изменений в БД вы вызываете Web-сервис, можно значи тельно повысить производительность, предварительно вызвав метод DataSet.Get и вернув изменения Web-сервису.

Примечание Говоря, что метод возвращает новый объект DataSet, содержащий только измененные я слегка прайду. Новый объект возвращаемый методом по структуре соответствует оригинальному DataSet и содержит ные вами измененные записи. Однако, кроме того, он может содержать и другие необходимые для поддержания ссылочной целостности. Предположим, например, что у вас есть DataSet, включаю щий объекты DataTable с информацию о клиентах и заказах, а также объект определяющий отношение между двумя DataTable, Если вы добавите несколько заказов и затем вызовете метод Data возвращенный объект будет содержать эти новые запи си, а также соответствующие записи из таблицы DataTable с информа цией о клиентах. В противном случае новый объект DataSet нарушил бы ограничение, с DataRelation.

Я уже рассказывал, как при обновлении управле ние параллелизмом на основе полей типа В многоуровневом прило жении этот процесс несколько усложняется. Предположим, в логике обновления объекта используемого Web-сервисом, реализовано оптимистичное ГЛАВА Сложные случаи обновления данных управление параллелизмом на основе полей типа timestamp. Вы также новые значения timestamp одним из описанных ранее методов. Но что произош ло с этими значениями?

Вы выбрали новые значения и поместили их в объект DataSet это — отдельный от используемого в клиентском приложении объект DataSet. Как поместить новые значения timestamp в объект DataSet клиен тского приложения?

Можно просто заставить Web-сервис возвращать новый объект DataSet, содер жащий те же данные, что и клиентское приложение. Но хотя этот способ и га рантирует данных клиентского приложения, он не обеспечивает оптимального использования полосы пропускания.

Более экономное решение (рис. 11-6) — возвращать полученный им объект DataSet, включив в него полученные time stamp, Однако таким образом решается лишь часть проблемы. у клиентского приложения есть новые значения timestamp, но как DataSet, воз Web-сервисом, с объектом клиентского с оригинальными ALFKI Maria Anders ANATR значение> Moreno the Thomas DataSet с измененными TSCo!

ftUHd Alfreds ANATR АпаТгщШо ANTON Antonio AROUT Mew К Объект и переданный Web-сервису Alfreds Futterkiste Рис. 11-6. Возвращение новых сгенерированных сервером значений при помощи Web-сервиса 428 Часть III работа с данными: объект DataSet модели Метод Merge объекта DataSet Простейшее решение воспользоваться методом объекта DataSet. Он по зволяет объединить содержимое DataSet с содержимым другого объек та DataSet, DataTable или массива объектов Принцип данной функцио нальности проиллюстрирован на рис.

Основной DataTable А Основной объект после вызова метода A А (null) Рис. Пример результатов вызова метода Merge объекта DataSet Каждый объект DataSet изначально содержит по одному объекту DataTable с одинаковым именем. После того как вы метод основного объекта DataSet и передадите ему второй объект основной DataSet будет содер жать все оригинальные столбцы, а также столбцы второго DataSet. Кроме основной DataSet будет содержать записи из второго объекта DataSet.

Тем не менее этот пример не особенно полезен. Очень мало разработчиков станут совмещать два объекта DataSet, включающих объекты DataTable с одина ковым именем и совершенно разными структурами. На рис. 11-8 показан более типичный пример. Два объекта DataSet содержат объекты DataTable с идентичными структурами. Поле первичного ключа обоих DataTable ID.

После вызова метода Merge основной объект DataSet содержит дополнитель ный столбец из второго DataSet. Содержимое основного DataSet также изменит ся в результате вызова метода Merge. В предыдущем примере данный метод про сто добавил имеющиеся записи в основной DataSet. Здесь он выполняет слияние содержимого двух объектов DataSet.

Отличие данного примера заключается в первичном ключе. Если при слиянии данных ADO.NET встретит две записи с одинаковым значением первичного клю ча, он совместит их содержимое в одну запись. В этом примере у обоих объектов DataSet есть записи со значениями первичного ключа 1 и 2. Кроме того, у каждо го DataSet есть не имеющая аналога в другом объекте.

ГЛАВА Сложные случаи обновления данных DataTable Второй DataTable 1 Main Main 1 2nd 2nd 2 Main Main 2 2nd 3 Main Main 3 2nd 2nd после вызова метода Merge ID-Key A 1 2nd Main 2nd 2 2nd Main 2nd 3 Main 4 2nd 2nd Рис. 11-8. Типичный пример результатов вызова метода Merge объекта DataSet Заметьте: в результатах вызова метода Merge приоритет имеют данные объек та DataSet, добавляемые в содержимое другого объекта. Когда произво дит слияние значения столбца Column А основного заменяются значениями одноименного столбца из второго объекта DataSet. На рис. это записи со значениями поля ID, равными 1 и 2.

Теперь, когда вы стали лучше разбираться в принципах работы метода Merge, подведем итог.

Наше клиентское приложение получает от Web-сервиса информацию о кли ентах. Пользователь изменяет содержимое возвращенного DataSet.

приложение с помощью метода создает новый DataSet, только измененные записи, и передает этот меньший по размеру объект Web сервису.

Web-сервис передает изменения в БД, используя в логике обновления значе ния для оптимистичного управления параллелизмом. Кроме Web сервис выбирает из измененных записей новые значения timestamp (одним из описанных ранее в этой главе способов) и помещает их в свой объект DataSet.

По завершении данной операции Web-сервис возвращает DataSet с включенны ми в него новыми значениями timestamp. Клиентское приложение получает этот объект и производит слияние его данных с содержимым основного DataSet (см.

фрагмент кода ниже), чтобы интегрировать новые значения timestamp в основ ной объект DataSet (рис.

430 Часть III Автономная работа с данными: объект DataSet модели Основной объект DataSet с измененными Contact т ANATR ANTON Antonio Moreno New # Объект DataSet с новыми значениями timestamp, Со трапу TSCol New Contact AR OUT Around the Horn New Contact #2 <Новое основного DataSet после слияния с данными DataSet, TSCol Alfreds New ANATR AnaTrujillo значение> ANTON Antonio Moreno Antonio Moreno <0ригинальное Around Horn New # Рис. Слияние только что выбранных с содержимым имеющегося объекта DataSet Visual Basic Dim As New Dim As DataSet = Dim As DataSet = = Visual objWebService = new WebServiceClassO;

DataSet dsMain = DataSet dsChanges = dsChanges = ГЛАВА 11 Сложные случаи обновления данных Метод Merge и свойство Мы почти закончили. содержимое записей, которые вы изменили в основном объекте DataSet, вы что они содержат Тем не менее значение их свойства — Modified.

Если пользователь щелкнет в приложении кнопку для передачи изменений в объект DalaSet, возвращенный методом окажется в числе содержать записи, которые пользователь уже изменял ранее. Когда Web-сервис получит данный и передать сгенериру ет исключение, поскольку эти изменения уже есть в БД.

Мы что передали изменения в БД, но об этом не знает ADO.NET. После того как передаст изменения, ADO.NET поменяет значение свойства измененных записей с на Однако произойдет лишь в объекте DataSet Web-сервиса. В клиентского приложения ADO.NET не меняет значение свойства отредактированных поскольку объекты DataSet клиентского приложения и Web-сервиса никак не связаны, Слияние DataSet, Web-сервисом, с содержимым основного DataSet, не поможет нам изменить значение свойства RowState изме ненных записей. Нам надо задать этому свойству значение Unmodified, однако ADO.NET не сделает это за нас автоматически. Тем не менее, поскольку мы что успешно передали изменения из основного можно вернуть свойству RowState измененных записей вызвав после вызова метода Merge метод объекта DataSet:

Visual Dim objWebService As New Dim As DataSet = Dim dsChanges As DataSet = GetChangesO = Visual C# objWebService = DataSet dsMain = ObjWebService.GetDataSetO;

DataSet dsChanges = dsMain.GetChangesO;

dsChanges = dsMain.

Метод Merge и значения автоинкремента Слегка модифицируем наш пример. Теперь мы будем с информацией не о клиентах, а о заказах. В данном примере полем первичного ключа таблицы БД, содержащей информацию о заказах, будет с автоинкрементом — по анало гии с таблицей Orders БД 432 Часть Автономная работа с данными: объект DataSet Как и в предыдущем примере, клиентское приложение взаимодействует с БД Web-сервиса. Предположим, пользователь выбрал два заказа, разме щенных имеющимся клиентом, два новых его заказа и затем передал эти новые заказы в БД. Вы уже знаете, как с помощью метода передать Web сервису только измененные записи (рис.

DataSet с оригинальными EmployeelD x.

6 09/22/ 10692 4 10/31/ объект DataSet с 10692 ALFKI 4 10/31/ -t,..

Объект возвращенный и переданный ID -1 02/24/ Рис. 11-10. Передача Web-сервису только новых записей о заказах при помощи метода GetChanges Вы также умеете выбирать в объект используемый Web-сервисом для передачи новых заказов в БД, новые значения автоинкремента. Эти значения вклю чены в DataSet. возвращаемый Web-сервисом после передачи измененных зака зов в БД. Однако если объединить его содержимое с содержимым основного DataSet, мы не получим желаемого результата.

Результат аналогичен показанному на рис. Основной объект DataSet содержит оригинальные, ожидающие передачи в БД, заказы с зна чениями полей OrderlD, а также все заказы, возвращенные Web-сервисом, с ре альными значениями полей OrderlD? В чем дело?

Данный метод Merge сравнивает записи различных объектов DataSet с исполь зованием первичного ключа объекта Записи, которые нам требуется объединить метода Merge, имеют разные значения полей первичного ключа. Метод Merge не понимает, что заказы в объекте DataSet, возвращенном Web сервисом, соответствуют записям о заказах в основном объекте ожидаю ГЛАВА 11 Сложные случаи обновления данных щем передачи в БД. В результате метод просто добавляет записи из DataSet \Yeb в основной Понятно, что нам требуется совсем иное и здесь пригодится пара решений.

оригинальными данными 10643 ALFKI 6 09/22/ ALFKt I объект с новыми 12000 ALFKt 12001 ALFKI Объект и переданный Web-сервису :

10643 ALFKI 4 10/31/ -\ -2 ALFKI 12000 ALFKt 12001 ALFKI Рис. Результат слияния объекта возвращенного Web-сервисом, с основным объектом DataSet Очистка перед вызовом метода Merge к результатам вызова метода Merge на рис. Нашей целью было объединить содержимое двух объектов DataSet и добавить новые значения стол бца в существующий объект DataSet. Результаты не так уж и далеки от тре буемых. Новые значения OrderlD добавлены, но также есть и копии новых зака зов с значениями OrderlD.

Для достижения желаемого результата можно удалить новые заказы из объек та DataSet перед тем, как объединить его содержимое с DataSet, возвращаемым Web 434 Часть III Автономная работа с данными: объект DataSet модели сервисом. Следующий фрагмент с помощью метода ет значение свойства которых — и удаляет их из объекта DataSet выполнить слияние его с данными объекта Web-сервисом.

Basic Dim objWebService As New WebServiceClassO Dim As DataSet = Dim As DataSet = = objWebService. SubmitChanges{dsChanges) слиянием содержимого основного DataSet и данных DataSet, Web-сервисом, удаляем из основного DataSet ожидающие передачи в БД Dim As DataTable = Dim As For Each in Visual C# objWebService = new WebServiceClassO;

DataSet dsMain = DataSet dsChanges = dsMain.GetChangesO;

dsChanges содержимого основного DataSet и данных DataSet, Web-сервисом, удаляем из основного DataSet заказы, ожидающие передачи в 6Д DataTable tbl = reach row in Просмотр содержимого основного DataSet и удаление новых заказов, ожида ющих в БД. — слишком однако оно определенно устраняет проблему.

первичного ключа в объектах DataSet Есть еще одно решение, однако оно не для людей, слабо разбирающихся и коде.

Вы уже понимаете принципы работы метода Merge и знаете, почему в аналогичных рассматриваемому примеру, он объединяет содержимое объектов DataSet не так, как требуется. У которые нужно зна поля первичного ключа.

ГЛАВА случаи обновления данных Что, если изменить первичный ключ? Перед слиянием содержимого двух объек тов DataSet можно выбрать в каждом объекте новый столбец первич ного ключа. Если у записей объектов значения нового поля первичного ключа при слиянии содержимого объектов DataSet мы получим нужный результат. После слияния можно сделать столбцом первич ключа который был им Добавим в оригинальный объект новый столбец с именем (рис. Это просто столбец, который не соответствует какому-либо столбцу БД. Его назначение — помочь нам метод Merge выдать нужный результат.

Основной DataSet с новыми ID 10643 6 09/22/1997 10692 4 -1 ALFKI 7 ALFKI Т 02/24/2002 - Объект DataSet. возвращенный Web-сервисом и содержащий me ALFKI 7 02/24/2002 12001 ALFKI 7 Основной объект DataSet после слияния с возвращенным 10643 ALFKI 10692 ALFKI 4 10/31/1997 12000 ALFKI 7 02/24/2002 12001 ALFKI 7 02/24/2002 Рис. 11-12. Добавление псевдоключа (PseudoKey) в объект DataSet для переноса значений Как программно первичный ключ таблиц непосредственно перед слиянием и затем сделать первичным ключом являвшийся им но? не особенно но и не особенно сложное;

436 Часть III работа с данными: DataSet модели Basic As New Dim As DataSet = Dim dsChanges As DataSet = dsChanges = столбцом первичного ключа обеих таблиц столбец Pseudokey Dim As DataTable = Dim As = New DataColumnO Dim As DataTable = = New DataColumnO столбцом первичного ключа основной таблицы являвшийся им = pkOriginal Visual C# WebServiceClass objWebService = new DataSet dsMain = DataSet dsChanges = dsChanges = столбцом первичного ключа обеих таблиц столбец Pseudokey DataTable tblMain = pkOriginal = = new DataTable tblChanges = = new столбцом первичного ключа основной таблицы им = pkOriginal;

Но что насчет содержимого столбца Как генерировать для него уникальные если он не соответствует ни одному из столбцов БД. Мож но воспользоваться... другим столбцом с автоинкрементом.

ГЛАВА 11 Сложные обновления данных Выбор способа решения проблем с объединением содержимого Лично мне не нравится ни одно из описанных решений. Они по меньшей мере связанное со сменой первичного ключа, может оказаться очень сложным, особенно если у рассматриваемой таблицы есть в объекте дочерние объекты Если бы от меня выбрать решение, я бы предпочел то, которое перед вызовом метода Merge удаляет из оригинального объекта записи, ожидающие вставки в БД. Однако я вынужден признать:

даже эти неэлегантные варианты — значительный таг вперед по сравнению с ADO.

где вообще не удавалось эту проблему.

На прилагаемом к книге компакт-диске записано приложение демонстрирующее саму проблему и различные способы ее решения. Приложение изменяет содержимое иерархии с информацией о заказах и товарах.

По аналогии с БД полем первичного ключа таблицы, содержащей све дения о заказанных товарах, является поле с автоинкрементом, Приложение позволяет передавать изменения из объекта DataSet в БД как с помощью объектов так и посредством метода (рис. Чтобы гарантировать успешность обновления, код сначала передает новые заказы и только потом — заказанные товары, сначала удаляет заказанные товары, а затем — заказы. Передавать изменения из DataSet можно непосредственно в БД;

кроме того, можно изолировать их в новом объекте DataSet, вызвав метод и затем объединить с переданными изменениями. Кроме того, доба вив в каждый DataTable псевдоключ, вы сумеете выполнить слияние содер жимого двух объектов DataSet, как рассказывалось ранее.

mple to o* that Ihe Ihe 8 Merge" of - raws Ihe «и го»!

tor to use a has an added column each the you option the 5 on merge той DataSet Рис. Приложение ComplexHierarchy Последнее решение — вообще исключить проблему, спроектировав структуру данных так, чтобы перед передачей новых записей в БД удалось узнать значения первичного ключа. больше использует в своих БД уникальные идентификаторы (globally unique identified, И хотя вы, воз можно, не станете использовать столбец с в качестве первичного ключа 438 Часть III Автономная работа с данными: объект DataSet модели стоит его объекта и тем самым избежать проблемы, Изящная обработка неудачных попыток обновления Модель предназначена для работы с отсоединенными данными. Изме няя содержимое объекта пользователь не редактирует напрямую содер жимое БД. Вместо этого ADO.NET изменения объектов Затем эти передаются в БД средствами объекта DataAdapter.

Тем не менее данных БД после того, как пользо ватель выполнит исходный запрос, нельзя. Логика, по, которой объекты DataAdapter передают изменения в БД, основана на оптимистичном управлении параллелиз мом. Как и у оптимистов реальной жизни, в иногда все делается со всем не так, как требовалось.

Рассмотрим следующую ситуацию. Пользователь А выбрал из БД в объект DataSet о клиентах и затем запись о конкретном клиенте. В период времени между тем, как пользователь А выбрал информацию из БД и попытался передать сделанные изменения, пользователь Б изменил в БД эту же запись. В результате попытка пользователя А передать изменения завершится неудачей.

разработчики расценивают такое поведение как рассадник проблем, по давайте рассмотрим и альтернативный сценарий. Что если бы пользо А успешно передал Он переписал бы Б, даже не зная об этом.

Опережающее планирование конфликтов Если вы приложение, работающее с отсоединенными данными и изменения с оптимистического ления параллелизмом, существует вероятность того, что попытки обновления завершатся неудачей. Вам следует провести опережающее планирование и опре делить, как приложение будет на такие ситуации.

вы изменили содержимое 10 записей и пытаетесь передать сделанные изменения. Объект DataAdapter успешно передал изменения первых запи сей, однако передача изменений четвертой записи завершилась ошибкой. Как отреагирует это ваше приложение? Должен ли объект DataAdapter передать оставшиеся отложенные изменения в БД?

Свойство объекта DataAdapter Свойство ContinueUpdateOnError позволяет управлять как объект DataAdapter на неудачные попытки обновления. Значение данного свойства по умол чанию — False, т. е. при неудачной попытке обновления объект DataAdapter гене исключение Если необходимо, чтобы объект Data Adapter передал [ия в БД, задайте свойству ContinueUpdateOnError значение True.

Если значение свойства ContinueUpdateOnError True и одна из попыток об завершается DataAdapter не генерирует исключение. В этом ГЛАВА 11 Сложные случаи обновления данных случае он свойству HasErrors объекта DataRow а RowError того объекта — об ошибке параллелизма.

После вызова метода просмотрите значение свойства HasErrors объекта или и определите, все ли успеш но. Конечно, если DataSet или содержали ошибки до вызова такая недействительна, Некоторым разработчикам передать изменения в транзакции и их. если только все они переданы успешно. В такой ситуации дуется не изменять значение свойства по умолчанию, False, и откатить транзакцию, если метод сгенерирует исключение.

Информирование пользователя об ошибках Важно уведомить о обновления. Некоторые ком поненты упрощают записей, изменений которых заверши лась неудачно. Так, если вы отредактировали группу записей Windows-элементе управления и передача этих изменений ошибкой, в заго ловке неудачно обновленных записей предупреждающий значок. Если подвести к нему мышку, отобразится всплывающая с текстом сообще ния об ошибке.

Следующий код позволяет вывести информацию о неудачно обновленных за писях в случаях, когда обычный, пе связанный с данными элемент управления Visual Basic Try = If Dim As String = following not updated & Dim row As DataRow Each row In If strMessage & End If Next Else updates End If Catch ex As Exception following exception & vbCrLf & _ End Try 440 Часть III Автономная работа с данными: объект DataSet модели Visual C# { = if.

string = "The following not updated + in if strMessage += + + else updates ex) I following exception occurred: + Некоторые требовательные хотят знать не только о возникно вении ошибки, но и о причине ее возникновения, а также о том, как успешно передать обновление БД. Для начала посмотрим, как определить, почему пере дача обновления завершилась неудачно, если бы для каждой неудачной попытки обновления удавалось извлечь показанную на рис.

Вы пытались передать следующие данные ABCDE Inc.

Оригинальные данные записи: ABCDE $100. Текущие данные в БД:

Рис. 11-14. Вывод информации о неудачных попытках обновления Вы уже умеете обращаться с помощью объекта DataRow к текущему и ориги нальному содержимому записи.

Visual Basic Dim As = row As DataRow = Balance & Balance Due: & _ ГЛАВА Сложные случаи обновления данных Visual C# DataTable = Balance Due: + Balance Но как выбрать текущее содержимое нужных записей из БД?

Выборка текущего содержимого конфликтующих записей Для текущего содержимого конфликтующих записей надо событием объекта DataAdapter. Следующий фрагмент кода определя ет, столкнулся ли объект DataAdapter с ошибками при передаче обновления, Если ошибка — исключение параллелизма, код с помощью параметризованного запроса выберет текущее содержимое соответствующей записи БД.

Чтобы сделать фрагмент кратким и удобочитаемым, я опустил определения объектов DataAdapter и Объект — это содер жащий параметризованный запрос для получения содержимого записи БД. Пара метр данного запроса — поле первичного ключа БД. В объекте DataTable таким полем является ID. Код использует в качестве параметра поля ID запи си, изменений которой завершилась неудачно, подставляет его в запрос, выполняет последний и помещает его результаты в отдельный объект DataSet.

Есть также вероятность того, что обновляемая запись уже не существует в БД.

Код определяет, вернул ли запрос запись, и соответствующим образом задает зна чение свойства объекта DataRow.

Visual Basic Private Sub sender As Object, e As If = AndAlso Is Then = Dim As Integer = If = 1 Then = "The has been modified by another user."

Else = "The row no longer exists in the database."

End If = End If End Sub Visual C# private void sender, OleDbRowUpdatedEventArgs e) if == 442 Часть III Автономная работа с данными: DataSet модели == { = int = if == 1} = "The has been modified by another else = "The no longer exists in the = Примечание Если не изменить значение свойства Status на Continue или при неудачной передаче обновления DataAdapter автомати чески задаст свойству текст сообщения об ошибке.

Этот фрагмент кода выбирает содержимое соответствующих БД в отдельный объект чтобы изучить их данные после передачи обнов лений. Теперь вы знаете все необходимое для создания диалогового анало гичного показанному на рис, Если сначала не Сообщить пользователям о причине неудачной передачи обновления полезно, однако для передачи изменений вряд ли захотят повторно обращаться к БД и вносить в ее содержимое тот же самые новые данные. Как упростить этот про цесс средствами объектной модели Давайте еще раз вспомним, почему в первый раз попытку обновления пере дать не удалось. Данные, используемые объектом DataAdapter для контроля парал уже не соответствуют текущему содержимому записи БД. При контроле содержимого в логике обновления DataAdapter использует оригинальное содер жимое объекта Пока мы не обновим оригинальные значения мы не сможем передать в БД хранящиеся в этом опъекте изменения, сколько бы раз мы не вызывали метод Если нам удастся изменить оригинальное содержимое объекта DataRow без потери его текущего содержимого, то хранящиеся в нем изменения будут пере даны успешно, при что содержимое соответствующей записи БД не ус пело опять измениться.

Импорт «новых оригинальных» значений с помощью метода В одном из предыдущих разделов о слиянии содержимого двух объектов DataSet с помощью метода Обнаружив две записи с оди наковыми значениями первичного ключа, метод Merge объединяет их содержи мое в одну запись. Кроме того, он позволяет указать, что необходимо сохранить изменения, внесенные в содержимое объекта DataSet.

В предыдущем фрагменте кода мы перехватывали событие объек та DataAdapter. Если обновление текущей записи код вы бирал текущее содержимое соответствующей записи БД в новый объект DataSet ГЛАВА Сложные случаи данных под названием Предполагая, что имя основного объекта DataSet — MainDataSet. с помощью следующей строки кода слияние содержимое MainDataSet с содержимым Visual Basic True) Visual C# Код не изменяет текущее содержимое в основном объекте DataSet. Он только перезаписывает оригинальные значения объектов DataRow из конфликтующего объекта DataSet.

Имея в основном объекте DataSet «новые оригинальные» данные, можно по пробовать передать оставшиеся отложенные изменения в БД. Если после того как мы получили содержимое соответствующих записей в событии оно не менялось, передача обновлений завершится успешно.

Помните: получить информацию о записях, которых больше нет в БД, нельзя, Если попытка обновления завершилась ошибкой из-за отсутствия в БД обновить оригинальные значения записи с описанного выше подхода невозможно. Чтобы повторно добавить текущее содержимое объекта DataRow в БД, удалите из набора Rows объекта DataTable и затем снова добавьте его. При этом значение свойства объекта DataRow изменится на Added. Когда вы будете передавать изменение с помощью объекта он попытается вставить запись в БД.

Приложение Conflicts На прилагаемом к книге компакт-диске записан пример приложения под нием Conflicts (рис. выявление, анализ и устранение проблем с передачей обновлений. Приложение выбирает данные из БД объект DataSet и изменяет их. Я спроектировал приложение так. чтобы оно изменяло часть записей непосредственно в имитируя работу другого пользователя. вы щелкнете кнопку для в БД изменений, в DataSet, часть попыток передачи закончится ошибкой из-за действий «другого Приложение обрабатывает эти неудачные попытки и выбирает текущее содер жимое соответствующих записей БД в другой объект DataSet. При перемещении по основному объекту DataSet приложение отображает состояние записи, а так же ее текущее и оригинальное содержимое. Если передача записи завершилась ошибкой, отображается также и текущее содержимое соответствую щей записи БД. Прежде, чем вы попытаетесь повторно передать изменения в БД, приложение заставит вас разрешить имеющиеся конфликты обновления.

Часть III Автономная работа с данными: объект DataSel модели appicalion to and the buttons Ihe You к The Modified By Value Value Рис. Приложение Conflicts Работа с распределенными транзакциями В главе 4 рассказывалось об объекте Transaction ADO.NET. Он сгруппи ровать результаты нескольких по одному соединению, в одну единицу работы, Допустим, ваша БД содержит банковскую информацию. Перевести деньги со сберегательного на текущий счет можно, выполнив два следующих запроса:

UPDATE Savings SET = WHERE = UPDATE Checking SET = BalanceDue + WHERE AccountID = Чтобы гарантированно объединить два изменения в единицу работы, которую можно подтвердить или перед выполнением запросов следует создать новый объект Transaction. Если возникнет ошибка или один из запросов не най дет нужной записи, транзакцию необходимо откатить. В противном случае изме нения, сделанные будут подтверждены. На рис. показано, как поместить оба изменения в одну транзакцию впрочем, вам уже известно).

Но что, если сведения о сберегательном и текущем счетах хранятся в разных БД?

Можно открыть соединения с обеими БД и на каждом из них начать транзак цию. Затем, если при снятии средств со сберегательного счета или внесении средств на текущий счет произойдет ошибка, обе транзакции следует откатить. Если же все нормально, их надо подтвердить. Все просто. Данная ситуация проиллюстрирована на рис.

ГЛАВА Сложные случаи обновления данных КОМПОНЕНТ Транзакция средств со на Рис. 11-16. Помещение нескольких изменений содержимого БД в одну компонент Транзакция БД Внес | средств текущих сберегательного на счета Рис. 11-17. Помещение изменений содержимого разных БД в отдельные транзакции Предположим, вы подтвердили снятие средств со сберегательного но прежде, чем вы успели подтвердить внесение средств на текущий счет, разорва лось подключение к сети. БД выявит разорванное соединение и автоматически откатит транзакцию. Получится, что деньги со сберегательного счета вы но на текущий счет их не внесли.

Хм. Возможно, использование отдельных транзакций на каждом соединении — не слишком надежное решение проблемы. Чтобы повысить надежность приложение должно плотнее взаимодействовать с БД для координирования тран закций и решения подобных описанной выше, Необходима транзакция, позволяющая открывать несколько подключений к БД. объединяю щая несколько называется распределенной transaction), Примечание Обработке транзакций и СОМ+ посвящено множество книг.

нятно, что полностью раскрыть эту тему на нескольких страницах не возможно, Я расскажу лишь об основах обработки транзакций и рабо ты с компонентами СОМ+. Подробнее об обработке транзакций — в книге Philip A. Bernstein и Eric Newcomer «Principles of Transaction Morgan 1997.

Координаторы транзакций и диспетчеры ресурсов В распределенной транзакции участвуют два основных компонента — диспетчер ресурсов и координатор Диспетчер ресурсов выполняет нужную вам работу — изменяет содержимое БД или считывает сообщение из очереди — и затем сообщает о результатах ее выполнения.

446 Часть HI Автономная работа с данными: объект DataSet модели Координатор транзакций взаимодействует с диспетчерами ванными в транзакции, и управляет текущим состоянием этой транзакции. Если один из диспетчеров уведомляет координатора о возникновении ошибки, тот оповещает другие диспетчеры о необходимости отменить результаты выполняемой в контексте данной транзакции. Если все диспетчеры сообщают об успешном выполнении возложенных на них задач, координатор указывает им подтвердить результаты выполненной работы.

Двухфазная фиксация Все диспетчеры ресурсов реализуют технологию под двухфазная фик сация (two-phase Координатор транзакций указывает каждому диспет черу подготовить выполненные во время транзакции. Это первый этап процесса. В действительности диспетчеры еще не подтвердили сделанные изме нения. Они лишь готовятся сделать это.

Когда все диспетчеры сообщат о готовности подтвердить изменения, коорди натор укажет им выполнить такое подтверждение. Тем не менее, если хотя бы один диспетчер сообщит о невозможности подготовить изменения, координатор ука жет всем диспетчерам отменить результаты работы, проделанной во время тран закции.

Поясним все это на примере реальной ситуации. Координатор указывает дис петчерам подготовиться к подтверждению изменений, и те сообщают, что гото вы и могут сделать это. Координатор передал диспетчеру, отвечающему за сбере гательный счет, сообщение о необходимости подтвердить изменения, но до того как координатор успел обратиться к диспетчеру текущего произошла фа тальная ошибка (например, прекратилась подача электроэнергии). Что дальше?

Итог транзакции зависит от координатора транзакций и диспетчера ресурсов.

Оба компонента должны хранить сведения о состоянии транзакции. Координа тор должен произвести восстановление после сбоя, определить, что транзакция по-прежнему не завершена, и обратиться к соответствующим диспетчерам ресурсов, чтобы завершить ее.

У диспетчера ресурсов должна возможность подтвердить изменения, подготовленные им на первом этапе процесса фиксации. Предположим, прекра щение подачи электроэнергии, поставившее завершение транзакции под сомне ние, затронуло компьютер с БД текущих счетов. СУБД требуется произвести вос становление после сбоя, определить, что транзакция по-прежнему не завершена, предоставить возможность подтвердить изменения и обратиться к координатору, чтобы завершить транзакцию.

Как вы понимаете, на создание координатора транзакций или диспетчера ре сурсов нужно много времени и усилий.

Распределенные транзакции в Framework Первоначально Microsoft реализовала координатор транзакций и сопутствующие технологии для ОС Windows как пакет дополнений Windows NT 4. Теперь данная функциональность интегрирована в Windows и относится к службе Component Services.

ГЛАВА Сложные случаи обновления данных Прелесть данной в том, что для функций управ ления транзакциями, предоставляемых службой Component Services, придется написать лишь небольшой объем кода. Вы пишете код, как обычно, и затем ука зываете Component Services подтвердить или отменить Служба берет труд управления распределенной на себя. На рис. при веден пример взаимодействия с несколькими БД в распределенной транзакции при помощи Component Services.

Координатор распределенных Снятие Внесение средств со сберегательного на текущий счет счета Рис. 11-18. Взаимодействие с несколькими БД в распределенной транзакции при помощи Component Services Поддержка распределенных транзакций в БД Для использования распределенных транзакций в БД вашей СУБД нужен диспет чер способный взаимодействовать с координатором транзакций из состава Component Services.

Диспетчеры ресурсов некоторых СУБД (например, SQL Server и Oracle) под держивают такую функциональность, однако диспетчерами многих других СУБД (Access. DBASE и т. д.) она не поддерживается. Перед планированием архитекту ры приложения на основе распределенных транзакций убедитесь, что диспетчер ресурсов вашей БД реализует подтверждение и способен взаимодей ствовать со службой Component Services.

На самом деле область применения распределенных транзакций ограни чивается исключительно БД. служба Microsoft Message Queuing ляет отправлять и получать сообщения в составе распределенных транзакций.

Создание собственных компонентов Реализация поддержки распределенных транзакций в — отно сительно простая задача. Во-первых, убедитесь, что ваш проект — библиотека классов. Служба Component Services рассчитана на работу без пользовательского интерфейса. Вы ведь не хотите, чтобы время ожидания транзакции истекло из-за того, что на сервере выводятся невидимые для пользователей модальные диало говые окна? Во-вторых, убедитесь, что проект содержит ссылку на пространство 448 Часть III Автономная работа с данными: объект DataSet модели имен и что класс наследует от класса Теперь можно писать код, использующий транзакции.

Для регистрации библиотек в службе Component Services вам потребуются две утилиты командной строки — Sn.exe и Первая находится во вложен ной папке Bin каталога Framework SDK и генерирует для вашей библиотеки строгое имя (strong name). Вторая, расположенная в каталоге Framework, регистрирует вашу библиотеку в Component Services.

В коде следует использовать имя приложения, соответствующее библиотеке.

и сослаться на строгое имя файла:

Visual Basic Imports приложения строгое сборки Visual C# имя строгое имя сборки [assembly:

Подробнее об этих параметрах и использовании утилит командной строки — в документации Атрибут Далеко не все объекты, выполняющиеся под управлением Component Services, могут работать с распределенными транзакциями, Так, бизнес-компоненты приложения зачастую передают под управление Component Services для оптимального исполь зования пула соединений.

Атрибут TransactionOption вашего класса определяет, будет ли класс вать в В следующем фрагменте кода экземпляры класса всегда выпол няются в транзакции. Если экземпляр создан в контексте транзакции, он будет в ней участвовать. В противном случае для экземпляра создается собственная тран закция.

Visual Basic Public Class clsDistributedTransaction Inherits ServicedComponent ГЛАВА Сложные случаи обновления данных Visual C# public class :

Атрибуту можно задать любое значение из одноименного перечисления (табл.

Таблица 11-2. Элементы перечисления Константа Значение Описание Disabled 0 Значение по умолчанию. Компонент не участвует в тран закции NotSupported 1 Компонент выполняется вне контекста транзакции, та существует Supported 2 Компонент участвует в транзакции, если та существует, но наличия транзакции не требует 3 Компонент участвует в транзакции, если та существует.

Если же транзакции не существует, для компонента созда ется собственная транзакция 4 Компонент всегда создается в новой транзакции Регистрация ADO.NET-соединения в транзакции Одно из преимуществ модели Component Services — то, что специально писать код, регистрирующий ADO.NET-соединение в транзакции Component Services, не требуется. Вам даже не надо использовать Выполнение всей работы возлагается на службу Component Services. Если ваш код выполняется в контексте транзакции. Component Services автоматически зарегистрирует ваше соединение в данной транзакции.

Подтверждение или отмена результатов работы Все, что осталось сделать, — добавить в компонент логику, определяющую, следу ет ли подтвердить или отменить результаты выполненной вами работы. Если вы обнаружите, что запросы не возвращают требуемых результатов или код перехва тывает неожиданное исключение и не способен обработать его, можно выпол нить одну строку кода и откатить всю работу, проделанную в контексте данной транзакции по разным соединениям. Вам следует лишь вызвать метод объекта ContextUtil, доступного вашему классу. Для подтверждения результатов работы применяется метод Visual Basic Public Sub Try If blnSuccess Then 450 Часть ill Автономная работа с данными: объект DataSet модели Else End If Catch ex As Exception ContextUtil. SetAbortO Throw New exception: & End Try Sub Visual public void MyTransactionalMethodO try queries.

if (blnSuccess) else ContextUtil. SetAbortO;

> catch (Exception ex) throw ContextUtil new exception: + } ;

Примечание Объект ContextUtil содержит сведения о контексте Подроб нее о возможностях данного объекта — в документации Помните: вызов метода в конце процедуры не обязательно озна чает, что служба Component Services подтвердит в контексте тран закции работу. Такой вызов — лишь первый этап процесса двухфазной фиксации.

Если хотя бы один из участвующих в транзакции компонентов вызовет метод координатор укажет диспетчерам ресурсов всех компонентов результаты работы, выполненной в контексте транзакции.

Здесь можно провести аналогию с церемонией бракосочетания — она носит характер, пока вы не скажете: Если ваш партнер в последний момент откажется, свадьба не состоится.

Упрощенная работа с распределенными транзакциями Разработчики, создававшие компоненты на основе предыдущих версий техноло гии Component Services (например, Microsoft Transaction Server), помнят методы SetComplete и SetAbort. Кроме того, есть новый способ сообщить транзакций о необходимости сделанные в ходе транзакции изме ГЛАВА Сложные случаи обновления данных нужно ли подтвердить или откатить результаты работы, выпол ненной в ходе очень просто: если не возникло неожиданной ошиб изменения Чтобы упростить данный процесс, задайте про цедуре Когда задан метода, Component Services предполагает, что если метод не сгенерирует необработанное исключе транзакцию следует подтвердить.

если в коде используется атрибут AutoComplete и вы перехватили исключение, для сообщения о необходимости откатить транзакцию нужно сге новое или повторно сгенерировать старое исключение.

Задать AutoComplete методов класса можно способом, показанным ниже:

Visual Basic Public Sub Visual C# public void ( Примечание Если вы изменяете содержимое БД средствами командных за просов или хранимых процедур, что нужное изменение осу ществлено. Помните: запрос, изменивший ноль не генерирует ошибки, DistributedTransaction На прилагаемом к книге компакт-диске записан работающий пример программы (на Visual Basic и Visual демонстрирующей возможности распреде ленных транзакций. Серверный компонент перемещает денежные средства меж ду и сберегательным счетами, каждый из которых связан с отдельным объектом Connection ADO.NET.

В действительности серверный компонент изменяет дебетовое сальдо обоих счетов с помощью отдельных дочерних классов. Кроме того, этот компонент вклю чает методы, позволяющие прервать даже после как оба дочер них класса завершили свою работу. Клиентское приложение (рис. получа ет дебетовое сальдо обоих счетов после каждого перевода денежных средств.

Данное приложение позволяет убедиться, что изменения, сделанные в контексте отмененной не вносятся в БД.

452 Часть III Автономная работа с данными: объект DataSet модели Г* Checking Balance: Г Savings To Го Рис. 11-19. Клиентское приложение Прочие преимущества использования служб Component Services Прочие преимущества — пул соединений, пул объектов и централизованное уп равление бизнес-логикой — напрямую связаны с выполнением бизнес-объектов под управлением Component Services. Подробнее об этом — в документации MSDN и документации Component Services. Кроме во вложенной папке Component каталога Framework SDK записаны использу ющие данную службу.

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

Тем не менее предоставляемые ADO.NET, позволяют успешно справ ляться с такими ситуациями. Вооружившись полученными в этой главе ми, вы сможете решать возникающие проблемы обновления средствами ADO.NET.

Вопросы, которые стоит задавать почаще Вопрос. Как мастер Data Adapter Configuration Wizard и объект CommandBuilder обработают ситуацию, в которой требуется обновить содержимое записи после передачи изменений? Похоже, что автоматически здесь сработает лишь часть функций, этими утилитами.

Ответ. Если БД допускает это, мастер добавит запросы, которые немедленно обновят отображаемые на экране данные после передачи новой или изменения существующей записи. БД типа Oracle и Access, в отличие от БД SQL Server и не поддерживают пакетные запросы, возвращающие записи. Если генерировать ГЛАВА Сложные случаи обновления данных запросы, обновляющие отображаемое содержимое записей, не требуется, щелк ните кнопку Advanced Options и снимите соответствующий флажок.

Объект не генерирует запросов, обновляющих отображаемые данные, независимо от используемой СУБД.

Вопрос. На моей БД определены каскадные ограничения ссылочной целостно сти. Когда я удаляю существующего клиента, БД автоматически удаляет размещен ные этим клиентом заказы. Однако если удалять клиентов и заказы в объекте то передача обновлений в БД заканчивается ошибкой. Тем не менее при просмотре содержимое БД выглядит так, как если бы изменения были переданы успешно. Что происходит?

Ответ. Если в объекте есть объект определяющий отноше ние между объектами Orders и Order Details. ADO.NET автоматически помечает связанные дочерние записи как удаленные. Некоторые БД позволяют определять каскадные ограничения ссылочной целостности, функционирующие аналогичным образом. Таким образом, когда вы передаете отложенное удаление заказа, БД удалит запись сам заказ и связанные с ним дочерние записи.

Однако ADO.NET не узнает, что БД каскадировала изменение. В объекте DataSet ADO.NET дочерние записи по-прежнему будут помечены как ожидающие удале ния. Если вы передадите в БД отложенные изменения из дочернего объекта Data Table, попытается удалить записи, которые уже были удалены в соот ветствии с определенными в БД правилами каскадирования.

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

Вопрос. Объект Recordset ADO предоставляет метод возвращающий ин формацию о записях, обновление которых завершилось с ошибкой. Есть ли эк вивалент данного метода в модели ADO.NET?

Ответ. Прямого эквивалента метода в модели ADO.NET нет. Однако дос тичь аналогичной функциональности можно, заполнив новый объект DataSet и объединив его содержимое с содержимым существующего объекта DataSet при помощи метода Merge. Подробнее об этом — в этой главе.

Работа с XML-данными этой главе речь пойдет об имеющихся в функциях для работы с дан ными в формате в частности о функциях для чтения и записи таких данных.

Но вас: не надейтесь основательно изучить прочитав эту гла ву. Чтобы стать профи, вам придется прочитать не одну книгу, посвященную му языку.

Однако хотя настоящая глава не претендует на то, чтоб называться справоч ником по и типа XSLT и XPath, она демонстри рует их мощь. Думаю, вы обладаете базовыми навыками работы с XML, XSLT и XParh, хотя это и совсем не обязательно. Даже если вы не можете выговорить языка — XML, прочитав эту вы проникнетесь симпатией к нему и, возмож но, захотите изучить его более полно.

Рассказывая о работе с XML-данными при помощи я опишу некоторые XML-функции Microsoft SQL Server 2000. Как я уже говорил, этому по священы целые книги, например «Программирование для Microsoft SQL Server с использованием XML» Грэма («Русская 2002). Я же пред что вы имеете представление об XML-функциях SQL Server 2000 и хоти те обращаться к ним с помощью ADO.NET. Однако даже если вы ничего не знаете о них, воспользуйтесь приведенными фрагментами кода и оцените предоставля емые ими возможности.

Мост через пропасть между XML и доступом к данным В настоящее время XML — одна из активно используемых разработчиками технологий. Недавно я побывал в крупном книжном магазине и поразился коли честву изданий, посвященных Это была если не первая, то вторая по под борке книг тематика в отделе компьютерной литературы, Работа с XML-данными В XML-документе можно хранить данные о группе клиентов и размещенных ими заказах. XML-документ чем-то напоминает объекты DataSet ADO.NET и Recordset ADO. Каждый из них позволяет хранить множество порций данных в четко опре деленной структуре.

Во времена Microsoft Visual и Microsoft Active Server (ASP) разработ чики обычно либо XML, либо технологию доступа к данными и очень редко — и то, и другое. Почему? Технологии плохо взаимодействовали между со бой. данные из XML-документа в объект модели ADO и об ратно — непростая В ADO реализованы функции, позволявшие сохранять содержимое Recordset в XML-формате и эти XML-данные обратно в объект Тем не менее, изучив содержимое XML-файла, создаваемого ADO, вы что он вклю чает ряд тегов схемы. Управлять схемой создаваемого ADO, Кроме того, ADO не способен считывать универсальные XML-документы. Загру зить данные в объект Recordset с помощью его метода Open можно, только если XML-документ сгенерирован с использованием схемы ожидае мой ADO.

Одна из главных задач команды разработчиков ADO.NET — навести мост че рез пропасть между XML и доступом к чтобы облегчить интеграцию этих двух технологий. Загружать данные из XML-документа в объект DataSet модели ADO.NET и обратно очень просто. При работе с SQL Server 2000 можно получать данные из БД в XML-формате и помещать их в XML-документ или объект DataSet ADO.NET. Кроме того, предусмотрены средства для синхронизации объекта DataSet и XML-документа, чтобы изменения одного отражались также и в другом.

Теперь я расскажу об этом более подробно.

Запись и считывание XML-данных Прежде всего поговорим о способах чтения и записи XML-данных с помощью объекта DataSet, Методы объекта DataSet для работы с XML-данными Объект DataSet обладает рядом методов, позволяющих просматривать его содер жимое в XML-представлении, а также загружать XML-данные в DataSet.

Метод Простейший из XML-методов, GetXml, позволяет извлекать содержимое объекта DataSet в строку. Этот метод даже слишком прост. Он не перегружен и не прини мает параметров.

На рис. показано содержимое объекта DataSet в окне консоли.

Вот фрагмент кода, генерирующий и выводящий данный объект DataSet:

Visual Basic Dim ds As New 456 Часть III Автономная работа с данными: объект DataSet модели Public Sub As DataSet) Dim As String = & _ "Initial strSQL = "SELECT OrderDate FROM Orders & _ "WHERE CustomerlD Dim daOrders, As OleDbDataAdapter = New strConn) strSQL = "SELECT OrderlD, UnitPrice & _ "FROM [Order Details] WHERE OrderlD IN (SELECT & _ "OrderlD FROM Orders WHERE CustomerlD = daDetails = New strConn) "Orders") "Order Details") End Sub Visual C# DataSet ds = new Console.

static void FillMyDataSet(DataSet ds) i string strConn, strSQL;

strConn = + StrSQL "SELECT CustomerlD, OrderDate Orders + CustomerlD = OleDbDataAdapter daOrders, daDetails;

daOrders = new strConn);

strSQL = "SELECT ProductID, Quantity, UnitPrice + "FROM [Order Details] WHERE OrderlD IN (SELECT " + "OrderlD Orders WHERE CustomerlD = daDetails = new strConn);

"Order Методы и Как уже говорилось, метод чересчур прост. Возможности метода гораздо шире. Он перегружен и позволяет записывать содержимое объекта DataSet в файл или объект, реализующий интерфейсы или Кроме того, метод WriteXml принимает значения из перечисления предоставляя более широкие возможности управления выводом. С помощью этих значений ужается указать, нужно ли включать вывод информацию схемы об объекте DataSet и следует ли записывать содержимое DataSet в формате ГЛАВА 12 Работа с XML-данными Я предпочитаю просматривать содержимое XML-документов в Microsoft Internet Explorer, а не в окне консоли, поскольку Explorer лучше форматирует данные. Следующий фрагмент кода с помощью метода записывает со держимое объекта DataSet файл (включая схемы) и открывает этот файл в Internet Explorer (рис. 12-2), Здесь используется процедура показанная ранее. Кроме ему необходима ссылка на библиотеку Microsoft Internet Controls — добавить ее можно на вкладке СОМ диалогового окна Add Reference.

Рис. 12-1. Просмотр содержимого объекта DataSet в XML-представлении с помощью метода С • • - i Рис. 12-2. Просмотр содержимого и схемы объекта DataSet в Internet Explorer 458 Часть Автономная работа с данными: DataSet модели Visual Basic Dim ds As New Dim As String = Public Sub strPathToXml As String) Dim ie As New = Sub Visual C# DataSet ds new string strPathToXml = static void strPathToXml) • ie = new object objEmpty = ref objEmpty, ref objEmpty, objEmpty, objEmpty);

= true;

' Примечание Метод Navigate класса InternetExplorer обладает множеством нео бязательных параметров. Язык С* не поддерживает необязательные па раметры при обращениях к составляющим модели СОМ. В качестве опус каемых параметров следует передать Type Missing.

У объекта DataSet есть перегруженный метод ReadXml, позволяющий загружать в DataSet данные. Можно сказать, это — инвертированный вариант метода Он позволяет считывать данные из файла или реализующий интерфей сы Stream. или Кроме того, метод ReadXml принимает чения из перечисления управляющие порядком считывания XML данных.

Методы и Объект DataSet предоставляет методы ReadXmlSchema и позволя ющие считывать и записывать только информацию схемы этого объекта. Оба метода могут работать с файлами и объектами, реализующими интерфейсы Stream, TextReader и XmlReader.

ГЛАВА Работа с XML-данными Метод способен загружать информацию схемы из документов с XML-схемами в стандарте XML Schema Definition (XSD) или XML Data Reduced (XDR), а также считывать вложенные схемы из XML-документов.

Объект DataSet также обладает методом Он аналогичен мето ду ReadXmlSchema. за исключением того, что второй параметр — мас сив строк с именами пространств имен, элементы которых в XML-документе сле дует игнорировать, Формирование схем В главах я добавлял метаданные и информацию схемы но в код, чтобы обеспечить более высокую производительность, чем при программ ном этих сведений в период выполнения. То верно и для фор мирования XML-схем, и первый пример здесь — метод Предположим, вы с помощью метода ReadXml загружаете данные в объект и ни ни не содержат схемы. Добавить записи в объект не включающий информацию схемы, нельзя. Прежде чем добавить содержимое в объект DataSet, метод ReadXml полностью этот документ. Чем больше документ, тем сильнее отрицательное влияние на производительность в результате схе мы на основе документа.

В результате такого подхода возможна еще одна проблема. Вполне вы не получите нужную вам схему. предположит, что все типы данных — строковые, и не создаст каких-либо ограничений. Почему? Допустим, XML документ содержит список лиц и адресов в следующем формате (XML теги опущены):

Main что этот документ — небольшая выборка на основе реальных данных вашей БД. В других записях БД у контактного лица может быть два адре са или адрес за пределами США с другим форматом почтового индекса.

если вы указываете сформировать схему на основе не содержащего информации схемы, предпримет все, чтобы создать соот ветствующую схему. Следовательно, информацию схемы следует предоставлять всегда, когда это возможно. Ее непосредственно код или XML-документ с нужной информацией схемы. Это позволит повысить дительность приложения и избежать ненужных проблем.

460 Часть Автономная работа с данными: объект DataSet модели Свойства ADO.NET, влияющие на схему XML-документа Формат задается еще одним способом. Как гласит пословица, дьявол кроется в деталях. Изучите на ряс. и Они содержат одни и те же сведения, но их схемы различаются.

вы попытаетесь загрузить данные в объект DataSet, уже включающий информацию схемы, ADO.NET проигнорирует не соответствующие схе ме DataSet. Таким образом, важно сравнивать схему объекта DataSet и схему за гружаемых данных.

> > С D > G us > P if rt>roducUD > < > > nits I 2 псе Рис. 12-3. XML-документ со списком заказов клиента /.

1 997- Рис. XML-документ с таким же списком, но в другом формате Управлять форматом, который ADO.NET использует при чтении и записи XML документов в процессе работы с объектом DataSet, можно посредством свойств объектов, входящих в состав DataSet. Фактически документы на рис. 12-3 и 12- созданы на основе одного объекта DataSet. Я просто изменил значения некото рых свойств.

Имена элементов и атрибутов Заметьте: имена элементов в двух документах В качестве имен со ответствующих элементов или ADO.NET использует свойство объектов. Имя корневого элемента определяется свойством объекта DataSet. Свойство объекта и свойство объекта ГЛАВА Работа с DataColumn аналогичным образом определяют имена элементов и атрибутов, таблицам и столбцам, Элементы или атрибуты: что выбрать?

В обоих документах также различается формат представления данных о заказах и входящих в их состав товарах. На рис. эта информация хранится в элементах, а на рис. — в атрибутах.

Управляют этим поведением при помощи свойства ColumnMappings объекта DataColumn. Значение свойства ColumnMappings по умолчанию — Element. Если нужно хранить данные столбца в атрибутах, а не в элементах, задайте этому свой ству значение Attribute. Кроме того, чтобы исключить содержимое столбца их XML следует задать свойству ColumnMappings значение Вложенные реляционные данные На рис. 12-3 сведения о составе заказов расположены в конце документа, а на рис. 12-4 эти сведения вложены в элементы, соответствующие заказам.

ют вложенностью реляционных данных с помощью свойства Nested объекта Relation. Значение этого свойства по умолчанию — False, формат представления данных соответствует показанному на рис. 3. Если задать свойству Nested зна чение True, сформирует вложенную структуру (рис.

Пространства имен и префиксы Объекты DataTable и DataColumn обладают свойствами Namespace и Prefix, значение которых по умолчанию — пустая строка. На рис. свойству Namespace каждого объекта задано значение http://unvw.microsqft.com/MyNamespace, а свой ству Prefix — значение Кэширование изменений и XML-документы Вскоре после того, как в ADO появилась возможность считывать и объекты Recordset в XML-формате, у меня состоялась беседа с несколькими раз которые использовали объект Recordset в качестве посредника для получения данных из БД и записи их в XML-файл. Разработчики изменяли содер жимое и что для передачи изменений в БД достаточно просто загрузить данные обратно в объект Recordset модели ADO. Однако этот способ не работал, и вот почему.

В главах 6 и 10 рассказывалось, как объект модели ADO.NET хранит текущее и оригинальное содержимое чтобы удавалось передавать изме нения в БД Когда вы изменяете содержимое атрибута или элемента XML-документа, оригинальное значение этого атрибута или элемента в документе не сохраняет ся. Если содержимое измененного XML-документа в объект DataSet, ADO.NET не сможет определить изменены ли какие-либо записи, не говоря уж о том, как именно они изменены.

Фактически, если изменить содержимое объекта DataSet и, воспользовавшись методом сохранить результат в XML-документ при помощи показанно го ранее когда, изменения будут утеряны. По умолчанию метод WriteXml сохра няет в XML-документ только текущее содержимое записей.

462 Часть III Автономная работа с данными: объект DataSet модели XML-документы формата diffgram Как уже при вызове методу передавать значения из перечисления Один из элементов этого перечисления — Если передать его методу WriteXml, текущее и оригинальное содержимое объекта DataSet в XML-документ формата diffgram. На рис. 2-5 по казан пример такого документа. Позже этот документ можно будет загрузить об ратно в объект DataSet и передать отложенные изменения в БД посредством объек тов,. Рис. 12-5. Содержимое объекта записанное в XML-документ формата diffgram Следующий фрагмент кода генерирует XML-документ, показанный на рис. 12-5.

Он изменяет объект DataSet (изменяет одну запись, удаляет вторую и добавляет третью) и затем выводит его содержимое в виде XML-документа формата diffgram.

Изучив представленный документ, вы увидите, как именно изменения, вносимые кодом в содержимое соответствуют записям XML-документа формата diffgram. Код использует показанную ранее процедуру и поэтому ему требуется ссылка на библиотеку Microsoft Internet Controls.

Visual Basic Dim strConn, strSQL As String strConn = & "Initial strSQL = "SELECT TOP 3 FROM Dim da As New Dim ds As New Dim As DataTable = о первом клиенте пропускаем 12 Работа с XML-данными запись о втором клиенте = "Modified Company Name" запись о клиенте нового клиента "New Company содержимое в XML-документ формата выводим документ в Internet Explorer Dim strPathToXml As String = Visual C# string strConn, strConn = + "Initial strSQL = "SELECT TOP 3 FROM Customers";

da = new DataSet ds = new DataTable tbl = о первом клиенте пропускаем о втором клиенте = Company Name";

запись о третьем клиенте нового клиента object[] "New Company содержимое в XML-документ формата diffgram выводим документ в Internet Explorer string StrPathToXml = DataSet + = В показанных ранее фрагментах кода применяются XML-функции объекта DataSet, не слишком поражающие воображение. Если с их помощью просто содержимое объекта DataSet в файл и затем загружать эти данные обратно в тот факт, что ADO.NET сохраняет данные в XML-формате, не имеет значения.

Если вам нужно работать с содержимым объекта DataSet в XML формате, загрузите данные в объект XmlDocument. У этого объекта есть метод 464 Часть III Автономная работа с данными: объект DataSet модели позволяющий загружать содержимое XML-файла, поэтому сначала при помощи метода следует создать а затем воспользоваться мето дом и загрузить его. На первый взгляд все выглядит великолеп но, однако в результате вы получаете два объекта с одинаковыми данными, синх ронизировать которые не так-то просто. Если вы изменяете содержимое одного из них, вам потребуется найти и изменить соответствующие данные в другом объекте. Вот задача!

Использование объекта XmlDataDocument Простое решение данной проблемы — воспользоваться объектом XmlDataDocument.

Можно считать, что XmlDataDocument — это объект умеющий вза имодействовать с объектом DataSet. Класс XmlDataDocument является производ ным от и поэтому объект XmlDataDocument такие же функции, как и XmlDocument.

Объект XmlDataDocument обладает двумя ключевыми возможностями. Он по зволяет легко загружать содержимое DataSet в XmlDocument и наоборот. XmlData Document также синхронизирует себя с объектом DataSet. Данные DataSet доступны и через объект XmlDataDocument. Кроме того, изменения содержимого одного объекта отражаются на содержимом другого.

Работа с объектом DataSet как с XML-документом Если вы программируете на XML и привыкли обращаться к данным с помощью XML-документов, воспользуйтесь объектом XmlDataDocument и обращайтесь к содержимому объекта посредством XML-интерфейсов.

Например, создайте объект XmlDataDocument, синхронизированный с объек том DataSet, и просматривайте содержимое DataSet при помощи запросов Следующий фрагмент кода создает XmlDataDocument, синхронизированный с DataSet и содержащий информацию о заказах клиента и их составе. Код с помо щью запросов XPath получает из объекта XmlDataDocument данные о заказе. Здесь используется приводившаяся ранее процедура и поэтому в код следует добавить ссылку на пространство имен Visual Basic в начало модуля кода следующую строку Imports Dim As New Dim xmlDataDoc As New Dim nodOrder, As Dim As String = = = & = & = & ГЛАВА с XML-данными = For Each In & = & & "Quantity = & & = & _ Next nodDetail Visual в кода следующую строку using DataSet ds = new xmlDataDoc = new XmlNode string strXPathQuery;

= nodOrder = = Console. = = + strXPathQuery = (XmlNode nodDetail in { = = + nodDetail.

= + Console.

Кэширование обновлений XML-документа Как я уже говорил, способ, которым менты поддерживают сведения о не позволяет передавать изменения в БД. Объект реализует такую синхронизируя XML-доку мент и объект DataSet. Когда вы изменяете содержимое XML-документа, XmlData Document изменяет соответствующие данные объекта DataSet. Благодаря этому, DataSet содержит всю информацию, необходимую для передачи изменения в БД.

Следующий фрагмент кода демонстрирует эту функциональность. Он с помо щью запроса находит заказ и изменяет содержимое дочернего узла с 466 Часть Автономная работа с данными: объект DataSet модели клиента который разместил данный заказ. Затем код просматривает содержимое соответствующего объекта что в объекте есть и текущее, и поля Для передачи изменения в БД можно воспользоваться объектом и логикой.

Прежде чем изменить содержимое XML-документа, код задаст свойству объекта DataSet значение False. Без этой строчки кода ADO.NET гене рировала бы исключение. Если вы с помощью объекта XmlDataDocument попыта етесь изменить содержимое объекта DataSet, значение EnforceConstraints которого — True, система выдаст Basic в начало модуля кода следующую строку Imports Dim ds As New Dim tblOrders As OataTable = = New Dim As New Dim As Dim strXPathQuery As String = nodOrder = False = = True Dim row As DataRow = = & & "Current CustomerlD = & "Original CustomerlD = & _ Visual C# в начало модуля кода строку using DataSet ds = new DataSetO;

FillMyDataSet(ds);

OataTable tblOrders = new XmlDataDocument = new XmlNode nodOrder, nodDetail;

string strXPathQuery nodOrder = = false;

Работа с XML-данными = = true;

row = = + = CustomerlD = + Как видно, изменения теперь в объекте DataSet. По они пере даются в БД с помощью объекта Получение XML-данных из БД SQL Server Все больше и больше разработчиков хотят обращаться с результатами запросов к БД как с XML-документами. Чтобы предоставить им такую возможность, в SQL Server 2000 реализована поддержка запросов, возвращающих данные в формате Можно выбрать данные в объект DataSet и затем средствами объекта обращаться к ним как к менту, однако нагрузка на систему от таких операций выше, чем от простого получения данных в XML формате.

Использование запросов XML Б SQL Server 2000 реализован необязательный раздел запросов позволяющий получать результаты запросов в XML-формате. Рассмотрим пару примеров, использующих этот раздел, и обсудим, как загрузить результаты запроса в объект DataSet или Выполнение запроса XML в SQL Server Query Analyzer Простейший способ выполнить такой запрос и просмотреть его результаты воспользоваться SQL Server Query Analyzer. Рассмотрим элементарный запрос, возвращающий значения полей CustomerlD и первых двух запи сей таблицы Customers.

SELECT TOP 2 CompanyName FROM Customers Дополним его разделом:

" FOR XML AUTO, ELEMENTS" Блок FOR XML указывает SQL Server вернуть результаты запроса в XML-форма те;

AUTO — задать представляющим записи набора результатов, имя используемой в запросе таблицы: ELEMENTS — сохранить значения отдельных полей в виде элементов. По умолчанию SQL Server возвращает такую информа цию в виде атрибутов, а не элементов.

В меню Query выберите команду Results In Text и выполните запрос. Он ока жется аналогичным показанному на рис. за исключением того, что на рисунке я вручную отформатировал данные, чтобы их стало удобнее читать.

Конечно, выборка XML-данных в SQL Server Query Analyzer не очень-то полез на. Посмотрим, как ADO.NET загрузить эти данные в более доступные объекты.

468 Часть III Автономная работа с данными: объект DataSet модели SELECT ТОТ Customer ID SSL AUTO, v П ;

• с Рис. 12-6. Выполнение запроса FOR XML в SQL Server Query Analyzer Объект OleDbCommand не предназначен для получения результатов запросов FOR XML, но вы можете воспользоваться объектом Он относится к поставщику SQL Server Client Data Provider и предоставляет метод Execute который возвращает объект XmlReader, позволяющий обращаться к результатам запроса.

Загрузка результатов запроса в объект DataSet Модель ADO.NET упрощает результатов запроса в объект DataSet. Восполь зуйтесь методом и загрузите данные из объекта в как показано ниже:

Visual Basic в начало модуля кода строку Imports Imports Dim strConn, As String strConn = "Data & _ "Initial = "SELECT TOP 2 CompanyName FROM Customers " & "FOR XML AUTO, ELEMENTS" Dim As New SqlConnection(strConn) Dim As New SqlCommand(strSQL, Dim rdr As XmlReader = Dim ds As New Dim strPathToXml As String = ГЛАВА 12 XML-данными Visual в начало модуля кода следующую строку using using string strConn = "Data + "Initial StrSQL = "SELECT TOP 2 FROM Customers + "FOR XML AUTO, ELEMENTS";

= new SqlConnection(strConn);

= new = DataSet ds = new CloseC);

string = возможно, что при вызове методу в качестве второго параметра передается константа Fragment. Результаты запроса представ лены в формате однако не являют собой корректный документ. Еще раз взглянув на рис. вы увидите, что узла верхнего уровня нет.

Одно из требований к корректному XML-документу — наличие узла верхнего уровня. Результаты данного запроса считаются XML-фрагментом. Таким образом, в вызове метода мы указали, что информация в объекте XmlReader ~ это Загрузка результатов запроса в объект XmlDocument Если бы результаты запроса представляли собой корректный XML-документ, орга низовать загрузку данных в объект было бы просто. Достаточно вызвать метод XmlDocument Load и передать ему объект XmlReader.

Однако нам приходится дополнить объект XmlDocument узлом верхнего уров ня и затем добавлять в этот узел результаты запроса, по одному узлу за раз. как показано далее:

Basic в начало кода следующие строки Imports Imports Dim xmlDoc As New Dim As nodRoot = strConn, strSQL As String strConn = "Data & 470 Часть III Автономная работа с данными: объект DataSet модели "Initial = "SELECT TOP 2 Customers & _ "FOR AUTO, ELEMENTS" Dim As New Dim As New Dim As = Do Until ReadNode( Loop Dim As String = Visual C# в начало модуля кода следующие строки using using xmlDoc = new nodRoot;

nodRoot = string strSQL;

strConn = "Data + "Initial strSQL = "SELECT TOP 2 CustomerlD, Customers + "FOR XML AUTO, ELEMENTS";

= SqlCommand cmd = new = cmd.

cn.CloseO;

string = Поставщик данных SQL XML Data Provider Есть более простой способ из БД SQL Server — воспользо ваться поставщиком данных SQL XML Data Provider. Он не входит в состав Framework, к моменту издания книги будет доступен на Web-узлах и SQL Server. После установки этот поставщик данных можно использовать в приложениях, добавляя ссылку на пространство имен Назначение поставщика SQL XML Data Provider — упростить работу с SQL Server. Этот поставщик сильно отличается 12 Работа с от других, поскольку XML-функции SQL Server не являются обычными функция ми доступа к данным. Первая версия поставщика SQL XML Data Provider вклю чает только три объекта из состава поставщика данных (если только что-либо, настолько близкое к первой версии набора технологий, можно назвать SqlXmlCommand, SqlXmlAdapter и Рекомендую вам использовать для работы с результатами XML-запроса SQL поставщик SQL XML Data Provider, и вот почему.

Загрузка данных в объект с помощью объекта SqlXmlCommand Поместить результаты XML-запроса SQL Server в объект XmlDocument проще, использовать объект SqlXmlCommand. У SqlXmlCommand есть единственный кон структор, требующий строку подключения к БД SQL Server. Поставщик SQL XML Data Provider не обращается к БД SQL Server напрямую, но способен взаимо действовать с ней при помощи OLE DB. Следовательно, допустимо использовать ту же строку что и для объекта Как и в случае с OleDbConnection, нужный запрос задается посредством ства Затем, используя объект этот запрос выпол няется, и его результаты помещаются в объект Результаты запроса из предыдущего фрагмента кода — это XML-фрагмент, а не корректный XML-документ, поскольку узел верхнего уровня отсутствует. Объект SqlXmlCommand предоставляет свойство позволяющее добавить в резуль таты запроса узел верхнего уровня и создать корректный XML-документ. Таким образом, вместо того чтобы программно дополнять содержимое объекта XmlDocu ment узлом верхнего уровня и затем добавлять в этот узел результаты запроса по одному узлу за раз, можно воспользоваться методом Visual Basic в начало кода строки Imports Imports Dim As String & "Initial strSQL = "SELECT TOP 2 FROM Customers & "FOR XML AUTO, ELEMENTS" Dim As New = strSQL = "ROOT" Dim xmlDoc As New Dim As XmlReader = Dim As String = 472 Часть Автономная работа с данными: объект DataSet Visual C# в начало модуля кода следующие строки using using string strConn, strSQL;

strConn = + "Initial StrSQL = "SELECT TOP 2 CompanyName Customers "FOR XML AUTO, ELEMENTS";

= new = strSQL;

= "ROOT";

XmlDocument = new XmlReader rdr = string = Загрузка данных в объект DataSet с помощью объекта Таким же образом загружают и содержимое объекта в DataSet, однако поставщик SQL XML Data Provider предоставляет более простой способ ре шения данной задачи — объект Он позволяет загрузить результаты запроса FOR XML в объект DataSet, точно так же, как объект позволяет загрузить результаты обычного SQL-запроса.

Воспользуйтесь кодом для создания объекта и затем создайте объект передав его конструктору новый объект SqlXmlCommand, Далее заполните объект DataSet результатами запроса, вызвав метод Visual Basic в начало модуля кода следующие строки Dim strConn, strSQL As String strConn = & "Initial StrSQL = "SELECT TOP 2 CustomerlD, CompanyName FROM Customers "FOR XML ELEMENTS" Dim cmd As New = strSQL = "ROOT" Dim da As New Dim ds As New Работа с XML-данными Dim As String = Visual C# в модуля кода следующие строки using string strConn, + "Initial strSQL = "SELECT TOP 2 CustomerlD, FROM Customers + "FOR AUTO, = new = strSOL;

= "ROOT";

Dim da As New Dim ds As New string strPathToXml = Использование шаблонов запросов Для более четкого форматом результатов запросов поставщик SQL XML Data Provider поддерживает XML-шаблоны запросов. По сути, шаблон запро сов — это XML-документ, содержащий запросы. Когда вы выполняете запрос, вхо дящий в состав шаблона (template query;

далее — шаблонный запрос), постав щик SQL XML Data Provider объединяет XML-данные шаблона с результата ми запроса.

Показанный ниже шаблон запросов включает два запроса SELECT...FOR XML, возвращающих сведения о размещенных конкретным клиентом заказах и их составе:

Часть III Автономная работа с данными: объект DataSet модели 3 • Orders > < Order My Рис, 12-7. Результаты выполнения шаблона Собственно запрос — это XML-документ. Поставщик SQL XML Data Provider просматривает элементы, относящиеся к пространству и выполняет текст, заключенный в элементах-запросах. Прочие элементы обрабатываются как обыч ные XML-данные и именно так и отображаются в результатах.

На рис. показан XML-документ, сгенерированный при выполнении этого запроса с использованием SQL XML Provider. Как видно, корневой эле мент шаблона запросов отображается и в итоговом документе.

Выполнение шаблонных запросов с использованием объекта Чтобы указать что вы работаете с шаблонными запро сами при помощи объекта SqlXmlCommand, задайте свойству этого объекта соответствующее значение из перечисления Чтобы указать путь к файлу с запросом, задайте свойству CommandType значение Template File. Если же вы собираетесь передать реальный текст запроса, задайте свойству CommandType значение Template.

запрос можно выполнить и сохранить его результаты в виде XML-доку мента или объекта DataSet, как я показывал ранее. Следующий фрагмент кода помещает результаты запроса в объект DataSet:

Visual Basic в начало модуля кода строки Imports Dim strPathToResults As String = Dim As String = Dim strConn String = & "Data & _ "Initial & _ ГЛАВА 12 Работа с XML-данными Dim cmd As New = strPathToQuery = Dim ds As New Dim da As New Visual C# в начало модуля кода следующие строки string = strPathToQuery = string strConn = Data + "Initial cmd = new = strPathToQuery;

cmd.CommandType = DataSet ds = new DataSetO;

da = new Параметризованные шаблонные запросы В шаблонные запросы разрешается также параметры. Следующий за прос все те же данные (сведения о размещенных и их составе), но не использует явно заданное значение поля а прини мает это значение параметр:

SELECT CustomerlD, FROM Orders WHERE CustomerlD = FOR AUTO, SELECT OrderlD, FROM [Order Details] WHERE OrderlD IN (SELECT OrderlD FROM Orders WHERE CustomerlD = FOR XML AUTO, ELEMENTS 476 Часть III Автономная работа с данными: объект DataSet модели Чтобы задать значение этого параметра программно, воспользуйтесь объек том SqlXmlParameter. Создать данный объект с помощью ключевого слова New Единственный способ сделать это — вызвать метод Получив объект SqlXmlParameter, задайте перед выполнением запроса его свойствам Name и Value нужные значения:

Visual Basic Dim cmd As param As SqlXmlParameter = = = Visual C# cmd;

SqlXmlParameter param = param.Name = = "GROSR";

Работа с запросами XPath Если бы у нас был XML-документ с полным перечнем заказов из БД мы смогли бы средствами следующего запроса XPath просмотреть заказы клиен та, идентификатор которого — GROSR:

Внимательно изучив перечисление вы увидите элемент XPath. Если задать свойству объекта значение передать в свойстве запрос XPath и затем выполнить запрос, систе ма сгенерирует исключение и уведомит вас, что запрос некорректен.

В действительности БД SQL Server не что делать с запросом XPath. По ставщик SQL XML Data Provider поддерживает запросы XPath, но все, что он на самом деле делаег — это преобразует такие запросы запросы XML, И хотя вы можете интерпретировать запрос XPath и выполнить данное преобра зование, поставщику SQL XML Data Provider требуется помощь.

Добавление информации схемы Чтобы помочь SQL XML Data Provider преобразовать запрос XPath, следует указать XML-схему, определяющую используемые в запросе таблицы и столбцы БД, а также структуру результатов запроса:

?> Работа с XML-данными /> /> /> /> /> /> Схема включает записи, которые ссылаются на таблицы и столбцы, связывают данные двух таблиц и SQL XML Data Provider определить SQL-тип данных столбца Тем не менее эта схе ма демонстрирует лишь малую толику возможностей, доступных при совместном использовании файла XML-схемы и поставщика SQL XML Data Provider. Под робнее об этом — в разделе in XSD документации SQL Создав файл схемы, можно задать путь к нему свойству объекта Следующий фрагмент кода выполняет обсуждавшийся выше запрос XPath и, используя описанную схему, возвращает сведения о размещенных конк ретным клиентом заказах и их составе. Результаты запроса помещаются в объект Visual Basic в начало кода следующие строки Imports Dim As String = Dim As String = 478 Часть III Автономная работа с данными: объект DataSet модели Dim strConn As String = & _ "Data & _ "Initial & _ Dim As New = = Dim As = Dim xmlDoc As New Visual C# в начало модуля кода следующие строки using using string = string strPathToSchema = string strConn "Initial cmd = new = = = XmlReader = xmlDoc = new rdr.CloseO;

XSLT-трансформация Как уже говорилось, разработано несколько способов для задания формата XML документа, причем два XML-документа могут содержать данные, но различаться по схеме. Сопутствующая технология под названием XSLT (extensible Stylesheet Language Transformations) позволяет изменять структуру Можно считать, что XSLT-трансформация — это XML-документ с набором инструкций, описывающих порядок преобразования содержимого другого XML документа. очень удобны, когда нужно изменить структуру документа. Кроме того, XSLT-трансформация позволяет преобразовать XML в HTML.

Чтобы применить к результатам своего запроса SQL XML задайте объекта SqlXmlCommand путь к файлу этой трансформации.

Подробнее об этом — дальше.

ГЛАВА 12 Работа с XML-данными Передача обновлений Поставщик SQL XML Data Provider позволяет передавать в БД обновления, У объекта SqIXmlAdapter есть метод позволяющий передавать в БД обновле ния, хранящиеся в объекте Если вы читали главу 10, нали чие такого метода у объекта DataAdapter вас не удивит.

Тем не менее SqIXmlAdapter передает обновления иначе, чем другие объекты Многие из таких объектов (например.

и предоставляют свойства, содержащие объекты Command с логикой, необходимой для передачи обновлений в БД. Обычно эти объекты Com mand содержат несколько параметров, связанных со столбцами объекта При вызове метода Update большинство объектов DataAdapter просматривают записи конкретного объекта DataTable. Обнаружив измененную запись, DataAdapter каждый раз с помощью соответствующего объекта. Command передает отложен ное изменение в БД и затем вызывает метод Объект работает иначе. В одном из предыдущих разделов главы я говорил об XML-документах формата (рис. Вместо того чтобы искать отложенные изменения в объекте DataSet, просматривая объекты по одному за раз, SqIXmlAdapter обрабатывает имеющиеся в объекте DataSet от ложенные изменения, генерируя соответствующий XML-документ формата diffgram.

Затем поставщик SQL XML Data Provider полностью обрабатывает этот доку мент, создавая сложный пакетный запрос для одновременной передачи всех из менений в БД.

Просмотрев содержимое XML-документа формата diffgram на рис.

возможно, поймете, как создавать наборы запросов INSERT, UPDATE и DELETE для передачи отложенных изменений в БД. Без вашей помощи поставщику SQL XML Data Provider создать эти запросы не удастся.

Помните файл аннотированной XML-схемы, с помощью которого SQL XML Data Provider преобразовывал запрос XPath в SQL-запрос? Сейчас проблема дру гая, но решение то же. При работе с запросом XPath мы задаем свойству объекта путь к нашему файлу схемы. Затем воспользуемся объек том SqIXmlAdapter и передадим изменения в БД, убедившись, что для объекта файл схемы, включающий сведения о таблицах и стол бцах, соответствующих содержимому XML-документа формата diffgram.

Фактически передать обновление в БД можно средствами объекта mand, свойству которого задано значение Просто создай те XML-документ формата diffgram, вызвав метод Затем настройте объект SqlXmlCommand для использования этого документа и файла схемы, и Следующий фрагмент кода демонстрирует данную функциональность:

Visual Basic в начало модуля кода строки Imports Imports Dim As String = 4 _ 480 Часть Автономная работа с данными: DataSet модели "Data & _ "Initial & Dim As New Dim strPathToSchema As String = = strPathToSchema = = SqlXmlCommandType.XPath = "ROOT" Dim da As New Dim ds As New = = "ALFKI" Dim strPathToDiffGram As String = cmd = New = strPathToSchema = = New изменений Dim strSQL As String = "UPDATE Orders SET = & _ "WHERE = OR 10785" cmd = New = strSQL = cmd.

Visual C# в начало модуля кода следующие строки using using string strConn = + "Initial cmd = new string strPathToSchema = = strPathToSchema;

= = SqlXmlCommandType.XPath;

= "ROOT";

SqlXmlAdapter da = new DataSet ds = new DataSetO;

ГЛАВА Работа с XML-данными = "ALFKI";

string = = new = = = new FileStream(strPathToDiffGram, cmd.

изменений string strSQL = "UPDATE Orders SET = + "WHERE = OR = 10785";

cmd = new = strSQL;

= cmd. ExecuteNonQueryO;

Примечание Код также включает командный запрос для отмены изменений к БД, благодаря чему этот фрагмент кода можно выполнять многократно.

Чтобы убедиться, что передал хранящиеся в XML-доку менте формата изменения, определите точку останова перед выполнением последнего запроса, Логика обновления, используемая объектом SqlXmlCommand Прежде чем продолжить, немного поговорим о логике, генерируемой объектом SqlXmlCommand для передачи изменений в БД. Это поможет вам глубже понять преимущества и недостатки передачи обновлений с использованием SQL XML Data Provider.

Когда фрагмент кода вызвал метод для пере дачи изменений из XML-документа формата diffgram, поставщик SQL XML Data Provider сгенерировал и передал SQL Server следующий пакетный запрос:

SET ON BEGIN TRAN INT, int SET = UPDATE Orders SET WHERE ( ) AND ( ) AND ( ) ;

SELECT = = IF != 0 OR 1) SET IF > 1) RAISERROR ( Error Description: Ambiguous update, unique identifier required Transaction aborted 16, 1) ELSE IF < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty update, no rows found Transaction aborted ', 16, 1) 482 Часть III Автономная работа с данными: DataSet UPDATE Orders SET WHERE ( ) AND ( ) AND ( ) ;

SELECT = = IF != 0 OR 1) SET = IF > 1) RAISERROR С Error Description: Ambiguous update, unique identifier required Transaction aborted ', 16, 1) ELSE IF < 1) RAISERROR ( Error Description: Empty no updatable rows found Transaction aborted ', 16, 1) IF != 0) ROLLBACK ELSE COMMIT SET OFF Для начала он указывает SQL Server отменить текущую транзакцию, если та ошибку, начинает транзакцию и определяет ряд переменных для хране ния данных. Затем код выполняет первый запрос UPDATE и помещает данные в переменные, чтобы определить, есть ли ошибки и сколько записей затронул за прос. Если запрос затронул одну запись и не сгенерировал ошибку, код продол жает выполнять командные запросы и проверять, успешно ли проходит обновле ние. Когда все командные запросы выполнены, код при отсутствии ошибок под тверждает транзакцию и параметр, указывавший SQL Server откатить транзакцию в случае ошибки.

Это впечатляющий и правильно структурированный код. Он представляет со бой большой и сложный пакет запросов, который, тем не менее, минимизирует число итераций, необходимых для передачи изменений в БД и проверки успеш ности данных операций. Если вы хотите реализовать подобную функциональность в своем приложении, генерируя собственные запросы, этот пакет — великолеп ный пример для подражания.

Понимать отдельные запросы пакета вам не но при передаче обновлений с использованием поставщика SQL XML Adapter все же помните несколько правил.

• Поставщик помещает данный пакет обновлений в транзакцию и, если при хотя бы одного запроса UPDATE возникнет ошибка, полностью откатывает эту транзакцию. Таким образом, передаются либо все либо ни одного.

• При передаче изменений поставщик не выбирает из БД каких-либо данных.

По завершении передачи вы не увидите новых значений автоинкремента или значений типа timestamp.

• Если изменения передаются средствами метода по успеш ном завершении передачи изменений объект вызывает метод SqlXmlAdapter не вызывает методы только тех объектов которые указаны в файле схемы.

Простой пример с использованием ADO.NET и XML До этого момента я показывал только изолированные примеры использования XML функций ADO.NET Настала пора объединить их в одном приложении, которое продемонстрирует совместную мощь языка XML и XML-функций ADO.NET.

ГЛАВА 12 Работа с XML-данными В приложении применяются параметризованные запросы, возвращающие спи сок заказов конкретного клиента. Но прежде, чем закатить глаза, заметьте: теперь мы будем получать объем информации — данные из четырех таблиц — Customers. Orders, Order Details и Products. Таким образом, список зака зов станет четче — в нем появятся названия компаний, а также названия заказан ных товаров.

Приложение преобразует результаты запросов в XML-формат, с помощью XSLT преобразует полученные XML-данные в HTML-код и затем дит этот код в Internet Explorer (рис. 12-8).

* Order 3 Orders dart Quantity Unit Price Item 10 Malacca Item Product Boston Crab Meat Рис. 12-8. Web-страница, генерируемая приложением-примером Наш пример — это консольное запускающее экземпляр Internet Explorer. Поскольку в качестве отправной точки используется именно консоль ное приложение, пример получился не слишком шикарный, но зато его легко использовать как ресурс для работы. Например, в двух случаях очень полезно преобразовать результаты запросов в HTML-код: при создании Web-при ложении и формировании отчетов. Полагаю, это самые яркие примеры исполь зования XML-функций ADO.NET.

Помните: в работе с HTML и XSLT я, в новичок. Я создал очень про стую Web-страницу с помощью Microsoft FrontPage и затем сравнил ее структуру со структурой XML-документа, сгенерированного мной при помощи ADO.NET и содержавшего данные о моих заказах. Затем я по материалам книги, ной XSLT, попытался разобраться, как создать XSLT-трансформацию для преоб разования XML-кода в HTML-код.

Я открыто говорю о недостатке опыта:

• потому что любой программист БД, не будучи профессионалом в XML, может в достаточной степени изучить XSLT для преобразования XML в HTML;

• чтобы за то, что Web-страница получилась не очень привлекательной, 484 Часть III Автономная работа с данными: объект DataSet модели Два пути к одному конечному пункту Вообще-то я вас поскольку создал не один, а два примера. Оба они ис пользуют одинаковую и создают HTML-файлы, но по-разному генерируют XML-документ с данными из БД Norhtwind.

Первый пример, подключается к локальной БД Norhtwind SDK MSDE при помощи поставщика OLE DB Provider. Он использует стандартные запросы и помещает их в объект DataSet. Чтобы обратиться к содержимому как к и выполнить для создания HTML-страницы, первый пример создает объект связанный с объектом DataSet.

Второй пример, основан на поставщике SQL XML Data Provider и XML-шаблоне запросов, который обращается к БД но исполь зует синтаксис FOR XML. Свойству объекта во втором примере задан путь к файлу ADO.NET и XML: счастливая пара ADO.NET широкую поддержку языка Благодаря XML-функци ям ADO.NET, разработчики без труда смогут переходить от традиционных объек тов доступа к данным к XML-объектам и обратно. Объект DataSet позволяет счи тывать и записывать данные и/или информацию схемы в XML-формате. Объект XmlDataSet позволяет легко обращаться к содержимому DataSet как к XML-доку менту. Поставщик SQL XML Data Provider позволяет использовать кции SQL Server 2000 и помещать результаты запросов в формате XML в файлы, XML-документы и объекты DataSet.

Вопросы, которые стоит задавать почаще Вопрос. Объекты DataTable и позволяют довольно гибко определять структуру XML-документа, создаваемого с помощью однако мне требуются еще более широкие возможности управления. Я хочу добавить в XML-файл инструкцию по обработке, ссылающуюся на таблицу стилей XSLT. Как это сделать?

Ответ. Воспользуйтесь объектом XmlDataDocument и обращайтесь к содержимому DataSet как к Задайте объекта DataSet значение False и затем с помощью объекта XmlDataDocument определите нужную вам структуру XML-документа. В обсуждаемой ситуации годится метод XmlDataDocument. Затем вызовите метод Save и сохраните результаты в XML-файл.

Visual Basic Dim As New DataSetC) = False Dim As New Dim as String = Dim as ГЛАВА 12 Работа с XML-данными = strPI) Dim As String = Visual C# DataSet d's = = false;

xmlDoc = new string strPI = xmlPI;

xmlPI = strPathToXmlFile = Я вызываю уже имеющиеся хранимые и мне нужно получать результаты запросов в однако. Как это сделать?

Ответ. Укажите SQL XML Data Provider преобразовать резуль таты запроса с XML-формат, задав свойству ClientSideXml объекта значение True, как показано ниже:

Basic Dim strConn, As String strConn = & _ "Initial StrSQL = "EXEC FOR XML NESTED" Dim cmd As = strSQL = True = "ROOT" Dim xmlDoc As New Dim As = Visual C# string strConn, strSQL;

strConn = + "Initial strSQL = "EXEC CustOrdersOrders FOR NESTED";

cmd = new = strSQL;

= true;

486 Часть Автономная работа с данными: DataSet модели = "ROOT";

= = Подробнее о преобразовании в формат XML на стороне клиента с помощью поставщика SQL XML Data Provider — в разделе «Comparing Client-Side XML Formatting to Server-Side XML Formatting» файла справки SQL XML СОЗДАНИЕ ЭФФЕКТИВНЫХ ПРИЛОЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ Создание эффективных Windows-приложений вы уже умеете работать с различными объектами модели ADO.NET. Вам пред лагают создать объект и поместить результаты запроса в объект при помощи объекта Нет проблем. Требуется добавить объект Data Relation для перемещения между дочерними и родительскими данными двух свя занных объектов Это сможет даже ребенок. Необходимо создать логи ку для передачи изменений в БД? Легко.

И хотя все эти навыки очень важны, их все же недостаточно, чтобы создать приложение, позволяющее просматривать и редактировать содержимое БД. Вам также потребуется создать пользовательский интерфейс.

В этой главе я расскажу о том, как создавать эффективные ния, используя полученные вами знания. В первой части этой главы обсуждаются этапы создания приложения, аналогичного созданному нами в главе 2 с помощью мастера Data Form Вы узнаете, как связывание с данными экономит вре мя при разработке пользовательского приложения, а также о различ ных способах обновления и Заключительная часть главы посвяще на различным методам работы с данными двоичных объектов (binary large object, BLOB) в Быстрое создание пользовательского интерфейса при помощи связывания с данными Предположим, вам нужно создать интерфейс пользователя. Вы можете код для получения из БД данных и передачи в БД изменений, но вам также требу ется вывести эти данные на форме и предоставить пользователям возможность ГЛАВА Создание эффективных Windows-приложений взаимодействовать с ними, добавляя, изменяя и удаляя записи данных. Помимо всего прочего, работу следует сделать как можно быстрее.

Понятно, что можно написать код, считывающий содержимое объекта и выводящий соответствующие данные в элементах управления формы.

Или же код, позволяющий перемещаться по записям данных, а также добавлять, изменять и удалять записи в объекте Если бы вам требовалось создать группу приложений, реализующих эти функции и различающихся по типу обрабатываемых данных, то для каждого из них пришлось бы писать одинаковые базовые процедуры.

Пакет Windows Forms из состава Microsoft Framework включает поддерж ку связывания с данными. Связывание предоставляет функциональность, анало гичную описывавшимся ранее процедурам для вывода содержимого DataSet в различных элементах управления, и реализует функции, позволяющие пользова телю изменять это содержимое. Если вкратце, с данными упрощает и ускоряет создание приложений для работы поскольку уменьшает объем кода, необходимый для создания пользовательского интерфейса.

Примечание В действительности связывание с данными позволяет работать не только с объектами DataSet. Элементы управления можно связывать с такими структурами как объекты DataSet и DataTable, масси вы и любые другие объекты, реализующие интерфейс Эта книга посвящена ADO.NET, и поэтому основное внимание я уделю с данными структур ADO.NET. Подробнее о связывании с данными дру гих структур — в соответствующих разделах документации Frame work SDK.

Но достаточно вступительных слов. Давайте с помощью связывания с данны ми создадим простое приложение для приема заказов. Это приложение (рис.

позволяет просматривать и изменять заказы, размещенные клиентами. Процесс создания приложения разделен на несколько этапов, демонстрирующих отдель ные средства связывания с данными.

Details Quantity Рис. Приложение для приема заказов На прилагаемом к книге компакт-диске записан готовая версия данного при а также версии для каждого из рассматриваемых далее этапов. Кроме того, 490 Часть Создание эффективных приложений с использованием на диске вы найдете версии созданные как с использованием Microsoft Visual Basic так и с использованием Microsoft Visual С* Примечание Приложение рассчитано на работу с БД Инструкции по установке версии Microsoft Desktop Engine (MSDE) и баз данных, по ставляемых вместе c.NET Framework SDK, см. главе 3 этой книги. В числе прочих и БД Northwind.

Этап 1. Создание объектов и DataSet Мы создаем Windows-приложение, и поэтому начать следует с создания нового Windows-проекта на который вы предпочитаете. Назовите приложение 13. Задайте форме по заголовок Edit Orders. В при ложении я задал свойству формы значение False, а свойству — значение Это что пользователь не сможет изменить размер формы. Задавать указанным свойствам такие значения не обязательно, но мне не когда пользователи изменяют размер формы, не рассчитанной на это.

Приложение предназначено для отображения сведений о заказах конкретно го клиента, и в связи с этим нам потребуется объект для выборки информации из БД Northwind. Выберите на вкладке Data панели инструментов элемент управления QleDbDataAdapter и перетащите его на форму. Запустится мастер Data Adapter Configuration Wizard (подробнее о нем — в главе 5).

В окне Connection мастера выберите существующее соединение с БД Northwind.

Если соединений нет. щелкните New Connection и создайте новое соединение. В окне Query Type оставьте переключатель в положении по умолчанию — Use SQL Statements. Затем в окне SQL Statement введите такой SQL-оператор:

SELECT CustomerlD, Orders CustomerlD = ?

Pages:     | 1 |   ...   | 4 | 5 || 7 | 8 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.