Доступ к версии поля 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.)