ExecuteReader изисква отворена и достъпна връзка. Текущото състояние на връзката е Свързване

Когато се опитвам да се свържа с MSSQL база данни чрез ASP.NET онлайн, ще получа следното, когато двама или повече души се свържат едновременно:

ExecuteReader изисква отворена и достъпна връзка. Текущото състояние на връзката е Свързване.

Сайтът работи добре на моя локален сървър.

Това е грубият код.

public Promotion retrievePromotion()
{
    int promotionID = 0;
    string promotionTitle = "";
    string promotionUrl = "";
    Promotion promotion = null;
    SqlOpenConnection();
    SqlCommand sql = SqlCommandConnection();

    sql.CommandText = "SELECT TOP 1 PromotionID, PromotionTitle, PromotionURL FROM Promotion";

    SqlDataReader dr = sql.ExecuteReader();
    while (dr.Read())
    {
        promotionID = DB2int(dr["PromotionID"]);
        promotionTitle = DB2string(dr["PromotionTitle"]);
        promotionUrl = DB2string(dr["PromotionURL"]);
        promotion = new Promotion(promotionID, promotionTitle, promotionUrl);
    }
    dr.Dispose();
    sql.Dispose();
    CloseConnection();
    return promotion;
}

Мога ли да знам какво може да се е объркало и как да го поправя?

Редактиране: Да не забравя, моят низ за свързване и връзката са статични. Вярвам, че това е причината. Моля за съвет.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;

person Guo Hong Lim    schedule 14.03.2012    source източник
comment
Не използвайте споделени/статични връзки в многонишкова среда като ASP.NET, тъй като генерирате заключвания или изключения (твърде много отворени връзки и т.н.). Изхвърлете вашия DB-Class в кофата за боклук и създайте, отворете, използвайте, затворете, изхвърлете ado.net обекти, където имате нужда от тях. Обърнете внимание и на изявлението за използване.   -  person Tim Schmelter    schedule 14.03.2012
comment
можете ли да ми дадете подробности за SqlOpenConnection();и sql.ExecuteReader(); функции?..   -  person ankit rajput    schedule 14.03.2012
comment
private void SqlOpenConnection() { try { conn = new SqlConnection(); conn.ConnectionString = conString; conn.Open(); } catch (SqlException ex) { throw ex; }}   -  person Guo Hong Lim    schedule 14.03.2012
comment
@GuoHongLim: забравих да спомена, че дори статичен conString не добавя нищо по отношение на производителността, тъй като той така или иначе е кеширан по подразбиране (като всяка конфигурационна стойност за текущото приложение).   -  person Tim Schmelter    schedule 15.03.2012
comment
...и само за да го направим известно-неизвестно: Гарантирането, че също така ще получите правилната обработка на транзакциите на вашата база данни / единица работа е оставено като упражнение за читателя.   -  person mwardm    schedule 10.03.2016


Отговори (2)


Съжалявам, че коментирам само на първо място, но публикувам почти всеки ден подобен коментар, тъй като много хора смятат, че би било умно да се капсулира функционалността на ADO.NET в DB-Class (аз също преди 10 години). Най-често те решават да използват статични/споделени обекти, тъй като изглежда, че е по-бързо от създаването на нов обект за каквото и да е действие.

Това не е нито добра идея по отношение на производителността, нито по отношение на безопасността при отказ.

Не ловете на територията на Connection-Pool

Има добра причина, поради която ADO.NET вътрешно управлява основните връзки към СУБД в ADO -NET Connection-Pool:

На практика повечето приложения използват само една или няколко различни конфигурации за връзки. Това означава, че по време на изпълнение на приложението много идентични връзки ще бъдат многократно отваряни и затваряни. За да минимизира разходите за отваряне на връзки, ADO.NET използва техника за оптимизация, наречена групиране на връзки.

Пулирането на връзки намалява броя на отварянето на нови връзки. Пулърът поддържа собствеността върху физическата връзка. Той управлява връзките, като поддържа набор от активни връзки за всяка дадена конфигурация на връзка. Всеки път, когато потребител извика Отваряне на връзка, пулерът търси налична връзка в пула. Ако е налична обединена връзка, тя я връща на повикващия, вместо да отваря нова връзка. Когато приложението извика 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 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;
}
person Tim Schmelter    schedule 14.03.2012
comment
това е наистина полезно за създаване на парадигма за работата на връзката. Благодаря за това обяснение. - person aminvincent; 31.07.2018
comment
добре написано, обяснение за нещо, което много хора случайно откриват и ми се иска повече хора да знаят това. (+1) - person Andrew Hill; 11.01.2019
comment
Благодаря ви, сър, мисля, че това е най-доброто обяснение по тази тема, което някога съм чел, тема, която е много важна и която много начинаещи грешат. Трябва да ви похваля за отличните ви умения за писане. - person Sasinosoft; 22.01.2020
comment
@Tim Schmelter как мога да накарам моите заявки, изпълнявани в различни нишки, да използват една транзакция за ангажиране/връщане назад, използвайки предложения от вас подход? - person geeko; 12.03.2020

Хванах тази грешка преди няколко дни.

В моя случай това беше, защото използвах транзакция на Singleton.

.Net не работи добре със Singleton, както е посочено по-горе.

Моето решение беше следното:

public class DbHelper : DbHelperCore
{
    public DbHelper()
    {
        Connection = null;
        Transaction = null;
    }

    public static DbHelper instance
    {
        get
        {
            if (HttpContext.Current is null)
                return new DbHelper();
            else if (HttpContext.Current.Items["dbh"] == null)
                HttpContext.Current.Items["dbh"] = new DbHelper();

            return (DbHelper)HttpContext.Current.Items["dbh"];
        }
    }

    public override void BeginTransaction()
    {
        Connection = new SqlConnection(Entity.Connection.getCon);
        if (Connection.State == System.Data.ConnectionState.Closed)
            Connection.Open();
        Transaction = Connection.BeginTransaction();
    }
}

Използвах HttpContext.Current.Items за моя екземпляр. Този клас DbHelper и DbHelperCore е мой собствен клас

person Damon Abdiel    schedule 29.09.2019