Для доступу до даних на платформі .NET використовується бібліотека класів ADO.NET. Основу цієї бібліотеки складають клас DataSet і класи, які реалізують такі основні інтерфейси: IDbConnection, IDbCommand, IDataReader, IDataAdapter. Використовуючи ADO.NET можливо реалізувати як класичні клієнт-серверні програми, в яких клієнтський додаток прямо звертається до бази даних за допомогою SQL-запитів, так і багаторівневі рассоединенные програми, в яких важливо мати можливість працювати з не мають постійного підключення до сервера баз даних копії даних.
Головну роль у створенні розз ’ єднаних додатків грає клас Dataset, що дозволяє створювати локальний набір взаємопов'язаних таблиць. Також необхідно відзначити, що за допомогою ADO.NET можна звертатися до різних джерел даних. Роботи з різними джерелами даних реалізована за допомогою керованих провайдерів (managed provider). Керований провайдер - набір класів, які реалізують інтерфейси IDbConnection, IDbCommand, IDataReader, IDbDataAdapter і призначений для роботи з певним джерелом даних. Для роботи з Microsoft SQL Server, Oracle використовуються провайдери SQL і Oracle, оптимізовані для роботи саме з цими СУБД. Крім того, до складу ADO.NET включений провайдер OLEDb, що дозволяє працювати з будь-якими джерелами даних, які підтримують протокол OLE DB.
Інтерфейс IDbConnection (реалізований в класі SqlConnection провайдера SQL) - призначений для створення з'єднання (connection) з джерелом даних. Для зазначення джерела даних для створення з'єднання використовується рядок з'єднання (connection string). Рядок містить з'єднання пари "параметр=значення"розділених символом крапка з комою. Інформація, яка має бути відображена в рядку з'єднання, залежить від джерела даних. При створенні з'єднання з Microsoft SQL Server необхідно вказати значення для наступних параметрів: server - ім'я комп'ютера (або його IP адреса), на якому встановлено Microsoft SQL Server; uid - обліковий запис користувача Microsoft SQL Server, від імені якого створюється підключення; pwd - пароль облікового запису користувача Microsoft SQL Server, від імені якого створюється підключення; database - ім'я бази даних, з якої необхідно створити з'єднання. Рядок з'єднання може бути вказана при натисненні конструктора об'єкта класу SqlConnection, або через присвоєння значення властивості ConnectionString інтерфейсу IDbConnection.
Для відкриття з'єднання з базою даних використовується метод void Open() інтерфейсу IDbConnection. Для закриття з'єднання з базою даних використовується метод void Close() інтерфейсу IDbConnection. Шаблон роботи з з'єднанням:
SqlConnection con = new SqlConnection("server=...;uid=...;pwd=...;database=...");
con.Open();
try{
...
}
finally{
con.Close();
}
На практиці використовуються два варіанти роботи з з'єднання з базою даних. Якщо кількість користувачів невелика і клієнтський додаток при цьому часто звертається до бази даних рекомендується створити один об'єкт з'єднання з базою даних для всього програми і тримати з'єднання відкритим протягом всього часу роботи програми.
Якщо кількість користувачів велике і клієнтський додаток не часто звертається до бази даних, рекомендується відкривати з'єднання тільки на час виконання запитів і використовувати організації пулу з'єднань (connection pooling). Відкриття з'єднання з базою даних зазвичай вимагає певного часу і ресурсів. Пул з'єднань містить колекцію відкритих з'єднань з базою даних. При виклику методу Open пул з'єднань перевіряє наявність вільного з'єднання в пулі і повертає його клієнтській програмі. При виклику методу Close з'єднання фактично не закривається, а повертається назад в пул з'єднань. З'єднання об'єднуються в пул з'єднань за ознакою збігу рядка з'єднання. Якщо відкривається з'єднання, в якому рядок з'єднання відрізняється від рядка, використаної в попередніх з'єднаннях, створюється новий пул з'єднань. Використання пулу з'єднань дозволяє істотно підвищити продуктивність додатків з великою кількістю одночасно працюючих користувачів. Використання пулу з'єднань увімкнено за замовчуванням при роботі з ADO.NET.
Рекомендується виносити інформацію про рядку з'єднання у зовнішній файл або зберігати її в реєстрі. Такий підхід дозволяє гнучко, безпечно і централізовано настроювати підключення до джерела даних. Для доступу до реєстру використовується клас RegistryKey. Для вказівки необхідної кореневої гілки реєстру використовуються статичні поля CurrentUser і LocalMachine класу Registry. Дані поля застосовуються для ініціалізації змінних типу RegistryKey. Для відкриття потрібної гілки реєстру використовується метод RegistryKey CreateSubKey(string subkey), який відкриває існуючу гілку реєстру або створює нову. Для читання значення з реєстру застосовується метод object GetValue(string name). Для запису значення реєстру застосовується метод void SetValue(string name,object value). Шаблон читання значення з реєстру:
RegistryKey key=Registry.CurrentUser;
key=key.CreateSubKey("гілка реєстру");
try {
value=(string)key.GetValue("параметр");
if (value == null)
throw new Exception();
}
catch {
key.SetValue("параметр", "значення");
}
Інтерфейс IDbCommand (реалізований в класі SqlCommand провайдера SQL) - призначений для виконання довільних SQL запитів до бази даних. Текст запиту SQL зазвичай вказується при натисненні конструктора об'єкта класу SqlCommand. Також при натисненні конструктора слід зазначити об'єкт з'єднання з базою даних. При формуванні рядка запиту SQL можна використовувати форматування рядків з допомогою статичного методу Format класу String.
Для виконання SQL запитів до бази даних, що не возвращает в якості результату набору записів, використовується метод int ExecuteNonQuery() інтерфейсу IDbCommand. При натисненні метод повертає кількість записів, задіяних при виконанні запиту. Цей метод застосовується при виконанні таких запитів, як, наприклад, INSERT, UPDATE, DELETE або виконання що їх містять збережених процедур, а також запитів мови маніпулювання даними і мови управління даними. Шаблон виконання методу ExecuteNonQuery:
SqlConnection con;
...
SqlCommand com = new SqlCommand("текст запиту SQL",con);
try{
com.ExecuteNonQuery();
}
catch(Exception ex){
}
Так як при виконанні запиту SQL можуть виникнути різні помилки, рекомендується розміщувати виклик методу ExecuteNonQuery в блок try-catch.
Для виконання SQL запити до бази даних, який повертає в якості результату набір записів, використовується метод IDataReader ExecuteReader(CommandBehavior behavior) інтерфейсу IDbCommand. У класі SqlCommand також визначено спрощений метод SqlDataReader ExecuteReader(). Метод ExecuteReader застосовується для отримання даних з бази даних, які в клієнтському додатку доступні тільки для читання (read-only) і можуть бути переглянуті тільки за порядком (forward-only). Використання ExecuteReader особливо ефективно для отримання великої кількості даних, оскільки отримані дані не кешуються в оперативній пам'яті.
Для читання наступного запису з об'єкту, що підтримує інтерфейс IDataReader, використовується метод bool Read() інтерфейсу IDataReader, що повертає значення true, якщо запис успішно прочитана, і false інакше. Необхідно звернути увагу на той факт, що метод Read повинен бути викликаний до першого звернення до полів запису. Для зчитування значень в полях черговий запису можна використовувати метод object GetSqlValue (int i) класу SqlDataReader, як параметр якого вказується номер поля (нумерація полів починається з нуля). Також для доступу до значень полів можна використовувати індексатор за номером або по імені поля. Шаблон виконання методу ExecuteReader:
SqlConnection con;
...
SqlCommand com = new SqlCommand("текст запиту SQL",con);
try{
SqlDataReader rd = cmd.ExecuteReader();
while (rd.Read()){
for (int i = 0; i < rd.FieldCount; i++)
object value=rd.GetSqlValue(i);
або object value=rd[i];
}
}
catch Exception(ex){
}
finally{
rd.Close();
}
Якщо імена полів відомі, то шаблон виконання методу ExecuteReader може виглядати наступним чином:
SqlConnection con;
...
SqlCommand com = new SqlCommand("текст запиту SQL",con);
try{
SqlDataReader rd = cmd.ExecuteReader();
while (rd.Read()){
object value=rd["ім'я поля"];
}
}
catch Exception(ex){
}
finally{
rd.Close();
}
Рекомендується виносити тексти запитів SQL, які використовуються при роботі з інтерфейсом IDbCommand, у зовнішній файл або в таблиці в базі даних. Подібне рішення дозволяє істотно спростити розробку і супровід програми. Зовнішній файл, який зберігає тексти запитів SQL, може мати формат XML. Для доступу до файлів у форматі XML використовується клас XmlDocument. Для відкриття необхідного файлу використовується метод void Load(string filename) класу XmlDocument.
Для знаходження в документі XML необхідного вузла за його імені використовується метод XmlNode SelectSingleNode(string xpath) класу XmlDocument. В якості параметра методом SelectSingleNode передається запит XPath, в якому вказується інформація про необхідному вузлі. На практиці часто використовуються наступні шаблони запитів: XPath
"//вузол" - вибір вузла за його імені;
"//вузол[@атрибут="значення"]" - вибір вузла за його імені і значенням атрибуту.
Для доступу до вмісту вузла застосовується клас XmlNode. Інформація у вузлі може знаходиться у формі текстового значення вузла або у формі атрибутів вузла. Для доступу до текстового значенням вузла використовуються властивості string Value{ get; set; } і string InnerText{ get; set; } класу XmlNode. Шаблон отримання текстового значення вузла за його імені:
XmlDocument doc=new XmlDocument();
try{
doc.Load("ім'я файлу");
XmlNode node=doc.SelectSingleNode("//ім'я вузла");
string value=node.Value;
}
catch Exception(ex){
}
Для доступу до атрибутів вузла використовується властивість XmlAttributeCollection Attributes{ get; } класу XmlNode, повертає колекцію об'єктів класу XmlAttribute. Доступ до елементів колекції атрибутів може проводиться за індексом або по імені. Для доступу до текстового значенням атрибуту використовується властивість string Value{ get; set; } класу XmlAttribute. Шаблон отримання атрибуту вузла за його імені:
XmlDocument doc=new XmlDocument();
try{
doc.Load("ім'я файлу");
XmlNode node=doc.SelectSingleNode("//ім'я вузла");
string value=node.Attributes["ім ’ я атрибута"].Value;
}
catch Exception(ex){
}
Для доступу до підлеглих вузлів використовується властивість XmlNodeList ChildNodes{ get; } класу XmlNode, повертає колекцію об'єктів класу XmlNode. Шаблон перебору підлеглих вузлів заданого на ім'я вузла:
XmlDocument doc=new XmlDocument();
try{
doc.Load("ім'я файлу");
XmlNode node=doc.SelectSingleNode("//ім'я вузла");
foreach(XmlNode child in node.ChildNodes){
string value=child.Value;
}
}
catch Exception(ex){
}
Для зберігання текстів запитів SQL можна використовувати XML файл з такою структурою:
<?xml version="1.0" encoding="UTF-8"?>
<sql>
<query name="ім'я запиту">текст запиту</query>
</sql>
Шаблон виконання запиту SQL, текст якого збережено у файлі XML з наведеної вище структурою:
query string;
XmlDocument doc=new XmlDocument();
try{
doc.Load("ім'я файлу");
XmlNode node=doc.SelectSingleNode("//query[@name="имя запиту"]");
query=node.Value;
}
catch Exception(ex){
}
SqlConnection con= new SqlConnection("server=...;uid=...;pwd=...;database=...");
con.Open();
SqlCommand com = new SqlCommand(query,con);
try{
com.ExecuteNonQuery();
}
catch Exception(ex){
}
Інтерфейс IDataAdapter (реалізований в класі SqlDataAdapter провайдера SQL) - призначений для заповнення даними з джерела даних об'єкта DataSet і подальшого оновлення джерела даних у відповідності зі зробленими в об'єкті DataSet змінами. Текст SQL запити для одержання даних зазвичай вказується при натисненні конструктора об'єкта класу SqlDataAdapter. Також при натисненні конструктора слід зазначити об'єкт з'єднання з базою даних. При формуванні рядка запиту SQL можна використовувати форматування рядків з допомогою статичного методу Format класу String.
Для заповнення об'єкта DataSet даними використовується метод int Fill(DataSet dataSet) інтерфейсу IDataAdapter. При натисненні метод повертає кількість записів, переданих в об'єкт DataSet. Шаблон виконання методу Fill:
SqlConnection con;
...
DataSet data;
SqlDataAdapter ad = new SqlDataAdapter("текст запиту SQL",con);
try{
ad.Fill(data);
}
catch Exception(ex){
}
Для доступу до таблиці в об'єкті DataSet використовується властивість DataTableCollection Tables{ get; } класу DataSet, повертає колекцію об'єктів класу DataTable. Доступ до елементів колекції таблиць може проводиться за індексом або по імені. Для доступу до потрібного запису таблиці використовується властивість DataRowCollection Rows { get; } класу DataTable, повертає колекцію об'єктів класу DataRow. Доступ до елементів колекції записів проводиться за індексом. Для доступу до потрібного поля запису таблиці використовується індексатор за номером або по імені поля класу DataRow. Шаблон читання даних об'єкта DataSet:
DataSet data;
...
for(int i=0; i < data.Tables[0].Rows.Count; i++){
for(int j=0; j < data.Tables[0].Columns.Count; j++){
object value=data.Tables[0].Rows[i][j];
}
}
Для зміни значень поля таблиці в об'єкті DataSet необхідно присвоїти нове значення поля потрібного запису.
DataSet data;
...
data.Tables[0].Rows[номер запису][номер поля]=значення;
data.Tables[0].Rows[номер запису]["назва поля"]=значення;
Для додавання нового запису в таблицю в об'єкті DataSet необхідно створити нову запис, викликавши метод DataRow NewRow() класу DataTable, привласнити значення полів створеної запис і додати запис в колекцію записів таблиці, викликавши метод void Add(DataRow row) властивості Rows класу DataTable.
DataSet data;
...
DataRow row=data.Tables[0].NewRow();
row["назва поля"]=значення;
...
data.Tables[0].Rows.Add(row);
Для видалення запису з таблиці в об'єкті DataSet необхідно викликати метод void Delete() класу DataRow, а потім викликати метод void AcceptChanges() класу DataTable.
DataSet data;
...
data.Tables[0].Rows[номер запису].Delete();
data.Tables[0].AcceptChanges();
Для збереження всіх зроблених в об'єкті DataSet змін до джерело даних необхідно викликати метод int Update(DataSet dataSet) інтерфейсу IDataAdapter. При натисненні метод повертає кількість записів, змінених в джерелі даних.
DataSet data;
...
try{
ad.Update(data);
}
catch Exception(ex){
}