Практическая работа по теме 2. "Программирование в средах современных информационных систем" Компоненты объекта DataSet. Определение метаданных для объекта DataTable. Вставка и удаление данных в DataTable. Доступ к данным с помощью объекта DataTable. Поиск, фильтрация и сортировка записей. Отношения между таблицами. Ограничения целостности данных. Визуальное программирование объекта DataSet. Оглавление Задание Задание Задание Задание Задание Задание Задание 1. 2. 3. 4. 5. 6. 7. Определение метаданных для объекта DataTable Вставка и удаление данных в объекте DataTable Доступ к данным с помощью объекта DataTable Поиск, фильтрация и сортировка записей Отношения между таблицами Ограничения целостности данных Визуальное программирование объекта DataSet Задание 1. Определение метаданных для объекта DataTable Цель Научиться программировать определение метаданных для объекта DataTable. Решение Написать код определения схемы объекта DataTable, указывая необходимые свойства ReadOnly, Unique, AllowDbNull, AutoIncrement для объектов DataColumn. Обсуждение Объект DataSet является ключевым объектом ADO.NET и служит универсальным контейнером данных, независимо от используемого источника данных. Объект DataSet и связанные с ним подчиненные объекты предлагают реляционное представление данных, хотя объект DataSet также способен загружать и сохранять свои данные в формате XML (Extensible Markup Language расширяемый язык разметки). Данные в объекте DataSet организованы в одной или нескольких объектах DataTable. Каждый объект DataTable существует независимо от источника данных, то есть он всегда отключен от источника данных, как и DataSet. Объекты DataTable всего лишь хранят таблицы данных и предлагают возможности манипулирования, передачи или связывания их с элементами управления пользовательского интерфейса. На рис.2.1 показана схема взаимодействия объекта DataSet и связанных с ним подчиненных объектов. Объекты DataTable содержит коллекции объектов DataRow, DataColumn и Constraint, а также коллекции объектов DataRelation, связанные с другими родительскими и дочерними объектами. Существует три способа ввода данных в таблицы DataTable объекта DataSet. Рис. 2.1. Схема взаимодействия объекта DataSet с подчиненными объектами Программирование определений метаданных и прямая вставка данных. Использование объекта DataAdapter для создания запроса по отношению к источнику данных. Загрузка XML-документа. В этой практической работе остановимся на способе программирования структуры и прямого ввода данных в объекты DataTable, так как необходимо научиться работать с объектом DataSet, понять его устройство, возможности и особенности. Два других способа будут рассмотрены в следующих практикумах. 1. Для изучения основных принципов технологии программирования схемы DataTable создайте Windows-форму в вашем проекте. 2. В свойстве Text формы укажите DataSet. 3. Добавьте в форму элементы управления. Форма должна включать командную кнопку "Create DataSet" и поле со списком ListBox для просмотра результатов выполнения программы (рис. 2.2). Щелчок по командной кнопке должен выполнять действия по созданию объекта DataSet, определения схемы объектов DataTable, входящих в структуру DataSet. 4. Код процедуры приведен ниже, скопируйте его в свой проект. Рис. 2.2. Интерфейс приложения для исследования объекта DataSet Private dsE As DataSet ' Создаем объект класса DataSet Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click CreateDataSet() 'Это процедура создания объектов DataSet и DataTable, ее код приведен ниже AddData() 'Это процедура вставки данных в объекты DataTable DisplayDataset(dsE) 'Это процедура просмотра данных в объектах DataTable, ее код приведен ниже End Sub 5. Не забудьте в верхней части кода проекта описать импортирование пространств имен для доступа к классам System.Data. Imports System.Data 6. Теперь рассмотрим процесс создания схемы объектов DataTable, из которых и состоит объект DataSet. Пусть наше хранилище (DataSet) содержит две таблицы Employees (Сотрудники) и Departments (Отделы). Private Sub CreateDataSet() 7. Создание объекта класса DataSet dsE = New 'Создание таблицы Dim dtEmp As DataTable DataTable("Employees") dtEmp.CaseSensitive = False DataSet DataTable = New Свойство таблицы CaseSensitive определяет, будут ли операции сортировки, поиска и фильтрации выполняться с учетом регистра символов. По умолчанию значение этого свойства определяется как значение свойства CaseSensitivity родительского объекта DataSet или принимается равным False, если объект DataTable создан независимо от объекта DataSet. Свойство CaseSensitivity применяется только для данных объекта DataTable и не влияет на имена самих объектов DataTable. Например, объект DataSet может иметь две таблицы с именами Employees и employees. Для работы с ними надо точно указывать имя таблицы с соблюдением регистра, это разные объекты. Но если имеется только одна таблица Employees, то ее имя можно вводить без соблюдения регистра - она одна. 8. Создадим в таблице столбцы dtEmp.Columns.Add("FirstName", Type.GetType("System.String")) dtEmp.Columns.Add("LastName", Type.GetType("System.String")) dtEmp.Columns.Add("DepartmentID", Type.GetType("System.Int32")) Обратите внимание на тот факт, что при описании структуры полей таблицы указываются .NET совместимые типы данных, а не используемые в базе данных типы. При отсутствии типа данных для него по умолчанию принимается строковый тип. 9. Требуется определить первичные ключи таблицы Employees. Это можно сделать с помощью присвоения одного или нескольких полей свойству PrimaryKey таблицы. Даже если первичный ключ создан на основе единственного поля, свойство PrimaryKey таблицы является массивом объектов DataColumn. В приведенных ниже строках кода первичный ключ таблицы Employees создается на основе двух столбцов FirstName и LastName. Dim pk(1) pk(0) = pk(1) = dtEmp.PrimaryKey = pk As DataColumn dtEmp.Columns("FirstName") dtEmp.Columns("LastName") При создании первичного ключа с помощью свойства PrimaryKey для объекта DataTable на основе одного поля для свойства AllowDbNull этого поля автоматически задается значение False, а для свойства Unique - значение True. А если первичный ключ создан на основе нескольких полей, то только для свойства AllowDBNull этих полей автоматически задается значение False. Добавим DataTable dsE.Tables.Add(dtEmp) в DataSet 10. Рассмотрим создание таблицы перегруженным конструктором Dim dtDep As dtDep = New dtDep.TableName = dtDep.MinimumCapacity = 5 DataTable DataTable "Departments" 11. Для свойства MinimumCapacity задается значение 5 (по умолчанию 25), то есть экземпляр объекта DataTable создается уже с пятью записями. dtDep.CaseSensitive = False 12. Создание столбцов таблицы перегруженным конструктором. Dim newColumn As New DataColumn With newColumn .ColumnName = "ID" .DataType = Type.GetType("System.Int32") .ReadOnly = True .Unique = True .AutoIncrement = True End With dtDep.Columns.Add(newColumn) 13. Перед добавлением полей в коллекцию Columns для них задаются значения необходимых свойств (ReadOnly, Unique, AllowDbNull, AutoIncrement). Присвоение свойству ReadOnly значения True указывает на то, что значение поля нельзя изменить. Присвоение свойству Unique значения True указывает на то, что значение данного поля во всех записях таблицы должно быть уникальным. Это свойство реализуется с помощью автоматического создания ограничения UniqueConstraint для данного поля . Далее этот процесс повторяется необходимое количество раз. newColumn = New DataColumn With newColumn .ColumnName = "DepartmentName" .DataType = Type.GetType("System.String") .Unique = True .AutoIncrement = False End With dtDep.Columns.Add(newColumn) Dim pk1(0) As DataColumn pk1(0) = dtDep.Columns("ID") dtDep.PrimaryKey = pk1 dsE.Tables.Add(dtDep) End Sub Самостоятельно Запрограммируйте определение метаданных для объекта DataSet, включающего таблицы модели Dogovor. Модель состоит из четырех таблиц: Dogovor, Pocupatel, Tovar и Post. Структура таблиц представлена на рис.2.3. Рис. 2.3. Структура таблиц модели Dogovor Задание 2. Вставка и удаление данных в объекте DataTable Цель Научиться программировать непосредственный (прямой) ввод данных в объект DataTable. Решение Написать код вставки записей в объект DataTable, создавая экземпляры объекта DataRow для нужной таблицы с помощью метода NewRow. Обсуждение После определения объекта DataTable и его схемы можно начинать ввод данных. Ниже приводится код вставки записей в таблицы Departments и Employees. Технологию ввода записей можно описать следующими шагами: Сначала создается новый экземпляр объекта DataRow для нужной таблицы с помощью метода NewRow. Затем присваиваются значения полям этой записи. После этого запись включается в коллекцию записей Rows таблицы с помощью метода Add свойства Rows таблицы. 1. Добавьте следующий код в свой проект. Private Sub AddData() Dim dtDep As DataTable = dsE.Tables("Departments") Dim dtEmp As DataTable = dsE.Tables("Employees") 2. Вставка записей в таблицу Departments Dim rowDep rowDep = rowDep("ID") rowDep("DepartmentName") As = = DataRow dtDep.NewRow 22 "Отдел сбыта" dtDep.Rows.Add(rowDep) rowDep = rowDep("ID") rowDep("DepartmentName") dtDep.Rows.Add(rowDep) dtDep.NewRow = 33 = "Отдел рекламы" 3. Вставка записей в таблицу Employees Dim rowEmp rowEmp = rowEmp("FirstName") rowEmp("LastName") rowEmp("DepartmentID") dtEmp.Rows.Add(rowEmp) rowEmp = rowEmp("FirstName") rowEmp("LastName") rowEmp("DepartmentID") dtEmp.Rows.Add(rowEmp) End Sub As = = = = DataRow dtEmp.NewRow "Волнушкин" "Владимир" = 22 dtEmp.NewRow "Белогруздев" "Григорий" = 33 Строки можно не только вставлять, но и удалять целиком. Удаление записи основано на методах Remove, RemoveAt объекта DataRowCollection (то есть свойства Rows объекта DataTable). Эти методы полностью удаляет запись из коллекции. Еще один способ удаления записи основан на методе Delete объекта DataRow. Этот метод отмечает запись для удаления, которое на самом деле произойдет только после вызова метода AcceptChanges. После вызова метода Remove все указанные записи будут удалены необратимо, даже если после этого вызвать метод RejectChanges. Стоит сказать о методах AcceptChanges и RejectChanges, которые фиксируют или наоборот, откатывают последние изменения в классах DataTable, DataSet и DataRow. Эти методы актуальны при работе с подключением объекта DataSet к источнику данных. Сейчас мы вводим информацию напрямую в объект DataSet, не связанный ни с каким источником, поэтому в нашем случае эти методы не имеют конкретного применения. Но в реальных условиях, когда DataSet заполняется при подключении к конкретному источнику данных (например, базе данных SQL Server) эти методы имеют большое значение при редактировании данных и последующем внесении правок в исходную базу данных . Практическое использование методов Delete и Remove рассмотрим в задании 4, когда научимся искать нужную нам запись или массив записей. Самостоятельно Напишите код вставки данных в таблицы модели Dogovor. Задание 3. Доступ к данным с помощью объекта DataTable Цель Написать код отображения данных в объектах DataTable. Решение Применить индексы или циклический обход всех элементов коллекций Rows и Columns для отображения содержимого ранее созданных таблиц. Обсуждение Поскольку объект DataSet и содержащийся в нем объект DataTable всегда наполнены данными и не подключены к источнику данных, метод доступа к записям имеет свои особенности в сравнении с другими моделями доступа к данным (например, ODBC, DAO или RDO). Поскольку все данные доступны одновременно, в модели ADO.NET не существует понятия "текущая запись". Поэтому нет никаких свойств или методов для перемещения от одной записи к другой. Каждый объект DataTable имеет свойство Rows, которое является набором объектов DataRow. Доступ к отдельному объекту осуществляется с помощью индекса или оператора For Each. Таким образом, в модели ADO.NET предлагается более простой и эффективный способ доступа и перемещения, аналогичный доступу к элементам массива. В коде, приведенном ниже, показано как отображается содержимое ранее созданных таблиц с загруженными в них данными. В данном случае применяется циклический обход всех элементов коллекции, то есть коллекций Rows и Columns, для отображения содержимого таблицы Employees индексы для отображения содержимого таблицы Departments. Private Sub DisplayDataset() Циклический обход всех элементов Rows и Columns. Оператор For Each Dim dr As DataRow Dim dc As DataColumn ListBox1.Items.Add("DISPLAY DATASET") ListBox1.Items.Add("===============") For Each dr In dsE.Tables("Employees").Rows For Each dc In dsE.Tables("Employees").Columns ListBox1.Items.Add(dc.ColumnName & ":" & dr(dc)) Next ListBox1.Items.Add("---------------") Next ListBox1.Items.Add("") Использование индексов для организации цикла обхода Dim row As Integer Dim col As Integer For row = 0 To dsE.Tables("Departments").Rows.Count 1 For col = 0 To dsE.Tables("Departments").Columns.Count - 1 ListBox1.Items.Add(dsE.Tables("Departments").Columns(col).ColumnName & ":" & dsE.Tables("Departments").Rows(row)(col)) Next col ListBox1.Items.Add("----------------------") Next row End Sub 1. Попробуем создать процедуру общего типа для обхода не только записей и полей, но и таблиц объекта DataSet. Текст процедуры предлагается ниже, обратите внимание на входной параметр процедуры - это отображаемый объект DataSet, то есть при вызове процедуры вы должны передать ей в качестве входного параметра имя объекта DataSet, содержимое которого вы хотите отобразить. Private Sub DisplayDataSet(ByVal ds As DataSet) 'Общая процедура для отображения содержимого объекта DataSet Отображаемый объект DataSet передается как параметр Dim dt As DataTable Dim dr As DataRow Dim dc As DataColumn ListBox1.Items.Add("DISPLAY DATASET") ListBox1.Items.Add("===============") For Each dt In ds.Tables ListBox1.Items.Add("") ListBox1.Items.Add("TABLE: " & dt.TableName) ListBox1.Items.Add("========================") For Each dr In dt.Rows For Each dc In dt.Columns ListBox1.Items.Add(dc.ColumnName & ":" & dr(dc)) Next dc ListBox1.Items.Add("--------------------------") Next dr Next dt End Sub 2. Теперь мы можем запустить полученное приложение, щелкнув по кнопке Create DataSet. В результате должен быть создан объект DataSet, наполнен таблицами и данными, которые затем отображаются в ListBox (рис. 2.4). Рис. 2.4. Результат создания объекта DataSet с таблицами Employees и Departments, наполнения их данными и последующего отображения Самостоятельно Используя разработанную процедуру для отображения выполните отображение таблиц вашей модели Dogovor. объекта DataSet, Задание 4. Поиск, фильтрация и сортировка записей Цель Научиться работать с указанными записями объекта DataSet. Решение Использовать методы Find и Select, а также состояние и версии записей. Обсуждение Обновление данных в объекте DataSet можно организовать с помощью следующего кода доступа к нужной записи: dtEmp.Rows(2)("DepartmentID") = 22 С практической стороны этот способ не совсем удачен (нужно знать номер записи), более эффективный и безопасный способ основан на поиске нужной строки (или нескольких строк). Рассмотрим другие способы доступа к данным. Метод Find принадлежит свойству DataRowCollection объекта DataTable, который используется для поиска и возвращения единственной строки, указанной с помощью значения первичного ключа таблицы. Dim r r = dtDep.Rows.Find("11") As DataRow Здесь переменной r присваивается объект DataRow с указанным значением первичного ключа или значение Nothing, если такая запись не будет найдена. Теперь мы умеем искать нужную запись, попробуем удалять эту запись из набора записей методом Remove. Пример поиска записи по ключевому полю с последующим ее удалением приведен ниже, результат поиска и удаления показан на рис. 2.5. 1. Добавьте в проект кнопку Find и программный код для нее. Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Dim dtDep As DataTable = dsE.Tables("Departments") Dim dtEmp As DataTable = dsE.Tables("Employees") Dim r As DataRow r = dtDep.Rows.Find("11") MsgBox(r.Item("ID") & " отделэто " & r.Item("DepartmentName")) dtDep.Rows.Remove(r) r = dtDep.Rows.Find("11") DisplayDataset() End Sub Если первичный ключ таблицы основан на нескольких полях, то соответствующие значения первичного ключа перелаются в виде элементов массива (типа Object) методу Find. Dim d(1) As Object d(0) = "Волнушкин" d(1) = "Владимир" r = dtEmp.Rows.Find(d) MsgBox(r.Item("DepartmentID")) Метод Select объекта DataTable возвращает массив объектов DataRow. Возвращаемые строки могут соответствовать критерию фильтрования, порядку сортировки и/или спецификации состояния (объект DataViewRowState пространства имен System.Data). 2. Добавьте в форму вашего проекта еще одну кнопку Select и попробуйте различные варианты поиска: с сортировкой и без нее, с фильтрацией по различным критериям. Рис. 2.5. Результат поиска записи по ключевому полю Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click Dim dtEmp As DataTable = dsE.Tables("Employees") Dim selectedRows() As DataRow selectedRows = dtEmp.Select("LastName='Владимир'") Dim i As Integer For i = 0 To selectedRows.GetUpperBound(0) MsgBox(selectedRows(i)("FirstName")) Next End Sub 3. Для возврата записей, отсортированных в порядке убывания, можно отредактировать строку с методом Select так, как показано ниже. Результат поиска с сортировкой и без нее можно увидеть на рис. 2.6. Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click Dim dtEmp As DataTable = dsE.Tables("Employees") Dim selectedRows() As DataRow selectedRows = dtEmp.Select("LastName='Владимир'", "FirstName DESC") Dim i As Integer ListBox1.Items.Add("") ListBox1.Items.Add("========================") ListBox1.Items.Add("Записи, найденные Select с сортировкой по убыванию") For i = 0 To selectedRows.GetUpperBound(0) ListBox1.Items.Add(selectedRows(i)("FirstName")) Next End Sub Рис. 2.6. Использование метода Select без сортировки и с сортировкой по убыванию Указание состояния записи в качестве аргумента метода Select позволяет извлекать записи с определенной версией данных непосредственно в процессе их редактирования. 4. Например, для извлечения всех исходных записей даже после их редактирования (но еще до вызова метода AcceptChanges) следует указать значение OriginalRows перечисления DataViewRowState, как показано ниже. selectedRows = dtEmp.Select(Nothing, DataViewRowState.OriginalRows) Nothing, 5. Для отбора вновь добавленных записей с именем Владимир следует указать значение Added перечисления DataViewRowState. selectedRows = dtEmp.Select("LastName='Владимир'", Nothing, DataViewRowState.Added) 6. А если требуется отобрать вновь добавленные записи с именем Владимир и отсортировать их по фамилии, то в таком случае следует оформить строку поиска следующим образом: selectedRows = dtEmp.Select("LastName='Владимир'", "FirstName DESC", DataViewRowState.Added) В таблице приведены все возможные варианты состояния записи, которые могут быть представлены членами перечисления DataViewRowState. Упомянутые в таблице изменения связаны с последней загрузкой данных или вызовом метода AcceptChanges. Члены перечисления DataViewRowState Член Описание Added Вновь созданные записи CurrentRows Все текущие записи (включая новые, измененные или неизмененные записи) Deleted Все записи, отмеченные как удаленные ModifiedCurrent Текущая версия измененной записи ModifiedOriginal Исходная версия измененной записи None Нет сведений OriginalRows Все исходные записи, включая удаленные, кроме новых записей Unchanged Все неизмененные записи неизмененные и Самостоятельно Напишите процедуры поиска строки по значению ключевого поля и поиска массива строк по любому поисковому критерию для проекта, реализующего объект DataSet Dogovor. Задание 5. Отношения между таблицами Цель Научиться устанавливать соответствия между полями двух таблиц, как это принято в реляционных базах данных. Решение Использовать объект DataRelation, свойства ParentRelations и ChildRelations. Обсуждение Поскольку объект DataSet может содержать несколько таблиц, то вполне естественно, что между ними могут существовать отношения (по крайней мере, если речь идет о реляционных базах данных). В модели ADO.NET для этого предусмотрен объект DataRelation. Объект DataRelation устанавливает соответствие между полями в двух таблицах, которые имеют родительско-дочерние отношения или связаны первичным и внешним ключами. Объект DataRelation выполняет две основные функции: Позволяет переходить от одной связанной таблицы к другой и обратно, то есть при работе с родительской записью можно получить доступ к ее дочерним записям, а при работе с дочерней записью - доступ к ее родительской записи. Позволяет задавать и поддерживать ссылочную целостность, например, каскадные обновления данных в связанных таблицах при выполнении каких-либо изменений в любой из связанных таблиц. 1. Продолжим работу с нашим проектом. Установим Departments и Employees родительско-дочерние отношения. между таблицами Пусть в каждом отделе могут работать один или более сотрудников, но каждый сотрудник работает в одном и только одном отделе. 2. Создайте новую кнопку CreateRelations в форме проекта и добавьте к ней программный код, позволяющий установить указанные отношения между таблицами. Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click Dim rel As DataRelation CreateDataSet() 'Создание отношения между таблицами Departments и Employees rel = dsE.Relations.Add("relDepEmp", dsE.Tables("Departments").Columns("ID"), dsE.Tables("Employees").Columns("DepartmentID")) DisplayRelations(dsE) End Sub 3. Сначала нужно создать объект DataRelation. Каждый объект DataSet содержит коллекцию отношений, которая доступна как свойство Relations. Это свойство имеет тип DataRelationCollection и поддерживает несколько перегруженных версий метода Add. Версия, которая была использована нами, принимает три аргумента: имя отношения, ссылку на объект DataColumn в родительской таблице, а также ссылку на объект DataColumn в дочерней таблице. Если отношение между таблицами охватывает более одного поля, то следует использовать другую версию метода Add с аргументами-массивами объектов DataColumn. 4. Процедура DisplayRelations циклически обходит все отношения свойства relations объекта DataSet и выводит в ListBox имя отношения, имя родительской таблицы и ее поле, которое входит в созданное отношение, а также имя дочерней таблицы и ее поле, которое входит в созданное отношение. Private Sub DisplayRelations(ByVal ds As DataSet) Dim rel As DataRelation ListBox1.Items.Add("") ListBox1.Items.Add("DISPLAY RELATIONS") For Each rel In ds.Relations ListBox1.Items.Add("NAME: " & rel.RelationName) ListBox1.Items.Add("PARENT: " & rel.ParentTable.ToString & "--" & rel.ParentColumns(0).ColumnName) ListBox1.Items.Add("CHILD: " & rel.ChildTable.ToString & "--" & rel.ChildColumns(0).ColumnName) Next ListBox1.Items.Add("") End Sub 5. После компоновки проекта вы сможете увидеть установленные отношения между таблицами (рис. 2.7). Рис. 2.7. Установление отношений между таблицами Кроме коллекции Relations объекта DataSet, которая содержит все отношения, определенные между таблицами объекта DataSet. Каждый объект DataTable также содержит две коллекции отношений (то есть два свойства): ParentRelations и childRelations. Которые содержат отношения между данным объектом dataTable и связанной с ним другой (дочерней или родительской) таблицей. 6. После создания отношения между таблицами можно организовать доступ к связанным данным в них. Начните с создания кнопки Child Rows и вставки программного кода, приведенного ниже. Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click Dim rel As DataRelation CreateDataSet() AddData() rel = dsE.Relations.Add("relDepEmp", dsE.Tables("Departments").Columns("ID"), dsE.Tables("Employees").Columns("DepartmentID")) DisplayChildRows(dsE.Tables("Departments")) End Sub Процедура DisplatChildRows содержит трижды вложенный цикл для отображения всех полей из каждой связанной таблицы (в нашем случае только одной) для каждой записи родительской таблицы. При этом каждая запись родительской таблицы передается циклу как аргумент, происходит обход всех отношений, определенных в свойстве ChildRelations таблицы, отображается имя таблицы, имя поля в родительской таблице, а также значение поля в текущей записи. Затем вызывается метод GetChildRows с текущим отношением в качестве аргумента и возвращается массив объектов DataRow, содержащих дочерние записи. 7. Для каждой записи отобразите все поля с именем дочерней таблицы. Private Sub DisplayChildRows(ByVal dt As DataTable) Dim rel As DataRelation Dim relatedRows() As DataRow Dim row As DataRow Dim col As DataColumn Dim i As Integer Dim rowData As String ListBox1.Items.Add("") ListBox1.Items.Add("CHILD ROWS") For Each row In dt.Rows For Each rel In dt.ChildRelations ListBox1.Items.Add(dt.TableName & ": " & rel.ParentColumns(0).ColumnName & "= " & row(rel.ParentColumns(0).ToString)) relatedRows = row.GetChildRows(rel) For i = 0 To relatedRows.GetUpperBound(0) rowData = "---" & rel.ChildTable.TableName & ": " For Each col In rel.ChildTable.Columns rowData = rowData & " " & relatedRows(i)(col.ToString) Next col ListBox1.Items.Add(rowData) Next i Next rel Next row End Sub 8. Запуск полученного кода должен отобразить родительские и дочерние записи из таблиц Employees и Departments (рис. 2.8). Рис. 2.8. Результаты отображения родительских и дочерних записей Самостоятельно Задайте отношения для таблиц объекта DataSet Dogovor. Используя приведенную выше процедуру отображения родительских и дочерних данных из связанных таблиц, выполните отображение связанных данных для таблиц DataSet Dogovor. Задание 6. Ограничения целостности данных Цель Обеспечить целостность данных для уникальных добавлении, удалении или изменении данных. данных, а также при Решение Использовать имеющиеся в модели ADO.NET ограничения целостности данных: UniqueConstraint и ForeignKeyConstraint. Обсуждение Ограничениями называются правила, которые вводятся для поддержания целостности данных в таблице. В модели ADO.NET применяется два типа ограничений целостности данных: UniqueConstraint и ForeignKeyConstraint. Ограничение UniqueConstraint гарантирует, что все значения в указанных полях будут уникальны в рамках всей таблицы. Ограничение ForeignKeyConstraint определяет связь на основе первичного и внешнего ключа в двух таблицах и выполняемые действия в случае добавления, удаления или изменения значений родительской записи (то есть первичного ключа). Хотя ограничения можно создавать непосредственно, фактически они создаются косвенно. В нашем проекте ограничения уже созданы, так как объект UniqueConstraint автоматически был создан и включен в коллекцию Constraints объекта DataTable при объявлении свойства Unique объекта DataColumn равным True. Кроме того, объекты UniqueConstraint и ForeignKeyConstraint автоматически создаются при создании отношения между двумя таблицами. Объект UniqueConstraint был создан для связанных полей в родительской таблице, а объект ForeignKeyConstraint - для связанных полей в дочерней таблице. 1. Для создания и отображения ограничений таблиц используемого объекта DataSet в нашем проекте создадим еще одну кнопку Constraints и вставим код, приведенный ниже. Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click Dim dt As DataTable, rel As DataRelation CreateDataSet() 2. Создание отношения между таблицами Departments и Employees rel = dsE.Relations.Add("relDepEmp", dsE.Tables("Departments").Columns("ID"), dsE.Tables("Employees").Columns("DepartmentID")) For Each dt In dsE.Tables DisplayConstraints(dt) Next dt End Sub Процедура DisplayConstraints принимает в качестве параметра объект DataTable и отображает информацию об ограничениях указанной таблицы. Для этого выполняется циклический обход всех членов свойства-коллекции Constraints указанной таблицы. Каждое найденное ограничение проверяется, то есть выясняется имеет ли оно тип UniqueConstraint или ForeignKeyConsctaint. Оба типа являются производными классами от класса Constraint, поэтому могут существовать в рамках одной типизированной коллекции. Однако эти объекты все же обладают разным набором свойств, поэтому необходимо идентифицировать тип ограничения. Для ограничения UniqueConstraint определенных в данном ограничении. отображаются имена всех полей, 3. Для ограничения ForeignKeyConstraint отображается имя связанной родительской таблицы вместе с именами всех связанных полей в этой таблице (рис. 2.9). Рис.2.9. Результаты отображения информации о созданных ограничениях для таблиц Departments и Employees Private Sub DisplayConstraints(ByVal dt As DataTab Dim i As Inte Dim cs As Constra Dim uCS As UniqueConstra Dim fkCS As ForeignKeyConstra Dim columns() As DataColu ListBox1.Items.Add("") ListBox1.Items.Add("CONSTRAINTS FOR TABLE: " & dt.TableNam ListBox1.Items.Add("======================================") For Each cs In dt.Constra ListBox1.Items.Add("Constraint Name: " & cs.ConstraintNam ListBox1.Items.Add("Type: " & cs.GetType().ToStri If TypeOf cs Is UniqueConstraint T uCS = CType(cs, UniqueConstra 'Обработка полей в виде масс columns = uCS.Colum 'Вывод имен по For i = 0 To columns.Length ListBox1.Items.Add("column Name: " & columns(i).ColumnNam Next ElseIf TypeOf cs Is ForeignKeyConstraint Th fkCS = CType(cs, ForeignKeyConstra 'Обработка дочерних полей и вывод их им columns = fkCS.Colum For i = 0 To columns.Length ListBox1.Items.Add("Column Name: " & columns(i).ColumnNam Next Вывод имени связанной родительской табли ListBox1.Items.Add("Related Table Name: " & fkCS.RelatedTable.TableNam 'Обработка связанных родительских полей и вывод их им columns = fkCS.RelatedColum For i = 0 To columns.Length ListBox1.Items.Add("Related Column Name: " & columns(i).ColumnNam Next End ListBox1.Items.Add("======================================= Next End Sub 4. После запуска проекта вы должны увидеть информацию об ограничениях, созданных автоматически (рис. 2.8). Объект ForeignKeyConstraint имеет три свойства-правила: UpdateRule, DeleteRule, AcceptRejectRule. Эти свойства-правила управляют действиями, выполняемыми над записями при редактировании данных в связанных таблицах. Свойства UpdateRule и DeleteRule принимают одно из значений перечисления Rule, описанного ниже в таблице. Члены перечисления Rule Член Описание Cascade Удаление или обновление данных в родительской записи также выполняется для связанных дочерних записей. Это значение по умолчанию None Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей. Это может привести к появлению дочерних записей, которые ошибочно ссылаются на отсутствующие родительские записи Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей, но для них SetDefault задается используемое по умолчанию значение, указанное в свойстве DefaultValue SetNull Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей, но для них задается значение DBNull Еще одно свойство AcceptRejectRule может принимать значения Cascade (по умолчанию) или None при вызове метода AcceptChanges (или RejectChanges) для связанных дочерних записей. Это свойство указывает на автоматический вызов (Cascade) методов AcceptChanges или RejectChanges для дочерних записей при вызове этих методов для родительской записи. В случае None вызов методов для родительской записи никак не повлияет на связанные с ней дочерние записи. Самостоятельно Примените процедуру просмотра имеющихся ограничений для таблиц объекта DataSet Dogovor. Убедитесь, что все уникальные поля таблиц и установленные вами связи между таблицами реализованы в объектах UniqueConstraint и ForeignKeyConstraint. Задание 7. Визуальное программирование объекта DataSet Цель Создать объект DataSet средствами визуального программирования Решение Использовать панель инструментов Data среды Visual Studio.NET. Обсуждение Панель элементов управления Data среды Visual Studio,NET содержит компонент DataSet, который позволяет задавать значения свойств для набора данных с помощью окна свойств Properties вместо создания специального кода. Воспользуемся этим способом для создания объекта DataSet, аналогичного тому, который программировался в предыдущих заданиях (таблицы Empoyees и Departments). Выполните перечисленные далее действия: 1. Добавьте в проект еще одну Windows-форму. Свойству Text формы присвойте значение DataSet_Properties. 2. Разместите на форме элемент управления ListBox. 3. Из панели элементов управления Data перетащите в форму компонент DataSet. В открывшемся диалоговом окне выберите переключатель Untyped Dataset (Нетипизированный набор данных), нажмите Ок. Этот компонент невидим во время выполнения приложения, поэтому в режиме создания приложения он будет находиться под формой. 1. В окне свойств Properties этого компонента укажите значение dsE для свойства Name. 2. В окне свойств Properties этого компонента выберите свойство Tables и щелкните на кнопке с многоточием для отображения диалога Tables Collection Editor (Редактор коллекции таблиц). 3. Щелкните на кнопке Add для отображения свойств первой таблицы создаваемого набора. 4. В панели свойств Table1 Properties укажите значение Employees для свойства TableName (рис. 2.10). 5. В панели свойств Empoyees Properties выберите свойство Columns и щелкните на кнопке с многоточием для отображения диалога Columns Collection Editor (Редактор коллекций полей). Рис. 2.10. Диалоговое окно Tables Collection Editor после указания таблицы Employees 6. Щелкните на кнопке Add для отображения свойств первого поля таблицы Employees. 7. В панели свойств Column1 Properties укажите значение FirstName для свойства ColumnName первого поля. 8. Щелкните на кнопке Add для отображения свойств второго поля таблицы Empoyees. 9. В панели свойств Column1 Properties укажите значение LastName для свойства ColumnName второго поля. 10. Щелкните на кнопке Add для отображения свойств третьего поля таблицы Employees. 11. В панели свойств column1 Properties укажите значение DepartmentID для свойства СolumnName и значение System.Int32 для свойства DataType третьего поля. После выполнения этих действий диалог Column Collection Editor будет выглядеть так, как показано на рис. 2.11. Рис. 2.11. Диалоговое окно Columns Collection Editor со свойствами полей таблицы Employees 12. Щелкните на кнопке Close в диалоге Column Collection Editor. Чтобы вернуться в диалоговое окно Tables collection Editor для включения в набор данных dsE еще одной таблицы Departments. 13. Щелкните на кнопке Add для отображения свойств второй таблицы набора данных dsE. 14. В панели свойств Table1 Properties укажите значение Departments для свойства TableName второй таблицы. 15. Укажите значение 5 для свойства MinimumCapacity второй таблицы. 16. В панели свойств Departments properties укажите свойство columns и щелкните на кнопке с многоточием для отображения диалога Columns Collection Editor. 17. Щелкните на кнопке Add для отображения свойств первого поля таблицы departments. 18. Укажите значение ID для свойства ColumnName и значение System.Int32 для свойства DataType первого поля. 19. В панели свойств ID Properties укажите значение True для свойства ReadOnly, значение True для свойства Unique и значение true для свойства AutoIncrement первого поля. 20. Щелкните на кнопке Add для отображения свойств второго поля таблицы Departments. 21. В панели свойств Column1 properties укажите значение DepartmentName для свойства ColumnName. 22. В панели свойств DepartmentName properties укажите значение true для свойства Unique и значение False для свойства AllowDBNull первого поля. 23. Щелкните на кнопке Close в диалоговом окне Columns Collection Editor для возвращения в диалоговое окно Tables Collection Editor. 24. Итак, вы создали набор данных dsE с таблицами Employees и Departments, указывая для свойств те же значения, которые использовались в коде. Продолжим работу с этими компонентами и определим отношения между таблицами набора данных dsE. 25. В окне свойств Properties компонента dsE выберите свойство Relations, а затем щелкните на кнопке с изображением многоточия, чтобы отобразить диалог Relations Collection Editor (Редактор коллекции отношений). 26. Щелкните на кнопке Add для вызова диалога Relation набора dsE. 27. В текстовом окне Name укажите значение relDepartmentEmployees. 28. В текстовом окне ParentTable выберите таблицу Departments. 29. В текстовом окне ChildTable выберите таблицу Employees. 30. В поле со списком столбца Key Columns раздела columns выберите поле ID, в результате этого значение ID будет присвоено свойству ParentColumns объекта relDepartmentEmployees. 31. Аналогично в поле со списком столбца Foreign Key Columns раздела Columns выберите поле DepartmentID, в результате этого значение DepartmentID будет присвоено свойству ChildColumns объекта relDepartmentEmployees. Воспользуйтесь предлагаемыми по умолчанию значениями в списках UpdateRule (Правило обновления,) DeleteRule (Правило удаления) и Accept/RejectRule (Правило подтверждения/отказа), которые соответствуют свойствам UpdateRule, DeleteRule и Accept/RejectRule. 32. Щелкните на кнопке ОК для закрытия диалога Relation, а затем на кнопке Close диалога Relations Collection Editor. Теперь остается указать значения свойства PrimaryKey для каждой таблицы. 33. В окне свойств Properties компонента dsE выберите свойство Tables, а затем щелкните на кнопке с изображением многоточия, чтобы отобразить диалог Tables Collection Editor. 34. В списке членов набора данных Members выберите свойство PrimaryKey и щелкните на кнопке с изображением стрелки в правой части этого свойства для развертывания списка полей. 35. Установите флажки для поля (или нескольких полей) первичного ключа из списка доступных полей. Если первичный ключ охватывает несколько полей, то выберите их в правильном порядке. В нашем случае выберите сначала поле FirsName,а затем LastName, как показано на рис. 2.12. Рис. 2.12. Выбор нескольких полей для определения первичного ключа 36. Нажмите клавишу ENTER для утверждения созданного первичного ключа. 37. В списке членов набора данных Members выберите таблицу Departments. 38. В панели свойств Departments Properties выберите свойство PrimaryKey и щелкните на кнопке со стрелкой для развертывания списка полей. 39. Установите флажок для поля ID первичного ключа из списка доступных полей. 40. Щелкните на кнопке Close для закрытия текущего диалога. Чтобы убедиться в идентичности результатов, полученных в режиме создания компонентов, по сравнению с созданным ранее кодом скопируйте и вставьте некоторые процедуры из формы DataSet в форму DataSet_Properties, а затем запустите полученное приложение, выполнив перечисленные ниже действия. 1. Выберите и скопируйте процедуру AddData из формы DataSet в форму DataSet_Properties. 2. Повторите п.1 для процедуры DisplayDataSet и DisplayChildRows. 3. Включите следующий программный код в код обработчика события загрузки формы DataSet_Properties_Load в форме DataSet_Properties: Private Sub Form5_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load AddData() DisplayDataset() DisplayChildRows(dsE.Tables("Departments")) End Sub Private Sub AddData() End Sub Private Sub DisplayDataset() End Sub Private Sub DisplayChildRows(ByVal dt As DataTable) End Sub После запуска созданного приложения в поле со списком формы DataSet_Properties будут отображены все данные набора dsE и дочерние записи из таблицы Departments (рис. 2.13). Анализируя код, сгенерированный при редактировании свойств в режиме создания компонентов, можно убедиться в том, что он очень похож на созданный вручную код, описанный в предыдущих заданиях. Для просмотра автоматически созданного кода нужно выбрать форму в окне Solution Explorer, щелкнуть правой кнопкой мыши, выбрать команду меню View Code в контекстном меню, а затем раскрыть раздел Windows Form Designer generation code. Рис. 2.13. Отображение всех данных набора Department_Employees и дочерних записей с помощью параметров объекта DataSet, заданных в режиме создания компонентов (визуального программирования). Самостоятельно Создайте объект DataSet в режиме создания компонентов (панель Data) для модели Dogovor. Проверьте идентичность результатов создания таблиц, установления связей, заполнения таблиц для случая программного кодирования объекта DataSet и для случая визуального программирования. Воспользуйтесь разработанными процедурами отображения дочерних записей связанных таблиц.