Проведите описанный ранее опыт: добавьте нового студента, введите его с экзамены (пару штук), нажмите Save, удалите вновь добавленного студента, нажмите Save. Убедитесь, что DBConcurrencyException подстерегает нас в том же месте, что и ранее. Причину сбоя следует искать в синтаксисе команд UPDATE и DELETE, которые создал построитель. Единственное, что можно (пока) предложить в алгоритме обновления с помощью DbDataAdapter, это добавить следующий блок кодов в конец обработчика adapter_RowUpdated события RowUpdated. Напомним, что этот метод обслуживает оба адаптера.
if ((e.Status == UpdateStatus.ErrorsOccurred) &&
e.Errors is DBConcurrencyException)
{
string tbl = id == "StudID" ? "Studs" : "Exams";
MessageBox.Show(e.Errors.Message + "\r\nwhile trying to " +
e.StatementType + " a row in " + tbl + " table");
e.Status = UpdateStatus.Continue;
}
Установка статуса (e.Status) параметра в значение Continue говорит адаптеру, чтобы он продолжал обновление, несмотря на ошибку. Запустите и убедитесь, что удаление студентов проходит успешно, но не проходит удаление экзаменов удаляемого студента. Попытки вносить изменения с помощью DbDataAdapter и CommandBuilder в сценарии со связанными таблицами и автоинкрементируемыми ключами похожи на прогулку по зыбкой трясине.
Воспользуемся советом Karli Watson, автора книги Beginning C# 2005 Databases. — Wiley Publishing, Inc., 2006. Он работает с типизированным набором данных, который автоматически генерирует студия. Это, как минимум, 1500 строк кода (объем кода зависит от количества таблиц), процесс осознания которого (а мы не автоматы, чтобы опустить этот шаг) потребует заметных усилий и времени.
Karli Watson описывает следующий прием. В момент удаления (точнее, до удаления) каждой строки главной таблицы дается предупреждение пользователю о том, что зависимые строки подчиненной таблицы также будут удалены. Если пользователь подтверждает удаление, то с помощью метода GetChildRows определяется множество связанных строк (оно имеет тип DataRow[ ]), и они по очереди удаляются из DataSet методом Delete класса DataRow. После этого факт удаления закрепляется методом AcceptChanges класса DataSet. Все это происходит в обработчике нажатия кнопки Delete. Так как удаление не всегда инициируется кнопками, то мы будем обрабатывать другое событие, а именно, UserDeletingRow класса DataGridView. Добавьте обработчик этого события для объекта gridStud и введите в него такой код.
void gridStud_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
DataRowView rv = bsStuds.Current as DataRowView;
if (rv == null)
return;
DataRow row = rv.Row;
DataRow[] exams = row.GetChildRows(ds.Relations[0].RelationName);
if (exams.Length > 0)
{
if (MessageBox.Show(
"Deleting this student you will delete his (her) exams. Continue?",
"Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
{
foreach (DataRow r in exams)
r.Delete();
bsStuds.EndEdit();
ds.AcceptChanges();
}
}
}
Вновь проверьте результат: добавьте студента, введите его с экзамены (пару штук), нажмите Save, удалите вновь добавленного студента, нажмите Save. Убедитесь, что DBConcurrencyException не возникает, но радоваться рано. Усложните опыт: добавьте студента, введите его с экзамены (три штуки), нажмите Save, удалите средний из трех экзаменов, затем удалите вновь добавленного студента, нажмите Save. Убедитесь, что DBConcurrencyException тут, как тут.
Пока мы работаем с обобщенным (нетипизированным) DataSet, не все советы Karli Watson можно проверить на практике. Но и в нашем случае следует найти способ внесения любых изменений в базу данных. Ниже мы приведем решение этой проблемы, но для этого придется отказаться от таких объектов, как DbDataAdapter и CommandBuilder.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.