Най-важното действие
Когато програма на 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е алтернативата, но достъпът е с два указателя — ред и колона.