- •В ведение в ado.Net
- •Потребитель данных
- •Поставщики данных
- •Источник данных — xml-файл
- •Элементы управления на форме Меню
- •Как устроены планки меню, статуса и панелей инструментов
- •Планка инструментов (ToolStrip)
- •Навигатор связей
- •Контейнер, расщепляющий форму
- •Создание таблиц и внедрение их в DataSet
- •Пояснения
- •Запись и чтение данных
- •Отображение данных связанной таблицы
- •Добавляем ограничение
- •Вторая, подчиненная таблица
- •Автоматическая навигация по записям связанной таблицы
- •Элемент управления BindingNavigator
- •Реакции на события в DataTable
- •Канонизация имен студентов
- •Вычисляемые колонки DataTable (Expression-Based DataColumn Objects)
- •Поиск в таблице DataTable и в компоненте DataGridView
- •Коррекция пользовательского интерфейса
- •Результат поиска
- •Поиск в DataGridView
- •Отбор данных из DataTable и отображение их в ListView
- •Как легко вносятся ошибки
- •Образ таблицы DataView
- •Методы Find и FindRows класса DataView
- •Задание
- •Элемент управления, который позволяет управлять отображением колонок DataGridView
Добавляем ограничение
Если мы хотим не только просматривать данные, но и вносить изменения, то для сохранения целостности по ссылкам, необходимо добавить код, создающий объект класса ForeignKeyConstraint. Он задает ограничение, накладываемое на данные обеих таблиц. Ограничение (constraint) типа FK-PK (ForeignKey-PrimaryKey) следит за тем, чтобы не нарушалась целостность данных по ссылкам. Следующий фрагмент надо добавить в метод RelateAndBind (до создания DataRelation).
ForeignKeyConstraint cs = new ForeignKeyConstraint("SE", ds.Tables[0].Columns[0], ds.Tables[1].Columns[1]);
cs.DeleteRule = Rule.Cascade;
cs.UpdateRule = Rule.Cascade;
ds.Tables[1].Constraints.Add(cs);
ds.EnforceConstraints = true;
Здесь мы создаем объект класса ForeignKeyConstraint, настраиваем его и вставляем в коллекцию Constraints подчиненной таблицы. Последний оператор включает ограничения. Иногда их надо выключать, например, при начальном заполнении таблицы, или при чтении данных из файла. В эти моменты целостность данных по ссылкам (временно) может отсутствовать. По завершении этих операций ограничения следует вновь включить.
Вторая, подчиненная таблица
В класс формы уже имеется массив с именами курсов лекций:
static string[] courses = { "Math", "Mechanics", "Physics", "English", "Oracle", "OpenGL", "History" };
Объект Random rand уже объявлен нами, но еще не создан. Он позволит сгенерировать случайные параметры для строк таблицы экзаменов.
Создайте объект rand в подходящий момент жизни приложения.
Добавьте в InitTables код заполнения таблицы экзаменов (двойной цикл по студентам и по курсам лекций). Для каждого студента сгенерируйте случайное количество сданных экзаменов, случайные даты и оценки. Пользуйтесь методами класса Random.
Подсказка. Добавьте код внутрь метода InitTables.
foreach (object[] stud in studs)
{
int num = 0;
foreach (string c in courses)
{
if (num++ != rand.Next(courses.Length))
ds.Tables[1].Rows.Add(CreateRandomExam((int)stud[0], c));
}
}
Доработайте код метода CreateRandomExam.
DataRow CreateRandomExam(int studID, string course)
{
// Такой способ создания строки надежен, так как новая строка уже имеет определенный нами ранее набор колонок
DataRow row = ds.Tables[1].NewRow();
row[1] = studID;
row[2] = course;
row[3] = // Напишите логическое выражение (сдал-ли студент зачет). Успех должен быть в 90% случаев.
row[4] = 2;
if ((bool)row[3])
{
row[4] = // Используйте rand
row[5] = // Новая дата, отстающаяся от DateTime.Now на случайное число лет (не более 2), случайный месяц и день
}
return row;
}
Привяжите gridExam к данным таблицы Exams. Вы можете выбрать ее из DataSet (ds.Tables[1]). Отладьте приложение так, чтобы оно позволяло работать с данными двух таблиц. Для начала пусть оба компонента DataGridView отображают все данные таблиц, к которым они привязаны.
Автоматическая навигация по записям связанной таблицы
Элемент DataGrid (он отсутствует в Toolbox, но его можно туда добавить) позволяет одновременно, с учетом иерархии таблиц, отображать данные нескольких таблиц. Более новый элемент типа DataGridView этого делать не позволяет. Для него предлагается другая техника отслеживания данных (навигации по данным связанной таблицы). Механизм DataBinding следит за перемещением пользователя по строкам gridStud и синхронизирует позицию CurrencyManager в записях таблицы Studs (внутри DataSet, то есть, в памяти). В момент изменения позиции текущей записи в главной таблице он автоматически перезагружает данные gridExam с учетом связи между таблицами студентов и их экзаменов. Здесь главную роль играют объекты классов DataRelation и BindingSource.
Бывают случаи, когда создают DataRelation и отказываются от создания ForeignKeyConstraint. Например, если вы хотите отобразить данные двух таблиц Клиенты и Заказы, но запретить пользователю редактировать их. В этом случае объект ForeignKeyConstraint не нужен, так как целостность данных не может быть нарушена пользователем. В то же время объект DataRelation нужен, так как он используется механизмом DataBinding для синхронизации отображения данных. Суть синхронизации состоит в том, что отображаются не все заказы, а заказы только одного, активного в данный момент клиента.
Синхронный режим функционирования обоих grid осуществляется с помощью двух объектов класса BindingSource. Этот класс расширяет возможности технологии Data Binding, с которой вы познакомились ранее, когда использовали коллекцию типа BindingList. Тогда мы пользовались услугами класса CurrencyManager (cинхронизатора текущей позиции). Теперь ему на смену пришел более мощный класс BindingSource. Он поглотил (инкапсулировал) CurrencyManager. Объект класса BindingSource (обычно в паре с объектом класса BindingNavigator) реализует все рутинные операции, необходимые для отображения связанных данных. BindingSource поддерживает как простое, так и сложное связывание.
В классе формы уже существует объявление массива синхронизаторов данных (объектов класса BindingSource).
BindingSource[] bs;
Создайте массив в конструкторе: bs = // Ваш код;
Вставьте внутрь метода RelateAndBind код создания и инициализации элементов массива.
bs[0] = new BindingSource (ds, ds.Tables[0].TableName);
bs[1] = new BindingSource (bs[0], ds.Relations[0].RelationName);
Способ привязки к данным определяется свойствами DataSource и DataMember объектов BindingSource. Они заданы параметрами конструктора. Обратите внимание на различия в значении этих свойств для объектов bs[0] и bs[1]. Первый объект привязан к данным таблицы Studs всего набора данных (DataSet), а второй — к данным своего старшего брата, пропущенным сквозь фильтр связи "StudExam". Это имя ранее было присвоено объекту типа DataRelation, связывающему две таблицы (DataTable), помещенные в DataSet. Вспомните, что имена свойств играют решающую роль в механизме DataBinding.
Такие настройки позволяют отслеживать перемещения по строкам главной таблицы (Studs) и отображать результаты экзаменов текущего студента (связанные данные). Для того, чтобы этот механизм заработал, замените источники данных для обеих DataGridView. Вместо набора данных ds задайте:
gridStud.DataSource = bs[0];
gridExam.DataSource = bs[1];
Установку свойств DataMember для обоих DataGridView удалите, так как новые источники данных уже имеют свойство DataMember и оно было установлено при их создании. Запустите приложение и проверьте его работу во всех режимах.
