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

114

Al intentar conectarme a la 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: sin olvidar, mi cadena de conexión y mi conexión son 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
fuente
24
No utilice conexiones compartidas / estáticas en un entorno de subprocesos múltiples como ASP.NET, ya que está generando bloqueos o excepciones (demasiadas conexiones abiertas, etc.). Tire su DB-Class a la basura y cree, abra, use, cierre y deseche los objetos ado.net donde los necesite. Eche un vistazo también a la instrucción using.
Tim Schmelter
2
¿Puede darme detalles sobre SqlOpenConnection (); y sql.ExecuteReader (); funciones? ..
ankit rajput
private void SqlOpenConnection () {try {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {lanzar ex; }}
Guo Hong Lim
@GuoHongLim: me olvidé de mencionar que incluso una estática conStringno agrega nada en términos de rendimiento, ya que de todos modos se almacena en caché de forma predeterminada (como todos los valores de configuración para la aplicación actual).
Tim Schmelter
... y solo para convertirlo en un conocido-desconocido: Asegurarse de que también obtenga la unidad de trabajo / manejo de transacciones de la base de datos correcta se deja como un ejercicio para el lector.
mwardm

Respuestas:

226

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

Esa no es una buena idea en términos de rendimiento ni en términos de seguridad contra fallas.

No caza furtivamente en el territorio de Connection-Pool

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

En la práctica, la mayoría de las aplicaciones utilizan solo una o algunas 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 el número de veces que se deben abrir nuevas conexiones. El agrupador 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 dada. Siempre que un usuario llama a Open en una conexión, el agrupador busca una conexión disponible en el grupo. 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 cerrarlo. Una vez que la conexión se devuelve al grupo, está lista para ser reutilizada en la próxima llamada 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. Esta es "solo" una bandera para que el grupo de conexiones sepa cuándo una conexión se puede reutilizar o no. Pero es una bandera muy importante, porque si una conexión está "en uso" (asume el grupo de conexiones), se debe abrir una nueva conexión física al DBMS, lo que es muy costoso.

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

Si incluso está utilizando conexiones estáticas, está creando un bloqueo para cada hilo que intente acceder a este objeto. ASP.NET es un entorno de subprocesos múltiples por naturaleza. Así que hay una gran posibilidad de que estos bloqueos provoquen problemas de rendimiento en el mejor de los casos. En realidad, tarde o temprano obtendrá muchas excepciones diferentes (como que su 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 cree, abra (en caso de conexiones), úselos, ciérrelos y deséchelos donde los necesite (por ejemplo, en un método)
  • utilice using-statementpara eliminar y cerrar (en caso de conexiones) implícitamente

Eso es cierto no solo para Connections (aunque es más notable). Cada objeto que se implemente IDisposabledebe eliminarse (más simple por using-statement), más aún en el System.Data.SqlClientespacio de nombres.

Todo lo anterior habla en contra de una DB-Class personalizada que encapsula y reutiliza todos los objetos. Esa es la razón por la que comenté que lo tirara a la basura. Esa es solo una fuente de problemas.


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 implicitely in an using-statement
                throw;
            }
        }
    }
    return promo;
}
Tim Schmelter
fuente
esto es realmente útil para dar paradigma de trabajo de conexión. Gracias por esta explicacion.
aminvincent
bien escrito, una explicación de algo que muchas personas descubren accidentalmente, y desearía que más personas supieran esto. (+1)
Andrew Hill
1
Gracias señor, creo que esta es la mejor explicación sobre este tema que jamás he leído, un tema que es muy importante y que muchos novatos se equivocan. Debo felicitarlo por su excelente habilidad para escribir.
Sasinosoft
@Tim Schmelter, ¿cómo puedo hacer que mis consultas que se ejecutan en diferentes subprocesos utilicen una sola transacción para comprometerse / retroceder utilizando su enfoque sugerido?
geeko
1

Cogí 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
fuente