Практическое занятие № 13
Тема: Обработка данных в отношении многие-ко-многим
Цель работы: получить практические навыки по настройке подключения к БД и обработке таблиц, связанных отношением многие-ко-многим
Ход работы
Пусть имеется БД catalog для хранения данных о книгах, читателях и учете выдачи и возврата книг. БД имеет три таблицы, связанные отношением многие-ко-многим.
Необходимо подключить БД к проекту. При этом путь к БД должен храниться в INI файле.

Указания: на панели "Источники данных" щелкните на кнопке "Добавить новый источник данных" (если панель не отображается, то выбери команду меню "Данные - Показать источники данных"). Запуститься мастер создания подключения к БД. В окне выберите значение "База данных", затем значение "Набор данных". На следующем шаге щелкните на кнопке "Создать подключение". В новом окне Вам будет предложено подключить файлы БД SQL Server. Щелкните на кнопке "Изменить" и выберите формат БД "Microsoft Access". После этого укажите нужный файл БД (путь к файлу не удаляйте, среда C# самостоятельно скопирует указанный файл в папку проекта и скорректирует путь к нему). Так как база защищена паролем, то щелкните на кнопке "Дополнительно" и в строке "Jet OLEDB: DataBase Password" укажите пароль "rpo". Для проверки соединения щелкните на кнопке "Проверить подключение". После задания всех параметров мастер предложит выбрать способ сохранения пароля к БД: не включать в строку подключения или включать в строку подключения. Выберите первый вариант для безопасности.
После задания параметров БД при переходе на следующий шаг мастера появится запрос на копирование файла БД в папку с проектом, подтвердите эту операцию.
На следующем шаге мастер предложит сохранить строку подключения в файл конфигурации приложения. Так как пароль доступа не включен в строку подключения, то можете согласиться на сохранение в файл.
На следующем шаге выберите все таблицы и щелкните "Готово".
Сразу нужно настроить поведение полей-счетчиков в таблицах. В окне "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". Отобразится структура таблиц. Выделите поле id_book в таблице
books и измените свойства: AutoIncrementSeed = 1, AutoIncrementStep = 1. Выделите поле id_reader в таблице readers и измените свойства: AutoIncrementSeed = 1, AutoIncrementStep = 1. Выделите поле id_uchet в таблице uchet и измените свойства: AutoIncrementSeed = 1, AutoIncrementStep = 1.
Отобразите на форме данные из таблиц как показано на рисунке.

Указания: для формы задайте свойства: Caption=Учет выдачи книг, Position=DesktopCenter, Borderstyle=bsSingle (нерастягиваемая форма), MiximizeBox = false (нет кнопки разворачивания окна).
Для подключения таблицы books нанесите на форму компонент DataRowView. Появится окно, в котором нужно указать подключаемую таблицу books. На форме появятся дополнительные компоненты для работы с таблицей: catalogDataSet - для доступа к подключению, booksBindingSource - для работы с набором данных, booksTableAdapter - для работы с копией таблицы БД. Названия книг будут отображать в алфавитном порядке. Для этого для компонента booksBindingSource задайте свойство Sort = nazv_book (имя поля для сортировки). Для сетки задайте свойства: AllowUserToAddRows = false (запрет добавления новых строк в сетке), AllowUserToDeleteRows = false (запрет на удаление данных в сетке без подтверждения), AutoSizeColumnsMode=Fill (ширина колонок подстраивается под размер сетки). Для настройки параметров колонок сетки откройте ее свойство Columns. Вы увидите список всех ее колонок. Колонки id_book и label сделайте невидимыми (Visible=false), для остальных колонок в свойстве HeaderText укажите названия как на рисунке, в свойстве Width задайте для колонок подходящую ширину.
Для подключения таблицы readers нанесите на форму компонент DataRowView. Появится окно, в котором нужно указать подключаемую таблицу readers. На форме появятся дополнительные компоненты для работы с таблицей: readersBindingSource - для работы с набором данных, readersTableAdapter - для работы с копией таблицы БД. Фамилии читателей будут отображать в алфавитном порядке. Для этого для компонента readersBindingSource задайте свойство Sort = fam (имя поля для сортировки). Для сетки задайте свойства: AllowUserToAddRows = false (запрет добавления новых строк в сетке), AllowUserToDeleteRows = false (запрет на удаление данных в сетке без подтверждения), AutoSizeColumnsMode=Fill (ширина колонок подстраивается под размер сетки). Для настройки параметров колонок сетки откройте ее свойство Columns. Вы увидите список всех ее колонок. Колонку id_reader сделайте невидимой (Visible=false), для остальных колонок в свойстве HeaderText укажите названия как на рисунке.
Для отображения данных об учете выдачи книг создадим связанный набор данных по таблицам readers и uchet. В панели "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". Щелкните правой кнопкой на фоне окна со структурами таблиц и выберите команду "Добавить - Table Adapter". В новом окне введите текст запроса:
//отбираем данные учета выдачи для текущей книги
//номер книги задается через параметр запроса
SELECT id_uchet, fam, imya, otch, data_in, data_out
FROM uchet INNER JOIN readers
ON uchet.id_reader = readers.id_reader
where id_book=?
В результате будет создана виртуальная таблица с методом заполнения Fiil(id_book). При вызове этого метода нужно будет в качестве параметра подставлять id_book нужной книги.
Переименуйте созданную виртуальную таблицу под именем BookUchet. Для ее подключения нанесите на форму компонент DataRowView. Появится окно, в котором нужно указать подключаемую таблицу BookUchet. На форме появятся дополнительные компоненты для работы с таблицей: bookUchetBindingSource - для работы с набором данных, bookUchetTableAdapter - для работы с копией таблицы БД. Данные будем отображать в хроническом порядке выдачи книг. Для этого для компонента bookUchetBindingSource задайте свойство Sort = data_out Desc (сортировка по дате выдачи по убыванию). Для сетки задайте свойства: AllowUserToAddRows = false (запрет добавления новых строк в сетке), AllowUserToDeleteRows = false (запрет на удаление данных в сетке без подтверждения), AutoSizeColumnsMode=Fill (ширина колонок подстраивается под размер сетки). Для настройки параметров колонок сетки откройте ее свойство Columns. Вы увидите список всех ее колонок. Колонку id_uchet сделайте невидимой (Visible=false), для остальных колонок в свойстве HeaderText укажите названия как на рисунке.
Если запустить программу на выполнение, то сетки будут пустыми, потому что в строке соединения мы не сохранили пароль к БД и подключение не установлено.
Для формы откройте событие Load. В этом событии будут находится команды Fill() для таблиц books и readers, загружающие копии таблиц в компоненты TableAdapter. Над этими командами добавьте команду подстановки пароля в строку соединения:
Properties.Settings.Default["catalogConnectionString"] += ";Jet OLEDB:Database Password=rpo";
Третья сетка должна заполняться данными о выдачи текущей книги. При выборе книги в первой сетке, в третьей сетке будем отображать информацию о всех фактах выдачи указанной книги.
Указания: для набора данных booksBindingSource в событии CurrentChanged введите код:
private void booksBindingSource_CurrentChanged(object sender, EventArgs e)
{
//заполняем набор данных в соответствии в номером текущей книги
bookUchetTableAdapter.Fill(catalogDataSet.BookUchet,
Convert.ToInt32((booksBindingSource.Current as DataRowView)["id_book"]));
}
В верхней сетке будет отмечать цветом выданные книги. В таблицы имеется поле label. Если label = 1 (книга выдана), то строка закрашивается цветом. Если label = 0 (книга возвращена), то цвет возвращается в исходный.
Указания: в сетке dataGridView1 в событии RowPrePaint введите код:
private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
//описываем переменную для сохранения исходного цвета
Color c = new Color();
//сохраняем исходный цвет сетки
c = dataGridView1.DefaultCellStyle.BackColor;
//если 7-я колонка (соответствует полю "label") равна 0 (книга возвращена)
if (dataGridView1.Rows[e.RowIndex].Cells[6].Value.ToString() == "0")//текущую строку закрашиваем исходным цветом
dataGridView1.Rows[e.RowIndex].DefaultCellStyle.BackColor = c;
//если 7-я колонка (соответствует полю "label") равна 1 (книга выдана)
else if (dataGridView1.Rows[e.RowIndex].Cells[6].Value.ToString()=="1")
//текущую строку закрашиваем светло-оранжево-розовым цветом
dataGridView1.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightSalmon;
}
Добавьте на форму два текстовых поля. Первое поле предназначено для ввода и поиска книги по ее шифру. Второе поле предназначено для ввода и поиска читателя по его шифру.
Указания: нанесите на форму компонент TextBox и в событии TextChanged введите код:
private void textBox1_TextChanged(object sender, EventArgs e)
{
//в наборе данных по таблице books находим номер записи,
//у которой шифр равен значению в первом текстовом поле (метод Find())
//затем устанавливаем указатель в наборе данных на найденный номер записи
booksBindingSource.Position=booksBindingSource.Find("shifr",textBox1.Text);
}
Нанесите на форму компонент TextBox и в событии TextChanged введите код:
private void textBox2_TextChanged(object sender, EventArgs e)
{
//в наборе данных по таблице readers находим номер записи,
//у которой шифр равен значению во втором текстовом поле (метод Find())
//затем устанавливаем указатель в наборе данных на найденный номер записи
readersBindingSource.Position=readersBindingSource.Find("shifr",textBox2.Text);
}
Реализуйте функцию учета выдачи книги. При этом, если для книги label = 1 (книга выдана), то операция не выполняется.
Указания: набор данных BookUchet построен на основе запроса по двум таблицам. Это делаем невозможным стандартное добавление записей (методом AddNew()).
Для решения проблемы добавление будем выполнять с помощью запроса INSERT.
В панели "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". В контекстном меню таблицы BookUchet выберите команду "Добавить - Запрос", в появившемся окне выберите тип запроса INSERT и в следующем окне введите текст запроса:
//в новой записи заполняем код книги, код читателя и дату выдачи
INSERT INTO uchet
(id_book, id_reader, data_out)
VALUES (?, ?, ?)
На следующем шаге в качестве имени метода укажите имя AddVydacha. В результате будет создан новый метод с параметрами AddVydacha(id_book, id_reader, data_out).
Нанесите на форму первую кнопку и для кнопки введите код:
private void button1_Click(object sender, EventArgs e)
{
//если в таблице books поле label = 1 (книга выдана)
if ((booksBindingSource.Current as DataRowView)["label"].ToString() == "1")
{
//выдаем сообщение об ошибке
MessageBox.Show("Указанная книга уже выдана",
"Операция отменена",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
//завершаем работу подпрограммы
return;
}
//для записи данных о выдаче книги в таблицу uchet
//вызываем метод AddVydacha и передаем в него параметры
//id_book из первой таблицы,
//id_reader из второй таблицы и текущую дату
bookUchetTableAdapter.AddVydacha(
Convert.ToInt32((booksBindingSource.Current as DataRowView)["id_book"]),
Convert.ToInt32((readersBindingSource.Current as DataRowView)["id_reader"]),
Convert.ToDateTime(DateTime.Today));
//обновляем данные в сетке для текущей книги
bookUchetTableAdapter.Fill(catalogDataSet.BookUchet,
Convert.ToInt32((booksBindingSource.Current as DataRowView)["id_book"]));
//в таблице book после выдачи книги ставим в поле label значение 1
(booksBindingSource.Current as DataRowView)["label"] = 1;
//сохраняем данные в первой таблице
booksBindingSource.EndEdit();
booksTableAdapter.Update(catalogDataSet.books);
}
Реализуйте функцию возврата книги. Пользователь в третьей сетке выбирает запись о выданной книге и регистрирует факт возврата. При этом в таблице uchet в поле data_in вводится текущая дата, а в поле label таблицы books ставим значение 0 (книга возвращена). Также нужно предусмотреть, что если поле data_in в таблице uchet заполнено, то вернуть книгу нельзя, так как она уже возвращена.
Указания: так как набор данных BookUchet создан по двум таблицам, то изменить ее можно только с помощью запроса UPDATE.
В панели "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". В контекстном меню таблицы BooksUchet выберите команду "Добавить - Запрос", в появившемся окне выберите тип запроса UPDATE и в следующем окне введите текст запроса:
//для текущей записи записываем дату возврата книги
UPDATE uchet
SET data_in = ?
WHERE (id_uchet = ?)
На следующем шаге в качестве имени метода укажите имя UpdVydacha. В результате будет создан новый метод с параметрами UpdVydacha(data_in, id_uchet).
Нанесите на форму вторую кнопку и для кнопки введите код:
private void button2_Click(object sender, EventArgs e)
{
//если поле data_in не пустое (книга уже возвращена)
if ((bookUchetBindingSource.Current as DataRowView)["data_in"].ToString() != "")
{
//выдаем сообщение об ошибке
MessageBox.Show("Указанный читатель уже вернул книгу",
"Операция не выполнена",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
//завершаем работу подпрограммы
return;
}
//для записи данных о возврате книги
//вызываем метод UpdVydacha и передаем в него параметры
//текущую дата и id_uchet из набора данных BookVydacha
bookUchetTableAdapter.UpdVydacha(
Convert.ToDateTime(DateTime.Today),
Convert.ToInt32((bookUchetBindingSource.Current as DataRowView)["id_uchet"]));
//обновляем данные в сетке для текущей книги
bookUchetTableAdapter.Fill(catalogDataSet.BookUchet,
Convert.ToInt32((booksBindingSource.Current as DataRowView)["id_book"]));
//в таблице books после выдачи книги ставим в поле label значение 0
(booksBindingSource.Current as DataRowView)["label"] = 0;
//сохраняем данные в первой таблице
booksBindingSource.EndEdit();
booksTableAdapter.Update(catalogDataSet.books);
}
Реализуйте функцию удаления из третьей сетке записи о выданной книге. При этом, если поле data_in пустое (книга не возвращена), то удалить запись не возможно.
Указания: так как набор данных BookUchet создан по двум таблицам, то удалить из него данные можно только с помощью запроса DELETE.
В панели "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". В контекстном меню таблицы BooksUchet выберите команду "Добавить - Запрос", в появившемся окне выберите тип запроса DELETE и в следующем окне введите текст запроса:
DELETE from uchet
where id_uchet=?
На следующем шаге в качестве имени метода укажите имя DelVydacha. В результате будет создан новый метод с параметрами DelVydacha(id_uchet).
Нанесите на форму третью кнопку и для кнопки введите код:
private void button3_Click(object sender, EventArgs e)
{
//если в таблице нет данных
//подпрограмма завершает работу
if (bookUchetBindingSource.Count == 0) return;
//если поле data_in пустое (книга не возвращена)
if ((bookUchetBindingSource.Current as DataRowView)["data_in"].ToString()=="")
{
//выдаем сообщение об ошибке
MessageBox.Show("Указанная книга не возвращена",
"Отмена удаления",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
//подпрограмма завершает работу
return;
}
//выдаем запрос на удаление записи
if (MessageBox.Show("Удалить запись о выдаче книги?",
"Подтвердите",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2)==DialogResult.Yes)
{
//вызываем метод DelVydacha и передаем в него параметр
//поле id_uchet из набора данных BookVydacha
bookUchetTableAdapter.DelVydacha(Convert.ToInt32((bookUchetBindingSource.Current as DataRowView)["id_uchet"]));
//обновляем данные в сетке для текущей книги
bookUchetTableAdapter.Fill(catalogDataSet.BookUchet,
Convert.ToInt32((booksBindingSource.Current as DataRowView)["id_book"]));
}
}