Вибачте за те, що я коментую в першу чергу, але я щодня публікую подібний коментар, оскільки багато хто думає, що було б розумно включити функцію ADO.NET в DB-клас (мені теж 10 років тому). Здебільшого вони вирішують використовувати статичні / спільні об'єкти, оскільки це здається швидше, ніж створювати новий об’єкт для будь-яких дій.
Це не є гарною ідеєю ні з точки зору ефективності, ні з точки зору відмови.
Не бракуйте на території З'єднання-Басейну
Є вагома причина, чому ADO.NET внутрішньо керує базовими підключеннями до СУБД у пулі підключень ADO-NET :
На практиці більшість додатків використовують лише одну або кілька різних конфігурацій для з'єднань. Це означає, що під час виконання програми багато ідентичних з'єднань будуть неодноразово відкриватися та закриватися. Для мінімізації витрат на відкриття з'єднань ADO.NET використовує оптимізаційну техніку, що називається об'єднанням з'єднань.
Пул з'єднання зменшує кількість разів, коли потрібно відкривати нові з'єднання. Пуллер підтримує право власності на фізичний зв'язок. Він управляє з'єднаннями, зберігаючи живий набір активних з'єднань для кожної заданої конфігурації з'єднання. Щоразу, коли користувач зателефонує Відкрити під час з'єднання, пулер шукає доступне з'єднання в пулі. Якщо об'єднане з'єднання доступне, він повертає його абоненту замість відкриття нового з'єднання. Коли програма закликає Закрити підключення, пулер повертає його до об'єднаного набору активних з'єднань, а не закриває. Після того, як з'єднання буде повернуто до пулу, воно буде готове до повторного використання під час наступного відкритого дзвінка.
Тож очевидно, що немає ніяких причин уникати створення, відкриття чи закриття з'єднань, оскільки насправді вони взагалі не створені, відкриті та закриті. Це "лише" прапор для пулу з'єднань, щоб знати, коли з'єднання можна повторно використовувати чи ні. Але це дуже важливий прапор, тому що якщо з'єднання "використовується" (пул з'єднання передбачає), нове фізичне з'єднання повинне бути відкрите до СУБД, що дуже дорого.
Отже, ви не отримуєте поліпшення продуктивності, а навпаки. Якщо досягнуто максимального розміру пулу (100 за замовчуванням), ви навіть отримаєте винятки (занадто багато відкритих з'єднань ...). Таким чином, це не тільки вплине на ефективність, але також стане джерелом неприємних помилок та (без використання транзакцій) області демпінгу даних.
Якщо ви навіть використовуєте статичні з'єднання, ви створюєте блокування для кожного потоку, який намагається отримати доступ до цього об’єкта. ASP.NET - це багатопотокове середовище за своєю природою. Таким чином, є чудовий шанс для цих блокувань, що спричиняє проблеми в роботі в кращому випадку. Насправді рано чи пізно ви отримаєте багато різних винятків (наприклад, ваш ExecuteReader вимагає відкритого та доступного з'єднання ).
Висновок :
- Не використовуйте з’єднання або будь-які об’єкти ADO.NET взагалі.
- Не робіть їх статичними / спільними (у VB.NET)
- Завжди створюйте, відкривайте (у разі підключення), використовуйте, закривайте та розміщуйте їх там, де вам потрібно (fe в методі)
- використовуйте
using-statement
для утилізації та закриття (у разі підключення) неявно
Це вірно не лише для З'єднань (хоча і найбільш помітно). Кожен об'єкт, що реалізує, IDisposable
повинен розміщуватися (найпростіше using-statement
), тим більше в System.Data.SqlClient
просторі імен.
Все вищесказане говорить проти користувацького DB-класу, який інкапсулює та повторно використовує всі об'єкти. Ось чому я прокоментував це сміття. Це лише джерело проблеми.
Редагувати : Ось можлива реалізація вашого retrievePromotion
-методу:
public Promotion retrievePromotion(int promotionID)
{
Promotion promo = null;
var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString))
{
var queryString = "SELECT PromotionID, PromotionTitle, PromotionURL FROM Promotion WHERE PromotionID=@PromotionID";
using (var da = new SqlDataAdapter(queryString, connection))
{
// you could also use a SqlDataReader instead
// note that a DataTable does not need to be disposed since it does not implement IDisposable
var tblPromotion = new DataTable();
// avoid SQL-Injection
da.SelectCommand.Parameters.Add("@PromotionID", SqlDbType.Int);
da.SelectCommand.Parameters["@PromotionID"].Value = promotionID;
try
{
connection.Open(); // not necessarily needed in this case because DataAdapter.Fill does it otherwise
da.Fill(tblPromotion);
if (tblPromotion.Rows.Count != 0)
{
var promoRow = tblPromotion.Rows[0];
promo = new Promotion()
{
promotionID = promotionID,
promotionTitle = promoRow.Field<String>("PromotionTitle"),
promotionUrl = promoRow.Field<String>("PromotionURL")
};
}
}
catch (Exception ex)
{
// log this exception or throw it up the StackTrace
// we do not need a finally-block to close the connection since it will be closed implicitely in an using-statement
throw;
}
}
}
return promo;
}