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;

Ответ 1

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

Это не хорошая идея с точки зрения эффективности, ни с точки зрения безопасности при прорыве.

Не используйте на панели Connection-Pool

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

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

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

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

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

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

Заключение:

  • Не используйте повторно соединения или любые объекты ADO.NET.
  • Не делайте их static/shared (в 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;
}

Ответ 2

Я поймал эту ошибку несколько дней назад.

В моем случае это было потому, что я использовал транзакцию на синглтоне.

.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 - мой собственный класс