Обработка баз данных на Visual Basic®.NET

Мак-Манус Джеффри П.

Голдштейн Джеки

Прайс Кевин Т.

ГЛАВА 5

ADO.NET: объект DataSet

 

 

Объект DataSet является центральным и наиболее революционным элементом модели доступа к данным ADO.NET. По сути, это кэш-область в оперативной памяти для данных из одного или нескольких источников. Его можно представить как полноценную базу данных, которая полностью находится в оперативной памяти. Вероятно, наиболее важной характеристикой объекта DataSet является его использование в отключенном режиме. Описанные в главе 4, "Модель ADO.NET: провайдеры данных", управляемые объекты провайдеров данных предоставляют функции, необходимые при физическом подключении к базе данных или другому источнику данных. А объект DataSet и связанные с ним объекты (DataTable, DataRow, DataColumn и DataRelation) предлагают богатую функциональность в отключенном от источника данных режиме работы.

Еще одной ключевой характеристикой объекта DataSet является независимость загруженных данных от их источника. Он содержит данные и позволяет манипулировать ими в реляционной манере. Поэтому DataSet и подчиненные ему объекты имеют универсальный способ применения и не связаны с каким-то отдельным провайдером данных. Таким образом, существует только один объект DataSet и не существует никаких других специализированных объектов DataSet, например SqlDataSet, OledbDataSet или OdbcDataSet.

Если объекту DataSet не известен источник данных, то как же данные загружаются в него и как изменения данных передаются источнику? Ответ на эти вопросы можно получить, познакомившись с объектом DataAdapter, который служит мостом между объектом DataSet и физическим источником данных. Объект DataAdapter содержит специальные команды чтения данных из источника данных, а также команды обновления, удаления и вставки данных в источнике. Более подробно он рассматривается в главе 6, "ADO.NET: объект DataAdapter".

 

Компоненты объекта DataSet

Объект DataSet является ключевым объектом ADO.NET и служит универсальным контейнером данных, независимо от используемого источника данных. Объект DataSet и связанные с ним подчиненные объекты предлагают реляционное представление данных, хотя он также способен загружать и сохранять свои данные в формате XML. Объект DataSet предлагает явную модель хранения данных в оперативной памяти, которая существует в полностью отключенном от источника данных состоянии и может легко передаваться между разными адресными пространствами и компьютерами.

Ниже перечислены возможности, которыми обладает объект DataSet.

• Хранение данных приложения. Объект DataSet может легко и гибко использоваться для хранения локальных данных приложения. Доступ к данным так же прост, как и доступ к данным в массиве, но объект DataSet предлагает дополнительные функции, например для сортировки и фильтрации,

• Использование удаленных данных. Объект DataSet автоматически использует формат XML для маршалинга данных (т.е. их передачи от одного компьютера к другому). Эта возможность существен но упрощает разработку приложений на основе служб, SOAP или удаленного доступа к данным на более низком уровне.

• Кэширование данных. Объект DataSet может кэшировать данные во время создания распределенных приложений на основе ASP.NET или других технологий, сокращая объем передаваемых по сети данных.

• Устойчивое хранение данных. Объект DataSet предлагает методы сохранения своих данных и информации о схеме данных в стандартном формате XML.

• Взаимодействие с пользователями. Объект DataSet эффективно поддерживает взаимодействие с пользователями для разных графических интерфейсов пользователя, комбинируя функции сортировки, фильтрации и прокрутки с возможностями их связывания с разными представлениями данных на основе Windows Forms и Web Forms.

Данные в объекте DataSet организованы в одной или нескольких объектах DataTable. Каждый объект DataTable существует независимо от источника данных, т.е. он всегда отключен от источника данных, как и DataTable. Объект DataTable всего лишь хранит несколько таблиц данных и предлагает возможности манипулирования, передачи или связывания их с элементами управления пользовательского интерфейса. На рис. 5.1 показана схема взаимодействия объекта DataSet и связанных с ним подчиненных объектов.

РИС. 5.1. Схема взаимодействия объекта DataSet с подчиненными объектами

Объекты, которые являются подчиненными по отношению к объекту DataSet, обладают перечисленными ниже возможностями.

• Объект DataTable содержит коллекции объектов DataRow, DataColumn и Constraint, а также коллекции объектов DataRelation, связанные с другими родительскими и дочерними объектами. Представление данных в этом объекте аналогично представлению данных в объекте Recordset в ADO 2.X.

• Объект DataColumn является базовой единицей извлечения данных и определения схемы для объекта DataTable. Он содержит специализированную информацию для каждого поля объекта DataTable, включая имя, тип данных и другие атрибуты (например, свойства Unique, Readonly, AllowDBNull и Кроме того, он имеет свойство Expression для вычисления значения поля или создания итогового поля.

• Объект DataRow представляет одну запись объекта DataTable и используется для добавления, извлечения и изменения данных в объекте DataTable. С его помощью можно осуществлять последовательный или непосредственный доступ к нужным записям объекта DataTable.

• Объект DataRelation определяет отношение между двумя таблицами объекта DataSet. Он представляет классическое отношение между родительской и дочерней таблицей (т.е. между ключевым и внешним полями двух таблиц). Переходы между отношениями выполняются с помощью коллекций ChildRelations и ParentRelations (объектов DataRelation) объекта DataTable.

• Объект Constraint определяет правило, согласно которому поддерживается целостность данных в объекте DataTable. Он содержит уже знакомое ограничение UniqueConstraint, гарантирующее уникальность значений таблицы, а также ограничение ForeignKeyConstraint, определяющее действия по ношению к строкам в связанной таблице. Ограничение может относиться к одному или нескольким объектам DataColumn. Каждый объект DataTable имеет свойство Constraint с коллекцией ограничений для данной таблицы.

 

Ввод данных в объект DataSet

 

Для ввода данных в таблицы DataTable объекта DataSet предусмотрены перечисленные ниже способы.

1. Программирование определений метаданных и прямая вставка данных.

2. Использование объекта DataAdapter для создания запроса по отношению к источнику данных.

3. Загрузка XML-документа.

В этой главе представлен первый из перечисленных выше способов, в главе 6, "ADO.NET: объект DataAdapter", — второй, а в главе 10, "ADO.NET и XML", — третий. Этот раздел начинается с описания базовых функций объектов DataSet и DataTable.

Учтите, что после загрузки данных в объект DataSet, использованный метод загрузки далее уже не имеет никакого значения, потому что все последующие операции с данными выполняются идентично.

 

Определение схемы объекта DataTable

Для иллюстрации основных принципов работы следует создать простую форму с помощью перечисленных ниже действий.

1. Запустите интегрированную среду разработки приложений Visual Studio .NET.

2. Создайте новый проект Visual Basic Windows Application. Для этого в диалоговом окне New Project (Новый проект) выберите тип проекта Visual Basic Project в области Project Types (Типы проектов), а затем шаблон Windows Application (Приложение Windows) в области Templates (Шаблоны).

3. Назовите проект DataSetCode.

4. Укажите путь к файлам проекта.

5. Увеличьте размер формы Form1.

6. В окне Properties укажите значение frmDataSets для свойства (Name) и значение DataSets для свойства Text формы Form1.

7. В верхнем левом углу формы создайте кнопку, перетаскивая ее из панели элементов управления.

8. В окне Properties укажите значение btnCreateDS для свойства (Name) и значение Create DataSet для свойства Text этой кнопки.

9. В правой части формы создайте поле со списком, перетаскивая его из панели элементов управления.

10. В окне Properties укажите значение lstOutput для свойства (Name).

11. Увеличьте размер поля со списком, чтобы оно занимало до 80% всей площади формы.

В верхней части файла введите следующий код:

Imports System

Imports System.Data

Затем в определении класса формы frmDataSets введите приведенный ниже код.

Private dsEmployeelnfo As DataSet

Private Sub btnCreateDS_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles btnCreateDS.Click

 CreateDataSet()

 AddData()

 DisplayDataSet()

End Sub

Подпрограмма btnCreateDS_Click обработки щелчков на кнопке Create DataSet вызывает три другие подпрограммы для каждой фазы работы приложения. Переменная dsEmployeelnfo является объектом DataSet, доступ к которому выполняется с помощью подпрограмм внутри подпрограммы btnCreateDS_Click.

НА ЗАМЕТКУ

Обычно объекты DataTable содержатся в объекте DataSet, но объект DataSet можно использовать непосредственно, как правило в наиболее простых ситуациях, когда не требуется организовывать связь между несколькими таблицами. 

Итак, прежде всего нужно определить схему (или структуру) всех используемых таблиц. Для этого нужно определить все объекты DataColumn таблицы и указать их свойства, как показано на примере подпрограммы CreateDataSet () в листинге 5.1. 

ЛИСТИНГ 5.1. Код создания Объектов DataSet и DataTable

Private Sub CreateDataSet()

 ' Создание объекта dsEmployeeInfo.

 dsEmployeelnfo = New DataSet()

 ' Создание таблицы Employees.

 Dim dtEmployees As DataTable = New DataTable("Employees")

 dtEmployees.CaseSensitive = False

 dtEmployees.Columns.Add("FirstName", Type.GetType("System.String"))

 dtEmployees.Columns.Add("LastName", Type.GetType("System.String"))

 dtEmployees.Columns.Add("DepartmentID", Type.GetType("System.Int32"))

 ' Вставка таблицы Employees в объект EmployeeInfo.

 dsEmployeeInfo.Tables.Add(dtEmployees)

 ' Создание таблицы Departments с

 ' помощью перегруженной версии конструктора.

 ' Это более длительный способ при создании стандартных полей,

 ' но он позволяет задавать другие свойства полей (например, Readonly & Unique)

 ' до включения поля DataColumn в коллекцию Columns.

 Dim dtDepartments As DataTable

 dtDepartments = New DataTable()

 dtDepartments.TableName = "Departments"

 dtDepartments.MinimumCapacity = 5

 dtDepartments.CaseSensitive = False

 Dim NewColumn As New DataColumn()

 With NewColumn

  .ColumnName = "ID"

  .DataType = Type.GetType("System.Int32")

  .Readonly = True

  .Unique = True

  .AutoIncrement = True

 End With

 dtDepartments.Columns.Add(NewColumn)

 NewColumn = New DataColumn()

 With NewColumn

  .ColumnName = "DepartmentName"

  .DataType = Type.GetType("System.String")

  .Unique = True

  .AllowDBNull = False

 End With

 dtDepartments.Columns.Add(NewColumn)

 ' Включение таблицы Departments в объект dsEmployeeInfo.

 dsEmployeeInfo.Tables.Add(dtDepartments)

End Sub

После создания экземпляра dsEmployeeInfo объекта DataSet создается таблица Employees с помощью перегруженных конструкторов объекта DataTable с использованием параметра – имени таблицы. Затем задается значение False для свойства CaseSensitivity объекта DataTable. Это свойство определяет, будут ли операции сортировки, поиска и фильтрации выполняться с учетом регистра символов. По умолчанию значение этого свойства определяется как значение свойства CaseSensitivity родительского объекта DataSet или принимается равным False, если объект DataTable создан независимо от объекта DataSet.

НА ЗАМЕТКУ

Свойство CaseSensitivity применяется только для данных объекта DataTable и не влияет на имена самих объектов DataTable. Например, объект DataSet может  иметь две таблицы (или отношения) с именами или mytable или Mytable. Для работы с ними нужно записывать имена с точным указанием регистра символов, поскольку их поиск ведется с учетом регистра (case-sensitive search). Однако это не обязательно при наличии только одной таблицы с таким именем, поскольку при этом используется поиск без учета регистра (case-insensitive search).

Затем создаются определения полей с помощью метода Add объекта Column по указанному имени поля и типу данных. Учтите, что здесь указываются .NET-совместимые типы данных, а не используемые в базе данных типы. При отсутствии типа данных для него по умолчанию принимается строковый тип. Наконец, таблица Employee включается в объект dsEmployeeInfo.

Далее этот процесс повторяется для таблицы Departments, но теперь уже с перегруженными конструкторами и функциями. Таким образом та же цель достигается другим способом. Разработчик может самостоятельно выбрать наиболее подходящий способ на основе собственного вкуса, корпоративных стандартов или особенностей поставленной задачи.

Для свойства MinimumCapacity объекта dtDepartments задается значение 5, т.е. экземпляр объекта DataTable создается уже с пятью записями. Указание для него другого значения (отличного от используемого по умолчанию значения 25) позволяет управлять выделением ресурсов и оптимизировать производительность в критических ситуациях. Конечно, на самом деле эти пять записей фактически появятся только после того, как пользователь добавит их в таблицу DataTable, а пока для них резервируется место.

Кроме того, перед добавлением полей в коллекцию Columns для них задаются значения других свойств. Перечисленные ниже свойства Readonly, Unique, AllowDBNull и AutoIncrement уже наверняка знакомы тем, кто имеет опыт создания приложений для работы с базами данных.

Присвоение свойству Readonly значения True указывает на то, что значение поля нельзя изменить.

Присвоение свойству Unique значения True указывает на то, что значения данного поля во всех записях таблицы должны быть уникальными. Это свойство реализуется с помощью автоматического создания ограничения UniqueConstraint для данного поля. Этот способ более подробно рассматривается в разделе об ограничениях таблицы далее в главе.

Присвоение свойству AllowDBNull значения True указывает на то, что в данном поле допускается использование неопределенных значений.

Присвоение свойству AutoIncrement значения True указывает на то, что значение поля увеличивается при каждом добавлении в таблицу новой записи. Для указания начального значения и приращения используются свойства AutoIncrementSeed и AutoIncrementStep.

НА ЗАМЕТКУ

Объект DataTable принимает новую запись и присваивает автоматически увеличенное значение для поля, свойство которого AutoIncrement имеет значение True, только если значение поля отличается от принимаемого по умолчанию.

Среди других свойств объекта DataColumn следует отметить MaxLength (для полей с данными типа String), DefaultValue и Table. Поле также можно определить с выражением для вычисления значения, создания итогового поля или фильтрования строк. Такое выражение может состоять из имен полей текущей записи или других записей, констант, операторов, символов подстановки, итоговых и других функций. Более подробную информацию и примеры таких выражений можно найти в справочных материалах для свойства Expression объекта DataColumn.

 

Вставка данных в объект DataTable

После определения объекта DataTable и его схемы можно начинать ввод данных.

В листинге 5.2 приводится код вставки записей с данными в DataTable. Подпрограмма AddData включает четыре записи с данными в таблицу Departments и три записи с данными в таблицу Employees следующим образом.

1. Сначала создается новый экземпляр объекта DataRow для нужной таблицы с помощью метода NewRow.

2. Затем присваиваются значения полям этой записи.

3. После этого запись включается в коллекцию записей Rows таблицы с помощью метода Add свойства Rows таблицы.

ЛИСТИНГ 5.2. Код программного ввода данных в объект DataTable

Private Sub AddData ()

 Dim dtDepartments As DataTable = dsEmployeeInfo.Tables ("Departments")

 Dim dtEmployees As DataTable = dsEmployeeInfo.Tables("Employees")

 ' Вставка четырех записей в таблицу Departments.

 Dim rowDept As DataRow

 rowDept = dtDepartments.NewRow

 rowDept("ID") = 11

 rowDept("DepartmentName") = "Administration"

 dtDepartments.Rows.Add(rowDept)

 rowDept = dtDepartments.NewRow rowDept("ID") = 22

 rowDept("DepartmentName") = "Engineering"

 dtDepartments.Rows.Add(rowDept)

 rowDept = dtDepartments.NewRow

 rowDept("ID") = 33

 rowDept("DepartmentName") = "Sales"

 dtDepartments.Rows.Add(rowDept)

 rowDept = dtDepartments.NewRow rowDept("ID") =44

 rowDept("DepartmentName") = "Marketing"

 dtDepartments.Rows.Add(rowDept)

 ' Вставка трех записей в таблицу Employees.

 Dim rowEmployee As DataRow

 rowEmployee = dtEmployees.NewRow

 rowEmployee("FirstName") = "Jackie"

 rowEmployee("LastName") = "Goldstein"

 rowEmployee("DepartmentID") = 22

 dtEmployees.Rows.Add(rowEmployee)

 rowEmployee = dtEmployees.NewRow

 rowEmployee("FirstName") = "Jeffrey"

 rowEmployee("LastName") = "McManus"

 rowEmployee("DepartmentID") = 33

 dtEmployees.Rows.Add(rowEmployee)

 rowEmployee = dtEmployees.NewRow

 rowEmployee("FirstName") = "Sam"

 rowEmployee("LastName") = "Johnson"

 rowEmployee("DepartmentID") = 33

 dtEmployees.Rows.Add(rowEmployee)

End Sub

НА ЗАМЕТКУ

Новую запись можно вставить в таблицу, передавая методу Add массив объектов, содержащих данные в порядке следования полей в определении таблицы. В листинге 5.2 этот способ мог быть представлен так:

Dim empData(2) As Object

empData(0) = "Sam"

empData(1) = "Johnson"

empData(3) = 33

dtEmployees.Rows.Add(empData)

 

Обновление данных в объекте DataSet

 

Для обновления отдельной записи таблицы необходимо просто организовать доступ к нужной записи и присвоить новое значение одному из полей. Например, для изменения номера отдела, к которому относится Sam Johnson, можно использовать следующую строку кода:

dtEmployees.Rows(2) ("DepartmentID") = 2

НА ЗАМЕТКУ

В этой строке кода номер записи (2) приводится из предположения, что нам известно расположение записей в таблице. Но с практической точки зрения этот способ не совсем удачен, более эффективный и безопасный способ основан на поиске нужной строки (или нескольких строк). Он описывается в разделе о доступе к данным в объекте DataTable далее в главе.

Таким образом, в данные можно вносить произвольное количество изменений, но все они не будут зафиксированы до тех пор, пока не будет вызван метод AcceptChanges. Для отката изменений, внесенных после загрузки данных или последнего вызова метода AcceptChanges, используется метод RejectChanges.

НА ЗАМЕТКУ

Метод AcceptChanges (и метод RejectChanges) можно использовать на нескольких разных уровнях, т.е. в классах DataTable, DataSet и DataRow. Вызов метода AcceptChanges объекта DataSet приведет к вызову этого метода для каждой таблицы объекта DataSet. Аналогично вызов метода AcceptChanges DataTable приведет к вызову этого метода для каждой записи объекта DataTable. Таким образом можно зафиксировать изменения отдельно для каждой записи или сразу для всех данных объекта DataSet. To же самое относится к методу RejectChanges.

Строки можно вставлять и удалять целиком. До сих пор был показан только процесс создания записей, а удаление записи основано на методе Remove объекта DataRowCollection (т.е. свойство Rows объекта DataTable). Этот метод полностью удаляет запись из коллекции. Другой способ удаления записи основан на методе Delete объекта DataRow. Этот метод отмечает запись для удаления, которое на самом деле произойдет только после вызова метода AcceptChanges.

После вызова метода Remove все данные записи будут удалены необратимо, даже если после этого вызвать метод RejectChanges.

 

Состояние и версия записи

Каждый объект DataRow имеет свойство RowState, которое обозначает текущее состояние или статус записи. Кроме того, каждая запись хранит информацию о четырех разных версиях своего значения. По мере редактирования записи изменяется ее состояние и версия значения. В табл. 5.1 приведено краткое описание свойства RowState, а в табл. 5.2 – краткое описание свойства DataRowVersion.

Таблица 5 1. Свойство RowState

Член перечисления Описание
Unchanged Никаких изменений не внесено с момента последнего вызова метода AcceptChanges или после загрузки данных с помощью объекта DataAdapter
Added Запись вставлена в коллекцию DataRowCollection (т.е. в свойство Rows объекта DataTable), но метод AcceptChanges еще не вызывался
Deleted Метод Delete вызван для удаления записи, но метод AcceptChanges еще не вызывался
Modified Запись изменена, но метод AcceptChanges еще не вызывался
Detached Запись создана, но не добавлена в коллекцию DataRowCollection, либо метод Remove вызван для удаления записи из коллекции DataRowCollection, либо метод Delete вызван для удаления записи и вызван метод AcceptChanges  

Таблица 5.2. Свойство DataRowVersion

Член перечисления Описание
Original Исходные значения записи. Эта версия не существует для строки со значением Added свойства состояния записи RowState
Current Текущее (возможно измененное) значение записи. Эта версия не существует для строки со значением Deleted свойства состояния записи RowState
Default Используемая по умолчанию версия записи, которая зависит от текущего состояния записи. Если состояние записи RowState имеет значение Deleted, то по умолчанию используется версия Original, если значение Detached – версия Proposed. Во всех остальных случаях по умолчанию используется версия Current
Proposed Предлагаемое значение записи. Эта версия существует только во время редактирования (начинается с вызова метода BeginEdit и заканчивается вызовами методов EndEdit или CancelEdit) либо для записи, которая еще не включена в коллекцию DataRowCollection  

Если запись находится в состоянии Deleted в момент вызова метода AcceptChanges, то она удаляется из коллекции DataRowCollection. В противном случае версия записи Original обновляется версией Current, а состояние записи становится равным Unchanged.

НА ЗАМЕТКУ

Поскольку все четыре версии записей возможны далеко не всегда, то можно вызвать метод HasVersion для объекта DataRow, чтобы проверить конкретную версию в текущем состоянии. Методу HasVersion передается значение одного из членов перечисления DataRowVersion, а он возвращает логическое значение, которое указывает, существует ли данная версия.

Наоборот, если запись находится в состоянии Added в момент вызова метода RejectChanges, то запись удаляется из коллекции DataRowCollection. В противном случае версия записи Current обновляется версией Original, а состояние записи становится равным Unchanged.

НА ЗАМЕТКУ

Доступ к версии поля DataRow (при условии, что она существует) можно получить, указывая нужную версию в качестве второго параметра для метода объекта DataRow при явном или неявном вызове этого метода:

dtEmployees.Rows(2).Item("lastname", DataRowVersion.Proposed)

или

dtEmployees.Rows(2)("lastname", DataRowVersion.Original)

Здесь следует особое внимание обратить на версию Proposed объекта DataRow. При вызове метода BeginEdit объекта DataRow обычные действия и события приостанавливаются, что позволяет пользователю вносить несколько изменений в запись, не применяя правил проверки вводимых значений. В этом режиме вносимые изменения отражаются не в версии Current, а в версии Proposed. И только после вызова метода EndEdit значения версии Proposed становятся (переносятся) значениями версии Current. Любые изменения можно отменить, вызывая метод CancelEdit до вызова метода EndEdit. Учтите, что изменения будут зафиксированы только после вызова метода AcceptChanges.

 

Обработка ошибок ввода данных в записи и поля

В модели ADO.NET предусмотрен гибкий механизм определения и обработки ошибок ввода данных в записи и поля объекта DataTable. Этот механизм позволяет использовать в приложении правила проверки вводимых данных, сообщать об обнаруженных ошибках, но откладывать их исправление до определенного момента в потоке выполнения приложения. (Не путайте их с обычными системными исключительными ситуациями времени выполнения, которые обрабатываются стандартными конструкциями платформы.NET Framework на основе операторов Try-Catch-Finally.)

Если приложение обнаруживает ошибку ввода данных, то оно создает описание найденной ошибки для записи или отдельного поля. Для обозначения наличия ошибки в отдельной записи используется свойство RowError объекта DataRow, например ему присваивается строка с комментарием "Something wrong here" (Здесь произошла какая-то ошибка).

myDataRow.RowError = "Something wrong here"

А для обозначения наличия ошибки в отдельном поле используется метод SetColumnError объекта DataRow, например с его помощью присваивается строка с комментарием "Bad data in this column" (Неверные данные в этом поле).

myDataRow.SetColumnError (2, "Bad data in this column")

Для извлечения строк с сообщениями о таких ошибках в полях или записях можно использовать свойство RowError или вызвать метод GetColumnError. Для сброса значения свойства RowError нужно присвоить ему пустую строку (" ") или использовать метод ClearErrors объекта RowError, который очищает свойство RowError и удаляет все сообщения об ошибках, заданные методом SetColumnError.

Объект DataRow содержит свойство HasErrors, которое имеет значение True, если в поле или записи обнаружены какие-либо ошибки. Значение этого свойства отображается на свойство HasErrors объекта DataTable, которое при наличии ошибок в поле или записи также равно True. Аналогично, если свойство HasErrors для любой таблицы DataTable объекта DataSet имеет значение True, то значение свойства HasErrors объекта DataSet также равно True. Метод GetErrors объекта DataTable возвращает массив объектов DataRow, которые содержат ошибки. В целом этот простой механизм позволяет быстро определить любые ошибки ввода данных, как показано в листинге 5.3.

Листинг 5.3. Пример обнаружения ошибок во всех таблицах объекта DataSet

Private Sub ResolveErrors(myDataSet as DataSet)

 Dim rowsWithErrors() As DataRow

 Dim myTable As DataTable

 Dim myCol As DataColumn

 Dim currRow As Integer

 For Each myTable In myDataSet.Tables

  If myTable.HasErrors Then

   ' Извлечение всех записей с ошибками.

   RowsWithErrors = myTable.GetErrors()

   For currRow = 0 To rowsWithErrors.GetUpper

    For Each myCol In myTable.Columns

     ' Найти поля с ошибками и выбрать

     ' способ их обработки.

     ' Ошибка в поле извлекается с помощью метода

     ' rowsWithErrors(currRow).GetColumnError(myCol)

    Next

    ' очистка ошибок.

    rowsWithErrors(currRow).ClearErrors

   Next currRow

  End If

 Next

End Sub

 

Доступ к данным с помощью объекта DataTable

 

Поскольку объект DataSet и содержащийся в нем объект DataTable всегда наполнены данными и не подключены к источнику данных, метод доступа к записям данных в них существенно отличается от методов доступа в ADO и других моделях доступа к данным (например, ODBC, DAO или RDO). Поскольку все данные доступны одновременно, в модели ADO.NET не существует понятия текущей записи. Поэтому нет никаких свойств или методов для перемещения от одной записи к другой. Каждый объект DataTable имеет свойство Rows, которое является набором объектов DataRow. Доступ к отдельному объекту осуществляется с помощью индекса или оператора For Each. Таким образом, в модели ADO.NET предлагается более простой и эффективный способ доступа и перемещения, аналогичный доступу к элементам массива.

В листинге 5.4 показан код подпрограммы DisplayDataSet, которая отображает содержимое ранее созданных таблиц с загруженными в них данными. В ней применяется циклический обход всех элементов коллекции, т.е. коллекций Rows и Columns, для отображения содержимого таблицы Employees. Далее используется альтернативный метод доступа к записям и полям с помощью числового индекса для отображения содержимого таблицы Departments.

ЛИСТИНГ 5.4. Код отображения данных в объектах DataTable

Private Sub DisplayDataSet()

 Dim dr As DataRow

 Dim dc As DataColumn

 Me.lstOutput.Items.Add("DISPLAY DATASET")

 Me.lstOutput.Items.Add("============")

 ' Отображение данных из таблицы Employees.

 For Each dr In dsEmployeeInfo.Tables("Employees").Rows

  For Each dc In _

   dsEmployeeInfo.Tables("Employees").Columns

   Me.lstOutput.Items.Add( _

    dc.ColumnName & ": " & dr(dc))

  Next

  Me.lstOutput.Items.Add ("============")

 Next

 Me.lstOutput.Items.Add("")

 ' Отображение данных из таблицы Departments.

 ' Пример использования индексов вместо оператора For Each.

 Dim row As Integer

 Dim col As Integer

 For row = 0 To dsEmployeeInfo.Tables("Departments").Rows.Count – 1

  For col = 0 To dsEmployeeInfo.Tables("Departments").Columns.Count – 1

   Me.lstOutput.Items.Add( _

    dsEmployeeInfo.Tables("Departments").Columns(col).ColumnName & ":" & _

    dsEmployeeInfo.Tables("Departments").Rows(row)(col))

  Next col

  Me.lstOutput.Items.Add("============")

 Next row

End Sub

Аналогично можно создать подпрограмму более общего типа для обхода не только записей и полей, но и таблиц объекта DataSet, как показано в листинге 5.5.

ЛИСТИНГ 5.5. Код обхода таблиц из объекта DataSet

Private Sub DisplayDataSet(ByVal ds As DataSet)

 ' Общая подпрограмма для отображения содержимого объекта DataSet.

 ' Отображаемый объект DataSet передается как параметр.

 Dim dt As DataTable

 Dim dr As DataRow

 Dim dc As DataColumn

 Me.lstOutput.Items.Add("DISPLAY DATASET")

 Me.lstOutput.Items.Add("============")

 For Each dt In ds.Tables

  Me.lstOutput.Items.Add(")

  Me. lstOutput.Items.Add("TABLE: " & dt.TableName)

  Me.lstOutput.Items.Add(" ")

  For Each dr In dt. Rows

   For Each dc In dt.Columns

    Me.lstOutput.Items.Add(dc.ColumnName S ": " & dr(dc))

   Next

   Me.lstOutput.Items.Add ("============")

  Next

 Next dt

End Sub

Обратите внимание, что здесь перегружается уже упомянутый ранее метод DisplayDataSet, который теперь принимает в качестве параметра объект DataSet.

Попробуйте запустить полученное приложение; для этого введите упомянутый ранее код в проект DataSetCode и щелкните на кнопке Create DataSet. В результате этого действия создается объект DataSet и наполняется данными, которые затем отображаются в текстовом поле формы, как показано на рис. 5.2.

РИС. 5.2. Результат создания объекта DataSet с таблицами Employees и Departments, наполнения их данными и последующего отображения

НА ЗАМЕТКУ

Для проверки обобщенной версии подпрограммы DisplayDataSet в подпрограмме btnCreateDS_Click следует заменить вызов ее исходной версии DisplayDataSet новой перегруженной версией DisplayDataSet(dsEmployeeInfо) с параметром dsEmployeeInfo.

 

Поиск, фильтрация и сортировка записей

Иногда нужно работать не со всеми, а только с некоторыми записями объекта DataSet, например с одной записью или подмножеством всех записей. Для этого можно использовать методы Find и Select.

Метод Find принадлежит свойству DataRowCollection объекта DataTable, который используется для поиска и возвращения единственной строки, указанной с помощью значения первичного ключа таблицы.

Перед использованием метода Find для обнаружения некоторой строки в таблице Departments, которая определена в листинге 5.1, нужно определить первичный ключ таблицы. Это можно сделать с помощью присвоения одного или нескольких полей свойству PrimaryKey таблицы. (Даже если первичный ключ создан на основе единственного поля, свойство PrimaryKey таблицы является массивом объектов DataColumn.)

В приведенных ниже строках кода, которые следует добавить в конце подпрограммы CreateDataSet из листинга 5.1, первичный ключ таблицы Departments создается на основе поля DepartmentName.

Dim pk(0) As DataColumn

pk(0) = dtDepartments.Columns("DepartmentName")

dtDepartments.PrimaryKey = pk

НА ЗАМЕТКУ

При создании первичного ключа с помощью свойства PrimaryKey для объекта DataTable на основе одного поля для свойства AllowDBNull этого поля автоматически задается значение False, а для свойства Unique — значение True. А если первичный ключ создан на основе нескольких полей, то только для свойства AllowDBNull этих полей автоматически задается значение False.

После определения первичного ключа метод Find используется так, как показано ниже.

Dim desiredRow As DataRow

desiredRow = dtDepartments.Rows.Find("sales")

Здесь переменной desiredRow присваивается объект DataRow с указанным значением первичного ключа или значение Nothing, если такая запись не будет найдена.

Если первичный ключ таблицы основан на нескольких полях, то соответствующие значения первичного ключа передаются в виде элементов массива (типа Object) методу Find.

' Указание первичного ключа.

Dim pk(0) As DataColumn

pk(0) = dtEmployees.Columns("FirstName")

pk(1) = dtEmployees.Columns("LastName")

dtEmployees.PrimaryKey = pk

' Попытка поиска нужной записи.

Dim desiredRow As DataRow

Dim desiredValues (1) As Object

desiredValues(0) = "Sam"

desiredValues(1) = "Johnson"

desiredRow = dtEmployees.Rows.Find(desiredValues)

Метод Select объекта DataTable возвращает массив объектов DataRow. Возвращаемые строки могут соответствовать критерию фильтрования, порядку сортировки и/или спецификации состояния (объект DataViewRowState пространства имен System.Data).

Приведенный ниже код возвращает и отображает имена всех сотрудников с фамилией Johnson.

Dim selectedRows () As DataRow

selectedRows = dtEmployees.Select("LastName = 'Johnson'")

Dim i As Integer

For i = 0 to selectedRows.GetUpperBound(0)

 MessageBox.Show(selectedRows(i)("FirstName"))

Next

Для возврата записей, отсортированных в порядке убывания, можно отредактировать строку с методом Select так, как показано ниже.

selectedRows = dtEmployees. Select ("LastName = 'Johnson'", "FirstName DESC")

Наконец, указание состояния записи в качестве аргумента метода Select позволяет извлекать записи с определенной версией данных непосредственно в процессе их редактирования. Например, для извлечения всех исходных значений записей даже после их редактирования (но еще до вызова метода AcceptChanges) следует указать значение OriginalRows перечисления DataViewRowState, как показано ниже.

selectedRows = dtEmployees.Select(Nothing, Nothing, DataViewRowState.OriginalRows)

Для отбора вновь добавленных записей с фамилией Johnson следует указать значение Added перечисления DataViewRowState, как показано ниже.

selectedRows = dtEmployees. Select ("LastName = 'Johnson'", Nothing, DataViewRowState.Added)

А если нужно отобрать вновь добавленные записи с фамилией Johnson и отсортировать их по имени, то в таком случае следует использовать приведенный ниже код.

selectedRows = dtEmployees.Select("LastName = 'Johnson'", "FirstName DESC", DataViewRowState.Added)

В табл. 5.3 приведены все возможные варианты состояния записи, которые могут быть представлены членами перечисления DataViewRowState. Упомянутые здесь изменения связаны с последней загрузкой данных или вызовом метода АcсеptChanges.

Таблица 5.3. Члены перечисления DataViewRowState 

Член Описание
Added Вновь созданные записи
CurrentRows Все текущие записи (включая новые, измененные или неизмененные записи)
Deleted Все записи, отмеченные как удаленные
ModifiedCurrent Текущая версия измененной записи
ModifiedOriginal Исходная версия измененной записи
None Нет сведений
OriginalRows Все исходные записи, включая неизмененные и удаленные, кроме новых записей
Unchanged Все неизмененные записи 

 

Отношения между таблицами

Поскольку объект DataSet может содержать несколько таблиц, то вполне естественно, что между ними могут существовать какие-то отношения (по крайней мере, если речь идет о реляционных базах данных). В модели ADO.NET для этого предусмотрен объект DataRelation.

Объект DataRelation устанавливает соответствие между полями в двух таблицах, которые имеют родительско-дочерние отношения или связаны первичным и внешним ключами. Классический пример такого отношения существует между таблицами с данными о клиентах и с данными о заказах, где одна запись клиента может быть связана с несколькими записями его заказов. Запись клиента является родительской, а записи заказов — дочерними. Продолжим обсуждение этой темы на примере родительской таблицы  Department и дочерней таблицы Employees, которые находятся в одном объекте  DataSet.

Объект DataRelation выполняет две разные функции.

• Позволяет переходить от одной связанной таблицы к другой и обратно, т.е. при работе с родительской записью можно получить доступ к ее дочерним записям, а при работе с дочерней записью — доступ к ее родительской записи.

• Позволяет задавать и поддерживать ссылочную целостность, например каскадные обновления данных в связанных таблицах при выполнении каких-либо изменений в любой из связанных таблиц.

Продолжим работу с упомянутым ранее проектом DataSetCode.

1. Создайте новую кнопку непосредственно под кнопкой Create DataSet, перетаскивая ее из панели элементов управления.

2. В окне свойств Properties укажите значение btnCreateRelations для свойства (Name) и значение Create Relations для свойства Text.

3. Вставьте код, показанный в листинге 5.6.

Листинг 5.6. Код создания и отображения отношений между таблицами

Private Sub btnCreateRelations_Click( _

 ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles btnCreateRelations.Click

 Dim As DataRelation

 CreateDataSet()

 ' Создание отношения между таблицами Departments и Employees.

 rel = dsEmployeeInfо.Relations.Add(_

  "relDepartmentEmployees", _

  dsEmployeeInfo.Tables("Departments").Columns("ID"), _

  dsEmployeeInfo.Tables("Employees").Columns("DepartmentID"))

 DisplayRelations(dsEmployeeInfo)

End Sub

Private Sub DisplayRelations(ByVal ds As DataSet)

 Dim rel As DataRelation

'  Вывод имен полей созданного отношения.

 Me.lstOutput.Items.Add("")

 Me.lstOutput.Items.Add("DISPLAY RELATIONS")

 For Each rel In ds.Relations

  ' Вывод имени отношения.

  Me.lstOutput.Items.Add("NAME: " & rel.RelationName)

  ' Вывод имени родительской таблицы и ее поля,

  ' которое входит в созданное отношение.

  Me.IstOutput.Items.Add("PARENT: " & _

   rel.ParentTable.ToString & " – " & _

   rel.ParentColumns(0).ColumnName)

  ' Вывод имени дочерней таблицы и ее поля,

  ' которое входит в созданное отношение.

  Me.lstOutput.Items.Add("CHILD: " & _

   rel.ChildTable.ToString & " – " & _

   rel.ChildColumns(0).ColumnName)

 Next

 Me.lstOutput.Items.Add("")

End Sub

Сначала нужно создать объект DataRelation. Каждый объект DataSet содержит коллекцию отношений, которая доступна как свойство этого объекта Relations. Это свойство имеет тип DataRelationCollection и поддерживает несколько перегруженных версий метода Add. Версия, использованная в листинге 5.6, принимает три аргумента: имя отношения, ссылку на объект DataColumn в родительской таблице, а также ссылку на объект DataColumn в дочерней таблице. Если отношение между таблицами охватывает более одного поля, то следует использовать другую версию метода Add с аргументами-массивами объектов DataColumn.

Подпрограмма DisplayRelations циклически обходит все отношения свойства Relations объекта DataSet и выводит в текстовом поле имя отношения, имя родительской таблицы и ее поле, которое входит в созданное отношение, а также имя дочерней таблицы и ее поле, которое входит в созданное отношение.

НА ЗАМЕТКУ

Для создания более обобщенной версии подпрограммы DisplayRelations можно было бы вставить код циклического обхода всех полей в свойствах-массивах РаrentColumns и ChildColumns, а не просто отображать их первые элементы.

После компоновки проекта DataSetCode и запуска полученного приложения щелкните на кнопке Create Relations, и в текстовом поле будет выведена информация о вновь созданном отношении между таблицами Employees и Departments.

Кроме коллекции Relations объекта DataSet, которая содержит все отношения, определенные между таблицами объекта DataSet, каждый объект DataTable также содержит две коллекции отношений (т.е. два свойства): ParentRelations и ChildRelations, которые содержат отношения между данным объектом DataTable и связанной с ним другой (дочерней или родительской) таблицей.

После создания отношения между таблицами можно организовать доступ к связанным данным в них. Начните с создания кнопки и вставки кода, приведенного в листинге 5.7.

1. Создайте новую кнопку непосредственно под кнопкой Create Relations, перетаскивая ее из панели элементов управления.

2. В окне свойств Properties укажите значение btnChildRows для свойства (Name) и значение Child Rows для свойства Text.

3. Вставьте код, показанный в листинге 5.7.

Листинг 5.7. Код отображения родительских и дочерних данных из связанных таблиц

Private Sub btnChildRows_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles btnChildRows.Click

 Dim rel As DataRelation CreateDataSet()

 AddData()

 ' Создание отношения между таблицами Departments и Employees

 rel = dsEmployeeInfо.Relations.Add("relDepartmentEmployees", _

  dsEmployeeInfo.Tables("Departments").Columns("ID"), _

  dsEmployeeInfo.Tables("Employees").Columns("DepartmentID"))

 DisplayChildRows(dsEmployeeInfo.Tables("Departments"))

End Sub

Private Sub DisplayChidRows(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

 Me.lstOutput.Items.Add("")

 Me.lstOutput.Items.Add("CHILD ROWS")

 For Each row In dt.Rows

  For Each rel In dt.ChildRelations

   Me.lstOutput.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

    Me.lstOutput.Items.Add(rowData)

   Next i

  Next rel

 Next row

End Sub

Подпрограмма btnChildRows_Click для обработки щелчков на кнопке Child Rows сначала создает объект DataSet и объекты DataTable с помощью подпрограммы CreateDataSet (код которой приведен в листинге 5.1), а затем наполняет их данными с помощью подпрограммы AddData (код которой приведен в листинге 5.2). После этого между таблицами Employees и Departments создается отношение с помощью кода, который приведен в листинге 5.6. Наконец, для вывода данных из записей в текстовом поле формы вызывается подпрограмма DisplayChildRows, которой в качестве аргумента (родительской таблицы) передается таблица Departments.

Подпрограмма DisplayChildRows содержит трижды вложенный цикл для отображения всех полей из каждой связанной дочерней таблицы (в данном случае только одной) для каждой записи родительской таблицы. При этом каждая запись родительской таблицы передается циклу как аргумент, происходит обход всех отношений, определенных в свойстве ChildRelations таблицы, отображается имя таблицы, имя поля в родительской таблице, а также значение поля в текущей записи. Затем вызывается метод GetChildRows с текущим отношением в качестве аргумента и возвращается массив объектов DataRow, содержащих дочерние записи. Для каждой записи отображаются все поля с префиксами в виде четырех звездочек и имени дочерней таблицы.

НА ЗАМЕТКУ

Некоторые версии метода GetChildRows принимают дополнительный аргумент, который указывает на используемую версию возвращаемых записей с помощью членов перечисления DataRowVersion из табл. 5.2. Аналогичные методы предусмотрены для извлечения родительской записи (или нескольких родительских записей) по заданной дочерней записи.

Здесь у читателя может возникнуть вопрос: почему упоминается несколько родительских записей? Разве дочерняя запись может иметь несколько родительских записей? Дело в том, что кроме родительских полей, заданных на основе полей с уникальными значениями, могут создаваться родительские поля на основе полей с повторяющимися значениями. Поэтому в таких случаях вместо одной родительской записи (GetParentRow) приходится извлекать несколько родительских (GetParentRows).

РИС. 5.3. Результаты отображения родительских и дочерних записей из таблиц Employees и Departments

После компоновки проекта DataSetCode и запуска полученного приложения щелкните на кнопке Child ROWS, и в текстовом поле будут выведены значения всех родительских записей из таблицы Employees для каждой родительской записи из таблицы Departments (рис. 5.3).

 

Ограничения

Ограничениями называются правила, которые вводятся для поддержания целостности данных в таблице. В модели ADO.NET применяется два типа ограничений целостности данных: UniqueConstraint и ForeignKeyConstraint. Ограничение UniqueConstraint гарантирует, что все значения в указанных полях будут уникальны в рамках всей таблицы. Ограничение ForeignKeyConstraint определяет связь на основе первичного и внешнего ключа в двух таблицах и выполняемые действия в случае добавления, удаления или изменения родительской записи (т.е. первичного ключа). При нарушении заданных ограничений генерируется исключительная ситуация.

Учтите, что ограничения применяются только тогда, когда свойство EnforceConstraints объекта DataSet имеет значение True, которое используется по умолчанию.

Хотя ограничения можно создавать непосредственно, довольно часто они создаются косвенно. Фактически в приведенном ранее примере уже было создано несколько ограничений. Объект UniqueConstraint автоматически создается и включается в коллекцию Constraints объекта DataTable, если для свойства Unique объекта DataColumn задано значение True. Кроме того, объекты UniqueConstraint и ForeignKeyConstraint автоматически создаются при создании отношения между двумя таблицами. В таком случае объект UniqueConstraint создается для связанных полей в родительской таблице, а объект ForeignKeyConstraint — для связанных полей в дочерней таблице.

НА ЗАМЕТКУ

Конечно, объект DataRelation можно создать без определения ограничений, но эффективность такого способа весьма спорна.

Для создания и отображения ограничений таблиц используемого объекта DataSet в данном примере создадим еще одну кнопку и вставим код, приведенный в листинге 5.8.

1. Создайте новую кнопку непосредственно под кнопкой Child ROWS, перетаскивая ее из панели элементов управления.

2. В окне свойств Properties укажите значение btnConstraints для свойства (Name) и значение Constraints для свойства Text.

3. Вставьте код, показанный в листинге 5.8.

Листинг 5.8. Код отображения ограничений

Private Sub btnConstraints_Click(ByVal sender As _

 System.Object, ByVal e As System.EventArgs) _

 Handles btnConstraints.Click

 Dim dt As DataTable Dim rel As DataRelation

 CreateDataSet()

 ' Создание отношения между таблицами Departments и Employees.

 rel = dsEmployeeInfo.Relations.Add(_

  "relDepartmentEmployees", _

  dsEmployeeInfo.Tables("Departments").Columns("ID"), _

  dsEmployeeInfo.Tables("Employees").Columns("DepartmentID"))

 For Each dt In dsEmployeeInfo.Tables

  DisplayConstraints(dt)

 Next dt

End Sub

Private Sub DisplayConstraints(ByVal dt As DataTable)

 Dim i As Integer

 Dim cs As Constraint

 Dim uCS As UniqueConstraint

 Dim fkCS As ForeignKeyConstraint

 Dim columns() As DataColumn

 Me.lstOutput.Items.Add("")

 Me.lstOutput.Items.Add( _

  "CONSTRAINTS FOR TABLE: " & dt.TableName)

 Me.lstOutput.Items.Add( _

 "====================================")

 For Each cs In dt.Constraints

  Me.lstOutput.Items.Add( _

   "Constraint Name: " & cs.ConstraintName)

  Me.lstOutput.Items.Add( _

   "Type: " & cs.GetType().ToString())

  If TypeOf cs Is UniqueConstraint Then

   uCS = CType(cs, UniqueConstraint)

   ' Обработка полей в виде массива

   columns = uCS.Columns

   ' Вывод имен полей.

   For i = 0 То columns.Length – 1

    Me.lstOutput.Items.Add( _

     "Column Name: " & columns(i).ColumnName)

   Next i

  ElseIf TypeOf cs Is ForeignKeyConstraint Then

   fkCS = CType(cs, ForeignKeyConstraint)

   ' Обработка дочерних полей и вывод их имён

   columns = fkCS.Columns

   For i = 0 To columns.Length – 1

    Me.lstOutput.Items.Add( _

     "Column Name: " & columns(i).ColumnName)

   Next i

   ' Вывод имени связанной родительской таблицы.

   Me.lstOutput.Items.Add( _

    "Related Table Name: " & _

    fkCS.RelatedTable.TableName)

   ' Обработка связанных родительских полей и вывод их имен

   columns = fkCS.RelatedColumns

   For i = 0 То columns.Length – 1

    Me.lstOutput.Items.Add( _

    "Related Column Name: " & _

    columns(i).ColumnName)

   Next i

  End If

  Me.lstOutput.Items.Add("====================================")

 Next cs

End Sub

Подпрограмма btnConstraints_Click обрабатывает щелчки на кнопке Constraints: создает объекты DataSet, DataTable и DataRelation (с помощью кода из прежних листингов), а затем вызывает подпрограмму DisplayConstraints для отображения информации о созданных ограничениях.

Подпрограмма общего типа DisplayConstraints принимает в качестве параметра объект DataTable и отображает информацию об ограничениях указанной таблицы. Для этого выполняется циклический обход всех членов свойства-коллекции Constraints указанной таблицы. Каждое найденное ограничение проверяется, т.е. выясняется, имеет ли оно тип UniqueConstraint или ForeignKeyConstraint. Оба они являются производными классами от класса Constraint, поэтому могут сосуществовать в рамках одной типизированной коллекции. Однако эти объекты все же обладают разным набором свойств, поэтому разработчику потребуется идентифицировать тип ограничения в коллекции и преобразовать его к соответствующему уточненному типу. Для ограничения UniqueConstraint отображаются имена всех (одного или нескольких) полей, определенных в данном ограничении. А для ограничения ForeignKeyConstraint отображается имя связанной родительской таблицы вместе с именами всех (одного или нескольких) связанных полей в этой таблице.

РИС. 5.4. Результаты отображения информации о созданных ограничениях для таблиц Employees и Departments

После компоновки проекта DataSetCode и запуска полученного приложения щелкните на кнопке Constraints, и в текстовом поле будет выведена информация о созданных ограничениях (рис. 5.4). Учтите, что все три показанных ограничения (одно для таблицы Employees и два для таблицы Departments) созданы автоматически, так как во время создания отношения между таблицами для свойства Unique объекта DataColumn задано значение True.

Объект ForeignKeyConstraint имеет три свойства-правила, которые управляют действиями, предпринимаемыми при редактировании данных в связанных таблицах. Например, свойства UpdateRule и DeleteRule определяют действия, выполняемые при обновлении и удалении записей в родительской таблице. Эти свойства могут принимать одно из значений перечисления Rule, члены которого описаны в табл. 5.4.

Таблица 5.4. Члены перечисления Rule 

Член перечисления Описание
Cascade Удаление или обновление данных в родительской записи также выполняется для связанных дочерних записей. Это значение используется по умолчанию
None Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей. Это может привести к появлению дочерних записей, которые ошибочно ссылаются на отсутствующие родительские записи
SetDefault Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей, но для них задается используемое по умолчанию значение, указанное в свойстве DefaultValue
SetNull Удаление или обновление данных в родительской записи не выполняется для связанных дочерних записей, но для них задается значение DBNull

Еще одно свойство AcceptRejectRule может принимать значения Cascade (или None) при вызове метода AcceptChanges (или RejectChanges) для связанных дочерних записей. По умолчанию для него используется значение Cascade, которое указывает на автоматический вызов методов AcceptChanges или RejectChanges для дочерних записей при вызове этих методов для связанной с ними родительской записи. Если свойство AcceptRejectRule имеет значение None, то вызов одного из этих двух методов для родительской записи никак не повлияет на связанные с ней дочерние записи.

 

Применение объекта DataSet

Панель элементов управления Data среды Visual Studio .NET содержит компонент DataSet, который позволяет задавать значения свойств для набора данных с помощью окна свойств Properties вместо создания специального кода. Этот способ работы с компонентом DataSet аналогичен способам работы с компонентами Connection и Command, которые описываются в главе 4, "Модель ADO.NET: провайдеры данных". Задайте конфигурацию объекта DataSet и связанных с ними объектов с теми же определениями, которые используются в приведенных ранее фрагментах кода.

Для этого выполните перечисленные ниже действия.

1. Создайте другую форму frmDataSetComponent в проекте DataSetCode.

2. В окне свойств Properties формы укажите значение DataSet для свойства Text и значение frmDataSetComponent для свойства (Name).

3. Увеличьте размер формы frmDataSetComponent.

4. Создайте в форме поле со списком, перетаскивая его из панели элементов управления.

5. В окне свойств Properties поля со списком укажите значение lstOutput для свойства (Name).

6. Увеличьте размер поля со списком lstOutput так, чтобы оно покрывало до 80% площади формы.

7. Из панели элементов управления Data перетащите в форму компонент DataSet. В появившемся на экране диалоговом окне выберите переключатель Untyped dataset (Нетипизированный набор данных) и щелкните на кнопке OK. Этот компонент невидим во время выполнения приложения, поэтому в режиме создания приложения он будет находиться под формой.

8. В окне свойств Properties этого компонента укажите значение dsEmployeeInfо для свойства (Name).

9. В окне свойств Properties этого компонента выберите свойство Tables и щелкните на кнопке с многоточием в правой части этого свойства для отображения диалогового окна Tables Collection Editor (Редактор коллекции таблиц).

10. Щелкните на кнопке Add для отображения свойств первой таблицы создаваемого набора данных.

11. В панели свойств Table1 Properties укажите значение Employees для свойства TableName, как показано на рис. 5.5.

Рис. 5.5. Диалоговое окно Tables Collection Editor после указания таблицы Employees

12. В панели свойств Employees Properties выберите свойство Columns и щелкните на кнопке с многоточием в правой части этого свойства для отображения диалогового окна Columns Collection Editor (Редактор коллекции полей).

13. Щелкните на кнопке Add для отображения свойств первого поля таблицы Employees.

14. В панели свойств Column1 Properties укажите значение FirstName для свойства ColumnName первого поля.

15. Щелкните на кнопке Add для отображения свойств второго поля таблицы Employees.

16. В панели свойств Column1 Properties укажите значение LastName для свойства ColumnName второго поля.

17. Щелкните на кнопке Add для отображения свойств третьего поля таблицы Employees.

18. В панели свойств Column1 Properties укажите значение Department ID для свойства ColumnName и значение System.Int32 для свойства DataType третьего поля.

После выполнения этих действий диалоговое окно Columns Collection Editor будет выглядеть так, как показано на рис. 5.6.

Рис. 5.6. Диалоговое окно Columns Collection Editor со свойствами полей таблицы Employees

19. Щелкните на кнопке Close в диалоговом окне Columns Collection Editor, чтобы вернуться в диалоговое окно Tables Collection Editor для включения в набор данных dsEmployeeInfо еще одной таблицы Departments.

20. Щелкните на кнопке Add для отображения свойств второй таблицы набора данных dsEmployeeInfо.

21. В панели свойств Table1 Properties укажите значение Departments для свойства TableName второй таблицы.

22. Укажите значение 5 для свойства MinimumCapacity второй таблицы.

23. В панели свойств Departments Properties укажите свойство Columns и щелкните на кнопке с многоточием в правой части этого свойства для отображения диалогового окна Columns Collection Editor (Редактор коллекции полей).

24. Щелкните на кнопке Add для отображения свойств первого поля таблицы Departments.

25. Укажите значение ID для свойства СolumnName и значение System.Int32 для свойства DataType первого поля.

26. В панели свойств ID Properties укажите значение True для свойства Readonly, значение True для свойства Unique и значение True для свойства AutoIncrement первого поля.

27. Щелкните на кнопке Add для отображения свойств второго поля таблицы Departments.

28. В панели свойств Column1 Properties укажите значение DepartmentName для свойства ColumnName.

29. В панели свойств DepartmentName Properties укажите значение True для свой ства Unique и значение False для свойства AllowDBNull первого поля.

30. Щелкните на кнопке Close в диалоговом окне Columns Collection Editor для возвращения в диалоговое окно Tables Collection Editor, а затем щелкните еще раз на кнопке Close для закрытия диалогового окна Tables Collection Editor.

Итак, вы создали набор данных dsEmployeesInfo с таблицами Employees и Departments, указывая для свойств компонентов те же значения, которые использовались в коде из листинга 5.1.

Продолжим работу с этими компонентами и определим отношения между таблицами набора данных dsEmployeesInfo.

1. В окне свойств Properties компонента dsEmployeesInfo выберите свойство Relations, а затем щелкните на кнопке с изображением многоточия, чтобы отобразить на экране диалоговое окно Relations Collection Editor (Редактор коллекции отношений).

2. Щелкните на кнопке Add для отображения в диалоговом окне Relation свойств первого отношения между таблицами набора данных dsEmployeesInfo.

3. В текстовом поле Name диалогового окна Relation укажите значение relDepartmentEmployees свойства Name.

4. В текстовом поле ParentTable диалогового окна Relation выберите таблицу Departments свойства ParentTable.

5. В текстовом поле ChildTable диалогового окна Relation выберите таблицу Employees свойства ChildTable.

6. В поле со списком столбца Key Columns раздела Columns диалогового окна Relation выберите поле ID, в результате этого значение ID будет присвоено свойству ParentColumns объекта relDepartmentEmployees.

7. Аналогично в поле со списком столбца Foreign Key Columns раздела Columns диалогового окна Relation выберите поле DepartmentID, в результате значение DepartmentID будет присвоено свойству ChildColumns объекта relDepartmentEmployees.

8. Воспользуйтесь предлагаемыми по умолчанию значениями в списках Update rule (Правило обновления), Delete rule (Правило удаления) и Accept/Reject rule (Правило подтверждения/отказа), которые соответствуют свойствам UpdateRule, DeleteRule и AcceptRejectRule.

9. Щелкните на кнопке OK для закрытия диалогового окна Relation, а затем на кнопке Close для закрытия диалогового окна Relations Collection Editor.

Теперь остается указать значение свойства PrimaryKey для каждой таблицы.

1. В окне свойств Properties компонента dsEmployeesInfo выберите свойство Tables, а затем щелкните на кнопке с изображением многоточия, чтобы отобразить на экране диалоговое окно Tables Collection Editor.

2. В списке членов набора данных Members выберите таблицу Employees.

3. В панели свойств Employees Properties выберите свойство PrimaryKey и щелкните на кнопке с изображением стрелки в правой части этого свойства для развертывания списка полей.

4. Установите флажки для поля (или нескольких полей) первичного ключа из списка доступных полей. Если первичный ключ охватывает несколько полей, то выберите их в правильном порядке. В данном примере выберите сначала поле FirstName, а затем LastName, как показано на рис. 5.7.

РИС. 5.7. Выбор нескольких полей для определения первичного ключа

5. Нажмите клавишу для утверждения созданного первичного ключа.

6. В списке членов набора данных Members выберите таблицу Departments.

7. В панели свойств Departments Properties выберите свойство PrimaryKey и щелкните на кнопке с изображением стрелки в правой части этого свойства для развертывания списка полей.

8. Установите флажок для поля DepartmentName первичного ключа из списка доступных полей.

9. Щелкните на кнопке Close для закрытия диалогового окна Tables Collection Editor.

Чтобы убедиться в идентичности результатов, полученных в режиме создания компонентов, по сравнению с созданным ранее кодом скопируйте и вставьте некоторые подпрограммы из формы frmDataSets в форму frmDataSetComponent, а затем запустите полученное приложение, выполнив перечисленные ниже действия.

1. Выберите и скопируйте подпрограмму AddData из формы frmDataSets в форму frmDataSetComponent.

2. Повторите п. 1 для подпрограмм DisplayDataSet и DisplayChildRows.

3. Включите следующий код в обработчик события загрузки формы frmDataSetComponent_Load в форме frmDataSetComponent:

Private Sub frmDataSetComponent_Load(ByVal sender As _

 System.Object, ByVal e As System.EventArgs) _

 Handles MyBase.Load

 AddData()

 DisplayDataSet()

 DisplayChildRows(dsEmployeelnfo.Tables("Departments"))

End Sub

4. Щелкните правой кнопкой мыши на проекте DataSetCode в окне Solution Explorer и выберите в контекстном меню команду Properties, чтобы открыть диалоговое окно DataSetCode Property Pages.

5. Выберите раздел General в папке Common Properties в правой части диалогового окна DataSetCode Property Pages, а затем выберите форму frmDataSetComponent в поле Startup object.

После запуска созданного приложения в поле со списком формы frmDataSetComponent будут отображены все данные набора dsEmployeeInfo и дочерние записи из таблицы Departments (рис. 5.8).

НА ЗАМЕТКУ

Анализируя код, сгенерированный при редактировании свойств в режиме создания компонентов, можно убедиться в том, что он очень похож созданный вручную код, описанный в предыдущих разделах. Для просмотра автоматически полученного кода нужно выбрать форму в окне Solution Explorer, щелкнуть правой кнопкой мыши, выбрать команду меню View Code в контекстном меню, а затем раскрыть раздел Windows Form Designer generated code. 

РИС. 5.8. Отображение всех данных набора dsEmployeeInfo и дочерних записей с помощью параметров объекта DataSet, заданных, в режиме создания компонентов

 

Резюме

В этой главе подробно описываются объекты и концепции, которые (вместе с материалом из главы 4, "Модель ADO.NET: провайдеры данных") являются ключевыми для методов создания приложений баз данных на основе ADO.NET и Visual Basic .NET. Здесь показано, как объект DataSet и связанные с ним объекты DataTable, DataRelation, DataRow и DataColumn предоставляют разработчикам гибкую и, мощную модель программирования для обработки данных при отключении от физического источника данных. В главе 6, "ADO.NET: объект DataAdapter", демонстрируются способы использования объекта DataAdapter для наполнения данными объекта DataSet и автоматического обновления источника данных при изменении данных в объекте DataSet.

 

Вопросы и ответы

Насколько я понял, источник данных можно использовать непосредственно (с помощью команд управления данными) или косвенно (в неподключенном стоянии). В каких случаях предпочтительнее использовать каждый из этих способов?

Метод на основе объекта DataSet (в неподключенном состоянии) по сравнению с непосредственным применением команд управления данными обладает несколькими преимуществами. Он предлагает более простой и единообразный способ перемещения данных между разными уровнями и местоположениями в распределенной базе данных, а также между разными приложениями баз данных благодаря встроенной поддержке языка XML. В нем предусмотрен механизм кэширования данных, что позволяет сортировать, фильтровать и искать данные без доступа к источнику данных. Наконец, он позволяет извлекать данные из нескольких таблиц или даже нескольких разных источников данных, а также манипулировать ими индивидуально или совместно на основе заданных между ними отношений.

Прямой способ доступа к источнику данных с помощью объекта Command также обладает определенными преимуществами. Некоторые операции, например изменение структуры базы данных, можно выполнить только с помощью прямого доступа. При прямом доступе даже стандартные команды SQL или хранимые процедуры могут быть выполнены быстрее и эффективнее, что позволяет добиться более высокой производительности и масштабируемости. Кроме того, этот способ позволяет сократить объем оперативной памяти для объекта DataSet, особенно при отсутствии насущной необходимости в кэшировании данных, например при создании Web-страницы или заполнении данными поля со списком.

Итак, когда же предпочтительнее использовать прямой доступ к базе данных вместо объекта DataSet? Прежде всего в тех случаях, когда операция может быть выполнена только с помощью объекта Command. Это относится к вызовам хранимых процедур, которые выполняют манипуляции с данными и возвращают только одно значение и/или значения параметров, а также к DDL-командам изменения структуры базы данных. Кроме того, не рекомендуется использовать объект DataSet, если данные используются только для чтения, используются недолго, а потому не оправданны их загрузка и хранение в оперативной памяти, либо используются сервером и их не нужно передавать на другие уровни приложения или компьютеры. В большинстве других случаев предпочтительнее обращаться к объекту DataSet.