ExecuteReader requer uma conexão aberta e disponível. O estado atual da conexão é Conectando

114

Ao tentar se conectar ao banco de dados MSSQL via ASP.NET online, obtenho o seguinte quando duas ou mais pessoas se conectam simultaneamente:

ExecuteReader requer uma conexão aberta e disponível. O estado atual da conexão é Conectando.

O site funciona bem no meu servidor localhost.

Este é o código aproximado.

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;
}

Posso saber o que pode ter dado errado e como faço para corrigir isso?

Edit: para não esquecer, minha string de conexão e conexão são estáticas. Acredito que seja esse o motivo. Por favor informar.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;
Guo Hong Lim
fonte
24
Não use conexões compartilhadas / estáticas em um ambiente multithreading como ASP.NET, pois você está gerando bloqueios ou exceções (muitas conexões abertas, etc.). Jogue sua classe de banco de dados na lata de lixo e crie, abra, use, feche e descarte objetos ado.net onde você precisar deles. Dê uma olhada na instrução using também.
Tim Schmelter
2
você pode me dar detalhes sobre SqlOpenConnection (); e sql.ExecuteReader (); funções? ..
ankit rajput
private void SqlOpenConnection () {try {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {lance ex; }}
Guo Hong Lim
@GuoHongLim: esqueci de mencionar que mesmo um estático conStringnão acrescenta nada em termos de desempenho, já que é armazenado em cache por padrão de qualquer maneira (como todo valor de configuração para o aplicativo atual).
Tim Schmelter,
... e apenas para torná-lo conhecido-desconhecido: garantir que você também obtenha o tratamento / unidade de trabalho da transação do banco de dados correto é deixado como um exercício para o leitor.
mwardm

Respostas:

226

Desculpe por apenas comentar em primeiro lugar, mas estou postando quase todos os dias um comentário semelhante, já que muitas pessoas pensam que seria inteligente encapsular a funcionalidade ADO.NET em uma classe de banco de dados (eu também há 10 anos). Principalmente, eles decidem usar objetos estáticos / compartilhados, pois parece ser mais rápido do que criar um novo objeto para qualquer ação.

Isso não é uma boa ideia em termos de desempenho nem em termos de segurança contra falhas.

Não roube o território do Connection-Pool

Há um bom motivo pelo qual o ADO.NET gerencia internamente as conexões subjacentes ao DBMS no pool de conexões ADO-NET :

Na prática, a maioria dos aplicativos usa apenas uma ou algumas configurações diferentes para conexões. Isso significa que durante a execução do aplicativo, muitas conexões idênticas serão abertas e fechadas repetidamente. Para minimizar o custo de abertura de conexões, o ADO.NET usa uma técnica de otimização chamada pooling de conexões.

O pool de conexão reduz o número de vezes que novas conexões devem ser abertas. O pooler mantém a propriedade da conexão física. Ele gerencia conexões mantendo ativo um conjunto de conexões ativas para cada configuração de conexão fornecida. Sempre que um usuário chama Open em uma conexão, o pooler procura uma conexão disponível no pool. Se uma conexão agrupada estiver disponível, ele a retornará ao chamador em vez de abrir uma nova conexão. Quando o aplicativo chama Close na conexão, o pooler o retorna para o conjunto agrupado de conexões ativas em vez de fechá-lo. Depois que a conexão é retornada ao pool, ela está pronta para ser reutilizada na próxima chamada aberta.

Obviamente, não há razão para evitar a criação, abertura ou fechamento de conexões, pois, na verdade, elas não são criadas, abertas e fechadas. Este é "apenas" um sinalizador para o pool de conexão saber quando uma conexão pode ser reutilizada ou não. Mas é um sinalizador muito importante, porque se uma conexão está "em uso" (o pool de conexão assume), uma nova conexão física deve ser aberta para o DBMS, o que é muito caro.

Portanto, você não está obtendo melhoria de desempenho, mas o oposto. Se o tamanho máximo do pool especificado (100 é o padrão) for atingido, você poderá até obter exceções (muitas conexões abertas ...). Portanto, isso não só afetará tremendamente o desempenho, mas também será uma fonte de erros desagradáveis ​​e (sem usar transações) uma área de despejo de dados.

Se você estiver usando conexões estáticas, estará criando um bloqueio para cada thread que tentar acessar este objeto. ASP.NET é um ambiente multithreading por natureza. Portanto, há uma grande chance para esses bloqueios, o que causa, na melhor das hipóteses, problemas de desempenho. Na verdade, mais cedo ou mais tarde você terá muitas exceções diferentes (como o ExecuteReader requer uma conexão aberta e disponível ).

Conclusão :

  • Não reutilize conexões ou quaisquer objetos ADO.NET.
  • Não os torne estáticos / compartilhados (em VB.NET)
  • Sempre crie, abra (no caso de conexões), use, feche e descarte-os onde você precisar deles (por exemplo, em um método)
  • use o using-statementpara descartar e fechar (no caso de conexões) implicitamente

Isso é verdade não apenas para Conexões (embora seja mais perceptível). Cada objeto implementado IDisposabledeve ser descartado (mais simples por using-statement), ainda mais no System.Data.SqlClientnamespace.

Tudo o que foi dito acima fala contra uma classe de banco de dados personalizada que encapsula e reutiliza todos os objetos. Essa é a razão pela qual eu comentei para o lixo. Essa é apenas uma fonte de problema.


Edit : Aqui está uma possível implementação de seu retrievePromotion-método:

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;
}
Tim Schmelter
fonte
isso é realmente útil para dar paradigma de trabalho de conexão. Obrigado por esta explicação.
aminvincent
bem escrito, uma explicação para algo que muitas pessoas descobrem acidentalmente, e gostaria que mais pessoas soubessem disso. (+1)
Andrew Hill
1
Obrigado senhor, acho que esta é a melhor explicação sobre o assunto que já li, um assunto que é muito importante e que muitos novatos se enganam. Devo cumprimentá-lo por sua excelente habilidade de escrita.
Sasinosoft
@Tim Schmelter, como posso fazer com que minhas consultas em diferentes threads utilizem uma única transação para confirmação / reversão usando sua abordagem sugerida?
geeko
1

Eu peguei esse erro alguns dias atrás.

No meu caso, foi porque eu estava usando uma Transação em um Singleton.

.Net não funciona bem com o Singleton conforme declarado acima.

Minha solução foi esta:

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();
    }
}

Usei HttpContext.Current.Items para minha instância. Esta classe DbHelper e DbHelperCore é minha própria classe

Damon Abdiel
fonte