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

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

Четене на данни от базата

SqlConnection, SqlCommand и четецът SqlDataReader: свързващият низ, цикълът while (reader.Read()) и трите начина да приберем прочетеното в програмата.

Най-важното действие

Когато програма на C# работи с база от данни, най-често ѝ се налага едно: да извлече данни и да ги обработи — да ги анализира, да ги изведе форматирани, да ги прехвърли другаде. Този урок е точно за това действие, а то не е от най-простите за програмиране.

Ключово

Схемата е винаги една и съща: свързващ низ → връзка → команда със заявка → четец → цикъл, който взема ред по ред.

Подготовка

Класът, който ще поеме данните

Взимаме класа Student от предишния урок, но сега добавяме по един сетър и гетър за всеки атрибут — за да можем да задаваме и използваме стойностите в програмата:

public class Student
{   public int idStudent { get; set; }   //уникален номер
    public string name { get; set; }     //име
    public string surname { get; set; }  //презиме
    public string family { get; set; }   //фамилия
    public string egn { get; set; }      //ЕГН
    public DateTime birt_day { get; set; }   //дата на раждане
    public string birt_place { get; set; }
    public string district { get; set; }
    public string e_mail { get; set; }
    public string mobile { get; set; }
    ...
}

Свързващият низ

Спомни си как в Модул 3 програмата вземаше данни от база на Access — трябваше ни „магически“ низ. Тук е същото, но низът е специфичен за MS SQL Server. Публикуваните варианти са два:

ВидНизКога
СтандартенServer=…;Database=…;UserId=…;Password=…;когато се свързваш с потребителско име и парола, регистрирани в сървъра
ПоверителенServer=…;Database=…;Trusted_Connection=True;когато се свързваш през профила си в Windows

В този урок ползваме втория — той е идентификацията на потребителя, която вече използвахме:

Server=localhost;Database=School;Trusted_Connection=True;

Поставяме го в променливата connString, а SQL заявката за извличането — в queryString.

Бележка

Ако вместо поверителния използваш стандартния низ, програмата става зависима от ролята на потребителя, с чието име и парола се свързва. Това може да създаде проблеми — за тях става дума в следващ урок.

Четенето

За процеса ни трябват по една инстанция на класовете SqlConnection и SqlCommand. На първия подаваме свързващия низ, на втория — текста на заявката и връзката. За да ги ползваш, добави в началото на програмата:

using System.Data.SqlClient;
  • connection.Open() отваря връзката; атрибутът State казва в какво състояние е тя — при успех се извежда State: Open.
  • command.ExecuteReader() създава четец — инстанция на SqlDataReader, свързана с командата.
  • reader.Read() прочита следващия ред и връща true; след последния ред връща false и цикълът спира.
  • Към четеца гледаш като към масив: reader[0], reader[1]… по поредния номер на атрибута в схемата на таблицата.
public class ShowDataTest
{   static string connString = @"Server=localhost;Database=School;" +
                               "Trusted_Connection=True;";
    static string queryString = "SELECT idStudent, name, surname,"
                              + " family FROM Student";
    public static void ShowData()
    {   using (SqlConnection connection = new SqlConnection(connString))
        {   SqlCommand command = new SqlCommand(queryString, connection);
            connection.Open();
            Console.WriteLine("State: {0}", connection.State);
            using (SqlDataReader reader = command.ExecuteReader())
            {   while (reader.Read())
                {   Console.WriteLine(String.Format("{0} {1} {2} {3}",
                        reader[0], reader[1], reader[2], reader[3]));
                }
            }
        }
    }
}

class Program
{   static void Main(string[] args)
    {   ShowDataTest.ShowData();
        Console.WriteLine("Ready!");
    }
}

На конзолата излиза State: Open, а след него — по един ред за всеки ученик от таблицата Student.

Да приберем прочетеното в обекти

За по-сложни обработки е удобно данните да се съхранят в инстанции на класа Student. Добавяме списък и в тялото на цикъла пълним атрибутите вместо да извеждаме направо:

public static List<Student> stdList = new List<Student>();

// в тялото на while:
std.idStudent = (int)reader[0];
std.name      = reader[1].ToString();
std.surname   = reader[2].ToString();
std.family    = reader[3].ToString();
stdList.Add(std);

Забележи различния начин на преобразуване. Четецът връща стойностите като универсалния тип object: при целочислените се прави явно преобразуване (int), а при низовите — с метода ToString().

Внимавай с една инстанция

Ако създадеш std веднъж, извън цикъла, и добавяш все нея, в списъка влизат N указателя към един и същ обект — накрая всички елементи показват данните на последния прочетен ред. Новата инстанция трябва да се създава вътре в цикъла, за всеки ред поотделно.

Четене в DataTable

Има и друг начин: стойностите да се прочетат в инстанция на сложно структурирания клас DataTable. За прехвърлянето от SQL таблицата в таблицата на програмата е нужен специален агент — адаптер, инстанция на SqlDataAdapter:

private static DataTable dataTable = new DataTable();

string queryString = "SELECT * FROM Student";
SqlConnection connection = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(queryString, connection);
connection.Open();
// създаване на адаптер за четене
SqlDataAdapter da = new SqlDataAdapter(cmd);
// осъществяване на връзка и четене на данните в dataTable
da.Fill(dataTable); connection.Close(); da.Dispose();
// за достъп до стойностите
foreach (DataRow row in dataTable.Rows)
{   foreach (DataColumn column in dataTable.Columns)
    { Console.WriteLine(row[column]); }
}

Свързването с базата е същото. Разликата е в средата: след Fill() връзката се затваря с Close(), а адаптерът се освобождава с Dispose(). Основната трудност идва накрая — достъпът до елементите става с два указателя: DataRow row за ред и DataColumn column за колона. Итерирането по редове и стълбове не може да стане по друг начин освен с foreach.

Бележка

Този подход извежда само стойностите. За да работиш истински с DataTable, е нужно доста подробно запознаване с методите на класа — то е извън целите на учебника.

Практикум

Задача 1

Въведи програмите от урока, компилирай ги и провери работоспособността им.

Задачи 2 и 3

Промени метода ShowData() така, че да чете всички данни от таблицата Student. Напиши подобни методи и за другите таблици в базата School.

Въпроси

Какво е предназначението на сетърите и гетърите при дефиниране на атрибутите на клас? Намери на connectionstrings.com/sql-server други начини на свързване — особено свързването с отдалечен сървър по TCP/IP адрес и комуникационен порт. Разгледай в документацията класа DataTable.

Какво трябва да запомниш

  • Свързващият низ е или поверителен (Trusted_Connection=True), или стандартен (с UserId и Password).
  • SqlConnection държи връзката, SqlCommand — заявката, а SqlDataReader чете резултата.
  • while (reader.Read()) минава ред по ред; Read() връща false след последния ред.
  • Четецът връща object — числата искат явно преобразуване, низовете — ToString().
  • Нова инстанция за всеки ред, иначе списъкът се пълни с указатели към един и същ обект.
  • DataTable + SqlDataAdapter е алтернативата, но достъпът е с два указателя — ред и колона.