Практическое занятие № 12
Тема: Обработка БД с помощью запросов
Цель работы: получить практические навыки по использованию запросов на обновление, выполнению расчетов с помощью запросов
Ход работы
Пусть в СУБД Access спроектирована база данных (скачать). База защищена паролем "rpo" и имеет две таблицы.
Таблица "students", состоит из полей:
| Имя поля | Тип | Размер |
| id_stud | Счетчик | Ключевое |
| fam | Текстовый | 15 |
| imya | Текстовый | 15 |
| otch | Текстовый | 15 |
| finance | Текстовый | 10 (Госзаказ, Контракт) |
| datar | Дата/время | |
| srbal | Числовой | Одинарной точности с плавающей точкой. Значение по умолчанию - 0 |
| foto | Поле OLE | |
| id_grup | Числовой | Внешний ключ для связи с таблицей groups |
Таблица "groups", состоит из полей:
|
Имя поля |
Тип |
Размер |
| id_grup | Счетчик | Ключевое |
| grupa |
Текстовый |
30 |
В программе на C# выполните подключение базы данных
Указания: на панели "Источники данных" щелкните на кнопке "Добавить новый источник данных" (если панель не отображается, то выбери команду меню "Данные - Показать источники данных"). Запуститься мастер создания подключения к БД. В окне выберите значение "База данных", затем значение "Набор данных". На следующем шаге щелкните на кнопке "Создать подключение". В новом окне Вам будет предложено подключить файлы БД SQL Server. Щелкните на кнопке "Изменить" и выберите формат БД "Microsoft Access". После этого укажите нужный файл БД (путь к файлу не удаляйте, среда C# самостоятельно скопирует указанный файл в папку проекта и скорректирует путь к нему). Так как база защищена паролем, то щелкните на кнопке "Дополнительно" и в строке "Jet OLEDB: DataBase Password" укажите пароль "rpo". Для проверки соединения щелкните на кнопке "Проверить подключение". После задания всех параметров мастер предложит выбрать способ сохранения пароля к БД: не включать в строку подключения или включать в строку подключения. Выберите первый вариант для безопасности.
После задания параметров БД при переходе на следующий шаг мастера появится запрос на копирование файла БД в папку с проектом, подтвердите эту операцию.
На следующем шаге мастер предложит сохранить строку подключения в файл конфигурации приложения. Так как пароль доступа не включен в строку подключения, то можете согласиться на сохранение в файл.
На следующем шаге выберите обе таблицы students и groups, с которыми планируете работать в проекте, и щелкните "Готово".
Сразу нужно настроить поведение полей-счетчиков в таблицах. В окне "Источники данных" щелкните на кнопке "Изменить набор данных в конструкторе". Отобразится структура таблиц. Выделите поле id_stud в таблице students и измените свойства: AutoIncrementSeed = 1, AutoIncrementStep = 1. Выделите поле id_grup в таблице groups и измените свойства: AutoIncrementSeed = 1, AutoIncrementStep = 1.
Настройка корректного удаления новых записей
Если добавить новую запись в таблицу и сразу попытаться ее удалить, то часто появляется сообщение об ошибке, связанное с тем, что мастер подключения БД неправильно формирует команды на добавление, обновление и удаление данных. Для решения этой проблемы в окне со структурой таблиц щелкните правой кнопкой на строке "studentsTableAdapter" таблицы students, выберите команду "Настроить". В новом окне щелкните на кнопке "Дополнительно", в следующем окне выключите флажок "Использовать оптимистическую блокировку", щелкните ОК и Готово.
Повторите эти действия для таблицы groups.
Создайте программу для работы с данными таблицы. При этом форма должна иметь вид:

Указания: для формы задайте свойства: Caption=Работа с запросами, Position=DesktopCenter, Borderstyle=bsSingle (нерастягиваемая форма), MiximizeBox = false (нет кнопки разворачивания окна).
Для подключения таблицы groups нанесите на форму компонент DataRowView. Появится окно, в котором нужно указать подключаемую таблицу groups. На форме появятся дополнительные компоненты для работы с таблицей: bazaDataSet - для доступа к подключению, groupsBindingSource - для работы с набором данных, groupsTableAdapter - для работы с копией таблицы БД. Названия групп будет отображать в алфавитном порядке. Для этого для компонента groupsBindingSource задайте свойство Sort = grupa (имя поля для сортировки). Для сетки задайте свойства: AllowUserToAddRows = false (запрет добавления новых строк в сетке), AllowUserToDeleteRows = false (запрет на удаление данных в сетке без подтверждения), AlternatingRowsDefaultCellStyle - BackColor = серый (цвет фона четных строк сетки), AutoSizeColumnsMode=Fill (ширина колонок подстраивается под размер сетки). Для настройки параметров колонок сетки откройте ее свойство Columns. Вы увидите список всех ее колонок. Колонку id_grup сделайте невидимой (Visible=false), для остальных колонок в свойстве HeaderText укажите названия как на рисунке.
Для подключения подчиненной таблицы students нанесите на форму компонент DataRowView. Появится окно, в котором нужно раскрыть компонент groupsBindingSource и указать имя соединения groupsstudents. На форме появятся дополнительные компоненты для работы с таблицей: groupsstudentsBindingSource - для работы со связанным набором данных, studentsTableAdapter - для работы с копией таблицы БД. Для удобства чтения кода рекомендуется переименовать компонент groupsstudentsBindingSource по имени подчиненной таблицы, а не по имени соединения. Задайте для компонента свойство Name = studentsBindingSource.
Для сетки задайте свойства: AllowUserToAddRows = false (запрет добавления новых строк в сетке), ReadOnly=true, AllowUserToDeleteRows = false (запрет на удаление данных в сетке без подтверждения), AlternatingRowsDefaultCellStyle - BackColor = серый (цвет фона четных строк сетки), AutoSizeColumnsMode=Fill (ширина колонок подстраивается под размер сетки).
Для настройки параметров колонок сетки откройте ее свойство Columns. Вы увидите список всех ее колонок. Колони id_stud и id_grup сделайте невидимыми (Visible=false), для остальных колонок в свойстве HeaderText укажите названия как на рисунке.
Если запустить программу на выполнение, то сетка будет пустой, потому что в строке соединения мы не сохранили пароль к БД и подключение не установлено.
Для формы откройте событие Load. В этом событии будут находится две команды Fill(), загружающие копии таблиц в компоненты TableAdapter. Над этими командами добавьте команду подстановки пароля в строку соединения:
Properties.Settings.Default["BazaConnectionString"] += ";Jet OLEDB:Database Password=rpo";
Обычно в программах для работы с БД при переходе между строками сеток автоматически происходит сохранение изменений, если они имели место. В сетке dataGridView это возможность не реализована. Нужно обеспечить такое сохранение данных.
Указания: для набора данных studentsBindingSource в событии CurrentChanged введите код:
private void groupsBindingSource_CurrentChanged(object sender, EventArgs e)
{
//если в наборе данных groups имеются изменения
if (bazaDataSet.groups.GetChanges() != null)
{
//сохраняем их в таблице БД
Validate();
groupsBindingSource.EndEdit();
groupsDataAdapter.Update(bazaDataSet.grups);
}
}
Для для набора данных studentsBindingSource в событии CurrentChanged введите код:
private void studentsBindingSource_CurrentChanged(object sender, EventArgs e)
{
//если в наборе данных students имеются изменения
if (bazaDataSet.students.GetChanges() != null)
{
Validate();
studentsBindingSource.EndEdit();
studentsDataAdapter.Update(bazaDataSet.students);
}
}
Реализуйте возможность изменения среднего балла всех студентов на процент, введенный пользователем в поле.
Указания: перейдите в окно "Источники данных" и щелкните на кнопке "Изменить набор данных в конструкторе". Так как будем менять данные в таблице students, то щелкните правой кнопкой на этой таблице и выберите команду "Добавить - Запрос". В окне укажите тип запроса UPDATE и введите текст запроса:
update students set srbal = srbal+srbal*?/100
В следующем окне введите имя метода для вызова запроса. Например, введите имя "UpdateSrbal". В результате будет создан метод UpdateSrbal(параметр), при вызове которого нужно будет в скобках указать значение процента изменения.
Для ввода значения процента нанесите на форму компонент textBox. Также нанесите на форму кнопку и для нее напишите код
Для кнопки напишите код:
void button1_Click(object sender, EventArgs e)
{
//вызываем метод для обновления поля srbal
studentsTableAdapter.UpdateSrbal(Convert.ToDecimal(textBox1.Text));
//обновляем данные в сетке
studentsTableAdapter.Fill(bazaDataSet.students);
}
Выполните расчет количества студентов, среднего, максимального и среднего баллов по всем студентам и по студентам выбранной группы.
Указания: нанесите на панель 8 компонентов label.
Для выполнения расчетов будем использовать запрос Select по таблице students. Перейдите в окно "Источники данных" и щелкните на кнопке "Изменить набор данных в конструкторе". Так как будем делать выбор по таблице students, то щелкните правой кнопкой на этой таблице и выберите команду "Добавить - Запрос". В окне укажите тип запроса "Инструкция SELECT, возвращающая строки" и введите текст запроса для расчета показателей по всей таблице:
select count(inn) as kolvo,
round(max(srbal),2) as maxbal,
round(min(srbal),2) as minbal,
round(avg(srbal),2) as sredbal
from students
В следующем окне после флажка "Вернуть таблицу данных" введите имя метода для вызова запроса. Например, введите имя "CalcAll". В результате будет создан метод CalcAll() для вызова этого запроса.
Для расчета показателей по конкретной группе аналогично создайте новый запрос с текстом:
select count(inn) as kolvo,
round(max(srbal),2) as maxbal,
round(min(srbal),2) as minbal,
round(avg(srbal),2) as sredbal
from students
where id_grup=?
В качестве имени метода введите текст CalcGroup. В результате будет создан метод CalcGroup(параметр) для вызова этого запроса. При этом в качестве параметра нужно будет указать номер нужной группы.
Создадим процедуру, которую будем вызывать в разных событиях.
void schet()
{
//описываем переменную для сохранения запроса
DataTable rez = new DataTable();
//в переменную записываем запрос по всей таблице
rez = studentsTableAdapter.CalcAll();
//в метках на форме отображаем результат запроса
label1.Text = "Всего студентов: " + rez.Rows[0]["kolvo"].ToString();
label2.Text = "Максимальный балл: " + rez.Rows[0]["maxbal"].ToString();
label3.Text = "Минимальный балл: " + rez.Rows[0]["minbal"].ToString();
label4.Text = "Средний балл: " + rez.Rows[0]["sredbal"].ToString();
//в переменную записываем запрос по конкретной группе
//в качестве параметра метода указываем
//текущее значение поля id_grup из главной таблицы (bindingSource1)
rez = studentsTableAdapter.CalcGroup(Convert.ToInt16((bindingSource1.Current as DataRowView)["id_grup"]));
//в метках на форме отображаем результат запроса
label5.Text = "Всего студентов: " + rez.Rows[0]["kolvo"].ToString();
label6.Text = "Максимальный балл: " + rez.Rows[0]["maxbal"].ToString();
label7.Text = "Минимальный балл: " + rez.Rows[0]["minbal"].ToString();
label8.Text = "Средний балл: " + rez.Rows[0]["sredbal"].ToString();
//удаляем переменную из памяти
rez.Dispose();
}
Созданная процедура вызывается с помощью команды
schet();
Данную процедуру нужно вызвать в нескольких событиях:
- при перемещении по таблицам в событии CurrentChanged компонентов groupsBindingSource (за пределами операторных скобок if) и studentsBindingSource (внутри операторных скобок if);
- в конце кода кнопки для обновления среднего балла студентов.
Обратите внимание, что несмотря на использования функции round() в запросе, полученные расчетные значения могут иметь большое количество знаков после запятой. Это происходит потому, что СУБД Access не поддерживает эту функцию и данные не округляются. Если программа работает с другой СУБД, то проблемы с округлением не будет и рассмотренный ниже код можно не использовать.
Для решения проблемы округления нужно переписать процедуру schet() с использование округления полученных значений. Сначала значение конвертируем в double, затем методом Math,Round() округляем, а затем конвертируем в текст для отображения на форме
void schet()
{
//описываем переменную для сохранения запроса
DataTable rez = new DataTable();
//в переменную записываем запрос по всей таблице
rez = studentsTableAdapter.CalcAll();
//в метках на форме отображаем результат запроса
//с помощью функции округления
label1.Text = Math.Round(Convert.ToDouble("Всего студентов: ", rez.Rows[0]["kolvo"]),2).ToString();
label2.Text = Math.Round(Convert.ToDouble("Максимальный балл: ", rez.Rows[0]["maxbal"),2).ToString();
label3.Text = Math.Round(Convert.ToDouble("Минимальный балл: ", rez.Rows[0]["minbal"]),2).ToString();
label4.Text = Math.Round(Convert.ToDouble("Средний балл: ", rez.Rows[0]["sredbal"]),2).ToString();
//в переменную записываем запрос по конкретной группе
//в качестве параметра метода указываем
//текущее значение поля id_grup из главной таблицы (gouprBindingSource)
rez = studentsTableAdapter.CalcGroup(Convert.ToInt32((groupsBindingSource.Current as DataRowView)["id_grup"]));
//в метках на форме отображаем результат запроса
//с помощью функции округления
label5.Text = Math.Round(Convert.ToDouble("Всего студентов: ", rez.Rows[0]["kolvo"]),2).ToString();
label6.Text = Math.Round(Convert.ToDouble("Максимальный балл: ", rez.Rows[0]["maxbal"),2).ToString();
label7.Text = Math.Round(Convert.ToDouble("Минимальный балл: ", rez.Rows[0]["minbal"]),2).ToString();
label8.Text = Math.Round(Convert.ToDouble("Средний балл: ", rez.Rows[0]["sredbal"]),2).ToString();
}
Разместите на форме два переключателя для отображения студентов по группам или общи списком.
Указания: нанесите на форму компонент RadioButton и задайте свойства: Text = Просмотр по группам, Checked = true. Нанесите второй RadioButton и задайте свойство Text = Просмотр общим списком.
Для первого переключателя в событии CheckedChanged напишем код:
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
//включаем просмотр по группам
//ссылаемся на BindingSource главной таблицы
studentsBindingSource.DataSource = groupsBindingSource;
//ссылаемся на имя соединения
studentsBindingSource.DataMember = "groupsstudents";
}
Для второго переключателя в событии CheckedChanged напишем код:
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
//включаем просмотр всех записей
//ссылаемся на подключение к БД
studentsBindingSource.DataSource = bazaDataSet;
//ссылаемся на подчиненную таблицу
studentsBindingSource.DataMember = "students";
}