У попередній статті ми написали перший ADO.NET-приклад і побачили, що все починається з SqlConnection. Ви могли подумати, що підключення до бази даних — це щось на кшталт File.Open(): відкрив, попрацював, закрив. Але реальність значно цікавіша та складніша.
Коли ви пишете connection.Open(), за лаштунками відбувається ціла серія подій: встановлення TCP-з'єднання, обмін SSL-сертифікатами, аутентифікація, вибір бази даних, перевірка прав доступу. І все це — по мережі, навіть якщо SQL Server працює на тій самій машині. Ваш додаток і СУБД — це два окремих процеси, які спілкуються через мережевий протокол.
Тому з'єднання з базою даних — це дорогий ресурс. Відкриття нового з'єднання займає 50–200 мілісекунд (залежно від мережі та конфігурації), що може здатися незначним для одного запиту, але стає критичним, коли ваш веб-додаток обробляє 1000 запитів на секунду. Саме тому ADO.NET має вбудований механізм пулу підключень (Connection Pooling), який ми детально розглянемо у цій статті.
Управління з'єднаннями — це один з тих аспектів, де помилки не завжди видно одразу. Додаток може працювати ідеально на вашій машині та «падати» під навантаженням у production. Розуміння того, як працюють з'єднання, допоможе вам уникнути цих проблем.
Microsoft.Data.SqlClient, базове розуміння мережевих протоколів (TCP/IP).Перш ніж вивчати API класу SqlConnection, давайте розберемося, що фізично відбувається, коли ваш код викликає connection.Open(). Це не магія — це конкретна послідовність мережевих операцій.
Розглянемо кожен крок:
Вся ця процедура може займати 50–500 мс залежно від мережі та конфігурації. Тепер ви розумієте, чому пул підключень — це не оптимізація, а необхідність.
Connection String (рядок підключення) — це спеціально відформатований рядок, який містить усі параметри для встановлення з'єднання з базою даних. Це перше, що ви передаєте у конструктор SqlConnection.
Connection String має формат ключ=значення;ключ=значення;. Ключі регістро-незалежні (case-insensitive), значення можуть бути в лапках, якщо містять спеціальні символи:
Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;
Деякі ключі мають альтернативні назви (aliases), наприклад, Server і Data Source — це одне й те саме, Database і Initial Catalog — теж. Microsoft зберігає ці aliases для зворотної сумісності зі старими версіями ADO.
localhost або . — локальний сервер192.168.1.100 — IP-адресаmyserver.company.com — DNS-ім'яmyserver\SQLEXPRESS — іменований інстанс(localdb)\MSSQLLocalDB — LocalDBmyserver,1434 — нестандартний порт (через кому!)USE [DatabaseName] в T-SQL.true, використовується Windows Authentication — облікові дані поточного користувача Windows. Якщо false, потрібно вказати User Id та Password.Integrated Security=true.true, false, strict. Починаючи з Microsoft.Data.SqlClient 4.0, за замовчуванням true.true, клієнт не перевіряє SSL-сертифікат сервера. Використовуйте лише для розробки!SqlException.SqlCommand.CommandTimeout).DataReader одночасно на одному з'єднанні.SQL Server підтримує два способи аутентифікації, і ваш Connection String буде відрізнятися залежно від обраного способу:
// Windows Authentication (Integrated Security)
// Використовує обліковий запис Windows поточного користувача
string connectionString =
"Server=localhost;" +
"Database=ShopDb;" +
"Integrated Security=True;" + // ← ключовий параметр
"TrustServerCertificate=True;";
Переваги: Не потрібно зберігати паролі в коді або конфігурації. Пароль ніколи не передається по мережі (використовується Kerberos або NTLM token). Це рекомендований спосіб для внутрішніх корпоративних додатків.
Недоліки: Працює лише в середовищі Windows Active Directory. Додаток запускається від імені конкретного Windows-користувача, якому потрібні права у SQL Server.
// SQL Server Authentication
// Використовує логін і пароль SQL Server
string connectionString =
"Server=localhost;" +
"Database=ShopDb;" +
"User Id=appUser;" + // ← логін SQL Server
"Password=SecureP@ss123!;" + // ← пароль
"Encrypt=True;" +
"TrustServerCertificate=True;";
Переваги: Працює на будь-якій платформі (Windows, Linux, macOS). Не залежить від Active Directory. Підходить для хмарних середовищ (Azure SQL Database).
Недоліки: Пароль потрібно десь зберігати — ризик витоку. Пароль передається по мережі (тому шифрування SSL/TLS критичне).
Де зберігати Connection String безпечно:
🔧 appsettings.json
appsettings.Development.json до .gitignore). Використовуйте IConfiguration для читання.🌍 Змінні середовища
Environment.GetEnvironmentVariable().🔐 Azure Key Vault / AWS Secrets Manager
🛡️ User Secrets
dotnet user-secrets set "ConnectionString" "..." зберігає секрет поза проєктом.Приклад читання з конфігураційного файлу:
{
"ConnectionStrings": {
"ShopDb": "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
using Microsoft.Extensions.Configuration;
// Побудова конфігурації
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables() // перевизначення через env variables
.Build();
// Отримання Connection String
string connectionString = configuration.GetConnectionString("ShopDb")
?? throw new InvalidOperationException("Connection string 'ShopDb' not found.");
ConfigurationBuilder потрібно встановити NuGet-пакети Microsoft.Extensions.Configuration та Microsoft.Extensions.Configuration.Json та Microsoft.Extensions.Configuration.EnvironmentVariables.SqlConnection — це конкретна реалізація абстрактного класу DbConnection для MS SQL Server. Цей клас є «вхідною точкою» для будь-якої взаємодії з SQL Server.
// Конструктор без параметрів — потрібно задати ConnectionString пізніше
var connection = new SqlConnection();
connection.ConnectionString = "Server=...;Database=...;";
// Конструктор з рядком підключення — найпоширеніший спосіб
var connection = new SqlConnection("Server=...;Database=...;");
Open(). Після відкриття з'єднання зміна ConnectionString кине InvalidOperationException.ConnectionState має значення: Closed, Open, Connecting, Executing, Fetching, Broken. На практиці ви найчастіше перевірятимете Closed та Open.ChangeDatabase()."16.00.4135"). Доступна лише після виклику Open().SqlException, якщо підключення неможливе (сервер недоступний, невірні облікові дані, база не існує тощо). Якщо з'єднання вже відкрите, кидає InvalidOperationException.Open(). Рекомендується для ASP.NET Core та UI-додатків — не блокує потік під час очікування TCP-з'єднання.Close() можна знову викликати Open() (з тим самим Connection String).Close(). Реалізує IDisposable, що дозволяє використовувати using-інструкцію для автоматичного закриття.USE [DatabaseName] в T-SQL). З'єднання повинно бути відкритим.SqlCommand, автоматично прив'язаний до цього з'єднання.SqlConnection має подію InfoMessage, яка дозволяє перехоплювати повідомлення від SQL Server, які зазвичай «мовчки» ігноруються. Це повідомлення, що надсилаються командою PRINT в T-SQL, а також попередження (severity < 10):
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;";
using SqlConnection connection = new SqlConnection(connectionString);
// Підписуємося на подію InfoMessage
connection.InfoMessage += (sender, e) =>
{
Console.WriteLine($"[SQL Message] {e.Message}");
foreach (SqlError error in e.Errors)
{
Console.WriteLine($" Source: {error.Source}, Number: {error.Number}, State: {error.State}");
}
};
connection.Open();
// Виконуємо запит з PRINT
using SqlCommand command = new SqlCommand(
"PRINT N'Привіт з SQL Server!'; SELECT 1;",
connection);
command.ExecuteNonQuery();
Розбір коду:
InfoMessage через делегат. Аргумент e типу SqlInfoMessageEventArgs містить властивість Message та колекцію Errors з деталями.PRINT — це T-SQL-команда, яка надсилає повідомлення клієнту.InfoMessage повідомлення PRINT просто «зникає» і ніколи не досягає вашого коду.SqlConnection реалізує інтерфейс IDisposable, що означає, що він утримує неуправлені ресурси (TCP-сокет), які потрібно звільнити. Якщо ви забудете закрити з'єднання, воно «витече» з пулу і не буде доступне для інших запитів. При досягненні ліміту пулу (за замовчуванням 100 з'єднань) нові спроби підключення будуть зависати і зрештою кинуть SqlException з таймаутом.
Це одна з найпідступніших помилок в ADO.NET — ваш код працює чудово під час розробки (один користувач, мало запитів), але «падає» під навантаженням у production.
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;";
// using-декларація: з'єднання автоматично закриється
// при виході зі scope (кінець методу)
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// ... працюємо з даними ...
// connection.Dispose() викличеться автоматично тут
Цей підхід — рекомендований для більшості сценаріїв. using-декларація гарантує, що Dispose() (і відповідно Close()) буде викликано навіть якщо виникне виняток. З'єднання закриється при виході зі scope — зазвичай в кінці методу.
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;";
// using-блок: з'єднання закриється в кінці блоку { }
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// ... працюємо з даними ...
} // ← connection.Dispose() тут
Класичний using-блок дає більш точний контроль над часом життя з'єднання — воно закриється рівно в кінці блоку, а не в кінці методу. Це може бути важливо, якщо після роботи з базою у вас є довга обробка результатів:
List<Product> products;
// З'єднання відкрите мінімально необхідний час
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
products = LoadProducts(connection);
} // ← з'єднання вже закрите!
// Довга обробка — з'єднання вже повернуте в пул
foreach (var product in products)
{
ProcessProduct(product); // може зайняти 10 секунд
}
// ❌ НІКОЛИ НЕ РОБІТЬ ТАК!
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
// ... працюємо з даними ...
// Якщо тут виникне виняток — з'єднання НІКОЛИ не закриється!
// Воно "витече" з пулу, і зрештою пул переповниться.
connection.Close(); // ← може не виконатися при винятку
SqlConnectionзавжди повинен бути обгорнутий у using. Без винятків. Навіть якщо ви «впевнені», що виняток не виникне. Навіть у тестовому коді. Це як ремінь безпеки — завжди.Уявіть, що з'єднання з базою даних — це готельний номер:
Open() — ви заселяєтесь (отримуєте ключ від номера).Close() / Dispose() — ви виписуєтесь (здаєте ключ).Якщо ви «забудете виписатися» (не закриєте з'єднання), номер буде зайнятий, навіть якщо ви вже поїхали. Коли всі 100 номерів зайняті забудькуватими гостями — нові гості не можуть заселитися і чекають у лобі (timeout exception).
Connection Pooling (пул підключень) — це один з найважливіших механізмів ADO.NET, який драматично покращує продуктивність. Він автоматично перевикористовує раніше створені з'єднання замість створення нових.
Без пулу кожен запит до бази вимагав би:
Бачите проблему? Накладні витрати на створення з'єднання (кроки 1-2) у десятки разів перевищують час самого запиту (крок 3). Для веб-додатка, що обробляє 1000 запитів на секунду, це катастрофа.
Пул вирішує цю проблему: створені з'єднання не знищуються, а повертаються в пул і доступні для повторного використання. Наступний Open() просто «дістає» готове з'єднання з пулу за ~0.01 мс замість 200 мс.
Ось повний життєвий цикл з'єднання з пулом:
Open(): Пул порожній → створюється нове TCP-з'єднання із SQL Server.Close() / Dispose(): З'єднання не закривається фізично, а повертається в пул зі статусом «вільне».Open() (з тим самим Connection String): Пул знаходить вільне з'єднання → повертає його за мілісекунди без TCP-рукостискання.Open() чекає, поки одне з них звільниться, або кидає SqlException після таймауту.Пули групуються за Connection String: два різних Connection String — два різних пулу. Причому порівняння символ-за-символом (case-sensitive, включаючи пробіли!):
// Це ДВА РІЗНИХ пули (різний регістр "database" vs "Database"):
"Server=localhost;database=ShopDb;..." // Пул 1
"Server=localhost;Database=ShopDb;..." // Пул 2
// Це теж ДВА РІЗНИХ пули (додатковий пробіл!):
"Server=localhost;Database=ShopDb;..." // Пул 3
"Server=localhost; Database=ShopDb;..." // Пул 4 (пробіл перед Database)
Параметри пулу задаються прямо у Connection String:
string connectionString =
"Server=localhost;" +
"Database=ShopDb;" +
"Trusted_Connection=True;" +
"TrustServerCertificate=True;" +
"Min Pool Size=5;" + // Мінімум 5 з'єднань завжди готові
"Max Pool Size=100;" + // Максимум 100 з'єднань у пулі
"Connection Lifetime=300;" + // З'єднання живе максимум 5 хвилин
"Pooling=True;"; // Пул увімкнений (true за замовчуванням)
| Параметр | За замовчуванням | Опис |
|---|---|---|
Pooling | true | Вмикає/вимикає пул |
Min Pool Size | 0 | Мінімальна кількість «живих» з'єднань |
Max Pool Size | 100 | Максимальний розмір пулу |
Connection Lifetime | 0 (безмежно) | Максимальний час життя з'єднання (секунди) |
Connection Idle Timeout | 300 | Час очікування, після якого неактивне з'єднання закриється |
Max Pool Size, замість того щоб знайти витік з'єднань.Якщо ви підозрюєте проблеми з пулом (витік з'єднань), ось як діагностувати:
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;";
// Отримуємо статистику пулу
Console.WriteLine("=== Перед Open() ===");
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Використовуємо DMV (Dynamic Management View) SQL Server
using SqlCommand command = new SqlCommand(
@"SELECT
DB_NAME(database_id) AS DatabaseName,
COUNT(*) AS ActiveConnections
FROM sys.dm_exec_sessions
WHERE program_name = '.Net SqlClient Data Provider'
GROUP BY database_id",
connection);
using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine($" БД: {reader["DatabaseName"]}, Активних з'єднань: {reader["ActiveConnections"]}");
}
}
Розбір коду:
sys.dm_exec_sessions — це «вікно» у внутрішній стан SQL Server, яке показує всі активні сесії.program_name, щоб бачити лише з'єднання від .NET-клієнтів. Зверніть увагу: якщо ви задали Application Name у Connection String, тут буде саме ця назва.Application Name у Connection String:"...;Application Name=ShopWebApp;"
З'єднання з базою даних — це мережева операція, і вона може не вдатися з багатьох причин: сервер вимкнений, мережа недоступна, невірний пароль, база не існує тощо. Правильна обробка цих помилок — ознака професійного коду.
Клас SqlException містить детальну інформацію про помилку:
Знання найпоширеніших кодів помилок дозволяє реагувати на них програмно:
| Код | Причина | Типове рішення |
|---|---|---|
| 2 | Сервер недоступний | Перевірити мережу, ім'я сервера, порт |
| 4060 | База даних не існує | Перевірити назву бази в Connection String |
| 18456 | Невірний логін/пароль | Перевірити облікові дані |
| 18452 | Логін не вказано | Додати User Id або Integrated Security=True |
| 233 | З'єднання розірвано | Retry (повторна спроба) |
| -2 | Таймаут з'єднання | Збільшити Connection Timeout або перевірити мережу |
| 10054 | З'єднання скинуте сервером | Retry, перевірити навантаження на сервер |
| 40613 | Azure SQL: БД недоступна | Retry з exponential backoff |
using System;
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;";
try
{
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
Console.WriteLine("✅ Підключення успішне!");
Console.WriteLine($" Сервер: {connection.DataSource}");
Console.WriteLine($" База: {connection.Database}");
Console.WriteLine($" Версія: {connection.ServerVersion}");
Console.WriteLine($" Connection ID: {connection.ClientConnectionId}");
// ... робота з даними ...
}
catch (SqlException ex) when (ex.Number == 2 || ex.Number == -1)
{
Console.WriteLine($"❌ SQL Server недоступний: {ex.Message}");
Console.WriteLine(" → Перевірте, чи запущено SQL Server та чи вірна адреса.");
}
catch (SqlException ex) when (ex.Number == 4060)
{
Console.WriteLine($"❌ База даних не знайдена: {ex.Message}");
Console.WriteLine(" → Перевірте назву бази даних у Connection String.");
}
catch (SqlException ex) when (ex.Number == 18456)
{
Console.WriteLine($"❌ Помилка аутентифікації: {ex.Message}");
Console.WriteLine(" → Перевірте логін та пароль.");
}
catch (SqlException ex)
{
Console.WriteLine($"❌ Помилка SQL Server (код {ex.Number}): {ex.Message}");
Console.WriteLine($" Severity: {ex.Class}, State: {ex.State}");
// Виводимо всі помилки з колекції
foreach (SqlError error in ex.Errors)
{
Console.WriteLine($" [{error.Number}] {error.Message} (Line: {error.LineNumber})");
}
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"❌ Помилка стану з'єднання: {ex.Message}");
Console.WriteLine(" → Можливо, з'єднання вже відкрите або Connection String пусте.");
}
Розбір коду:
when) — це C# 6.0+ feature, яка дозволяє перехоплювати лише SqlException з конкретним номером помилки.catch обробляє конкретний тип помилки і надає користувачу дієву рекомендацію (не просто «щось зламалось», а «перевірте X»).catch для решти SqlException — виводить повну інформацію, включаючи колекцію Errors.InvalidOperationException виникає при спробі відкрити вже відкрите з'єднання або якщо ConnectionString пусте.У реальних додатках, особливо в хмарних середовищах (Azure, AWS), мережеві збої — це норма, а не виняток. З'єднання може «впасти» через тимчасову недоступність мережі, перезавантаження SQL Server, або балансування навантаження. Правильна стратегія — повторна спроба (retry) з експоненційним відступом (exponential backoff).
Ідея експоненційного відступу проста: якщо перша спроба не вдалася, чекаємо 1 секунду. Якщо друга — чекаємо 2 секунди. Третя — 4 секунди. І так далі. Це запобігає «завалюванню» сервера повторними спробами від тисяч клієнтів одночасно.
using System;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
async Task<SqlConnection> OpenConnectionWithRetryAsync(
string connectionString,
int maxRetries = 3)
{
SqlConnection? connection = null;
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
connection = new SqlConnection(connectionString);
await connection.OpenAsync();
Console.WriteLine($"✅ Підключення успішне (спроба {attempt})");
return connection;
}
catch (SqlException ex) when (IsTransientError(ex) && attempt < maxRetries)
{
// Transient error — має сенс повторити
connection?.Dispose();
int delayMs = (int)Math.Pow(2, attempt) * 1000; // 2s, 4s, 8s
Console.WriteLine($"⚠️ Спроба {attempt} не вдалась (код {ex.Number}). " +
$"Повтор через {delayMs / 1000} сек...");
await Task.Delay(delayMs);
}
catch (SqlException ex)
{
// Non-transient error або остання спроба — кидаємо далі
connection?.Dispose();
throw new InvalidOperationException(
$"Не вдалося підключитися після {attempt} спроб.", ex);
}
}
throw new InvalidOperationException("Не вдалося підключитися до бази даних.");
}
bool IsTransientError(SqlException ex)
{
// Transient errors — ті, що можуть самостійно вирішитися
int[] transientErrors = { -2, 233, 10053, 10054, 10060, 40197, 40501, 40613, 49918, 49919, 49920 };
foreach (SqlError error in ex.Errors)
{
if (Array.IndexOf(transientErrors, error.Number) >= 0)
return true;
}
return false;
}
Розбір коду:
for з maxRetries спроб. Кожна невдала спроба збільшує затримку експоненційно (2, 4, 8 секунд).when фільтр для перехоплення лише transient (тимчасових) помилок. Якщо помилка не тимчасова (наприклад, невірний пароль), повторні спроби безглузді.connection?.Dispose() — звільняємо ресурси невдалого з'єднання (null-conditional, на випадок, якщо конструктор пройшов, але OpenAsync() впав).Math.Pow(2, attempt) * 1000 — експоненційний відступ: 2000 мс, 4000 мс, 8000 мс.IsTransientError() визначає, чи є помилка тимчасовою. Ці коди помилок рекомендовані Microsoft для retry-логіки.EnableRetryOnFailure().Замість конкатенації рядків для побудови Connection String, можна використовувати клас SqlConnectionStringBuilder. Він забезпечує type-safe доступ до параметрів і автоматичне екранування спеціальних символів:
using Microsoft.Data.SqlClient;
// Побудова через builder — типобезпечно, без друкарських помилок
var builder = new SqlConnectionStringBuilder
{
DataSource = "localhost",
InitialCatalog = "ShopDb",
IntegratedSecurity = true,
TrustServerCertificate = true,
ConnectTimeout = 30,
ApplicationName = "MyShopApp",
MinPoolSize = 5,
MaxPoolSize = 50
};
Console.WriteLine($"Connection String: {builder.ConnectionString}");
// Вивід: Data Source=localhost;Initial Catalog=ShopDb;Integrated Security=True;...
// Можна також розібрати існуючий Connection String
var parser = new SqlConnectionStringBuilder(
"Server=myserver;Database=mydb;User Id=admin;Password=secret123;");
Console.WriteLine($"Сервер: {parser.DataSource}");
Console.WriteLine($"База: {parser.InitialCatalog}");
Console.WriteLine($"Користувач: {parser.UserID}");
// Password доступний, але за замовчуванням не включається у ToString()
// (для безпеки)
Розбір коду:
SqlConnectionStringBuilder надає властивості для кожного параметра Connection String. Якщо ви допустите друкарську помилку (наприклад, DataSourse), компілятор повідомить про це — на відміну від формування рядка вручну.ConnectionString повертає сформований рядок, готовий для передачі в SqlConnection.SqlConnectionStringBuilder може також розбирати існуючий Connection String — корисно для читання конфігурації.SqlConnectionStringBuilder особливо корисний, коли Connection String формується динамічно — наприклад, частина параметрів береться з конфігурації, а частина — з UI. Це безпечніше за конкатенацію рядків.Об'єднаємо все вивчене в одну корисну утиліту:
using System;
using System.Diagnostics;
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;Application Name=ConnectionTester;";
Console.WriteLine("╔════════════════════════════════════════╗");
Console.WriteLine("║ 🔌 Утиліта перевірки підключення ║");
Console.WriteLine("╚════════════════════════════════════════╝");
Console.WriteLine();
// Вимірюємо час підключення
Stopwatch sw = Stopwatch.StartNew();
try
{
using SqlConnection connection = new SqlConnection(connectionString);
// Підписуємось на InfoMessage
connection.InfoMessage += (s, e) =>
Console.WriteLine($" 📨 SQL Message: {e.Message}");
// Вимірюємо час Open()
Stopwatch openSw = Stopwatch.StartNew();
connection.Open();
openSw.Stop();
Console.WriteLine("✅ З'єднання встановлено!");
Console.WriteLine($" ⏱️ Час підключення: {openSw.ElapsedMilliseconds} мс");
Console.WriteLine();
// Інформація про з'єднання
Console.WriteLine("📋 Інформація про з'єднання:");
Console.WriteLine($" Сервер: {connection.DataSource}");
Console.WriteLine($" База даних: {connection.Database}");
Console.WriteLine($" Версія: {connection.ServerVersion}");
Console.WriteLine($" Стан: {connection.State}");
Console.WriteLine($" Client ID: {connection.ClientConnectionId}");
Console.WriteLine($" Таймаут: {connection.ConnectionTimeout} сек");
Console.WriteLine();
// Інформація від SQL Server
Console.WriteLine("📊 Інформація від SQL Server:");
using SqlCommand cmd = connection.CreateCommand();
cmd.CommandText = "SELECT @@SERVERNAME";
Console.WriteLine($" Ім'я сервера: {cmd.ExecuteScalar()}");
cmd.CommandText = "SELECT @@VERSION";
string version = cmd.ExecuteScalar()?.ToString() ?? "N/A";
Console.WriteLine($" Повна версія: {version.Split('\n')[0]}"); // Тільки перший рядок
cmd.CommandText = "SELECT SUSER_SNAME()";
Console.WriteLine($" Поточний логін: {cmd.ExecuteScalar()}");
cmd.CommandText = "SELECT GETDATE()";
Console.WriteLine($" Час на сервері: {cmd.ExecuteScalar()}");
cmd.CommandText = "SELECT COUNT(*) FROM sys.databases";
Console.WriteLine($" Баз даних: {cmd.ExecuteScalar()}");
// Повторне підключення — демонстрація пулу
Console.WriteLine();
Console.WriteLine("🔄 Повторне підключення (демонстрація пулу):");
connection.Close();
Stopwatch reopenSw = Stopwatch.StartNew();
connection.Open();
reopenSw.Stop();
Console.WriteLine($" ⏱️ Час повторного підключення: {reopenSw.ElapsedMilliseconds} мс");
Console.WriteLine(" 💡 Зверніть увагу: повторне підключення значно швидше завдяки пулу!");
}
catch (SqlException ex)
{
Console.WriteLine($"❌ Помилка підключення (код {ex.Number}):");
Console.WriteLine($" {ex.Message}");
Console.WriteLine($" Severity: {ex.Class}, State: {ex.State}");
}
sw.Stop();
Console.WriteLine();
Console.WriteLine($"⏱️ Загальний час: {sw.ElapsedMilliseconds} мс");
Розбір коду:
Stopwatch для точного вимірювання часу підключення.connection.CreateCommand() — альтернативний спосіб створити SqlCommand, автоматично прив'язаний до з'єднання.cmd, змінюючи лише CommandText. Це безпечно і ефективно.Close() + Open() повторне підключення займе ~0-1 мс замість 50-200 мс, тому що з'єднання повертається з пулу.Створіть програму, яка:
Console.ReadLine()).SqlConnectionStringBuilder для розбору рядка.Напишіть програму, яка демонструє життєвий цикл з'єднання:
SqlConnection і виведіть State → очікується Closed.Open() і виведіть State → очікується Open.Close() і виведіть State → очікується Closed.Open() знову і виведіть State → очікується Open.Close() можна знову Open().Створіть програму, яка:
Напишіть програму, яка наочно демонструє роботу пулу:
Open() за допомогою Stopwatch.Створіть клас ConnectionFactory, який:
CreateConnection() → повертає відкритий SqlConnection.CreateConnectionAsync() → асинхронна версія.TestConnection() → повертає bool (вдалося/не вдалося підключитися).CreateConnection().Створіть клас MultiDatabaseManager, який:
Dictionary<string, string> (ім'я → Connection String).AddDatabase(name, connectionString).GetConnection(name) → повертає відкритий SqlConnection.TestAllConnections() → перевіряє всі збережені з'єднання і виводить зведену таблицю.GetServerInfo(name) → повертає версію сервера, кількість баз, поточного користувача.Connection String
SqlConnection + using
using для SqlConnection. Забуте з'єднання «витікає» з пулу і може вичерпати ліміт підключень.Connection Pooling
Обробка помилок
SqlException.Number визначає тип помилки. Для transient-помилок використовуйте retry з exponential backoff.Server, Database, Trusted_Connection)Dispose() (або using) для звільнення ресурсівDbCommand — як надсилати SQL-запити на сервер, виконувати INSERT, UPDATE, DELETE та отримувати результати через ExecuteReader(), ExecuteNonQuery() та ExecuteScalar().