ExecuteReader вимагає відкритого та доступного з'єднання. Поточний стан з'єднання - Підключення


114

При спробі підключення до бази даних MSSQL через ASP.NET в Інтернеті я отримаю наступне, коли двоє або більше людей підключаються одночасно:

ExecuteReader вимагає відкритого та доступного з'єднання. Поточний стан з'єднання - Підключення.

Сайт чудово працює на моєму сервері localhost.

Це приблизний код.

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;

24
Не використовуйте спільні / статичні з'єднання в середовищі з багатопотоковим записом, як ASP.NET, оскільки ви генеруєте блокування чи винятки (занадто багато відкритих з'єднань тощо). Киньте свій DB-клас у сміттєвий контейнер і створюйте, відкривайте, використовуйте, закривайте, розміщуйте об’єкти ado.net там, де вони вам потрібні. Ознайомтеся і з використанням-оператора.
Тім Шмелтер

2
чи можете ви мені детально розповісти про SqlOpenConnection (); і sql.ExecuteReader (); функції? ..
ankit rajput

private void SqlOpenConnection () {спробуй {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } улов (SqlException ex) {кинути екс; }}
Го Хон Лім

@GuoHongLim: я забув згадати, що навіть статичний conStringне додає нічого з точки зору продуктивності, оскільки він кеширується за замовчуванням (як і кожне значення конфігурації для поточної програми).
Тім Шмелтер

... і лише для того, щоб зробити це відомим-невідомим: Забезпечення того, що ви також отримаєте правильну обробку транзакцій бази даних / одиниці роботи, залишається як вправа для читача.
mwardm

Відповіді:


226

Вибачте за те, що я коментую в першу чергу, але я щодня публікую подібний коментар, оскільки багато хто думає, що було б розумно включити функцію 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;
}

це справді корисно для надання парадигми роботи з підключенням. Дякую за це пояснення.
aminvincent

добре написане, пояснення чомусь багато людей випадково виявляють, і я б хотів, щоб більше людей знали це. (+1)
Ендрю Хілл

1
Дякую, сер, я думаю, що це найкраще пояснення на цю тему, яке я коли-небудь читав, тематика, яка дуже важлива і що багато новачків помиляються. Я повинен похвалити вас за ваші чудові вміння писати
Sasinosoft

@Tim Schmelter, як я можу змусити мої запити, що працюють на різних потоках, використовувати одну транзакцію для здійснення / відкоту назад, використовуючи запропонований підхід?
geeko

1

Я зловив цю помилку кілька днів тому.

У моєму випадку це було тому, що я використовував транзакцію на одиночному.

.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 - це мій власний клас

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.