Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

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


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 DB (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 invada o território do Connection-Pool


Há uma boa razão pela qual o ADO.NET gerencia internamente as conexões subjacentes ao DBMS no ADO-NET Connection-Pool:

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 pool de conexões.

O pool de conexões 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 as conexões mantendo ativo um conjunto de conexões ativas para cada configuração de conexão. 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 em pool 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 ao conjunto agrupado de conexões ativas em vez de fechá-lo. Uma vez que a conexão é devolvida ao pool, ela está pronta para ser reutilizada na próxima chamada aberta.

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

Então você não está ganhando nenhuma melhoria de desempenho, mas o oposto. Se o tamanho máximo do pool especificado (100 é o padrão) for atingido, você ainda obteria exceções (muitas conexões abertas ...). Portanto, isso não apenas 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 tenta acessar esse objeto. ASP.NET é um ambiente multithread por natureza. Portanto, há uma grande chance de esses bloqueios causarem problemas de desempenho na melhor das hipóteses. Na verdade, mais cedo ou mais tarde você terá muitas exceções diferentes (como seu 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 a using-statement para descartar e fechar (no caso de conexões) implicitamente

Isso é verdade não apenas para conexões (embora mais perceptível). Cada objeto que implementa IDisposable deve ser descartado (mais simples por using-statement ), ainda mais no System.Data.SqlClient namespace.

Todos os itens acima falam 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. Isso é apenas uma fonte de problema.

Editar :aqui está uma possível implementação de sua 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 [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;
}