Съжалявам, че коментирам само на първо място, но публикувам почти всеки ден подобен коментар, тъй като много хора смятат, че би било разумно да се капсулира ADO.NET функционалността в DB-Class (аз също преди 10 години). Най-често те решават да използват статични/споделени обекти, тъй като изглежда по-бързо, отколкото да създадат нов обект за каквото и да е действие.
Това не е добра идея нито по отношение на производителността, нито по отношение на безопасността.
Не бракониерство на територията на Connection-Pool
Има добра причина, поради която ADO.NET вътрешно управлява основните връзки към СУБД в ADO-NET Connection-Pool:
На практика повечето приложения използват само една или няколко различни конфигурации за връзки. Това означава, че по време на изпълнение на приложението много идентични връзки ще бъдат многократно отваряни и затваряни. За да сведе до минимум разходите за отваряне на връзки, ADO.NET използва техника за оптимизиране, наречена пул на връзки.
Пулирането на връзки намалява броя пъти, през които трябва да се отварят нови връзки. Пулърът поддържа собствеността върху физическата връзка. Той управлява връзките, като поддържа активен набор от активни връзки за всяка дадена конфигурация на връзката. Всеки път, когато потребител извика Open при връзка, пулърът търси налична връзка в пула. Ако е налична обединена връзка, тя я връща на обаждащия се, вместо да отваря нова връзка. Когато приложението извика Close за връзката, пулърът го връща към обединения набор от активни връзки, вместо да го затваря. След като връзката се върне към пула, тя е готова за повторно използване при следващото отворено повикване.
Така че очевидно няма причина да избягвате създаването, отварянето или затварянето на връзки, тъй като всъщност те изобщо не се създават, отварят и затварят. Това е "само" флаг за пула за връзки, за да знае кога дадена връзка може да бъде използвана повторно или не. Но това е много важен флаг, защото ако връзката е "използвана" (пулът на връзките предполага), трябва да бъде отворена нова физическа връзка към СУБД, което е много скъпо.
Така че не получавате подобрение в производителността, а точно обратното. Ако се достигне максималният размер на пула (100 е по подразбиране), ще получите дори изключения (твърде много отворени връзки ...). Така че това не само ще повлияе значително на производителността, но и ще бъде източник на неприятни грешки и (без да използвате транзакции) зона за изхвърляне на данни.
Ако дори използвате статични връзки, вие създавате заключване за всяка нишка, която се опитва да получи достъп до този обект. ASP.NET е многонишкова среда по природа. Така че има голям шанс за тези ключалки, което в най-добрия случай причинява проблеми с производителността. Всъщност рано или късно ще получите много различни изключения (като вашият ExecuteReader изисква отворена и налична връзка ).
Заключение :
- Изобщо не използвайте повторно връзки или обекти на ADO.NET.
- Не ги правете статични/споделени (във VB.NET)
- Винаги създавайте, отваряйте (в случай на връзки), използвайте, затваряйте и ги изхвърляйте там, където имате нужда (напр. в метод)
- използвайте
using-statement
да изхвърли и затвори (в случай на връзки) имплицитно
Това е вярно не само за Connections (макар и най-забележимо). Всеки обект, реализиращ 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 [email protected]";
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;
}