ExecuteReader требует открытого и доступного соединения. Текущее состояние соединения: Соединение - PullRequest
100 голосов
/ 14 марта 2012

Когда я пытаюсь подключиться к базе данных 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;

1 Ответ

209 голосов
/ 14 марта 2012

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

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

Не вмешиваться в территорию пула соединений

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

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

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

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

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

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

Заключение :

  • Ни в коем случае не используйте соединения или любые объекты ADO.NET.
  • Не делайте их статичными / общими (в VB.NET)
  • Всегда создавайте, открывайте (в случае соединений), используйте, закрывайте и располагайте их там, где они вам нужны (например, вметод)
  • используйте 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;
}
...