Една форма не стига
Сложно приложение като информационна система не се побира в една екранна форма — за многобройните функции трябват различни комбинации от графични компоненти, а да ги държиш всички в една форма и да ги правиш видими при нужда е непостижимо. Затова за всяка функция се създава своя вторична форма.
- На основната форма свойството
IsMdiContainer(група Windows Style) се сменя отFalseнаTrue— това разрешава вторични форми. - От менюто
Project→Add New Item…→Form (Windows Forms)→Add. - Служебното име остава
Form2, а надписът (свойствотоText) става например „Ученик“ — с тази форма ще редактираме данни от таблицата Student.
public static Form2 studEdit;
private void button3_Click(object sender, EventArgs e)
{ if (studEdit != null)
{ studEdit.BringToFront(); }
else
{ studEdit = new Form2();
studEdit.FormClosed += delegate { studEdit = null; };
studEdit.Show(this);
}
}Инстанцията studEdit е дефинирана извън метода — ако беше вътре, всяко натискане на бутона щеше да създава нов екземпляр на формата. Затова пък се налага проверката if … else: ако формата вече е отваряна, само я показваме отпред с BringToFront(); ако не е — създаваме я и я отваряме с Show(). Конструкцията delegate гарантира, че при затваряне формата не изчезва, а остава в пасивно състояние и може да се отвори отново.
Бележка
Формите тук са контекст — в този курс не ги изграждаме, защото браузърът не изпълнява Windows Forms. Важното от урока е кодът зад бутоните, а той е същият и в конзолна програма.
Четене на един запис
Формата има текстови кутии за идентификатор, име, презиме, фамилия и e-mail и бутони „Търси“, „Запази“ и „Затвори“. Ето метода зад „Търси“:
private void button1_Click(object sender, EventArgs e)
{ button2.Enabled = false;
string id = idStudentBox.Text;
if (id == "") MessageBox.Show("Задайте идентификатор");
else
{ string connString = @"Server=localhost;Database=School;" +
"Trusted_Connection=True;";
string queryString = "SELECT * FROM Student " +
"WHERE idStudent = " + id + ";";
using (SqlConnection connection = new SqlConnection(connString))
{ SqlCommand command = new SqlCommand(queryString, connection);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{ reader.Read();
studNameBox.Text = reader[1].ToString();
studSurnameBox.Text = reader[2].ToString();
studFamilyBox.Text = reader[3].ToString();
studMailBox.Text = reader[8].ToString();
}
button2.Enabled = true;
}
}
}- Проверката за празен идентификатор е в началото — без него търсенето в базата е невъзможно.
reader.Read()се вика веднъж, не в цикъл — очакваме най-много един ред, защото търсим по уникален идентификатор.- Бутонът „Запази“ се блокира в началото (
Enabled = false) и се отблокира чак накрая — за да не може потребителят да запише нещо, преди изобщо да са прочетени данни.
Съвет
Тази защита може да е и по-интелигентна: бутонът да се отблокира само ако потребителят наистина е променил нещо. Спомни си събитията на текстовите кутии и избери подходящото, което се генерира при промяна на текста.
Записване на промените
След като данните са прочетени и редактирани, трябва да се върнат в базата. Методът зад бутона „Запази“:
private void button2_Click(object sender, EventArgs e)
{ string id = idStudentBox.Text;
string connString = @"Server=localhost;Database=School;" +
"Trusted_Connection=True;";
string queryString = "UPDATE Student SET " +
"name = '" + studNameBox.Text + "', " +
"surname = '" + studSurnameBox.Text + "', " +
"family = '" + studFamilyBox.Text + "', " +
"e-mail = '" + studEmailBox.Text + "', " +
" WHERE idStudent = " + id + ";";
using (SqlConnection connection = new SqlConnection(connString))
{ SqlCommand command = new SqlCommand(queryString, connection);
connection.Open();
command.ExecuteNonQuery();
}
}Ключово
Заявките, които не извличат данни — UPDATE, INSERT, DELETE — се пускат с метода ExecuteNonQuery(). За SELECT той не е нужен: там работи ExecuteReader().
| Заявка | Метод | Какво връща |
|---|---|---|
SELECT | ExecuteReader() | четец, от който се вземат редовете |
UPDATE, INSERT, DELETE | ExecuteNonQuery() | броя на засегнатите редове |
CRUD
Четирите основни операции върху данни имат общо име — CRUD:
| Буква | Операция | SQL |
|---|---|---|
| C | Create — създаване на нов запис | INSERT |
| R | Read — четене | SELECT |
| U | Update — промяна | UPDATE |
| D | Delete — изтриване | DELETE |
Формата от този урок покрива само R и U — чете един ученик и записва промените по него. Създаването и изтриването остават за следващия урок.
Внимание: заявка, слепена от чужд текст
Погледни отново как се строи заявката: към текста ѝ се долепя съдържанието на текстовите кутии. Това работи, докато потребителят пише обикновени неща — но не и когато напише фамилия О'Нийл. Апострофът затваря низа по-рано и заявката става синтактично грешна.
По-лошото е друго: ако в кутията за идентификатор някой напише 0 OR 1=1, условието става вярно за всички редове и UPDATE-ът променя цялата таблица. Това се нарича SQL инжекция.
Лекарството е познато от Модул 3 — параметризирана заявка. Стойностите не се слепват, а се подават отделно:
string queryString = "UPDATE Student SET family = @family " +
"WHERE idStudent = @id;";
SqlCommand command = new SqlCommand(queryString, connection);
command.Parameters.AddWithValue("@family", studFamilyBox.Text);
command.Parameters.AddWithValue("@id", int.Parse(idStudentBox.Text));
connection.Open();
command.ExecuteNonQuery();Внимание
Сървърът получава заявката и стойностите поотделно, затова каквото и да напише потребителят, то остава стойност и никога не става част от командата. Пиши така винаги, когато в заявката влиза текст, въведен от някого.
Практикум
Задачи 1 и 2
Създай вторичната форма за таблицата Student, както е описано в урока, и свържи с нея описаната функционалност. После направи същото и за другите таблици в базата School.
Задачи 3 и 4
Какво означава CRUD? Опиши коя функционалност от CRUD е реализирана в създадените форми.
Какво трябва да запомниш
- За всяка функция на ИС се прави отделна вторична форма; инстанцията ѝ се пази извън метода, за да не се създава наново при всяко отваряне.
ExecuteNonQuery()изпълнява UPDATE, INSERT и DELETE и връща броя засегнати редове.ExecuteReader()е за SELECT; при търсене по уникален ключRead()се вика веднъж, не в цикъл.- CRUD са четирите основни операции: Create, Read, Update, Delete.
- Заявка, слепена от текст на потребителя, се чупи на апостроф и отваря врата за SQL инжекция — ползвай параметри.