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

Al intentar conectarme a la base de datos MSSQL a través de ASP.NET en línea, recibiré 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 lo olvide, mi cadena de conexión y conexión están ambas en estática. Yo creo que esta es la razón. Por favor avise.

 public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString; public static SqlConnection conn = null; 

Perdón 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 ADO.NET en una clase DB (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 caves furtivamente en el territorio de Connection-Pool

Hay una buena razón por la cual 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 usan 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 apertura de 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 Abrir en una conexión, el grupo busca una conexión disponible en el grupo. Si hay una conexión agrupada disponible, 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, la agrupación la devuelve al conjunto agrupado de conexiones activas en lugar de cerrarla. 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 puede reutilizarse o no una conexión. Pero es un indicador muy importante, porque si una conexión está “en uso” (el conjunto de conexiones asume), se debe abrir una nueva conexión física al DBMS, que es muy costosa.

Por lo tanto, no está obteniendo 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 se obtendrían excepciones (demasiadas conexiones abiertas …). Por lo tanto, esto no solo tendrá un impacto tremendo en el rendimiento sino que también será una fuente de errores desagradables y (sin utilizar Transacciones) un área de dumping de datos.

Si incluso está utilizando conexiones estáticas, está creando un locking para cada hilo que intente acceder a este objeto. ASP.NET es un entorno multihilo por naturaleza. Entonces hay una gran oportunidad para estos lockings que ocasiona problemas de rendimiento en el mejor de los casos. 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 las conexiones ni ningún objeto ADO.NET en absoluto.
  • No los haga estáticos / compartidos (en VB.NET)
  • Siempre cree, abra (en el caso de las conexiones), use, cierre y deseche donde lo necesite (fe en un método)
  • utilice la using-statement para eliminar y cerrar (en el caso de las conexiones) implícitamente

Eso es cierto no solo para Connections (aunque es más notorio). Todo objeto que implemente IDisposable debe eliminarse (lo más simple mediante using-statement ), sobre todo en el espacio de nombres System.Data.SqlClient .

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 comencé a criticarlo. Esa es solo una fuente de problemas.


Editar : Aquí hay una posible implementación de su método 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 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("PromotionTitle"), promotionUrl = promoRow.Field("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; }