ExecuteReader requiere una conexión abierta y disponible. El estado actual de la conexión es Conectando.

Resuelto Guo Hong Lim asked hace 12 años • 2 respuestas

Al intentar conectarme a una base de datos MSSQL a través de ASP.NET en línea, obtendré lo siguiente cuando dos o más personas se conecten simultáneamente:

ExecuteReader requiere una conexión abierta y disponible. El estado actual de la conexión es Conectando.

El sitio funciona bien en mi servidor localhost.

Este es el 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;
}

¿Puedo saber qué pudo haber salido mal y cómo lo soluciono?

Editar: No olvidar que mi cadena de conexión y mi conexión están estáticas. Creo que esta es la razón. Por favor avise.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;
Guo Hong Lim avatar Mar 14 '12 23:03 Guo Hong Lim
Aceptado

Perdón por comentar solo en primer lugar, pero publico casi todos los días un comentario similar ya que muchas personas piensan que sería inteligente encapsular la funcionalidad ADO.NET en una clase DB (yo también hace 10 años). En su mayoría, deciden utilizar objetos estáticos/compartidos, ya que parece ser más rápido que crear un nuevo objeto para cualquier acción.

Esto no es una buena idea en términos de rendimiento ni de seguridad.

No caces furtivamente en el territorio del Connection-Pool

Hay una buena razón por la cual ADO.NET administra internamente las conexiones subyacentes al DBMS en el grupo de conexiones ADO-NET :

En la práctica, la mayoría de las aplicaciones utilizan sólo una o varias configuraciones diferentes para las conexiones. Esto significa que durante la ejecución de la aplicación, muchas conexiones idénticas se abrirán y cerrarán repetidamente. Para minimizar el costo de abrir conexiones, ADO.NET utiliza una técnica de optimización llamada agrupación de conexiones.

La agrupación de conexiones reduce la cantidad de veces que se deben abrir nuevas conexiones. El pooler mantiene la propiedad de la conexión física. Gestiona las conexiones manteniendo vivo un conjunto de conexiones activas para cada configuración de conexión determinada. Cada vez que un usuario llama a Open en una conexión, el pooler busca una conexión disponible en el pool. Si hay una conexión agrupada disponible, se la devuelve a la persona que llama en lugar de abrir una nueva conexión. Cuando la aplicación llama a Cerrar en la conexión, el agrupador la devuelve al conjunto agrupado de conexiones activas en lugar de cerrarla. Una vez que la conexión regresa al grupo, está lista para ser reutilizada en la próxima convocatoria abierta.

Entonces, obviamente, no hay razón para evitar crear, abrir o cerrar conexiones ya que en realidad no se crean, abren ni cierran en absoluto. Esto es "sólo" un indicador para que el grupo de conexiones sepa cuándo se puede reutilizar una conexión o no. Pero es una señal muy importante, porque si una conexión está "en uso" (se supone que el grupo de conexiones), se debe abrir una nueva conexión física al DBMS, lo que es muy costoso.

Por lo tanto, no obtendrá ninguna mejora en el rendimiento, sino todo lo contrario. Si se alcanza el tamaño máximo de grupo especificado (100 es el valor predeterminado), incluso obtendrá excepciones (demasiadas conexiones abiertas...). Por lo tanto, esto no sólo afectará enormemente el rendimiento, sino que también será una fuente de errores desagradables y (sin utilizar Transacciones) un área de volcado de datos.

Si incluso estás usando conexiones estáticas, estás creando un bloqueo para cada hilo que intenta acceder a este objeto. ASP.NET es un entorno multiproceso por naturaleza. Por lo tanto, existe una gran posibilidad de que se produzcan estos bloqueos, lo que, en el mejor de los casos, causa problemas de rendimiento. En realidad, tarde o temprano obtendrás muchas excepciones diferentes (como que tu ExecuteReader requiere una conexión abierta y disponible ).

Conclusión :

  • No reutilice conexiones ni ningún objeto ADO.NET en absoluto.
  • No los haga estáticos/compartidos (en VB.NET)
  • Siempre créelos, ábralos (en el caso de Conexiones), úselos, ciérrelos y deséchelos donde los necesite (por ejemplo, en un método)
  • use using-statementpara eliminar y cerrar (en el caso de Conexiones) implícitamente

Esto es cierto no sólo para Connections (aunque es más notable). Cada objeto implementado IDisposabledebe eliminarse (más simple por using-statement), especialmente en el System.Data.SqlClientespacio de nombres.

Todo lo anterior habla en contra de una clase DB personalizada que encapsula y reutiliza todos los objetos. Esa es la razón por la que comenté a tirarlo a la basura. Esa es sólo una fuente de problema.


Editar : Aquí hay una posible implementación de su retrievePromotionmé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 implicitly in an using-statement
                throw;
            }
        }
    }
    return promo;
}
Tim Schmelter avatar Mar 14 '2012 17:03 Tim Schmelter

Detecté este error hace unos días.

EN mi caso fue porque estaba usando una Transacción en un Singleton.

.Net no funciona bien con Singleton como se indicó anteriormente.

Mi solución fue 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();
    }
}

Usé HttpContext.Current.Items para mi instancia. Esta clase DbHelper y DbHelperCore es mi propia clase.

Damon Abdiel avatar Sep 29 '2019 00:09 Damon Abdiel