Итак, DataSet представляет собой буфер для хранения данных из базы. Этот буфер предназначен для хранения структурированной информации, представленной в виде таблиц, поэтому первым, самым очевидным вложенным объектом DataSet является DataTable. Внутри одного объекта DataSet может храниться несколько загруженных таблиц из базы данных, помещенных в соответствующие объекты DataTable. Всякая таблица состоит из столбцов (называемых также полями или колонками) и строк. Для обращения к ним и для управления столбцами и строками в объекте DataTable предназначены специальные объекты - DataColumn и DataRow. Между таблицами, как мы знаем, могут быть связи - здесь они представлены объектом DataRelation. Наконец, в таблицах есть первичные и вторичные ключи - объект Constraint со своими двумя подклассами UniqueConstraint и ForeighKeyConstraint описывают их. Я все вр
емя говорю "представлены", "описывают", избегая слов "отображают" и "определяются" - дело в том, что нельзя ставить знак равенства между, например, объектом DataRelation и связью таблиц. В загруженных таблицах не формируются автоматически все нужные объекты - кое-где мы должны делать это самостоятельно. Сами объекты имеют также довольно тонкую и сложную структуру, поэтому это было бы довольно грубым приближением. Однако, на первых порах, для понимания сути полезно держать в голове следующие "формулы":
DataSet = одна или несколько таблиц = один или несколько объектов DataTable. DataTable = таблица. DataColumn = столбец, поле, колонка. DataRow = строка. DataTable = таблица = несколько полей, столбцов, колонок = несколько объектов DataColumn. DataTable = таблица = несколько строк = несколько объектов DataRow. DataRelation = связь между таблицами.
Возникает вопрос: для чего нужны эти объекты, если мы прекрасно обходились и без них для вывода содержимого таблицы, например в элемент DataGrid? Дело в том, что для простого отображения информации создавать эти объекты не требуется, но тогда все данные будут однородными текстовыми переменными, подобно таблицам в документе Microsoft Word. DataSet не может сам сформировать структуру данных - тип переменных, первичные и вторичные ключи, связи между таблицами. Для управления структурой, для сложного отображения (например, вывод информации с привязкой к элементам, создаваемым в режиме работы приложения) и нужно определение этих объектов.
Лучший способ разобраться с работой всех объектов - применить их на практике. Создадим простую
(рис 8.1) Структура базы Tests (диаграмма "QuestVar")Каждый вопрос будет содержать несколько ответов, для синхронного перемещения по структуре нужна связь по полю questID. Галочка "Allow Nulls" (Разрешить пустые значения) снята для всех полей - это означает, что все поля будут обязательными для заполнения. Структура таблиц Questions и Variants приводится в таблице 8.1:
| Таблица Поле | Описание поля |
|---|---|
Questions - таблица вопросов |
questID Номер вопроса |
question Текст вопроса |
|
questType Тип вопроса (с одним правильным вариантом ответа или с несколькими) |
|
Variants - таблица вариантов ответов |
id Номер ответа |
questID Номер вопроса |
|
variant Текст варианта ответа |
|
isRight Является ли данный ответ верным |
Для нас не столь важно количество вопросов, поэтому их всего будет пять, относящихся к теме операционной системы Windows XP. Одному вопросу может соответствовать один правильный вариант ответа - в этом случае в поле questType указывается значение "0". Далее на форме эти варианты будут выводиться с элементами RadioButton. Для вопросов, у которых есть несколько правильных вариантов ответов, указывается в поле questType значение "1". На форме такие варианты будут выводиться с чекбоксами. В таблицах 8.2 и 8.3 приводится содержимое таблиц Questions и Variants.
| questID | Question | questType |
|---|---|---|
| 1 | Для переустановки операционной системы Windows XP вам необходимо экспортировать банк сообщений программы |
0 |
| 2 | При компиляции программы в среде Microsoft Visual Studio .NET возникает систематическая ошибка в модуле AssemblyInfo из-за неудачного выбора имени пользователя и организации (были использованы кавычки) при установке системы (Для просмотра: "Мой компьютер - правая кнопка - Свойства - вкладка "Общие""). Вам необходимо изменить эти параметры | 0 |
| 3 | Выберите группы, состоящие из файлов, размер которых после архивирования составляет 5-10% от исходного | 1 |
| 4 | Укажите ряд, состоящий из агрегатных (агрегаторных) функций SQL | 0 |
| 5 | Вы изменили ключ BootExecute в разделе реестра [HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager]. В результате было выполнено следующее действие: |
0 |
| id | questID | Variant | isRight |
|---|---|---|---|
| 1 | 1 | C:\Program Files\Outlook Express\Mail | 0 |
| 2 | 1 | C:\Documents and Settings\Имя_Пользователя\ Local Settings\Application Data\Identities\ {F4CB90C4-3FD5-406B-83FB-85E644627B87}\Microsoft\Outlook Express | 1 |
| 3 | 1 | C:\WINDOWS\system32\Microsoft\Outlook Express\Bases | 0 |
| 4 | 1 | C:\Documents and Settings\Default User\Cookies | 0 |
| 5 | 2 | "Мой компьютер - правая кнопка - Свойства - Вкладка "Общие" на подписи - правая кнопка - Свойства - Переименовать" | 0 |
| 6 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion] затем меняем параметры RegisteredOwner и RegisteredOrganization |
1 |
| 7 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT] удаляем раздел CurrentVersion |
0 |
| 8 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] |
0 |
| 9 | 3 | *.mpeg, *.mdb, *. Htm | 0 |
| 10 | 3 | *.xls, *.txt , *.mht | 1 |
| 11 | 3 | *. jpeg, *.gif, *.mp3 | 0 |
| 12 | 3 | *. doc, *. xml, *.bmp | 1 |
| 13 | 4 | update, insert, sum |
0 |
| 14 | 4 | where, like, create |
0 |
| 15 | 4 | count, min, max |
1 |
| 16 | 4 | select, avg, into |
0 |
| 17 | 5 | Отключилась проверка на ошибки всех дисковых разделов при загрузке Windows ХР | 1 |
| 18 | 5 | Установлен таймер на автоматическое отключение | 0 |
| 19 | 5 | Отключены все диагностические сообщения | 0 |
| 20 | 5 | Система перестанет загружаться | 0 |
В поле isRight таблицы Variants правильные ответы отмечаются значением "1". Понятно, что для практического применения
Создайте новое Windows-приложение и назовите его "Tests". Устанавливаем следующие свойства формы:
| Form1, форма, свойство | Значение |
|---|---|
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 550; 350 |
| Text | Тест |
Перетаскиваем на форму TextBox, , Label и пять кнопок, устанавливаем следующие значения свойств элементов:
| label1, свойство | Значение |
|---|---|
| Location | 16;8 |
| Text | Вопрос: |
| textBox1, свойство | Значение |
|---|---|
| Name | txtQuestion |
| Location | 12; 24 |
| Multiline | True |
| Size | 520; 90 |
| TabIndex | 8 |
| Text |
| groupBox1, свойство | Значение |
|---|---|
| Name | GbVariants |
| Location | 12; 120 |
| Size | 520; 150 |
| Text | Варианты ответов |
| button1, свойство | Значение |
|---|---|
| Name | btnFirst |
| Location | 24; 280 |
| Text | << |
| button2, свойство | Значение |
|---|---|
| Name | BtnPrev |
| Location | 99; 280 |
| Text | < |
| button3, свойство | Значение |
|---|---|
| Name | BtnNext |
| Location | 174; 280 |
| TabIndex | 0 |
| Text | > |
| button4, свойство | Значение |
|---|---|
| Name | BtnLast |
| Location | 249; 280 |
| Text | > |
| button5, свойство | Значение |
|---|---|
| Name | BtnCheck |
| Location | 360; 280 |
| Size | 150; 23 |
| Text | Результат |
Интерфейс приложения готов. Чекбоксы или элементы radioButton мы будем добавлять в режиме запуска приложения - в самом деле, длина всех ответов разная и делать привязку к статичным элементам нет смысла. Переключаемся на вкладку Data панели инструментов Toolbox и перетаскиваем на форму объект DataSet. В появившемся окне Add DataSet выбираем значение "Untyped DataSet" и нажимаем "OK". Появляется панель компонентов, с которой мы уже неоднократно имели дело. Свойству Name устанавливаем значение dsTests.
В свойстве Tables элемента (...), запускается редактор Table Collection Editor (рис. 8.2), нажимаем кнопку "Add" и вводим следующие значения свойств:
| TableName | Questions |
| Name | dtQuestions |

Значение (рис 8.2) (...), появляется редактор Columns Collection Editor (рис. 8.3), нажимаем кнопку Add. Всего нужно будет создать три поля: questID, question и questType:
| Поле Column1, свойство | Значение |
|---|---|
| ColumnName | questID |
| DataType | System.Int32 |
| Unique | True |
| Name | dсQuestID |
| Поле Column2, свойство | Значение |
|---|---|
| ColumnName | question |
| Name | dcQuestion |
| Поле Column3, свойство | Значение |
|---|---|
| ColumnName | questType |
| DataType | System.Int32 |
| Name | dcQuestType |
(рис 8.3) Запуск редактора Columns Collection EditorЗавершив работу с редактором Columns Collection Editor, нажимаем кнопку Close. Мы закончили создание объекта DataTable для таблицы Questions. Аналогичные действия надо проделать, чтобы создать DataTable для таблицы Variants (рис. 8.4) и соответствующих полей id, questID, variant и isRight:
| TableName | Variants |
| Name | dtVariants |
| Поле Column1, свойство | Значение |
|---|---|
| ColumnName | id |
| DataType | System.Int32 |
| Unique | True |
| Name | dcID |
| Поле Column2, свойство | Значение |
|---|---|
| ColumnName | questID |
| DataType | System.Int32 |
| Name | dcVariantQuestID |
| Поле Column3, свойство | Значение |
|---|---|
| ColumnName | variant |
| Name | dcVariant |
| Поле Column4, свойство | Значение |
|---|---|
| ColumnName | isRight |
| DataType | System.Boolean |
| Name | dcIsRight |
(рис 8.4) Создание объектов DataTable и DataColumn для таблицы VariantsВ базе данных Tests таблицы Questions и Variants мы связали по полю questID. Теперь при проектировании схемы базы нам следует также создать это отношение. В окне Properties объекта DataSet нажимаем на кнопку (_) в поле свойства Relations. В появившемся редакторе Relations Collection Editor нажимаем кнопку "Add" для добавления связи. Называем отношение "QuestionsVariants", а в качестве ключевого поля указываем questID (рис. 8.5):
(рис 8.5) Создание отношения QuestionsVariantsМы закончили работу с визуальными средствами. Программное создание объектов DataTable, DataColumn, DataRelation (см. далее) обеспечивает, при прочих равных условиях, большую производительность, однако редакторы позволяют быстрее осуществлять редактирование и обладают большей наглядностью. На первых порах проще работать именно с ними, но по мере роста опыта следует отказаться от их применения.
Переходим в код формы, подключаем пространство имен:
using System.Data.SqlClient;
В классе создаем перечисление QuestionType - вопросу с одним правильным вариантом будет соответствовать постоянная SingleVariant, вопросу с несколькими вариантами - :
public enum QuestionType
{
SingleVariant,
MultiVariant
}
Создаем перечисление Direction для навигации по вопросам:
public enum Direction
{
First,
Prev,
Next,
Last
}
В методе LoadDataBase заполняем объект DataSet данными из базы данных:
private void LoadDataBase()
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial
Catalog=Tests;Integrated Security=SSPI;");
SqlDataAdapter questAdapter =
new SqlDataAdapter("select * from questions", conn);
SqlDataAdapter variantsAdapter =
new SqlDataAdapter("select * from variants", conn);
conn.Open();
//Заполняем таблицу "Questions" данными из questAdapter
questAdapter.Fill(dsTests.Tables["Questions"]);
//Заполняем таблицу "Variants" данными из variantsAdapter
variantsAdapter.Fill(dsTests.Tables["Variants"]);
conn.Close();
}
Создаем два экземпляра класса
// Экземпляр clientAnswers для хранения ответов пользователей Hashtable clientAnswers = new Hashtable(); // Экземпляр keys для хранения правильных ответов Hashtable keys = new Hashtable();
Создаем метод InitAnswersKeysTable, в котором связываем элементы экземпляров Hashtable с записями таблиц
private void InitAnswersKeysTable()
{
// Создаем цикл, длина которого равна числу записей в таблице "Questions"
for(int i = 0; i < dsTests.Tables["Questions"].Rows.Count; i++)
{
// Выбираем записи из таблицы "Questions".
DataRow drquestion = dsTests.Tables["Questions"].Rows[i];
// Выбираем записи из таблицы "Variants".
DataRow[] drvariants =
drquestion.GetChildRows (dsTests.Relations["QuestionsVariants"]);
// Устанавливаем значение j, равное false для всех вариантов.
bool[] answers = new bool[drvariants.Length];
for(int j = 0; j < answers.Length; j++)
answers[j] = false;
// Добавляем значения к экземплярам Hashtable
keys.Add(drquestion, drvariants);
clientAnswers.Add(drquestion, answers);
}
}
Объект DataRow предназначен для просмотра и изменения содержимого отдельной записи в объекте DataTable. Для обращения к конкретной записи используется свойство Rows[i], где i - номер записи. Метод GetChildRows позволяет обращаться к дочерним записям, он принимает название отношения. Здесь мы фактически обращаемся к записям таблицы Variants. В экземпляре keys ключами будут записи из таблицы Questions, а значениями - элементы массива записей с вариантами ответов. В экземпляре clientAnswers ключами также будут записи из таблицы Questions, а значениями - элементы массива типа bool, зависящие от ответа пользователя.
В классе формы создаем экземпляр cmTest класса CurrencyManager для перемещения по записям:
CurrencyManager cmTest = null;
В методе InitDefaultSettings определяем настройки по умолчанию:
private void InitDefaultSettings()
{
//В свойство Tag каждой кнопки помещаем константу из перечисления Direction
btnFirst.Tag = Direction.First;
btnPrev.Tag = Direction.Prev;
btnNext.Tag = Direction.Next;
btnLast.Tag = Direction.Last;
//Для всех кнопок будет один обработчик btnFirst_Click
btnFirst.Click += new EventHandler(btnFirst_Click);
btnPrev.Click += new EventHandler(btnFirst_Click);
btnNext.Click += new EventHandler(btnFirst_Click);
btnLast.Click += new EventHandler(btnFirst_Click);
//Вызываем метод
LoadDataBase();
//Определяем действия для случая,
//если нет записей в таблице "Questions"
if(dsTests.Tables["Questions"].Rows.Count == 0)
{
txtQuestion.Text = "Нет данных о вопросах";
btnFirst.Enabled= false;
btnPrev.Enabled= false;
btnNext.Enabled= false;
btnLast.Enabled= false;
btnCheck.Enabled= false;
}
else
{
//Вызываем метод.
InitAnswersKeysTable();
//Связываем эземпляр cmTest с содержимым таблицы "Questions"
cmTest = (CurrencyManager)this.BindingContext[dsTests, "Questions"];
//Определяем обработчик для события
cmTest.PositionChanged += new EventHandler(cmTest_PositionChanged);
ShowQuestion(dsTests.Tables["questions"].Rows[0]);
//Включаем доступность кнопок ">" и ">>"
btnFirst.Enabled= false;
btnPrev.Enabled= false;
btnNext.Enabled= true;
btnLast.Enabled= true;
}
}
Создаем метод cmTest_PositionChanged, для обработки события PositionChanged объекта cmTest, в котором определяем доступность кнопок навигации:
private void cmTest_PositionChanged(object sender, EventArgs e)
{
if (cmTest.Position == 0)
{
btnPrev.Enabled = false;
btnFirst.Enabled = false;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
else if(cmTest.Position == dsTests.Tables["questions"].Rows.Count - 1)
{
btnNext.Enabled = false;
btnLast.Enabled = false;
btnPrev.Enabled = true;
btnFirst.Enabled = true;
}
else
{
btnPrev.Enabled = true;
btnFirst.Enabled = true;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
}
Создаем метод btnFirst_Click - общий обработчик для всех кнопок навигации:
private void btnFirst_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
Direction direction = (Direction)btn.Tag;
switch (direction)
{
case Direction.First:
cmTest.Position = 0;
break;
case Direction.Prev:
--cmTest.Position;
break;
case Direction.Next:
++cmTest.Position;
break;
case Direction.Last:
cmTest.Position = dsTests.Tables["questions"].Rows.Count - 1;
break;
}
int rowIndex = cmTest.Position;
//Вызываем метод ShowQuestion, который выводит вопросы на форму
ShowQuestion(dsTests.Tables["questions"].Rows[rowIndex]);
}
Вызываем метод InitDefaultSettings в конструкторе формы:
public Form1()
{
InitializeComponent();
InitDefaultSettings();
}
В методе ShowQuestion выводим вопрос на форму:
private void ShowQuestion(DataRow drquestion)
{
txtQuestion.Text = drquestion["question"].ToString();
//Вызываем метод ShowVariants, который выводит на форму варианты ответов
ShowVariants(drquestion);
}
В методе ShowVariants в зависимости от типа вопроса формируется набор элементов RadioButton или CheckBox c вариантами ответов, который затем выводится в элемент gbVariants:
private void ShowVariants(DataRow question)
{
//Удаляем все элементы из GroupBox
gbVariants.Controls.Clear();
//Снова создаем экземпляр childVariants
//для обращения к записям таблицы "Variants"
DataRow[] childVariants =
question.GetChildRows (dsTests.Relations["QuestionsVariants"]);
//Определяем тип вопроса
bool[] vars = (bool[])clientAnswers[question];
int i = 0;
QuestionType questType = (QuestionType)question["questType"];
switch(questType)
{
//Если вопрос имеет всего один правильный вариант,
//на форме будут созданы элементы Radiobutton
case QuestionType.SingleVariant:
foreach(DataRow childVariant in childVariants)
{
RadioButton rb = new RadioButton();
#region Ищем выбранный ответ в таблице ответов
bool selectedAnswer = (bool)vars[i++];
rb.Checked = selectedAnswer;
#endregion
//Определяем свойства созданного элемента RadioButton
rb.Text = childVariant["variant"].ToString();
rb.Tag = childVariant;
rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
int y = (gbVariants.Controls.Count == 0)?20:
((RadioButton)gbVariants.Controls[gbVariants.Controls.Count
- 1]).Bottom + 2;
//Определяем размеры создаваемых элементов RadioButton
//500 - ширина в пикселях, rb.Height+5 - высота
rb.Size = new Size(500, rb.Height+5);
rb.Location = new Point(10, y);
gbVariants.Controls.Add(rb);
}
break;
//Если вопрос имеет несколько правильных вариантов,
//на форме будут созданы элементы CheckBox
case QuestionType.MultiVariant:
foreach(DataRow childVariant in childVariants)
{
CheckBox chb = new CheckBox();
#region Ищем выбранный ответ в таблице ответов
bool selectedAnswer = (bool)vars[i++];
chb.Checked = selectedAnswer;
#endregion
//Определяем свойства созданного элемента RadioButton
chb.Text = childVariant["variant"].ToString();
chb.Tag = childVariant;
chb.CheckedChanged += new EventHandler(chb_CheckedChanged);
int y = (gbVariants.Controls.Count == 0)?20:
((CheckBox)gbVariants.Controls[gbVariants.Controls.Count
- 1]).Bottom + 2;
//Определяем размеры создаваемых элементов RadioButton
//500 - ширина в пикселях, chb.Height+5 - высота
chb.Size = new Size( 500, chb.Height+5);
chb.Location = new Point(10, y);
gbVariants.Controls.Add(chb);
}
break;
}
}
Когда пользователь отметит галочкой элементы CheckBox или выберет RadioButton, будет происходить событие CheckedChanged. В этом событии мы фиксируем положение отмеченного элемента - при возврате к решенному вопросу пользователь будет видеть свои ответы:
private void rb_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
if(!rb.Checked)
return;
//Создаем объект drvariant класса DataRow, с которым связываем
//свойство Tag элемента RadioButton
DataRow drvariant = (DataRow)rb.Tag;
//Отмечаем текущее положение объекта cmTest
int questIndex = cmTest.Position;
DataRow drquestion = dsTests.Tables["Questions"].Rows[questIndex];
int answIndex = gbVariants.Controls.IndexOf(rb);
//Выводим элемент RadioButton отмеченным, если он уже был выбран
bool[] answers = (bool[])clientAnswers[drquestion];
for(int i = 0; i < answers.Length; i++)
{
if(i == answIndex)
answers[i] = rb.Checked;
else
answers[i] = !rb.Checked;
}
}
private void chb_CheckedChanged(object sender, EventArgs e)
{
CheckBox chb = (CheckBox)sender;
//Создаем объект drvariant класса DataRow, с которым связываем
//свойство Tag элемента CheckBox
DataRow drvariant = (DataRow)chb.Tag;
//Отмечаем текущее положение объекта cmTest
int rowIndex = cmTest.Position;
DataRow drquestion = dsTests.Tables["Questions"].Rows[rowIndex];
int answIndex = gbVariants.Controls.IndexOf(chb);
//Выводим элемент CheckBox отмеченным, если он уже был выбран
bool[] answers = (bool[])clientAnswers[drquestion];
for(int i = 0; i< answers.Length; i++)
{
if (i == answIndex)
{
answers[i] = chb.Checked;
break;
}
}
}
Переключаемся в режим дизайна, щелкаем на кнопке "Результат" - в обработчике события подсчитываем количество правильных ответов:
private void btnCheck_Click(object sender, System.EventArgs e)
{
//Создаем счетчик
double counter = 0;
//Перебираем все ключи экземпляра key класса Hashtable,
//в котором хранятся ответы пользователя
foreach(object key in keys.Keys)
{
bool flag = true;
DataRow[] drvariants = (DataRow[])keys[key];
bool[] answers = (bool[])clientAnswers[key];
int i = 0;
foreach(DataRow variant in drvariants)
{
if(((bool)variant["isRight"]) == answers[i++])
continue;
else
{
flag = false;
break;
}
}
if (flag)
++counter;
}
//Делим количество правильных ответов на общее число ответов,
//результат умножаем на 100
int result = (int)(counter / dsTests.Tables["questions"].Rows.Count * 100);
MessageBox.Show(String.Format("Вы ответили правильно на {0}% вопросов.",
result), "Результат тестирования", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
Запускаем приложение (рис. 8.6). Мы рассмотрели простейший случай подсчета результатов - конечно же, в реальных приложениях кнопка "Результат" не может быть доступна в любой момент времени. Впрочем, это достаточно легко изменить.
(рис 8.6) Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестированияВ программном обеспечении к курсу вы найдете приложение Tests (Code\Glava4\Tests).
Еще в первой лекции мы узнали, что все объекты вкладки Data или все объекты ADO .NET можно создавать программно. Скопируйте папку приложения Tests и назовите ее "ProgrammTests". Открываем проект, удаляем объект DataSet с панели компонент формы. Для создания этого объекта программно достаточно следующей строчки кода:
DataSet dsTests = new DataSet();
Поскольку созданный экземпляр dsTests будет использоваться в нескольких методах, эту строку следует написать в классе формы. Здесь dsTests - это название объекта DataSet, которое мы затем используем в коде - это известно нам с первой лекции. Среда Visual Studio .NET однако, сгенерировала следующий фрагмент кода (его можно найти в исходном проекте "Tests"):
this.dsTests = new System.Data.DataSet();
...
//
// dsTests
//
this.dsTests.DataSetName = "NewDataSet";
this.dsTests.Locale = new System.Globalization.CultureInfo("ru-RU");
Как всегда, автоматический код избыточен - здесь указывается культура ru-RU. Дело в том, что у меня установлена русская версия Windows XP и соответствующие региональные настройки.
Среда определила еще свойство DataSetName = "NewDataSet", эквивалентное описание будет иметь следующий вид:
DataSet dsTests = new DataSet("NewDataSet");
Что же это за еще одно название объекта DataSet? Свойство DataSetName используется для работы с XSD-схемами, пока про это название (до лекции 10) мы можем просто забыть.
Создадим теперь объект DataTable для таблицы Questions:
DataTable dtQuestions = dsTests.Tables.Add("Questions");
//Или
//DataTable dtQuestions = new DataTable("Questions");
//dsTests.Tables.Add(dtQuestions);
Здесь мы создаем экземпляр dtQuestions объекта DataTable, затем вызываем метод Add свойства Tables объекта dsTests, которому передаем название таблицы Questions. Далее создаем поля в объекте dtQuestions:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32));
dсQuestID.Unique = true;
DataColumn dcQuestion = dtQuestions.Columns.Add("question");
DataColumn dcQuestType = dtQuestions.Columns.Add("questType", typeof(Int32));
Мы создаем поля, нужные для отражения соответствующих столбцов в таблице Questions. Перегруженный метод Add свойства Columns объекта dtQuestions позволяет задавать название столбца и его тип данных (рис. 8.7):
(рис 8.7) Создание поляСвойство Unique указывает, что в этом поле не должно быть повторяющихся значений, оно должно быть уникальным (здесь - поле questID является первичным ключом таблицы Questions).
Точно так же создаем объект DataTable для таблицы Variants и соответствующие поля:
//Cоздаем таблицу "Variants"
DataTable dtVariants = dsTests.Tables.Add("Variants");
//Заполняем поля таблицы "Variants"
DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32));
dcID.Unique = true;
dcID.AutoIncrement = true;
DataColumn dcVariantQuestID = dtVariants.Columns.Add("questID", typeof(Int32));
DataColumn dcVariant = dtVariants.Columns.Add("variant");
DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean));
Здесь мы дополнительно установили свойству AutoIncrement объекта dcID значение true. Свойство AutoIncrement (Счетчик) позволяет создать счетчик для поля, аналогичный типу данных "Счетчик" в Microsoft Access.
Теперь приступим к созданию связи между таблицами. В базе данных Microsoft SQL Tests между родительской таблицей Questions и дочерней Variants была установлена связь по полю questID, которое было в обеих таблицах. При программном создании объектов для поля questID таблицы Questions был создан объект dсQuestID, для этого же поля таблицы Variants создан объект dcVariantQuestID. В коде создание отношения между таблицами будет иметь следующий вид:
DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants",
dсQuestID, dcVariantQuestID);
dsTests.Relations.Add(drQuestionsVariants);
Здесь в конструкторе drQuestionsVariants - название экземпляра объекта (класса) DataRelation, а QuestionsVariants - свойство relationName - название связи, которая будет содержаться в объекте drQuestionsVariants. Другими словами, drQuestionsVariants - название экземпляра DataRelation, которое мы будем использовать в коде, а свойство relationName - всего лишь название отражаемой связи, которую можно удалить или переименовать.
Итак, мы создали все объекты для отображения таблиц, полей и даже связей между таблицами.
Теперь нам осталось определить некоторые свойства таблиц, называемые ограничениями. Свойство ограничения ( Constraint ) объекта DataTable бывает двух типов - UniqueConstraint и ForeignKeyConstraint. Свойство UniqueConstraint определяет первичный ключ таблицы, например, в таблице Questions ключевым полем является questID. Объект dсQuestID представляет это поле:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32));
Ограничение UniqueConstraint, налагаемое на объект dсQuestID, запрещает появление дублированных строк:
UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID); dtQuestions.Constraints.Add(UC_dtQuestions);
Однако при создании объекта dсQuestID мы ведь уже определяли его уникальность:
dсQuestID.Unique = true;
Действительно, последняя строка представляет собой неявный способ задания ограничения UniqueConstraint. Если мы уже определили уникальное поле или поля, используя свойство Unique, задавать ограничение UniqueConstraint не нужно.
Второе ограничение - ForeignKeyConstraint - определяет, как должны себя вести дочерние записи при изменении родительских записей и наоборот. Конечно, интерфейс нашего приложения вообще не подразумевает внесение изменений, но ADO .NET требует точного описания объектов для управления ими. Ограничение ForeignKeyConstraint содержит следующие три правила:
AcceptChanges объекта DataTable, для которого определено ограничение.Для этих правил могут применяться следующие значения:
Cascade - None - изменения в SetDefault - полю внешнего ключа в дочерних записях присваивается значение, заданное в свойстве DefaultValue этого поля;SetNull - полю внешнего ключа в дочерних записях присваивается значение Null.Значением по умолчанию для правил UpdateRule и DeleteRule является Cascade, для правила AcceptRejectRule - None. Дополнительно, правило AcceptRejectRule принимает значения только Cascade или None.
Создадим ограничение для связи QuestionsVariants:
ForeignKeyConstraint FK_QuestionsVariants = new ForeignKeyConstraint(dtQuestions.Columns["questID"], dtVariants.Columns["questID"]); dtVariants.Constraints.Add(FK_QuestionsVariants);
Здесь задается вначале родительская колонка, а затем дочерняя (рис. 8.8). Добавлять созданное ограничение следует к объекту DataTable, представляющему dtVariants )
(рис 8.8) Создание ограничения FK_QuestionsVariantsЭтот фрагмент кода оставляет значения правил UpdateRule, DeleteRule и AcceptRejectRule заданными по умолчанию, т.е. Cascade и None, что соответствует значениям настройки с помощью мастера Relation (рис. 8.9):
(рис 8.9) Мастер Relation и соответствующий фрагмент кодаЕсли бы нам потребовалось задать значение одному из правил, отличное по умолчанию, мы бы просто применили другой вариант конструктора (рис. 8.10):
(рис 8.10) В этом конструкторе можно задать значения правил RejectRule, DeleteRule и UpdateRule LoadDataBase в проекте ProgrammTests будет выглядеть так:
private void LoadDataBase()
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial
Catalog=Tests;Integrated Security=SSPI;");
SqlDataAdapter questAdapter =
new SqlDataAdapter("select * from questions", conn);
SqlDataAdapter variantsAdapter =
new SqlDataAdapter("select * from variants", conn);
//dsTests.EnforceConstraints = true;
//Cоздаем таблицу "Questions"
DataTable dtQuestions = dsTests.Tables.Add("Questions");
//Или
//DataTable dtQuestions = new DataTable("Questions");
//dsTests.Tables.Add(dtQuestions);
//Заполняем поля таблицы "Questions"
DataColumn dсQuestID =
dtQuestions.Columns.Add("questID", typeof(Int32));
dсQuestID.Unique = true;
//Или
//UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID);
//dtQuestions.Constraints.Add(UC_dtQuestions);
DataColumn dcQuestion = dtQuestions.Columns.Add("question");
DataColumn dcQuestType =
dtQuestions.Columns.Add ("questType", typeof(Int32));
//Cоздаем таблицу "Variants"
DataTable dtVariants = dsTests.Tables.Add("Variants");
//Заполняем поля таблицы "Variants"
DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32));
dcID.Unique = true;
dcID.AutoIncrement = true;
DataColumn dcVariantQuestID =
dtVariants.Columns.Add("questID", typeof(Int32));
DataColumn dcVariant = dtVariants.Columns.Add("variant");
DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean));
//Создаем ограничение
ForeignKeyConstraint FK_QuestionsVariants =
new ForeignKeyConstraint(dtQuestions.Columns["questID"],
dtVariants.Columns["questID"]);
dtVariants.Constraints.Add(FK_QuestionsVariants);
//Создаем отношение
DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants",
dсQuestID, dcVariantQuestID);
dsTests.Relations.Add(drQuestionsVariants);
conn.Open();
//Заполняем таблицу "Questions" данными из questAdapter
questAdapter.Fill(dsTests.Tables["Questions"]);
//Заполняем таблицу "Variants" данными из variantsAdapter
variantsAdapter.Fill(dsTests.Tables["Variants"]);
conn.Close();
}
Обратим внимание на несколько деталей этого метода. Значение true свойства EnforceConstraints объекта dsTests разрешает использование ограничений. По умолчанию в созданном объекте DataSet это свойство и так принимает значение true, поэтому этот фрагмент кода закомментирован. Следует иметь в виду, что для снятия всех ограничений достаточно установить свойству EnforceConstraints значение false. Ограничение FK_QuestionsVariants создается перед отношением drQuestionsVariants - вначале следует создавать ограничения, а затем определять отношения. Соединение conn открывается как можно позже - непосредственно перед заполнением данными объектов DataAdapter, - и тут же закрывается. Открыть его в начале метода и закрыть в конце было бы нерациональным.
В программном обеспечении к курсу вы найдете приложение Programm Tests (Code\Glava4\ProgrammTests).
При создании базы данных не следует помещать в нее значения, которые могут быть получены из уже имеющихся данных. В первой лекции мы создали базу данных BDTur_firm.mdb, в которой есть таблица "Туры". В этой таблице имеется поле "цена", где указывается стоимость туров. На практике может понадобиться значение цены, выраженное в различных валютах, значение с учетом налогов, значение со скидкой и т.п. Для каждого конкретного случая можно получить дополнительное поле, не вводя его в саму базу данных. Технология ADO .NET позволяет создавать объекты DataColumn, основанные на выражении. Создайте новое Windows-приложение и назовите его CustomExpression. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. Переходим в код формы, подключаем пространство имен:
using System.Data.OleDb;
В классе формы определяем строки CommandText и ConnectionString:
string commandText = "SELECT Информация, [Код тура], Название, Цена FROM Туры"; string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0"";Data Source=""D:\Uchebnik\Code\Glava1\BDTur_firm.mdb"";User ID=Admin; Jet OLEDB:Encrypt Database=False";
В конструкторе формы программно создаем все объекты для вывода таблицы "Туры" в элемент управления DataGrid:
public Form1()
{
InitializeComponent();
OleDbConnection conn = new OleDbConnection (connectionString);
OleDbCommand myCommand = new OleDbCommand();
myCommand.Connection = conn;
myCommand.CommandText = commandText;
OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
dataAdapter.SelectCommand = myCommand;
DataSet dsTours = new DataSet();
DataTable dtTours = dsTours.Tables.Add("Туры");
DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32));
dсIDtour.Unique = true;
DataColumn dcName = dtTours.Columns.Add("Название");
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcInformation = dtTours.Columns.Add("Информация");
conn.Open();
dataAdapter.Fill(dsTours.Tables["Туры"]);
conn.Close();
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
}
Запускаем приложение (рис. 8.11):
(рис 8.11) Вывод содержимого таблицы "Туры"Теперь добавим два объекта DataColumn, в которых будет вычисляться налог и скидка, после добавления объекта dcPrice:
...
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcPriceNDS = dtTours.Columns.Add("Цена c НДС", typeof(Decimal));
dcPriceNDS.Expression = "Цена*0.15+Цена";
DataColumn dcPricewithDiscount = dtTours.Columns.Add("Цена cо скидкой",
typeof(Decimal));
dcPricewithDiscount.Expression = "Цена-Цена*0.10";
...
Свойство Expression созданного объекта DataColumn задает выражения для всех значений заданного поля (рис. 8.12):
(рис 8.12) Значения полей "Цена с НДС" и "Цена со скидкой" получены в результате вычисленийСвойство Expression поддерживает также агрегатные функции, объединение строк, ссылки на родительские и
В программном обеспечении к курсу вы найдете приложение Custom Expression (Code\Glava4\CustomExpression).
Содержимое объекта DataSet представляет собой набор записей, который представлен объектами DataRow. В запущенном приложении содержимое объекта DataSet доступно для изменений, например, если данные выводятся в элемент управления DataGrid, то, перемещаясь по отдельным клеткам, можно править значения как в обычной электронной таблице. При этом происходит изменение объекта DataRow, соответствующее заданной записи. Рассмотрим программное создание и изменение записей. Создайте новое Windows-приложение и назовите его ProgrammDataRow. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. В конструкторе формы создаем экземпляр dtTours и поля, соответствующие таблице "Туры":
public Form1()
{
InitializeComponent();
DataTable dtTours = new DataTable();
DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32));
dсIDtour.Unique = true;
DataColumn dcName = dtTours.Columns.Add("Название");
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcInformation = dtTours.Columns.Add("Информация");
DataView myDataView = new DataView(dtTours);
dataGrid1.DataSource = myDataView;
}
Для того чтобы привязать созданные данные к элементу управления DataTable содержит объект DataView, причем этот объект, используемый по умолчанию, называется DataTable.DefaultView. Мы уже сталкивались с ним неоднократно, например, в предыдущем проекте CustomExpression для вывода данных:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Один объект DataTable может иметь несколько объектов DataView - это удобно для вывода одних и тех же данных, отфильтрованных или отсортированных различным образом. В следующей лекции мы рассмотрим подробно DataView. Запускаем приложение (рис. 8.13). Мы видим готовую структуру таблицы "Туры":
(рис 8.13) Структура таблицы "Туры"Мы не будем сейчас подключаться к какой-либо базе данных - попробуем заполнить таблицу записями программно. Для добавлений одной новой записи перед созданием экземпляра myDataView вставляем следующий фрагмент кода:
DataRow myRow = dtTours.NewRow(); myRow["Код тура"] = 1; myRow["Название"] = "Кипр"; myRow["Цена"] = 25000; myRow["Информация"] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow);
Запускаем приложение (рис. 8.14). В таблице появилась первая запись.
(рис 8.14) Добавление записи в таблицу Добавим еще одну запись:
DataRow myRow2 = dtTours.NewRow(); myRow2["Код тура"] = 2; myRow2["Название"] = "Греция"; myRow2["Цена"] = 32000; myRow2["Информация"] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Название, указываемое в квадратных скобках объектов myRow или myRow2, представляет собой имя столбца, которые мы определили в самом начале. К столбцу можно обращаться и по индексу - закомментируйте добавление двух записей и внесите следующий код:
DataRow myRow = dtTours.NewRow(); myRow[0] = 1; myRow[1] = "Кипр"; myRow[2] = 25000; myRow[3] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow); DataRow myRow2 = dtTours.NewRow(); myRow2[0] = 2; myRow2[1] = "Греция"; myRow2[2] = 32000; myRow2[3] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Нумерация столбцов начинается с нуля. Более удобный способ добавления записей - применение свойства ItemArray объекта DataRow, где можно задавать значения полей в виде массива:
DataRow myRow3 = dtTours.NewRow();
myRow3.ItemArray = new object[]{3,"Таиланд", 30000, null};
dtTours.Rows.Add(myRow3);
Здесь мы указали значение поля "Информация", равное null, - таким образом можно пропускать неизвестные поля (рис. 8.15):
(рис 8.15) Вставка записи с одним значением nullВ программном обеспечении к курсу вы найдете приложение Programm DataRow (Code\Glava4\ ProgrammDataRow).
Конечно, вставка записей вручную в объект DataSet, не связанный с хранилищем данных, имеет не очень большой смысл. Поэтому давайте рассмотрим, как вставлять (и изменять) данные в уже загруженный кэш данных. Скопируйте папку проекта CustomExpression и переименуйте ее в WorkWithRow. Чтобы не тратить время на привязку действий к элементам управления, будем далее писать код после отображения данных в элементе DataGrid:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Для изменения, например, пятой строки, мы указываем в свойстве Rows объекта dtTours ее индекс (4, нумерация начинается с нуля), затем вызываем метод BeginEdit для начала редактирования, устанавливаем группу свойств и в заключение принимаем изменения, вызывая метод EndEdit:
DataRow myRow=dtTours.Rows[4]; myRow.BeginEdit(); myRow["Код тура"] = 5; myRow["Название"] = "Турция"; myRow["Цена"] = "27000"; myRow["Информация"] = "Осенние скидки с 15 октября"; myRow.EndEdit();
Тот же самый результат мы получим с помощью свойства ItemArray:
DataRow myRow=dtTours.Rows[4];
myRow.BeginEdit();
myRow.ItemArray = new object[]{5,"Турция", 27000, null, null,
"Осенние скидки с 15 октября"};
myRow.EndEdit();
Здесь мы установили для третьего и четвертого полей, которые являются вычисляемыми, значения null, подразумевая, что они останутся по умолчанию, а при запуске заполнятся своими значениями (рис. 8.16):
(рис 8.16) Пропущенные вычисляемые поля заполняются своими значениями Для удаления заданной записи нужно создать объект DataRow, которому передается индекс строки, а затем вызвать метод Remove свойства Rows объекта DataTable:
DataRow myRow2 = dtTours.Rows[0]; dtTours.Rows.Remove(myRow2);
Этого достаточно для удаления строки, но для того, чтобы пометить заданную строку как удаленную, вызываем метод Delete:
myRow2.Delete();
В результате у нас удалится строка (рис. 8.17), причем объект DataTable пометит ее в качестве удаленной - это необходимо, чтобы избежать ошибок (например, в связанных записях).
(рис 8.17) Первая строка, имеющая индекс 0, была удалена В программном обеспечении к курсу вы найдете приложение WorkWith Row (Code\Glava4\ WorkWithRow).
При работе с данными приходится постоянно вносить изменения в записи - добавлять, редактировать или удалять. Объект DataRow обладает свойством RowState, позволяющим отслеживать текущий статус строки. Создайте новое приложение, назовите его "RowState". Перетаскиваем на форму элементы RichTextBox, , DataGrid, устанавливаем для них следующие свойства:
| richTextBox1, свойство | Значение |
|---|---|
| Dock | Bottom |
| Text |
| splitter1, свойство | Значение |
|---|---|
| Dock | Bottom |
| dataGrid1, свойство | Значение |
|---|---|
| Dock | Fill |
В конструкторе формы мы создаем всего одно поле, затем одну запись, статус которой будем отслеживать:
public Form1()
{
InitializeComponent();
DataTable dtTours = new DataTable("Туры");
DataColumn IDtour = new DataColumn("Код тура", typeof(Int32));
dtTours.Columns.Add(IDtour);
dataGrid1.DataSource = dtTours;
DataRow myRow;
// Создаем новую, отсоединенную запись
myRow = dtTours.NewRow();
richTextBox1.Text += Convert.ToString("Новая запись: "
+ myRow.RowState);
//Добавляем запись в объект DataTable
dtTours.Rows.Add(myRow);
richTextBox1.Text += Convert.ToString("\nДобавление записи: " +
myRow.RowState);
//Принимаем все изменения в объекте DataTable
dtTours.AcceptChanges();
richTextBox1.Text += Convert.ToString("\nМетод AcceptChanges: " +
myRow.RowState);
//Редактируем запись
myRow["Код тура"] = 1;
richTextBox1.Text += Convert.ToString("\nРедактирование строки: " +
myRow.RowState);
//Удаляем строку
myRow.Delete();
richTextBox1.Text += Convert.ToString("\nУдаление: " +
myRow.RowState);
//Отменяем все изменения в объекте DataTable
dtTours.RejectChanges();
richTextBox1.Text += Convert.ToString("\nМетод RejectChanges: " +
myRow.RowState);
}
Запускаем приложение. В текстовое поле выводится статус записи myRow (рис. 8.18):
(рис 8.18) Приложение RowStateЗначение Detached означает, что запись не относится к объекту DataTable. После добавления ее статус изменяется на Added - теперь она существует в объекте DataTable, но ее нет в базе данных. Конечно, здесь мы не рассматривали взаимодействие с источником записей, но это же значение будет у записей, добавляемых в DataGrid после вывода данных из базы при наличии подключения. Вызывая метод AcceptChanges объекта DataTable, мы принимаем все изменения, поэтому статус DataRow изменяется на - теперь запись считается "своей", она не была изменена после вызова метода. После вызова метода Delete запись помечается удаленной - она еще не полностью удалена, в случае RejectChanges объекта DataTable, мы восстанавливаем запись до состояния .
Свойство RowState всегда возвращает отдельное, доступное только для чтения значение. Это свойство используется для поиска записей, соответствующих заданному статусу, а также при передаче изменений в базу данных.
В программном обеспечении к курсу вы найдете приложение RowState (Code\Glava4\ RowState).
Свойство RowVersion предназначено для извлечения значения записи (объекта DataRow), зависящего от совершенных изменений. Возможны следующие версии записи:
Current - текущее значениеDefault - значение по умолчаниюOriginal - первоначальное значениеProposed - предполагаемое значениеСоздайте новое приложение, назовите его "RowVersion". Свойству Size формы устанавливаем значение 500; 300. Из окна Toolbox перетаскиваем следующие элементы и устанавливаем их свойства:
| panel1, свойство | Значение |
|---|---|
| Dock | Bottom |
| dataGrid1, свойство | Значение |
|---|---|
| Dock | Left |
| CaptionText | Туры |
| splitter1, свойство | Значение |
|---|---|
| Dock | Left |
| richTextBox1, свойство | Значение |
|---|---|
| Name | rtbReport |
| Dock | Fill |
| Text |
| button1, свойство | Значение |
|---|---|
| Name | BtnBeginEdit |
| Location | 14; 21 |
| Text | Begin Edit |
| button2, свойство | Значение |
|---|---|
| Name | btnEndEdit |
| Location | 106; 21 |
| Text | End Edit |
| button3, свойство | Значение |
|---|---|
| Name | btnCancelEdit |
| Location | 198; 21 |
| Text | Cancel Edit |
| button4, свойство | Значение |
|---|---|
| Name | btnDelete |
| Location | 290; 21 |
| Text | Удалить |
| button5, свойство | Значение |
|---|---|
| Name | btnClearReport |
| Location | 382; 21 |
| Size | 96; 23 |
| Text | Очистить отчет |
Переходим к коду. В классе формы объявляем объекты DataRow и DataTable:
DataRow myRow; DataTable dtTours;
В конструкторе формы создаем запись, определяем источник данных для элемента DataGrid, а также отключаем его доступность:
public Form1()
{
InitializeComponent();
dtTours = new DataTable("Туры");
DataColumn IDtour = new DataColumn("Название", typeof(string));
dtTours.Columns.Add(IDtour);
myRow = dtTours.NewRow();
dtTours.Rows.Add(myRow);
myRow["Название"] = "Таиланд";
dataGrid1.DataSource = dtTours;
dataGrid1.Enabled = false;
}
Создаем метод TestRowVersion, в котором будет проверяться свойство RowVersion записи:
private void TestRowVersion()
{
if(myRow.HasVersion(DataRowVersion.Original))
rtbReport.Text += String.Format("Значение original: {0}\n",
myRow["Название", DataRowVersion.Original]);
if(myRow.HasVersion(DataRowVersion.Current))
rtbReport.Text += String.Format("Значение current: {0}\n",
myRow["Название", DataRowVersion.Current]);
if(myRow.HasVersion(DataRowVersion.Default))
rtbReport.Text += String.Format("Значение default: {0}\n",
myRow["Название", DataRowVersion.Default]);
if(myRow.HasVersion(DataRowVersion.Proposed))
rtbReport.Text += String.Format("Значение proposed: {0}\n",
myRow["Название", DataRowVersion.Proposed]);
}
Метод HasVersion позволяет определить, поддерживает ли объект myRow версию данных, указываемую в скобках. В случае подтверждения будет выполняться код оператора - выводится в элемент rtbReport соответствующее сообщение.
В обработчике кнопки "Begin Edit" вызываем метод BeginEdit, устанавливаем новое значение записи:
private void btnBeginEdit_Click(object sender, System.EventArgs e)
{
myRow.BeginEdit();
myRow["Название"] = "Франция";
rtbReport.Text += "BeginEdit\n";
TestRowVersion();
}
В обработчике кнопки "End Edit" завершаем редактирование записи:
private void btnEndEdit_Click(object sender, System.EventArgs e)
{
myRow.EndEdit();
rtbReport.Text += "EndEdit\n";
TestRowVersion();
}
В обработчике кнопки "Cancel Edit" отказываемся от внесенных изменений:
private void btnCancelEdit_Click(object sender, System.EventArgs e)
{
myRow.CancelEdit();
rtbReport.Text += "CancelEdit\n";
TestRowVersion();
}
В обработчике кнопки "Удалить" удаляем объект myRow:
private void btnDelete_Click(object sender, System.EventArgs e)
{
myRow.Delete();
rtbReport.Text += "Запись удалена\n";
TestRowVersion();
}
В обработчике кнопки "Очистить отчет" просто удаляем содержимое текстового поля:
private void btnClearReport_Click(object sender, System.EventArgs e)
{
this.rtbReport.Text = "";
}
Запускаем приложение. После нажатия кнопки "BeginEdit" мы начинаем редактирование записи, вводится новое значение - "Франция". Оно становится значением по умолчанию "Default" и предполагаемым "Proposed", значение "Таиланд" является текущим "Current" (рис. 8.19, А). Отменяем редактирование, нажимая кнопку "Cancel Edit". При этом значение "Таиланд" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Б). Снова начинаем редактирование - картина повторяется (рис. 8.19, В). На этот раз завершаем его, нажимая кнопку "End Edit" - новое значение "Франция" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Г). Нажимаем кнопку "Удалить" - при этом удаляется сам объект myRow и дальнейшее изменение его з начений оказывается невозможным (рис. 8.19, Д).
(рис 8.19) Приложение "RowVersion"В программном обеспечении к курсу вы найдете приложение RowVersion (Code\Glava4\ RowVersion).
Подобная функциональность вряд может нас устроить в реальных приложениях - в самом деле, мы вставляем только заранее определенное значение "Франция". Скопируйте папку приложения RowVersion и назовите ее RowVersion2. Добавим на форму элементы "надпись" и "текстовое поле":
| label1, свойство | Значение |
|---|---|
| location | 24; 16 |
| Size | 72; 24 |
| Text | Название |
| textbox1, свойство | Значение |
|---|---|
| Name | txtmyRowName |
| location | 104; 16 |
| Size | 344; 20 |
| Text |
Для того чтобы эти элементы поместились на панели, последнюю немного растянем, а кнопки сдвинем вниз, к краю формы. Обработчики кнопок изменим следующим образом:
private void btnBeginEdit_Click(object sender, System.EventArgs e)
{
myRow.BeginEdit();
rtbReport.Text += "BeginEdit\n";
TestRowVersion();
txtmyRowName.Enabled = true;
}
private void btnEndEdit_Click(object sender, System.EventArgs e)
{
myRow["Название"] = txtmyRowName.Text;
myRow.EndEdit();
rtbReport.Text += "EndEdit\n";
TestRowVersion();
txtmyRowName.Enabled = false;
}
private void btnCancelEdit_Click(object sender, System.EventArgs e)
{
myRow.CancelEdit();
rtbReport.Text += "CancelEdit\n";
TestRowVersion();
txtmyRowName.Enabled = false;
}
Обработчик кнопки "Удалить" изменим так же - теперь будет удаляться текущий экземпляр myRow и тут же создаваться новый:
private void btnDelete_Click(object sender, System.EventArgs e)
{
myRow.Delete();
rtbReport.Text += "Запись удалена\n";
TestRowVersion();
myRow = dtTours.NewRow();
dtTours.Rows.Add(myRow);
}
В конструкторе формы отключим доступность текстового поля:
public Form1()
{
...
txtmyRowName.Enabled = false;
}
Запускаем приложение. Выполняя знакомую последовательность действий, получаем похожий отчет; теперь при удалении записи можно продолжить работу - объект myRow будет воссоздан (рис. 8.20):
(рис 8.20) Приложение RowVersion2В программном обеспечении к курсу вы найдете приложение RowVersion2 (Code\Glava4\ RowVersion2).
Объект DataTable содержит ряд событий, которые могут применяться для слежения за происходящими изменениями. Наиболее часто используются следующие события:
ColumnChanged - наступает после изменения содержимого поля таблицы;ColumnChanging - происходит в течение редактирования содержимого поля таблицы;RowChanged - наступает после изменения объекта DataRow (записи);RowChanging - происходит в течение редактирования объекта DataRow ;RowDeleted - наступает после удаления объекта DataRow ;RowDeleting - происходит при удалении объекта DataRow.Скопируйте папку приложения RowVersion, назовите ее "Data TableEvents". В конструкторе формы добавим обработку четырех событий объекта DataTable:
public Form1()
{
...
dtTours.RowChanging += new DataRowChangeEventHandler(dtTours_RowChanging);
dtTours.RowChanged += new DataRowChangeEventHandler(dtTours_RowChanged);
dtTours.RowDeleting += new DataRowChangeEventHandler(dtTours_RowDeleting);
dtTours.RowDeleted += new DataRowChangeEventHandler(dtTours_RowDeleted);
}
В соответствующих методах просто выводим сообщение в текстовое поле:
private void dtTours_RowChanging(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += String.Format("Событие - изменение записи\n",
e.Row["Название"]);
}
private void dtTours_RowChanged(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += "\nСобытие - запись изменена\n";
}
private void dtTours_RowDeleting(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += String.Format("Событие - удаление записи\n",
e.Row["Название"]);
}
private void dtTours_RowDeleted(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += "\nСобытие - запись удалена\n";
}
Запускаем приложение. Нажимаем кнопку "Begin Edit", затем "End Edit" - происходят события RowChanging и RowChanged. Удаляем запись - происходят события RowDeleting и RowDeleted (рис. 8.21).
(рис 8.21) Проект "DataTableEvents"В обработчиках событий можно добавить соответствующие действия, например, подтверждение изменения ( RowChanging ) или удаления ( RowDeleting ).
В программном обеспечении к курсу вы найдете приложение DataTable Events (Code\Glava4\ DataTableEvents).
Итак, DataSet представляет собой буфер для хранения данных из базы. Этот буфер предназначен для хранения структурированной информации, представленной в виде таблиц, поэтому первым, самым очевидным вложенным объектом DataSet является DataTable. Внутри одного объекта DataSet может храниться несколько загруженных таблиц из базы данных, помещенных в соответствующие объекты DataTable. Всякая таблица состоит из столбцов (называемых также полями или колонками) и строк. Для обращения к ним и для управления столбцами и строками в объекте DataTable предназначены специальные объекты - DataColumn и DataRow. Между таблицами, как мы знаем, могут быть связи - здесь они представлены объектом DataRelation. Наконец, в таблицах есть первичные и вторичные ключи - объект Constraint со своими двумя подклассами UniqueConstraint и ForeighKeyConstraint описывают их. Я все вр
емя говорю "представлены", "описывают", избегая слов "отображают" и "определяются" - дело в том, что нельзя ставить знак равенства между, например, объектом DataRelation и связью таблиц. В загруженных таблицах не формируются автоматически все нужные объекты - кое-где мы должны делать это самостоятельно. Сами объекты имеют также довольно тонкую и сложную структуру, поэтому это было бы довольно грубым приближением. Однако, на первых порах, для понимания сути полезно держать в голове следующие "формулы":
DataSet = одна или несколько таблиц = один или несколько объектов DataTable. DataTable = таблица. DataColumn = столбец, поле, колонка. DataRow = строка. DataTable = таблица = несколько полей, столбцов, колонок = несколько объектов DataColumn. DataTable = таблица = несколько строк = несколько объектов DataRow. DataRelation = связь между таблицами.
Возникает вопрос: для чего нужны эти объекты, если мы прекрасно обходились и без них для вывода содержимого таблицы, например в элемент DataGrid? Дело в том, что для простого отображения информации создавать эти объекты не требуется, но тогда все данные будут однородными текстовыми переменными, подобно таблицам в документе Microsoft Word. DataSet не может сам сформировать структуру данных - тип переменных, первичные и вторичные ключи, связи между таблицами. Для управления структурой, для сложного отображения (например, вывод информации с привязкой к элементам, создаваемым в режиме работы приложения) и нужно определение этих объектов.
Лучший способ разобраться с работой всех объектов - применить их на практике. Создадим простую
(рис 8.1) Структура базы Tests (диаграмма "QuestVar")Каждый вопрос будет содержать несколько ответов, для синхронного перемещения по структуре нужна связь по полю questID. Галочка "Allow Nulls" (Разрешить пустые значения) снята для всех полей - это означает, что все поля будут обязательными для заполнения. Структура таблиц Questions и Variants приводится в таблице 8.1:
| Таблица Поле | Описание поля |
|---|---|
Questions - таблица вопросов |
questID Номер вопроса |
question Текст вопроса |
|
questType Тип вопроса (с одним правильным вариантом ответа или с несколькими) |
|
Variants - таблица вариантов ответов |
id Номер ответа |
questID Номер вопроса |
|
variant Текст варианта ответа |
|
isRight Является ли данный ответ верным |
Для нас не столь важно количество вопросов, поэтому их всего будет пять, относящихся к теме операционной системы Windows XP. Одному вопросу может соответствовать один правильный вариант ответа - в этом случае в поле questType указывается значение "0". Далее на форме эти варианты будут выводиться с элементами RadioButton. Для вопросов, у которых есть несколько правильных вариантов ответов, указывается в поле questType значение "1". На форме такие варианты будут выводиться с чекбоксами. В таблицах 8.2 и 8.3 приводится содержимое таблиц Questions и Variants.
| questID | Question | questType |
|---|---|---|
| 1 | Для переустановки операционной системы Windows XP вам необходимо экспортировать банк сообщений программы |
0 |
| 2 | При компиляции программы в среде Microsoft Visual Studio .NET возникает систематическая ошибка в модуле AssemblyInfo из-за неудачного выбора имени пользователя и организации (были использованы кавычки) при установке системы (Для просмотра: "Мой компьютер - правая кнопка - Свойства - вкладка "Общие""). Вам необходимо изменить эти параметры | 0 |
| 3 | Выберите группы, состоящие из файлов, размер которых после архивирования составляет 5-10% от исходного | 1 |
| 4 | Укажите ряд, состоящий из агрегатных (агрегаторных) функций SQL | 0 |
| 5 | Вы изменили ключ BootExecute в разделе реестра [HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager]. В результате было выполнено следующее действие: |
0 |
| id | questID | Variant | isRight |
|---|---|---|---|
| 1 | 1 | C:\Program Files\Outlook Express\Mail | 0 |
| 2 | 1 | C:\Documents and Settings\Имя_Пользователя\ Local Settings\Application Data\Identities\ {F4CB90C4-3FD5-406B-83FB-85E644627B87}\Microsoft\Outlook Express | 1 |
| 3 | 1 | C:\WINDOWS\system32\Microsoft\Outlook Express\Bases | 0 |
| 4 | 1 | C:\Documents and Settings\Default User\Cookies | 0 |
| 5 | 2 | "Мой компьютер - правая кнопка - Свойства - Вкладка "Общие" на подписи - правая кнопка - Свойства - Переименовать" | 0 |
| 6 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion] затем меняем параметры RegisteredOwner и RegisteredOrganization |
1 |
| 7 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT] удаляем раздел CurrentVersion |
0 |
| 8 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] |
0 |
| 9 | 3 | *.mpeg, *.mdb, *. Htm | 0 |
| 10 | 3 | *.xls, *.txt , *.mht | 1 |
| 11 | 3 | *. jpeg, *.gif, *.mp3 | 0 |
| 12 | 3 | *. doc, *. xml, *.bmp | 1 |
| 13 | 4 | update, insert, sum |
0 |
| 14 | 4 | where, like, create |
0 |
| 15 | 4 | count, min, max |
1 |
| 16 | 4 | select, avg, into |
0 |
| 17 | 5 | Отключилась проверка на ошибки всех дисковых разделов при загрузке Windows ХР | 1 |
| 18 | 5 | Установлен таймер на автоматическое отключение | 0 |
| 19 | 5 | Отключены все диагностические сообщения | 0 |
| 20 | 5 | Система перестанет загружаться | 0 |
В поле isRight таблицы Variants правильные ответы отмечаются значением "1". Понятно, что для практического применения
Создайте новое Windows-приложение и назовите его "Tests". Устанавливаем следующие свойства формы:
| Form1, форма, свойство | Значение |
|---|---|
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 550; 350 |
| Text | Тест |
Перетаскиваем на форму TextBox, , Label и пять кнопок, устанавливаем следующие значения свойств элементов:
| label1, свойство | Значение |
|---|---|
| Location | 16;8 |
| Text | Вопрос: |
| textBox1, свойство | Значение |
|---|---|
| Name | txtQuestion |
| Location | 12; 24 |
| Multiline | True |
| Size | 520; 90 |
| TabIndex | 8 |
| Text |
| groupBox1, свойство | Значение |
|---|---|
| Name | GbVariants |
| Location | 12; 120 |
| Size | 520; 150 |
| Text | Варианты ответов |
| button1, свойство | Значение |
|---|---|
| Name | btnFirst |
| Location | 24; 280 |
| Text | << |
| button2, свойство | Значение |
|---|---|
| Name | BtnPrev |
| Location | 99; 280 |
| Text | < |
| button3, свойство | Значение |
|---|---|
| Name | BtnNext |
| Location | 174; 280 |
| TabIndex | 0 |
| Text | > |
| button4, свойство | Значение |
|---|---|
| Name | BtnLast |
| Location | 249; 280 |
| Text | > |
| button5, свойство | Значение |
|---|---|
| Name | BtnCheck |
| Location | 360; 280 |
| Size | 150; 23 |
| Text | Результат |
Интерфейс приложения готов. Чекбоксы или элементы radioButton мы будем добавлять в режиме запуска приложения - в самом деле, длина всех ответов разная и делать привязку к статичным элементам нет смысла. Переключаемся на вкладку Data панели инструментов Toolbox и перетаскиваем на форму объект DataSet. В появившемся окне Add DataSet выбираем значение "Untyped DataSet" и нажимаем "OK". Появляется панель компонентов, с которой мы уже неоднократно имели дело. Свойству Name устанавливаем значение dsTests.
В свойстве Tables элемента (...), запускается редактор Table Collection Editor (рис. 8.2), нажимаем кнопку "Add" и вводим следующие значения свойств:
| TableName | Questions |
| Name | dtQuestions |

Значение (рис 8.2) (...), появляется редактор Columns Collection Editor (рис. 8.3), нажимаем кнопку Add. Всего нужно будет создать три поля: questID, question и questType:
| Поле Column1, свойство | Значение |
|---|---|
| ColumnName | questID |
| DataType | System.Int32 |
| Unique | True |
| Name | dсQuestID |
| Поле Column2, свойство | Значение |
|---|---|
| ColumnName | question |
| Name | dcQuestion |
| Поле Column3, свойство | Значение |
|---|---|
| ColumnName | questType |
| DataType | System.Int32 |
| Name | dcQuestType |
(рис 8.3) Запуск редактора Columns Collection EditorЗавершив работу с редактором Columns Collection Editor, нажимаем кнопку Close. Мы закончили создание объекта DataTable для таблицы Questions. Аналогичные действия надо проделать, чтобы создать DataTable для таблицы Variants (рис. 8.4) и соответствующих полей id, questID, variant и isRight:
| TableName | Variants |
| Name | dtVariants |
| Поле Column1, свойство | Значение |
|---|---|
| ColumnName | id |
| DataType | System.Int32 |
| Unique | True |
| Name | dcID |
| Поле Column2, свойство | Значение |
|---|---|
| ColumnName | questID |
| DataType | System.Int32 |
| Name | dcVariantQuestID |
| Поле Column3, свойство | Значение |
|---|---|
| ColumnName | variant |
| Name | dcVariant |
| Поле Column4, свойство | Значение |
|---|---|
| ColumnName | isRight |
| DataType | System.Boolean |
| Name | dcIsRight |
(рис 8.4) Создание объектов DataTable и DataColumn для таблицы VariantsВ базе данных Tests таблицы Questions и Variants мы связали по полю questID. Теперь при проектировании схемы базы нам следует также создать это отношение. В окне Properties объекта DataSet нажимаем на кнопку (_) в поле свойства Relations. В появившемся редакторе Relations Collection Editor нажимаем кнопку "Add" для добавления связи. Называем отношение "QuestionsVariants", а в качестве ключевого поля указываем questID (рис. 8.5):
(рис 8.5) Создание отношения QuestionsVariantsМы закончили работу с визуальными средствами. Программное создание объектов DataTable, DataColumn, DataRelation (см. далее) обеспечивает, при прочих равных условиях, большую производительность, однако редакторы позволяют быстрее осуществлять редактирование и обладают большей наглядностью. На первых порах проще работать именно с ними, но по мере роста опыта следует отказаться от их применения.
Переходим в код формы, подключаем пространство имен:
using System.Data.SqlClient;
В классе создаем перечисление QuestionType - вопросу с одним правильным вариантом будет соответствовать постоянная SingleVariant, вопросу с несколькими вариантами - :
public enum QuestionType
{
SingleVariant,
MultiVariant
}
Создаем перечисление Direction для навигации по вопросам:
public enum Direction
{
First,
Prev,
Next,
Last
}
В методе LoadDataBase заполняем объект DataSet данными из базы данных:
private void LoadDataBase()
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial
Catalog=Tests;Integrated Security=SSPI;");
SqlDataAdapter questAdapter =
new SqlDataAdapter("select * from questions", conn);
SqlDataAdapter variantsAdapter =
new SqlDataAdapter("select * from variants", conn);
conn.Open();
//Заполняем таблицу "Questions" данными из questAdapter
questAdapter.Fill(dsTests.Tables["Questions"]);
//Заполняем таблицу "Variants" данными из variantsAdapter
variantsAdapter.Fill(dsTests.Tables["Variants"]);
conn.Close();
}
Создаем два экземпляра класса
// Экземпляр clientAnswers для хранения ответов пользователей Hashtable clientAnswers = new Hashtable(); // Экземпляр keys для хранения правильных ответов Hashtable keys = new Hashtable();
Создаем метод InitAnswersKeysTable, в котором связываем элементы экземпляров Hashtable с записями таблиц
private void InitAnswersKeysTable()
{
// Создаем цикл, длина которого равна числу записей в таблице "Questions"
for(int i = 0; i < dsTests.Tables["Questions"].Rows.Count; i++)
{
// Выбираем записи из таблицы "Questions".
DataRow drquestion = dsTests.Tables["Questions"].Rows[i];
// Выбираем записи из таблицы "Variants".
DataRow[] drvariants =
drquestion.GetChildRows (dsTests.Relations["QuestionsVariants"]);
// Устанавливаем значение j, равное false для всех вариантов.
bool[] answers = new bool[drvariants.Length];
for(int j = 0; j < answers.Length; j++)
answers[j] = false;
// Добавляем значения к экземплярам Hashtable
keys.Add(drquestion, drvariants);
clientAnswers.Add(drquestion, answers);
}
}
Объект DataRow предназначен для просмотра и изменения содержимого отдельной записи в объекте DataTable. Для обращения к конкретной записи используется свойство Rows[i], где i - номер записи. Метод GetChildRows позволяет обращаться к дочерним записям, он принимает название отношения. Здесь мы фактически обращаемся к записям таблицы Variants. В экземпляре keys ключами будут записи из таблицы Questions, а значениями - элементы массива записей с вариантами ответов. В экземпляре clientAnswers ключами также будут записи из таблицы Questions, а значениями - элементы массива типа bool, зависящие от ответа пользователя.
В классе формы создаем экземпляр cmTest класса CurrencyManager для перемещения по записям:
CurrencyManager cmTest = null;
В методе InitDefaultSettings определяем настройки по умолчанию:
private void InitDefaultSettings()
{
//В свойство Tag каждой кнопки помещаем константу из перечисления Direction
btnFirst.Tag = Direction.First;
btnPrev.Tag = Direction.Prev;
btnNext.Tag = Direction.Next;
btnLast.Tag = Direction.Last;
//Для всех кнопок будет один обработчик btnFirst_Click
btnFirst.Click += new EventHandler(btnFirst_Click);
btnPrev.Click += new EventHandler(btnFirst_Click);
btnNext.Click += new EventHandler(btnFirst_Click);
btnLast.Click += new EventHandler(btnFirst_Click);
//Вызываем метод
LoadDataBase();
//Определяем действия для случая,
//если нет записей в таблице "Questions"
if(dsTests.Tables["Questions"].Rows.Count == 0)
{
txtQuestion.Text = "Нет данных о вопросах";
btnFirst.Enabled= false;
btnPrev.Enabled= false;
btnNext.Enabled= false;
btnLast.Enabled= false;
btnCheck.Enabled= false;
}
else
{
//Вызываем метод.
InitAnswersKeysTable();
//Связываем эземпляр cmTest с содержимым таблицы "Questions"
cmTest = (CurrencyManager)this.BindingContext[dsTests, "Questions"];
//Определяем обработчик для события
cmTest.PositionChanged += new EventHandler(cmTest_PositionChanged);
ShowQuestion(dsTests.Tables["questions"].Rows[0]);
//Включаем доступность кнопок ">" и ">>"
btnFirst.Enabled= false;
btnPrev.Enabled= false;
btnNext.Enabled= true;
btnLast.Enabled= true;
}
}
Создаем метод cmTest_PositionChanged, для обработки события PositionChanged объекта cmTest, в котором определяем доступность кнопок навигации:
private void cmTest_PositionChanged(object sender, EventArgs e)
{
if (cmTest.Position == 0)
{
btnPrev.Enabled = false;
btnFirst.Enabled = false;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
else if(cmTest.Position == dsTests.Tables["questions"].Rows.Count - 1)
{
btnNext.Enabled = false;
btnLast.Enabled = false;
btnPrev.Enabled = true;
btnFirst.Enabled = true;
}
else
{
btnPrev.Enabled = true;
btnFirst.Enabled = true;
btnNext.Enabled = true;
btnLast.Enabled = true;
}
}
Создаем метод btnFirst_Click - общий обработчик для всех кнопок навигации:
private void btnFirst_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
Direction direction = (Direction)btn.Tag;
switch (direction)
{
case Direction.First:
cmTest.Position = 0;
break;
case Direction.Prev:
--cmTest.Position;
break;
case Direction.Next:
++cmTest.Position;
break;
case Direction.Last:
cmTest.Position = dsTests.Tables["questions"].Rows.Count - 1;
break;
}
int rowIndex = cmTest.Position;
//Вызываем метод ShowQuestion, который выводит вопросы на форму
ShowQuestion(dsTests.Tables["questions"].Rows[rowIndex]);
}
Вызываем метод InitDefaultSettings в конструкторе формы:
public Form1()
{
InitializeComponent();
InitDefaultSettings();
}
В методе ShowQuestion выводим вопрос на форму:
private void ShowQuestion(DataRow drquestion)
{
txtQuestion.Text = drquestion["question"].ToString();
//Вызываем метод ShowVariants, который выводит на форму варианты ответов
ShowVariants(drquestion);
}
В методе ShowVariants в зависимости от типа вопроса формируется набор элементов RadioButton или CheckBox c вариантами ответов, который затем выводится в элемент gbVariants:
private void ShowVariants(DataRow question)
{
//Удаляем все элементы из GroupBox
gbVariants.Controls.Clear();
//Снова создаем экземпляр childVariants
//для обращения к записям таблицы "Variants"
DataRow[] childVariants =
question.GetChildRows (dsTests.Relations["QuestionsVariants"]);
//Определяем тип вопроса
bool[] vars = (bool[])clientAnswers[question];
int i = 0;
QuestionType questType = (QuestionType)question["questType"];
switch(questType)
{
//Если вопрос имеет всего один правильный вариант,
//на форме будут созданы элементы Radiobutton
case QuestionType.SingleVariant:
foreach(DataRow childVariant in childVariants)
{
RadioButton rb = new RadioButton();
#region Ищем выбранный ответ в таблице ответов
bool selectedAnswer = (bool)vars[i++];
rb.Checked = selectedAnswer;
#endregion
//Определяем свойства созданного элемента RadioButton
rb.Text = childVariant["variant"].ToString();
rb.Tag = childVariant;
rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
int y = (gbVariants.Controls.Count == 0)?20:
((RadioButton)gbVariants.Controls[gbVariants.Controls.Count
- 1]).Bottom + 2;
//Определяем размеры создаваемых элементов RadioButton
//500 - ширина в пикселях, rb.Height+5 - высота
rb.Size = new Size(500, rb.Height+5);
rb.Location = new Point(10, y);
gbVariants.Controls.Add(rb);
}
break;
//Если вопрос имеет несколько правильных вариантов,
//на форме будут созданы элементы CheckBox
case QuestionType.MultiVariant:
foreach(DataRow childVariant in childVariants)
{
CheckBox chb = new CheckBox();
#region Ищем выбранный ответ в таблице ответов
bool selectedAnswer = (bool)vars[i++];
chb.Checked = selectedAnswer;
#endregion
//Определяем свойства созданного элемента RadioButton
chb.Text = childVariant["variant"].ToString();
chb.Tag = childVariant;
chb.CheckedChanged += new EventHandler(chb_CheckedChanged);
int y = (gbVariants.Controls.Count == 0)?20:
((CheckBox)gbVariants.Controls[gbVariants.Controls.Count
- 1]).Bottom + 2;
//Определяем размеры создаваемых элементов RadioButton
//500 - ширина в пикселях, chb.Height+5 - высота
chb.Size = new Size( 500, chb.Height+5);
chb.Location = new Point(10, y);
gbVariants.Controls.Add(chb);
}
break;
}
}
Когда пользователь отметит галочкой элементы CheckBox или выберет RadioButton, будет происходить событие CheckedChanged. В этом событии мы фиксируем положение отмеченного элемента - при возврате к решенному вопросу пользователь будет видеть свои ответы:
private void rb_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
if(!rb.Checked)
return;
//Создаем объект drvariant класса DataRow, с которым связываем
//свойство Tag элемента RadioButton
DataRow drvariant = (DataRow)rb.Tag;
//Отмечаем текущее положение объекта cmTest
int questIndex = cmTest.Position;
DataRow drquestion = dsTests.Tables["Questions"].Rows[questIndex];
int answIndex = gbVariants.Controls.IndexOf(rb);
//Выводим элемент RadioButton отмеченным, если он уже был выбран
bool[] answers = (bool[])clientAnswers[drquestion];
for(int i = 0; i < answers.Length; i++)
{
if(i == answIndex)
answers[i] = rb.Checked;
else
answers[i] = !rb.Checked;
}
}
private void chb_CheckedChanged(object sender, EventArgs e)
{
CheckBox chb = (CheckBox)sender;
//Создаем объект drvariant класса DataRow, с которым связываем
//свойство Tag элемента CheckBox
DataRow drvariant = (DataRow)chb.Tag;
//Отмечаем текущее положение объекта cmTest
int rowIndex = cmTest.Position;
DataRow drquestion = dsTests.Tables["Questions"].Rows[rowIndex];
int answIndex = gbVariants.Controls.IndexOf(chb);
//Выводим элемент CheckBox отмеченным, если он уже был выбран
bool[] answers = (bool[])clientAnswers[drquestion];
for(int i = 0; i< answers.Length; i++)
{
if (i == answIndex)
{
answers[i] = chb.Checked;
break;
}
}
}
Переключаемся в режим дизайна, щелкаем на кнопке "Результат" - в обработчике события подсчитываем количество правильных ответов:
private void btnCheck_Click(object sender, System.EventArgs e)
{
//Создаем счетчик
double counter = 0;
//Перебираем все ключи экземпляра key класса Hashtable,
//в котором хранятся ответы пользователя
foreach(object key in keys.Keys)
{
bool flag = true;
DataRow[] drvariants = (DataRow[])keys[key];
bool[] answers = (bool[])clientAnswers[key];
int i = 0;
foreach(DataRow variant in drvariants)
{
if(((bool)variant["isRight"]) == answers[i++])
continue;
else
{
flag = false;
break;
}
}
if (flag)
++counter;
}
//Делим количество правильных ответов на общее число ответов,
//результат умножаем на 100
int result = (int)(counter / dsTests.Tables["questions"].Rows.Count * 100);
MessageBox.Show(String.Format("Вы ответили правильно на {0}% вопросов.",
result), "Результат тестирования", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
Запускаем приложение (рис. 8.6). Мы рассмотрели простейший случай подсчета результатов - конечно же, в реальных приложениях кнопка "Результат" не может быть доступна в любой момент времени. Впрочем, это достаточно легко изменить.
(рис 8.6) Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестированияВ программном обеспечении к курсу вы найдете приложение Tests (Code\Glava4\Tests).
Еще в первой лекции мы узнали, что все объекты вкладки Data или все объекты ADO .NET можно создавать программно. Скопируйте папку приложения Tests и назовите ее "ProgrammTests". Открываем проект, удаляем объект DataSet с панели компонент формы. Для создания этого объекта программно достаточно следующей строчки кода:
DataSet dsTests = new DataSet();
Поскольку созданный экземпляр dsTests будет использоваться в нескольких методах, эту строку следует написать в классе формы. Здесь dsTests - это название объекта DataSet, которое мы затем используем в коде - это известно нам с первой лекции. Среда Visual Studio .NET однако, сгенерировала следующий фрагмент кода (его можно найти в исходном проекте "Tests"):
this.dsTests = new System.Data.DataSet();
...
//
// dsTests
//
this.dsTests.DataSetName = "NewDataSet";
this.dsTests.Locale = new System.Globalization.CultureInfo("ru-RU");
Как всегда, автоматический код избыточен - здесь указывается культура ru-RU. Дело в том, что у меня установлена русская версия Windows XP и соответствующие региональные настройки.
Среда определила еще свойство DataSetName = "NewDataSet", эквивалентное описание будет иметь следующий вид:
DataSet dsTests = new DataSet("NewDataSet");
Что же это за еще одно название объекта DataSet? Свойство DataSetName используется для работы с XSD-схемами, пока про это название (до лекции 10) мы можем просто забыть.
Создадим теперь объект DataTable для таблицы Questions:
DataTable dtQuestions = dsTests.Tables.Add("Questions");
//Или
//DataTable dtQuestions = new DataTable("Questions");
//dsTests.Tables.Add(dtQuestions);
Здесь мы создаем экземпляр dtQuestions объекта DataTable, затем вызываем метод Add свойства Tables объекта dsTests, которому передаем название таблицы Questions. Далее создаем поля в объекте dtQuestions:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32));
dсQuestID.Unique = true;
DataColumn dcQuestion = dtQuestions.Columns.Add("question");
DataColumn dcQuestType = dtQuestions.Columns.Add("questType", typeof(Int32));
Мы создаем поля, нужные для отражения соответствующих столбцов в таблице Questions. Перегруженный метод Add свойства Columns объекта dtQuestions позволяет задавать название столбца и его тип данных (рис. 8.7):
(рис 8.7) Создание поляСвойство Unique указывает, что в этом поле не должно быть повторяющихся значений, оно должно быть уникальным (здесь - поле questID является первичным ключом таблицы Questions).
Точно так же создаем объект DataTable для таблицы Variants и соответствующие поля:
//Cоздаем таблицу "Variants"
DataTable dtVariants = dsTests.Tables.Add("Variants");
//Заполняем поля таблицы "Variants"
DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32));
dcID.Unique = true;
dcID.AutoIncrement = true;
DataColumn dcVariantQuestID = dtVariants.Columns.Add("questID", typeof(Int32));
DataColumn dcVariant = dtVariants.Columns.Add("variant");
DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean));
Здесь мы дополнительно установили свойству AutoIncrement объекта dcID значение true. Свойство AutoIncrement (Счетчик) позволяет создать счетчик для поля, аналогичный типу данных "Счетчик" в Microsoft Access.
Теперь приступим к созданию связи между таблицами. В базе данных Microsoft SQL Tests между родительской таблицей Questions и дочерней Variants была установлена связь по полю questID, которое было в обеих таблицах. При программном создании объектов для поля questID таблицы Questions был создан объект dсQuestID, для этого же поля таблицы Variants создан объект dcVariantQuestID. В коде создание отношения между таблицами будет иметь следующий вид:
DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants",
dсQuestID, dcVariantQuestID);
dsTests.Relations.Add(drQuestionsVariants);
Здесь в конструкторе drQuestionsVariants - название экземпляра объекта (класса) DataRelation, а QuestionsVariants - свойство relationName - название связи, которая будет содержаться в объекте drQuestionsVariants. Другими словами, drQuestionsVariants - название экземпляра DataRelation, которое мы будем использовать в коде, а свойство relationName - всего лишь название отражаемой связи, которую можно удалить или переименовать.
Итак, мы создали все объекты для отображения таблиц, полей и даже связей между таблицами.
Теперь нам осталось определить некоторые свойства таблиц, называемые ограничениями. Свойство ограничения ( Constraint ) объекта DataTable бывает двух типов - UniqueConstraint и ForeignKeyConstraint. Свойство UniqueConstraint определяет первичный ключ таблицы, например, в таблице Questions ключевым полем является questID. Объект dсQuestID представляет это поле:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32));
Ограничение UniqueConstraint, налагаемое на объект dсQuestID, запрещает появление дублированных строк:
UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID); dtQuestions.Constraints.Add(UC_dtQuestions);
Однако при создании объекта dсQuestID мы ведь уже определяли его уникальность:
dсQuestID.Unique = true;
Действительно, последняя строка представляет собой неявный способ задания ограничения UniqueConstraint. Если мы уже определили уникальное поле или поля, используя свойство Unique, задавать ограничение UniqueConstraint не нужно.
Второе ограничение - ForeignKeyConstraint - определяет, как должны себя вести дочерние записи при изменении родительских записей и наоборот. Конечно, интерфейс нашего приложения вообще не подразумевает внесение изменений, но ADO .NET требует точного описания объектов для управления ими. Ограничение ForeignKeyConstraint содержит следующие три правила:
AcceptChanges объекта DataTable, для которого определено ограничение.Для этих правил могут применяться следующие значения:
Cascade - None - изменения в SetDefault - полю внешнего ключа в дочерних записях присваивается значение, заданное в свойстве DefaultValue этого поля;SetNull - полю внешнего ключа в дочерних записях присваивается значение Null.Значением по умолчанию для правил UpdateRule и DeleteRule является Cascade, для правила AcceptRejectRule - None. Дополнительно, правило AcceptRejectRule принимает значения только Cascade или None.
Создадим ограничение для связи QuestionsVariants:
ForeignKeyConstraint FK_QuestionsVariants = new ForeignKeyConstraint(dtQuestions.Columns["questID"], dtVariants.Columns["questID"]); dtVariants.Constraints.Add(FK_QuestionsVariants);
Здесь задается вначале родительская колонка, а затем дочерняя (рис. 8.8). Добавлять созданное ограничение следует к объекту DataTable, представляющему dtVariants )
(рис 8.8) Создание ограничения FK_QuestionsVariantsЭтот фрагмент кода оставляет значения правил UpdateRule, DeleteRule и AcceptRejectRule заданными по умолчанию, т.е. Cascade и None, что соответствует значениям настройки с помощью мастера Relation (рис. 8.9):
(рис 8.9) Мастер Relation и соответствующий фрагмент кодаЕсли бы нам потребовалось задать значение одному из правил, отличное по умолчанию, мы бы просто применили другой вариант конструктора (рис. 8.10):
(рис 8.10) В этом конструкторе можно задать значения правил RejectRule, DeleteRule и UpdateRule LoadDataBase в проекте ProgrammTests будет выглядеть так:
private void LoadDataBase()
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial
Catalog=Tests;Integrated Security=SSPI;");
SqlDataAdapter questAdapter =
new SqlDataAdapter("select * from questions", conn);
SqlDataAdapter variantsAdapter =
new SqlDataAdapter("select * from variants", conn);
//dsTests.EnforceConstraints = true;
//Cоздаем таблицу "Questions"
DataTable dtQuestions = dsTests.Tables.Add("Questions");
//Или
//DataTable dtQuestions = new DataTable("Questions");
//dsTests.Tables.Add(dtQuestions);
//Заполняем поля таблицы "Questions"
DataColumn dсQuestID =
dtQuestions.Columns.Add("questID", typeof(Int32));
dсQuestID.Unique = true;
//Или
//UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID);
//dtQuestions.Constraints.Add(UC_dtQuestions);
DataColumn dcQuestion = dtQuestions.Columns.Add("question");
DataColumn dcQuestType =
dtQuestions.Columns.Add ("questType", typeof(Int32));
//Cоздаем таблицу "Variants"
DataTable dtVariants = dsTests.Tables.Add("Variants");
//Заполняем поля таблицы "Variants"
DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32));
dcID.Unique = true;
dcID.AutoIncrement = true;
DataColumn dcVariantQuestID =
dtVariants.Columns.Add("questID", typeof(Int32));
DataColumn dcVariant = dtVariants.Columns.Add("variant");
DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean));
//Создаем ограничение
ForeignKeyConstraint FK_QuestionsVariants =
new ForeignKeyConstraint(dtQuestions.Columns["questID"],
dtVariants.Columns["questID"]);
dtVariants.Constraints.Add(FK_QuestionsVariants);
//Создаем отношение
DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants",
dсQuestID, dcVariantQuestID);
dsTests.Relations.Add(drQuestionsVariants);
conn.Open();
//Заполняем таблицу "Questions" данными из questAdapter
questAdapter.Fill(dsTests.Tables["Questions"]);
//Заполняем таблицу "Variants" данными из variantsAdapter
variantsAdapter.Fill(dsTests.Tables["Variants"]);
conn.Close();
}
Обратим внимание на несколько деталей этого метода. Значение true свойства EnforceConstraints объекта dsTests разрешает использование ограничений. По умолчанию в созданном объекте DataSet это свойство и так принимает значение true, поэтому этот фрагмент кода закомментирован. Следует иметь в виду, что для снятия всех ограничений достаточно установить свойству EnforceConstraints значение false. Ограничение FK_QuestionsVariants создается перед отношением drQuestionsVariants - вначале следует создавать ограничения, а затем определять отношения. Соединение conn открывается как можно позже - непосредственно перед заполнением данными объектов DataAdapter, - и тут же закрывается. Открыть его в начале метода и закрыть в конце было бы нерациональным.
В программном обеспечении к курсу вы найдете приложение Programm Tests (Code\Glava4\ProgrammTests).
При создании базы данных не следует помещать в нее значения, которые могут быть получены из уже имеющихся данных. В первой лекции мы создали базу данных BDTur_firm.mdb, в которой есть таблица "Туры". В этой таблице имеется поле "цена", где указывается стоимость туров. На практике может понадобиться значение цены, выраженное в различных валютах, значение с учетом налогов, значение со скидкой и т.п. Для каждого конкретного случая можно получить дополнительное поле, не вводя его в саму базу данных. Технология ADO .NET позволяет создавать объекты DataColumn, основанные на выражении. Создайте новое Windows-приложение и назовите его CustomExpression. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. Переходим в код формы, подключаем пространство имен:
using System.Data.OleDb;
В классе формы определяем строки CommandText и ConnectionString:
string commandText = "SELECT Информация, [Код тура], Название, Цена FROM Туры"; string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0"";Data Source=""D:\Uchebnik\Code\Glava1\BDTur_firm.mdb"";User ID=Admin; Jet OLEDB:Encrypt Database=False";
В конструкторе формы программно создаем все объекты для вывода таблицы "Туры" в элемент управления DataGrid:
public Form1()
{
InitializeComponent();
OleDbConnection conn = new OleDbConnection (connectionString);
OleDbCommand myCommand = new OleDbCommand();
myCommand.Connection = conn;
myCommand.CommandText = commandText;
OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
dataAdapter.SelectCommand = myCommand;
DataSet dsTours = new DataSet();
DataTable dtTours = dsTours.Tables.Add("Туры");
DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32));
dсIDtour.Unique = true;
DataColumn dcName = dtTours.Columns.Add("Название");
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcInformation = dtTours.Columns.Add("Информация");
conn.Open();
dataAdapter.Fill(dsTours.Tables["Туры"]);
conn.Close();
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
}
Запускаем приложение (рис. 8.11):
(рис 8.11) Вывод содержимого таблицы "Туры"Теперь добавим два объекта DataColumn, в которых будет вычисляться налог и скидка, после добавления объекта dcPrice:
...
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcPriceNDS = dtTours.Columns.Add("Цена c НДС", typeof(Decimal));
dcPriceNDS.Expression = "Цена*0.15+Цена";
DataColumn dcPricewithDiscount = dtTours.Columns.Add("Цена cо скидкой",
typeof(Decimal));
dcPricewithDiscount.Expression = "Цена-Цена*0.10";
...
Свойство Expression созданного объекта DataColumn задает выражения для всех значений заданного поля (рис. 8.12):
(рис 8.12) Значения полей "Цена с НДС" и "Цена со скидкой" получены в результате вычисленийСвойство Expression поддерживает также агрегатные функции, объединение строк, ссылки на родительские и
В программном обеспечении к курсу вы найдете приложение Custom Expression (Code\Glava4\CustomExpression).
Содержимое объекта DataSet представляет собой набор записей, который представлен объектами DataRow. В запущенном приложении содержимое объекта DataSet доступно для изменений, например, если данные выводятся в элемент управления DataGrid, то, перемещаясь по отдельным клеткам, можно править значения как в обычной электронной таблице. При этом происходит изменение объекта DataRow, соответствующее заданной записи. Рассмотрим программное создание и изменение записей. Создайте новое Windows-приложение и назовите его ProgrammDataRow. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. В конструкторе формы создаем экземпляр dtTours и поля, соответствующие таблице "Туры":
public Form1()
{
InitializeComponent();
DataTable dtTours = new DataTable();
DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32));
dсIDtour.Unique = true;
DataColumn dcName = dtTours.Columns.Add("Название");
DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal));
DataColumn dcInformation = dtTours.Columns.Add("Информация");
DataView myDataView = new DataView(dtTours);
dataGrid1.DataSource = myDataView;
}
Для того чтобы привязать созданные данные к элементу управления DataTable содержит объект DataView, причем этот объект, используемый по умолчанию, называется DataTable.DefaultView. Мы уже сталкивались с ним неоднократно, например, в предыдущем проекте CustomExpression для вывода данных:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Один объект DataTable может иметь несколько объектов DataView - это удобно для вывода одних и тех же данных, отфильтрованных или отсортированных различным образом. В следующей лекции мы рассмотрим подробно DataView. Запускаем приложение (рис. 8.13). Мы видим готовую структуру таблицы "Туры":
(рис 8.13) Структура таблицы "Туры"Мы не будем сейчас подключаться к какой-либо базе данных - попробуем заполнить таблицу записями программно. Для добавлений одной новой записи перед созданием экземпляра myDataView вставляем следующий фрагмент кода:
DataRow myRow = dtTours.NewRow(); myRow["Код тура"] = 1; myRow["Название"] = "Кипр"; myRow["Цена"] = 25000; myRow["Информация"] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow);
Запускаем приложение (рис. 8.14). В таблице появилась первая запись.
(рис 8.14) Добавление записи в таблицу Добавим еще одну запись:
DataRow myRow2 = dtTours.NewRow(); myRow2["Код тура"] = 2; myRow2["Название"] = "Греция"; myRow2["Цена"] = 32000; myRow2["Информация"] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Название, указываемое в квадратных скобках объектов myRow или myRow2, представляет собой имя столбца, которые мы определили в самом начале. К столбцу можно обращаться и по индексу - закомментируйте добавление двух записей и внесите следующий код:
DataRow myRow = dtTours.NewRow(); myRow[0] = 1; myRow[1] = "Кипр"; myRow[2] = 25000; myRow[3] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow); DataRow myRow2 = dtTours.NewRow(); myRow2[0] = 2; myRow2[1] = "Греция"; myRow2[2] = 32000; myRow2[3] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Нумерация столбцов начинается с нуля. Более удобный способ добавления записей - применение свойства ItemArray объекта DataRow, где можно задавать значения полей в виде массива:
DataRow myRow3 = dtTours.NewRow();
myRow3.ItemArray = new object[]{3,"Таиланд", 30000, null};
dtTours.Rows.Add(myRow3);
Здесь мы указали значение поля "Информация", равное null, - таким образом можно пропускать неизвестные поля (рис. 8.15):
(рис 8.15) Вставка записи с одним значением nullВ программном обеспечении к курсу вы найдете приложение Programm DataRow (Code\Glava4\ ProgrammDataRow).
Конечно, вставка записей вручную в объект DataSet, не связанный с хранилищем данных, имеет не очень большой смысл. Поэтому давайте рассмотрим, как вставлять (и изменять) данные в уже загруженный кэш данных. Скопируйте папку проекта CustomExpression и переименуйте ее в WorkWithRow. Чтобы не тратить время на привязку действий к элементам управления, будем далее писать код после отображения данных в элементе DataGrid:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Для изменения, например, пятой строки, мы указываем в свойстве Rows объекта dtTours ее индекс (4, нумерация начинается с нуля), затем вызываем метод BeginEdit для начала редактирования, устанавливаем группу свойств и в заключение принимаем изменения, вызывая метод EndEdit:
DataRow myRow=dtTours.Rows[4]; myRow.BeginEdit(); myRow["Код тура"] = 5; myRow["Название"] = "Турция"; myRow["Цена"] = "27000"; myRow["Информация"] = "Осенние скидки с 15 октября"; myRow.EndEdit();
Тот же самый результат мы получим с помощью свойства ItemArray:
DataRow myRow=dtTours.Rows[4];
myRow.BeginEdit();
myRow.ItemArray = new object[]{5,"Турция", 27000, null, null,
"Осенние скидки с 15 октября"};
myRow.EndEdit();
Здесь мы установили для третьего и четвертого полей, которые являются вычисляемыми, значения null, подразумевая, что они останутся по умолчанию, а при запуске заполнятся своими значениями (рис. 8.16):
(рис 8.16) Пропущенные вычисляемые поля заполняются своими значениями Для удаления заданной записи нужно создать объект DataRow, которому передается индекс строки, а затем вызвать метод Remove свойства Rows объекта DataTable:
DataRow myRow2 = dtTours.Rows[0]; dtTours.Rows.Remove(myRow2);
Этого достаточно для удаления строки, но для того, чтобы пометить заданную строку как удаленную, вызываем метод Delete:
myRow2.Delete();
В результате у нас удалится строка (рис. 8.17), причем объект DataTable пометит ее в качестве удаленной - это необходимо, чтобы избежать ошибок (например, в связанных записях).
(рис 8.17) Первая строка, имеющая индекс 0, была удалена В программном обеспечении к курсу вы найдете приложение WorkWith Row (Code\Glava4\ WorkWithRow).
При работе с данными приходится постоянно вносить изменения в записи - добавлять, редактировать или удалять. Объект DataRow обладает свойством RowState, позволяющим отслеживать текущий статус строки. Создайте новое приложение, назовите его "RowState". Перетаскиваем на форму элементы RichTextBox, , DataGrid, устанавливаем для них следующие свойства:
| richTextBox1, свойство | Значение |
|---|---|
| Dock | Bottom |
| Text |
| splitter1, свойство | Значение |
|---|---|
| Dock | Bottom |
| dataGrid1, свойство | Значение |
|---|---|
| Dock | Fill |
В конструкторе формы мы создаем всего одно поле, затем одну запись, статус которой будем отслеживать:
public Form1()
{
InitializeComponent();
DataTable dtTours = new DataTable("Туры");
DataColumn IDtour = new DataColumn("Код тура", typeof(Int32));
dtTours.Columns.Add(IDtour);
dataGrid1.DataSource = dtTours;
DataRow myRow;
// Создаем новую, отсоединенную запись
myRow = dtTours.NewRow();
richTextBox1.Text += Convert.ToString("Новая запись: "
+ myRow.RowState);
//Добавляем запись в объект DataTable
dtTours.Rows.Add(myRow);
richTextBox1.Text += Convert.ToString("\nДобавление записи: " +
myRow.RowState);
//Принимаем все изменения в объекте DataTable
dtTours.AcceptChanges();
richTextBox1.Text += Convert.ToString("\nМетод AcceptChanges: " +
myRow.RowState);
//Редактируем запись
myRow["Код тура"] = 1;
richTextBox1.Text += Convert.ToString("\nРедактирование строки: " +
myRow.RowState);
//Удаляем строку
myRow.Delete();
richTextBox1.Text += Convert.ToString("\nУдаление: " +
myRow.RowState);
//Отменяем все изменения в объекте DataTable
dtTours.RejectChanges();
richTextBox1.Text += Convert.ToString("\nМетод RejectChanges: " +
myRow.RowState);
}
Запускаем приложение. В текстовое поле выводится статус записи myRow (рис. 8.18):
(рис 8.18) Приложение RowStateЗначение Detached означает, что запись не относится к объекту DataTable. После добавления ее статус изменяется на Added - теперь она существует в объекте DataTable, но ее нет в базе данных. Конечно, здесь мы не рассматривали взаимодействие с источником записей, но это же значение будет у записей, добавляемых в DataGrid после вывода данных из базы при наличии подключения. Вызывая метод AcceptChanges объекта DataTable, мы принимаем все изменения, поэтому статус DataRow изменяется на - теперь запись считается "своей", она не была изменена после вызова метода. После вызова метода Delete запись помечается удаленной - она еще не полностью удалена, в случае RejectChanges объекта DataTable, мы восстанавливаем запись до состояния .
Свойство RowState всегда возвращает отдельное, доступное только для чтения значение. Это свойство используется для поиска записей, соответствующих заданному статусу, а также при передаче изменений в базу данных.
В программном обеспечении к курсу вы найдете приложение RowState (Code\Glava4\ RowState).
Свойство RowVersion предназначено для извлечения значения записи (объекта DataRow), зависящего от совершенных изменений. Возможны следующие версии записи:
Current - текущее значениеDefault - значение по умолчаниюOriginal - первоначальное значениеProposed - предполагаемое значениеСоздайте новое приложение, назовите его "RowVersion". Свойству Size формы устанавливаем значение 500; 300. Из окна Toolbox перетаскиваем следующие элементы и устанавливаем их свойства:
| panel1, свойство | Значение |
|---|---|
| Dock | Bottom |
| dataGrid1, свойство | Значение |
|---|---|
| Dock | Left |
| CaptionText | Туры |
| splitter1, свойство | Значение |
|---|---|
| Dock | Left |
| richTextBox1, свойство | Значение |
|---|---|
| Name | rtbReport |
| Dock | Fill |
| Text |
| button1, свойство | Значение |
|---|---|
| Name | BtnBeginEdit |
| Location | 14; 21 |
| Text | Begin Edit |
| button2, свойство | Значение |
|---|---|
| Name | btnEndEdit |
| Location | 106; 21 |
| Text | End Edit |
| button3, свойство | Значение |
|---|---|
| Name | btnCancelEdit |
| Location | 198; 21 |
| Text | Cancel Edit |
| button4, свойство | Значение |
|---|---|
| Name | btnDelete |
| Location | 290; 21 |
| Text | Удалить |
| button5, свойство | Значение |
|---|---|
| Name | btnClearReport |
| Location | 382; 21 |
| Size | 96; 23 |
| Text | Очистить отчет |
Переходим к коду. В классе формы объявляем объекты DataRow и DataTable:
DataRow myRow; DataTable dtTours;
В конструкторе формы создаем запись, определяем источник данных для элемента DataGrid, а также отключаем его доступность:
public Form1()
{
InitializeComponent();
dtTours = new DataTable("Туры");
DataColumn IDtour = new DataColumn("Название", typeof(string));
dtTours.Columns.Add(IDtour);
myRow = dtTours.NewRow();
dtTours.Rows.Add(myRow);
myRow["Название"] = "Таиланд";
dataGrid1.DataSource = dtTours;
dataGrid1.Enabled = false;
}
Создаем метод TestRowVersion, в котором будет проверяться свойство RowVersion записи:
private void TestRowVersion()
{
if(myRow.HasVersion(DataRowVersion.Original))
rtbReport.Text += String.Format("Значение original: {0}\n",
myRow["Название", DataRowVersion.Original]);
if(myRow.HasVersion(DataRowVersion.Current))
rtbReport.Text += String.Format("Значение current: {0}\n",
myRow["Название", DataRowVersion.Current]);
if(myRow.HasVersion(DataRowVersion.Default))
rtbReport.Text += String.Format("Значение default: {0}\n",
myRow["Название", DataRowVersion.Default]);
if(myRow.HasVersion(DataRowVersion.Proposed))
rtbReport.Text += String.Format("Значение proposed: {0}\n",
myRow["Название", DataRowVersion.Proposed]);
}
Метод HasVersion позволяет определить, поддерживает ли объект myRow версию данных, указываемую в скобках. В случае подтверждения будет выполняться код оператора - выводится в элемент rtbReport соответствующее сообщение.
В обработчике кнопки "Begin Edit" вызываем метод BeginEdit, устанавливаем новое значение записи:
private void btnBeginEdit_Click(object sender, System.EventArgs e)
{
myRow.BeginEdit();
myRow["Название"] = "Франция";
rtbReport.Text += "BeginEdit\n";
TestRowVersion();
}
В обработчике кнопки "End Edit" завершаем редактирование записи:
private void btnEndEdit_Click(object sender, System.EventArgs e)
{
myRow.EndEdit();
rtbReport.Text += "EndEdit\n";
TestRowVersion();
}
В обработчике кнопки "Cancel Edit" отказываемся от внесенных изменений:
private void btnCancelEdit_Click(object sender, System.EventArgs e)
{
myRow.CancelEdit();
rtbReport.Text += "CancelEdit\n";
TestRowVersion();
}
В обработчике кнопки "Удалить" удаляем объект myRow:
private void btnDelete_Click(object sender, System.EventArgs e)
{
myRow.Delete();
rtbReport.Text += "Запись удалена\n";
TestRowVersion();
}
В обработчике кнопки "Очистить отчет" просто удаляем содержимое текстового поля:
private void btnClearReport_Click(object sender, System.EventArgs e)
{
this.rtbReport.Text = "";
}
Запускаем приложение. После нажатия кнопки "BeginEdit" мы начинаем редактирование записи, вводится новое значение - "Франция". Оно становится значением по умолчанию "Default" и предполагаемым "Proposed", значение "Таиланд" является текущим "Current" (рис. 8.19, А). Отменяем редактирование, нажимая кнопку "Cancel Edit". При этом значение "Таиланд" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Б). Снова начинаем редактирование - картина повторяется (рис. 8.19, В). На этот раз завершаем его, нажимая кнопку "End Edit" - новое значение "Франция" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Г). Нажимаем кнопку "Удалить" - при этом удаляется сам объект myRow и дальнейшее изменение его з начений оказывается невозможным (рис. 8.19, Д).
(рис 8.19) Приложение "RowVersion"В программном обеспечении к курсу вы найдете приложение RowVersion (Code\Glava4\ RowVersion).
Подобная функциональность вряд может нас устроить в реальных приложениях - в самом деле, мы вставляем только заранее определенное значение "Франция". Скопируйте папку приложения RowVersion и назовите ее RowVersion2. Добавим на форму элементы "надпись" и "текстовое поле":
| label1, свойство | Значение |
|---|---|
| location | 24; 16 |
| Size | 72; 24 |
| Text | Название |
| textbox1, свойство | Значение |
|---|---|
| Name | txtmyRowName |
| location | 104; 16 |
| Size | 344; 20 |
| Text |
Для того чтобы эти элементы поместились на панели, последнюю немного растянем, а кнопки сдвинем вниз, к краю формы. Обработчики кнопок изменим следующим образом:
private void btnBeginEdit_Click(object sender, System.EventArgs e)
{
myRow.BeginEdit();
rtbReport.Text += "BeginEdit\n";
TestRowVersion();
txtmyRowName.Enabled = true;
}
private void btnEndEdit_Click(object sender, System.EventArgs e)
{
myRow["Название"] = txtmyRowName.Text;
myRow.EndEdit();
rtbReport.Text += "EndEdit\n";
TestRowVersion();
txtmyRowName.Enabled = false;
}
private void btnCancelEdit_Click(object sender, System.EventArgs e)
{
myRow.CancelEdit();
rtbReport.Text += "CancelEdit\n";
TestRowVersion();
txtmyRowName.Enabled = false;
}
Обработчик кнопки "Удалить" изменим так же - теперь будет удаляться текущий экземпляр myRow и тут же создаваться новый:
private void btnDelete_Click(object sender, System.EventArgs e)
{
myRow.Delete();
rtbReport.Text += "Запись удалена\n";
TestRowVersion();
myRow = dtTours.NewRow();
dtTours.Rows.Add(myRow);
}
В конструкторе формы отключим доступность текстового поля:
public Form1()
{
...
txtmyRowName.Enabled = false;
}
Запускаем приложение. Выполняя знакомую последовательность действий, получаем похожий отчет; теперь при удалении записи можно продолжить работу - объект myRow будет воссоздан (рис. 8.20):
(рис 8.20) Приложение RowVersion2В программном обеспечении к курсу вы найдете приложение RowVersion2 (Code\Glava4\ RowVersion2).
Объект DataTable содержит ряд событий, которые могут применяться для слежения за происходящими изменениями. Наиболее часто используются следующие события:
ColumnChanged - наступает после изменения содержимого поля таблицы;ColumnChanging - происходит в течение редактирования содержимого поля таблицы;RowChanged - наступает после изменения объекта DataRow (записи);RowChanging - происходит в течение редактирования объекта DataRow ;RowDeleted - наступает после удаления объекта DataRow ;RowDeleting - происходит при удалении объекта DataRow.Скопируйте папку приложения RowVersion, назовите ее "Data TableEvents". В конструкторе формы добавим обработку четырех событий объекта DataTable:
public Form1()
{
...
dtTours.RowChanging += new DataRowChangeEventHandler(dtTours_RowChanging);
dtTours.RowChanged += new DataRowChangeEventHandler(dtTours_RowChanged);
dtTours.RowDeleting += new DataRowChangeEventHandler(dtTours_RowDeleting);
dtTours.RowDeleted += new DataRowChangeEventHandler(dtTours_RowDeleted);
}
В соответствующих методах просто выводим сообщение в текстовое поле:
private void dtTours_RowChanging(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += String.Format("Событие - изменение записи\n",
e.Row["Название"]);
}
private void dtTours_RowChanged(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += "\nСобытие - запись изменена\n";
}
private void dtTours_RowDeleting(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += String.Format("Событие - удаление записи\n",
e.Row["Название"]);
}
private void dtTours_RowDeleted(object sender, DataRowChangeEventArgs e)
{
rtbReport.Text += "\nСобытие - запись удалена\n";
}
Запускаем приложение. Нажимаем кнопку "Begin Edit", затем "End Edit" - происходят события RowChanging и RowChanged. Удаляем запись - происходят события RowDeleting и RowDeleted (рис. 8.21).
(рис 8.21) Проект "DataTableEvents"В обработчиках событий можно добавить соответствующие действия, например, подтверждение изменения ( RowChanging ) или удаления ( RowDeleting ).
В программном обеспечении к курсу вы найдете приложение DataTable Events (Code\Glava4\ DataTableEvents).
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.