Лого на 91. НЕГ „Проф. Константин Гълъбов“

Модул 4 · Урок 17

Редактиране на данните

ExecuteNonQuery() за UPDATE, INSERT и DELETE, четирите операции на CRUD и защо заявка, слепена от текст на потребителя, е опасна.

Една форма не стига

Сложно приложение като информационна система не се побира в една екранна форма — за многобройните функции трябват различни комбинации от графични компоненти, а да ги държиш всички в една форма и да ги правиш видими при нужда е непостижимо. Затова за всяка функция се създава своя вторична форма.

  1. На основната форма свойството IsMdiContainer (група Windows Style) се сменя от False на True — това разрешава вторични форми.
  2. От менюто ProjectAdd New Item… Form (Windows Forms)Add.
  3. Служебното име остава 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().

ЗаявкаМетодКакво връща
SELECTExecuteReader()четец, от който се вземат редовете
UPDATE, INSERT, DELETEExecuteNonQuery()броя на засегнатите редове

CRUD

Четирите основни операции върху данни имат общо име — CRUD:

БукваОперацияSQL
CCreate — създаване на нов записINSERT
RRead — четенеSELECT
UUpdate — промянаUPDATE
DDelete — изтриване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 инжекция — ползвай параметри.