7. Работа с данными в таблицах ADO.NET7.1.
Добавление данных в таблицу7.2.
Состояния строки7.3.
Removing или Deleting DataRow из таблицы7.4.
Работа с данными в таблице7.5.
Добавление и чтение информации об ошибках строки7.6.
Принятие или отклонение изменений строк7.7.
Работа с событиями DataTable7.8.
Фильтрация и сортировка строк в таблице7.9.
Optimistic Concurrency7.10.
Исполнение транзакций
7. Работа с данными в таблицах ADO.NET
7.1. Добавление данных в таблицу
После того, когда создана таблица со столбцами и ключами,
можно добавлять данные. Представленный ниже код добавляет
десять строк в таблицу Customers.
[VB]
Dim iCounter as integer
Dim workRow as
DataRow
For i = 0 to
9
workRow = worktable.NewRow()
workRow("CustID") = iCounter
workRow("CustomerNameLast") = "CustName" &
i.ToString()
worktable.Rows.Add(workRow)
Next
[C#]
DataRow workRow =
null;
for (int
i = 0; i <= 9; i++)
{
workRow =
workTable.NewRow();
workRow[0] = i;
workRow[1] =
"CustName" + i.ToString();
workTable.Rows.Add(workRow);
}
В этом коде объявляется новая переменная с типом DataRow.
Новый объект DataRow возвращается при вызове метода NewRow.
DataTable создаёт объект DataRow, основанный на структуре
таблицы (как это определено в ColumnsCollection).
[VB]
Dim workRow As DataRow
workRow
= workTable.NewRow()
[C#]
Data.DataRow
workRow;
workRow = workTable.NewRow();
После этого, Вы можете работать с недавно созданными
строками через индекс или через имя столбца. Обе из ниже
следующих строк кода допустимы:
[VB]
workRow("CustID") = "CustName"
& i
workRow(1) = "CustName" & i
[C#]
workRow["CustID"] = "CustName"
+ i.ToString();
workRow[1] = "CustName" + i.ToString();
После того, как строка заполнена значениями, её можно
добавить в RowsCollection таблицы, используя метод Add.
[VB]
workTable.Rows.Add(workRow)
[C#]
workTable.Rows.Add(workRow);
В дополнение к добавлению строк этим путём, мы можем также
добавлять новую строку, получая значения из массива
(типизированного, как объект) и используя метод Add.
[VB]
workTable.Rows.Add(new Object()
{1, "CustName1"})
[C#]
workTable.Rows.Add(new Object[]
{1, "CustName1"});
Этот способ просто создаёт новую строку внутри таблицы и
устанавливает для значения столбца, значение из объекта
массив. Обратите внимание, что последовательность значений в
массиве согласована с последовательностью значений столбца в
таблице.
7.2. Состояния строки
Каждая DataRow имеет свойство RowState, которое определяет
её состояние.
В приведённом выше примере, RowState недавно
созданной строки установится в состояние Detached. Однако,
сразу после того, как строка будет добавлена в RowsCollection
методом Add, свойство RowState установится в
New.
Приведённая ниже таблица содержит возможные состояния,
которые строка может принимать.
RowState |
Описание |
Unchanged |
Никакие изменения не делались, начиная с
последнего запроса AcceptChanges |
New |
Строка была добавлена в таблицу, но
AcceptChanges не вызывался |
Modified |
Некоторый элемент строки был
изменены. |
Deleted |
Строка была удалена из таблицы с помощью
метода Delete |
Detached |
Либо строка была удалена, но
AcceptChanges не вызывался, либо строка была создана, но
не добавлена к таблице. |
7.3. Removing или Deleting DataRow из
таблицы
При выполнении операций удаления одной или более строк из
таблицы, обратите внимание на то, что removing не то же, что
deleting. Важность этого различия будет рассмотрена позже в
обсуждении способов сохранения значений таблицы в источнике
данных.
Чтобы удалить строку из таблицы, вызовите метод
Remove из RowsCollection таблицы (доступный через свойство
Rows объекта DataTable), воспользовавшись индексом или
непосредственно экземпляром строки.
[VB]
workTable.Rows.Remove(3)
[C#]
workTable.Rows.Remove(3);
ИЛИ
[VB]
workTable.Rows.Remove(workTable.Rows(3))
[C#]
workTable.Rows.Remove(workTable.Rows[3]);
После вызова этого метода, указанная строка будет удалена
из совокупности строк. В отличии от этого, Вы можете вызвать
метод Delete для DataRow, чтобы изменить её состояние на
Deleted:
[VB]
workTable.Rows(3).Delete
[C#]
workTable.Rows[3].Delete
Отметка строки, как удаленной, говорит о том, что когда
будет вызван метод AcceptChanges объекта DataTable, строка
будет удален из таблицы. Это приведёт к изменению RowState для
Deleted строки. Напротив, если вызван RejectChanges, RowState
этой строки возвратится к тому состоянию, которое до этого
соответствовало состоянию удалено (Deleted).
Различие между
методами Remove и Delete в том, когда имеет место фактическое
удаление.
Обратите внимание: Если RowState строки
установлен в New, это означает она только что была добавлен в
таблицу, а если состояние отмечено как Deleted, она будет
удалена из таблицы.
7.4. Работа с данными в таблице
Теперь, когда создана таблица содержащая столбцы, первичный
ключ и данные, можете заняться отображением её содержания. В
этом примере, DataSet фильтруется так, чтобы Вы работали
только с текущей версией строки.
[VB]
Dim CurrRows() As DataRow =
workTable.Select(Nothing, Nothing, _
System.Data.DataViewRowState.CurrentRows)
Dim list
As String =
System.String.Empty
Console.WriteLine("")
Console.WriteLine("Writing
current rows for " & workTable.Tablename)
Dim RowNo As
Integer
Dim ColNo As Integer
For RowNo = 0 to
CurrRows.Count - 1
For ColNo = 0 To
workTable.Columns.Count – 1
list = ""
list &= workTable.Columns(colNo).ColumnName & "
= " & _
CurrRows(RowNo)(ColNo).ToString
Console.WriteLine(list)
Next
Next
If CurrRows.Count < 1 Then
Console.WriteLine("No CurrentRows Found")
[C#]
DataRow[] CurrRows =
workTable.Select(null,
null,
System.Data.DataViewRowState.CurrentRows );
string list =
System.String.Empty;
Console.WriteLine("");
Console.WriteLine("Writing
Current rows for " + workTable.TableName );
for (int RowNo = 0;
RowNo <= CurrRows.Count -1; RowNo++)
{
for (int ColNo = 0;
ColNo <= workTable.Columns.Count - 1; ColNo++)
{
list =
System.String.Empty;
list +=
workTable.Columns[ColNo].ColumnName + " = "
+
CurrRows[RowNo][ColNo].ToString();
Console.WriteLine(list);
}
}
if (CurrRows.Count <
1 )
{
Console.WriteLine("No
Current Rows Found");
}
Метод Select класса DataTable дает Вам возможность отыскать
набор строк, соответствующий указанным критериям. Метод
возвращает массив объектов DataRow.
В примере метод
отыскивает все Current строки. Что такое строка Current?
DataTable поддерживает три версии каждой строки: Original,
Current и Proposed.
Первоначальная (Original) строка, это
такая версия строки, когда она была только что добавлено к
таблице. Текущая (Current) версия строки содержит любые
изменения, которые были сделаны к строке. Если Вы выполняете
изменение в строке к необходимому значению, новое значение
будет текущим значением, в то время как значение до изменений
можно считать первоначальным значением. Промежуточная
(Proposed) версия возможна при особых обстоятельствах и в
течение короткого периода времени. Proposed версия
представляет состояние, промежуточное между Original и
Current. Строка переходит в промежуточное состояние, когда
система или пользователь вызывают BeginEdit и редактируют
строку.
[VB]
workRow.BeginEdit
' Make some
changes to the row.
If
ValidateColValue(proposedValue)
workRow.EndEdit
Else
workRow.CancelEdit
End If
[C#]
workRow.BeginEdit
// make
some changes to the row
if
(ValidateColValue(proposedValue))
workRow.EndEdit;
else
workRow.CancelEdit;
Это означает, что в течение состояния строки Proposed, Вы
можете применять логику проверки правильности значений
отдельных столбцов, анализируя результаты события ColumnChange
объекта DataTable. Таблица передаёт ссылку на изменяемый
столбец и состояние Proposed в событии ColumnChange.
Пользователь может изменять состояние Proposed или
генерировать исключение, для отмены редактирования. Строка
выводится из состояния Proposed, когда вызывается EndEdit. Вы
можете также отменять любое изменение при редактировании, в
тот момент, когда строка находится в состоянии Proposed, с
помощью вызова CancelEdit.
7.5. Добавление и чтение информации об ошибках
строки
Поскольку конечные пользователи редактируют значения в
таблице, они могут допускать при этом ошибки. В этих случаях,
Вы не можете прервать действия пользователя, когда он
находятся в процессе редактирования данных, а просто выводите
информацию об ошибке в строке на его дисплей, когда
редактирование завершено. Объект DataRow поддерживает эту
возможность через свойство RowError для каждой
строки.
Представленные ниже два примера показывают как
выводить/читать (Add/Read) информацию об ошибке, связанной с
объектом DataRow.
Пример 1:
[VB]
Public Sub AddErrorToRow(ByVal
workTable As DataTable, ByVal Row As Integer, _
ByVal ErrorString As String)
workTable.Rows(Row).RowError = ErrorString
End Sub
[C#]
public void
AddErrorToRow(DataTable workTable, int Row, string
ErrorString)
{
workTable.Rows[Row].RowError
= ErrorString;
}
Пример 2:
[VB]
Public Sub WriteTableErrors(ByVal
workTable As DataTable)
If
workTable.HasErrors Then
Dim ErrorRows() As DataRow =
workTable.GetErrors()
Console.WriteLine("")
Console.WriteLine("=================")
Console.WriteLine("DataTable " &
workTable.TableName & " has " & _
ErrorRows.Count.ToString & " Errors!")
Dim i As Integer
For i
= 0 to Errors.Count – 1
Console.WriteLine("Row Error for row " & _
ErrorRows(i)("CustID").ToString & "Error Msg =" &
_
ErrorRows(i).RowError)
Next
Else
Console.WriteLine("")
Console.WriteLine("=================")
Console.WriteLine("DataTable " + workTable.TableName +
" Has no errors")
End If
End
Sub
[C#]
public void
WriteTableErrors(DataTable workTable)
{
if (
workTable.HasErrors )
{
DataRow[] ErrorsRows =
workTable.GetErrors();
Console.WriteLine("");
Console.WriteLine("=================");
Console.WriteLine("DataTable " + workTable.TableName +
" has " +
ErrorsRows.Count.ToString() + "
Errors!");
for (int i = 0; i
<= ErrorsRows.Count -1; i++)
{
Console.WriteLine("Row Error for row "
+
ErrorsRows[i]["CustID"].ToString() + " Error Msg="
+
ErrorsRows[i].RowError);
}
}
else
{
Console.WriteLine("");
Console.WriteLine("=================");
Console.WriteLine("DataTable
" + workTable.TableName + " Has no errors");
}
}
Первый пример берет номер строки (Row) и добавляет
сообщение об ошибках (ErrorString). Второй пример печатает
RowError для каждой строки workTable, используя ассоциируемый
с ошибками текст.
7.6. Принятие или отклонение изменений строк
Если Вы уверены относительно внесённых изменений,
необходимо принять эти изменения и передать текущие значения
строки, чтобы быть установить состояние Current. (Обратите
внимание, что всё это относится к данным, находящимся DataSet,
который размещён в памяти). Принятие или отклонение изменений
в DataSet вызовет обновление данных в источнике (для получения
дополнительной информации, прочтите ADO.NET Managed
Providers).
Представленный ниже код показывает два пути
принятия изменений строки.
Пример 1:
[VB]
workTable.AcceptChanges()
[C#]
workTable.AcceptChanges();
Пример 2:
[VB]
Public Function
AcceptChanges(ByVal workTable As DataTable) As
Integer
Dim acceptedRows As Integer = 0
Dim i As
Integer
For i = 0 To workTable.Rows.Count – 1
If Not workTable.Rows(i).HasErrors Then
acceptedRows
+= 1
workTable.Rows(i).AcceptChanges()
Else
workTable.Rows(i).RejectChanges()
End If
End If
AcceptChanges = acceptedRows
End Function
[C#]
public int
AcceptChanges(DataTable workTable)
{
int acceptedRows =
0;
for
(int i = 0; i <= workTable.Rows.Count -1 ; i++)
{
if
(!workTable.Rows[i].HasErrors )
{
acceptedRows++;
workTable.Rows[i].AcceptChanges();
}
else
{
workTable.Rows[i].RejectChanges();
}
}
return
acceptedRows;
}
Первый пример принимает все изменения, которые были сделаны
к таблице. Второй пример вносит изменения в цикле и принимает
каждое с помощью приёма Row-by-Row. Для исполнения
AcceptChanges, код примера исследует свойство HasErrors.
[VB]
If Not
workTable.Rows(i).HasErrors) Then
[C#]
if
(!workTable.Rows[i].HasErrors )
DataRow позволяет Вам вводить строку ошибки и ассоциировать
её со строкой в таблице. Если любой строке в таблице
ассоциировали строку ошибки для этой строки, свойство
HasErrors возвратит true. Во втором примере выполняется
исследование строк, и изменения принимаются для тех, которые
не имеют ошибок.
Как только изменения в строке приняты,
первоначальные состояние строки заменяется на текущее
состояние. Строка, которая рассматривалась, будет окончательно
изменена.
7.7. Работа с событиями DataTable
Объект DataTable обеспечивает ряд событий, которые могут
быть обработаны кодом пользователя. Эти события включают:
- ColumnChange
- RowChanged
- RowChanging
-
RowDeleting
- RowDeleted
В представленном ниже примере, создаются три метода:
MyColChanging, MyRowChanging и MyRowChanged. Каждый из этих
методов вызывается, когда столбец или строка будет
изменена.
[VB]
workTable.AddOnColumnChanging
_
(New System.Data.DataColumnChangeEventHandler(AddressOf
me.MyColChanging))
workTable.AddOnRowChanging _
(New
System.Data.DataRowChangeEventHandler(AddressOf
me.MyRowChanging))
workTable.AddOnRowChanged _
(New
System.Data.DataRowChangeEventHandler(AddressOf
me.MyRowChanged))
Private Sub MyColChanging _
(ByVal
sender As Object, ByVal e As
DataColumnChangeEventArgs)
End Sub
Private Sub
MyRowChanging _
(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
'Add this to capture the row
changing event
Console.WriteLine("Adding
row " + e.Row[0].ToString())
End Sub
Private Sub
MyRowChanged _
(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
If e.Action =
Delete Then
Console.WriteLine("deleting row " &
e.Row(0).ToString)
End If
End
Sub
[C#]
workTable.AddOnColumnChanging(new
DataColumnChangeEventHandler(MyColChanging));
workTable.AddOnRowChanging(new
DataRowChangeEventHandler(MyRowChanging));
workTable.AddOnRowChanged(new
DataRowChangeEventHandler(MyRowChanged));
Public
void MyColChanging(object sender, DataColumnChangeEventArgs
e)
{
}
public void MyRowChanging(object sender,
DataRowChangeEventArgs e) {
//Add this to capture
the row changing event
Console.WriteLine("Adding
row " + e.Row[0].ToString());
}
public void
MyRowChanged(object sender, DataRowChangeEventArgs
e){
if
(e.Action == Delete)
{
Console.WriteLine("deleting row " +
e.Row[0].ToString());
}
}
7.8. Фильтрация и сортировка строк в таблице
Метод Select дает возможность пользователям отбирать строки
по трём критериям: выражению фильтра, порядку сортировки и
DataViewRowState.
В представленном ниже примере, Вы
используете значение null для критериев инструкции, которые Вы
хотите игнорировать. В этом примере метод Select возвратит все
текущие строки.
[VB]
Dim CurrRows() As DataRow =
workTable.Select(Nothing, Nothing, _
System.Data.DataViewRowState.CurrentRows)
[C#]
DataRow[] CurrRows =
workTable.Select(null,
null,
System.Data.DataViewRowState.CurrentRows );
Вы можете запросить все строки с фамилией Smith,
упорядоченные по столбцу CustomerNameFirst.
[VB]
Dim myNames() As DataRow =
workTable.Select("CustomerLastName = 'Smith'"),
_
"CustomerNameFirst",
System.Data.DataViewRowState.CurrentRows)
[C#]
DataRow[] myNames =
worktable.Select("CustomerNameLast = 'Smith'",
"CustomerNameFirst", System
7.9. Optimistic Concurrency
В многопользовательской среде, есть два основных способа
обновления данных в СУБД: Optimistic и Pessimistic Concurrency
(параллелизм).
Pessimistic Concurrency - система
блокировок, которая не позволяет пользователю изменять данные,
которые используют другие пользователи. В Pessimistic модели,
когда пользователь исполняет действие, которое устанавливает
блокировку, другие пользователи не могут исполнять действия,
которые вошли бы в противоречие с блокировкой, пока владелец
блокировки не снимет её. Эта модель главным образом
используется в средах с высокой конкуренцией использования
данных, где стоимость защиты данных с помощью блокировок
меньше, чем стоимость обслуживания отката транзакций, если
происходят конфликты параллелизма.
Таким образом в
Pessimistic модели, когда пользователь читает отчет с
намерением его изменения, он устанавливает блокировку. Пока
этот пользователь не закончил модификацию и не освободит
блокировку, никто не сможет изменять этот отчет.
Напротив,
в Optimistic Concurrency модели пользователи не блокируют
данные при чтении. Когда пользователь хочет модифицировать
отчет, приложение должно проверить, изменил ли другой
пользователь отчет. Optimistic Concurrency вообще используется
в средах с низкой конкуренцией за данные.
В Optimistic
Concurrency есть традиционно три пути идентификации изменений
сделанных в отчёте. Первый способ просто сравнивает все
значения столбца, которые считаны из базы данных и текущие
значения.
Например:
13:00. User1 читает строку в базе данных со следующими
значениями:
CustID
LastName
FirstName
101
Smith
Bob
Column Name |
Original Value |
Current Value |
Value in DB |
CustID |
101 |
101 |
101 |
LastName |
Smith |
Smith |
Smith |
FirstName |
Bob |
Bob |
Bob |
13:01. User2 читает ту же самую строку
13:03. User2
изменяет имя Bob на Robert и обновляет базу данных.
Column Name |
Original Value |
Current Value |
Value in DB |
CustID |
101 |
101 |
101 |
LastName |
Smith |
Smith |
Smith |
FirstName |
Bob |
Robert |
Bob |
Так как во время модификации значения в базе данных
соответствуют оригиналу, модификация для user2 проходит
успешно.
13:05. User1 изменяет имя Bob на James и пробует обновить
строку.
Column Name |
Original Value |
Current Value |
Value in DB |
CustID |
101 |
101 |
101 |
LastName |
Smith |
Smith |
Smith |
FirstName |
Bob |
James |
Robert |
Так как первоначальное значение для user1 было Bob, а
значение в базе данных из-за модификации user2 стало Robert,
попытка модификации имени для user1 окончится сбоем.
Второй
и третий путь Optimistic Concurrency очень похожи друг на
друга: используется или date-time stamp столбец или столбец
версии строки. Всякий раз, когда пользователь успешно
модифицирует строку, date-time stamp (или версия строки)
столбец устанавливается на новое, уникальное
значение.
Таким образом, везде, где пользователь пытается
модифицировать строку, приложение сравнивает оригинальное
значение date-time stamp (или версия строки) и текущую версию
в базе данных. Если значения идентичны, значит строка не
изменилась и модификация может быть выполнена (с присвоением
нового значения для date-time stamp или столбца версии
строки).
Исполнение такого SQL запроса достаточно
простое:
Update Table1 set x =
newx1, y = newy where datetimestamp =
originaldatetime
ИЛИ
Update Table1 set x =
newx1, y = newy where rowversion =
originalrowversion
Чтобы сохранять date-time stamp или точную версию строки,
используйте триггер для таблицы, чтобы значения
модифицировались при любых изменениях в строке.
Тем не
менее, другой способ отслеживания модификаций состоит в
применении хранимых процедур. В VS7 и DataSetCommand
Configuration Wizard (DCW) включена автоматическая поддержка
этих задач, что весьма удобно, если Вы используете их для
создания хранимых процедур. Представленный ниже пример
отслеживает модификацию средствами DCW, через хранимую
процедуру, сгенерированную для таблицы авторов базы данных
pubs. Обратите внимание, что список параметров этой хранимой
процедура фактически содержит два набора параметров. Второй
набор (@param1 - @param9), это просто копия первых девяти
параметров.
CREATE PROCEDURE
AuthorUpdate
(
@au_id
id,
@au_lname varchar(40),
@au_fname
varchar(20),
@phone char(12),
@address
varchar(40),
@city varchar(20),
@state
char(2),
@zip char(5),
@contract
bit,
@Param1 id,
@Param2
varchar(40) /* Optimistic Concurrency Check */,
@Param3
varchar(20) /* Optimistic Concurrency Check */,
@Param4
varchar(40) /* Optimistic Concurrency Check */,
@Param5
varchar(20) /* Optimistic Concurrency Check */,
@Param6 bit /*
Optimistic Concurrency Check */,
@Param7 char(12)
/* Optimistic Concurrency Check */,
@Param8 char(2)
/* Optimistic Concurrency Check */,
@Param9 char(5)
/* Optimistic Concurrency Check */
)
AS
SET NOCOUNT
OFF;
UPDATE authors SET au_id = @au_id, au_lname =
@au_lname, au_fname = @au_fname, phone = @phone, address =
@address, city = @city, state = @state, zip = @zip, contract =
@contract
WHERE (au_id = @Param1) AND (address = @Param2) AND
(au_fname = @Param3) AND (au_lname = @Param4) AND (city =
@Param5) AND (contract = @Param6) AND (phone = @Param7) AND
(state = @Param8) AND (zip = @Param9);
Второй набор параметров содержит первоначальные значения
полей, которые были загружены из источника данных. Это
позволяет выполнить сравнение в предложении WHERE,
используемом в инструкции модификации.
После загрузки
DataSet, отчет Authors будет походить на следующее:
Au_id = "999-99-9999"
Au_Lname = "Smith"
Au_Fname =
"Jane"
Phone = "111 111-1111"
Address = "123
Main"
City = "Redmond"
State = "WA"
Zip =
"98052"
Contract = 1
Предположим, что в результате выполненной Вами модификации
изменится телефонный номер на 222 111-1111. Однако, ели до
вашей модификации, но после того, как Вы считали данные,
другой пользователь изменит фамилию Jane со Smith на Doe, при
попытке выполнения Вами модификации, она не сможет быть
выполнена, потому что столбец Au_Lname больше не соответствует
первоначальному значению.
Таким образом, в коде,
обслуживающем Вашу форму или объект, Вы можете устроить
проверку строк, на которые воздействуют другие пользователи,
чтобы определить, исполнена ли ваша транзакция или нет.
7.10. Исполнение транзакций
Транзакции используются, чтобы управлять блокировками в
базах данных. Например, в стандартных бухгалтерских проводках,
необходимо дебетовать один счёт и в то же самое время
кредитовать другой. Из-за возможных ошибок компьютера,
вызванных сбоем в его работе (отключение электропитания,
потеря сети и т.д.), есть вероятность того, что одна запись
изменена/добавлена, а другая нет. Транзакции дают возможность
избежать таких ситуаций. Так же, как в ADO, транзакции в
ADO.NET обрабатываются на уровне базы данных: т.е. ваша база
данных должна поддержать транзакции.
Есть три основных
команды для транзакций: begin, commit и rollback. Begin
отмечает начало транзакции. Все процедуры, используемые между
Begin и следующей командой (или Rollback или Commit)
рассматриваются частью транзакции. Рассмотрим следующий пример
команды T-SQL:
begin transaction
insert into account (account,amount,debitcredit)
values (100,100,'d')
insert into account
(account,amount,debitcredit) values (300,100,'c')
if
(@@ERROR > 0)
rollback
else
commit
В ADO.NET (как и в ADO) Вы можете управлять транзакциями
через объект Connection. Фактически, когда Вы используете
ADOConnection, используется та же самая основная модель
транзакций OLEDB. Поэтому, если Вы могли определять транзакции
для вашей базы данных используя ADO, Вы также можете
определять их и средствами ADO.NET.
Важно! Объект DataSet
также имеет commit модель (AcceptChanges, RejectChanges), но
это не затрагивает базу данных. Эта commit модель применяется
только для кэша данных в одном DataSet.