ExecuteReader требует открытого и доступного соединения. Текущее состояние подключения - Connecting

114

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

ExecuteReader требует открытого и доступного соединения. Текущее состояние подключения - Connecting.

Сайт отлично работает на моем сервере 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-Class в мусорное ведро и создавайте, открывайте, используйте, закрывайте и удаляйте объекты ado.net там, где они вам нужны. Взгляните также на оператор using.
Тим Шмелтер
2
не могли бы вы подробно рассказать о SqlOpenConnection (); и sql.ExecuteReader (); функции? ..
анкит раджпут
частный void SqlOpenConnection () {попробуйте {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {throw ex; }}
Гуо Хун Лим,
@GuoHongLim: я забыл упомянуть, что даже статика conStringничего не добавляет с точки зрения производительности, так как она в любом случае кешируется по умолчанию (как и каждое значение конфигурации для текущего приложения).
Тим Шмелтер
... и просто для того, чтобы сделать это известным-неизвестным: обеспечение правильности обработки транзакций / единиц работы в базе данных остается в качестве упражнения для читателя.
mwardm

Ответы:

227

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

Это не лучшая идея ни с точки зрения производительности, ни с точки зрения отказоустойчивости.

Не переманивайте на территории пула подключений

Есть веская причина, по которой ADO.NET внутренне управляет базовыми соединениями с СУБД в пуле соединений ADO-NET :

На практике большинство приложений используют только одну или несколько различных конфигураций для соединений. Это означает, что во время выполнения приложения многие идентичные соединения будут многократно открываться и закрываться. Чтобы минимизировать затраты на открытие соединений, ADO.NET использует метод оптимизации, называемый пулом соединений.

Пул соединений сокращает количество открытий новых соединений. Группа пула сохраняет владение физическим подключением. Он управляет подключениями, поддерживая в рабочем состоянии набор активных подключений для каждой заданной конфигурации подключения. Всякий раз, когда пользователь вызывает Open для соединения, пулер ищет доступное соединение в пуле. Если объединенное соединение доступно, оно возвращает его вызывающей стороне вместо открытия нового соединения. Когда приложение вызывает Close для соединения, пулер возвращает его в объединенный набор активных соединений вместо того, чтобы закрывать его. Как только соединение возвращается в пул, оно готово к повторному использованию при следующем вызове Open.

Итак, очевидно, что нет причин избегать создания, открытия или закрытия соединений, поскольку на самом деле они вообще не создаются, не открываются и не закрываются. Это «всего лишь» флаг для пула соединений, который определяет, можно ли повторно использовать соединение. Но это очень важный флаг, потому что, если соединение «используется» (предполагает пул соединений), новое физическое соединение должно быть открытым для СУБД, что очень дорого.

Таким образом, вы не получаете улучшения производительности, а наоборот. Если будет достигнут указанный максимальный размер пула (по умолчанию 100), вы даже получите исключения (слишком много открытых подключений ...). Таким образом, это не только сильно повлияет на производительность, но и станет источником неприятных ошибок и (без использования транзакций) области сброса данных.

Если вы даже используете статические соединения, вы создаете блокировку для каждого потока, пытающегося получить доступ к этому объекту. ASP.NET по своей природе является многопоточной средой. Так что есть отличная возможность для этих блокировок, которые в лучшем случае вызывают проблемы с производительностью. На самом деле рано или поздно вы получите много разных исключений (например, вашему ExecuteReader требуется открытое и доступное соединение ).

Вывод :

  • Ни в коем случае не используйте повторно соединения или какие-либо объекты ADO.NET.
  • Не делайте их статическими / общими (в VB.NET)
  • Всегда создавайте, открывайте (в случае подключений), используйте, закрывайте и удаляйте их там, где они вам нужны (например, в методе)
  • используйте using-statementдля удаления и закрытия (в случае Connections) неявно

Это верно не только для 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;
}
Тим Шмельтер
источник
это действительно полезно для создания парадигмы работы с подключением. Спасибо за это объяснение.
aminvincent
хорошо написано, объяснение того, что многие люди случайно обнаруживают, и я бы хотел, чтобы больше людей знали об этом. (+1)
Эндрю Хилл
1
Спасибо, сэр, я думаю, что это лучшее объяснение по этому поводу, которое я когда-либо читал, тема, которая очень важна, и многие новички ошибаются. Я должен сделать вам комплимент за ваши прекрасные писательские способности.
Sasinosoft,
@ Тим Шмельтер, как я могу заставить мои запросы, выполняемые в разных потоках, использовать одну транзакцию для фиксации / отката, используя предложенный вами подход?
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 - мой собственный класс

Дэймон Абдиэль
источник