• Компоненты объекта DataSet
  • Ввод данных в объект DataSet
  • Определение схемы объекта DataTable
  • Вставка данных в объект DataTable
  • Обновление данных в объекте DataSet
  • Состояние и версия записи
  • Обработка ошибок ввода данных в записи и поля
  • Доступ к данным с помощью объекта DataTable
  • Поиск, фильтрация и сортировка записей
  • Отношения между таблицами
  • Ограничения
  • Применение объекта DataSet
  • Резюме
  • Вопросы и ответы
  • ГЛАВА 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. Нажмите клавишу <Enter> для утверждения созданного первичного ключа.

    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.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх