Cuando se crea una conexión SQL en ASP.NET, por defecto siempre irá al POOL de conexiones administrado por el framework. Es así como ni al cerrarla ni haciéndole dispose, esa conexión realmente se cierra en el SQL Server como tal. El pool de conexiones existe para mejorar en general el rendimiento de las aplicaciones, dado que el hecho de abrir una conexión es bastante costoso. Así que aunque lógicamente la conexión está ConnectionString es requerida, se sirve una de las ya existentes en el pool de una manera más rápida que creandola desde 0 (from the scratch).

Si no se desea este comportamiento, basta con incluir en la conexión o en el WebConfig POOLING=FALSE

El pool para un ConnectionString en particular se puede configurar con el número máximo y mínimo de conexiones. Jugar con estos valores puede mejorar o empeorar el rendimiento de la aplicación. El valor por defecto es de 100.

Existe un error bastante grotesco que puede asustar mucho si no se entiende de connection pools:

A transport-level error has occurred when sending the request to the server. (provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.)

Ocurre por ejemplo si en el pool no hay conexiones disponibles, debido a que han sido terminadas por un ente distinto a la aplicación (alquien desconectó el cable de red, apagaron el server, le hicieron kill a las conexiones, etc.)

No ocurre porque la operación esté tardando demasiado. Y el que ocurran excepciones por éste último motivo no se arregla aumentando el timeout de la conexión. El timeout de la conexión se refiere al tiempo de espera para logran conexión con el server de db, antes de que una excepción sea lanzada.

El tiempo de timeout por defecto es de 15 segundos. Y en el connectionstring está medido siempre en segundos. Siempre ha tener valores positivos o 0. De lo contrario una excepción surge. Si es 0, significa que nunca se lanzará excepción por incapacidad de alcanzar al server. Esto no es buena idea, porque el sistema se puede volver irresponsivo, y no habría indicios de por qué sucede esto.

Para que los pool funcionen correctamente, debe existir un manejo responsable de las conexiones. Cómo se manejan responsablemente? Cerrándolas cada vez que no se vayan a requerir más.

SqlConnection conn = new SqlConnection(myConnectionString);
try
{
  conn.Open();
  doSomething(conn);
}
finally
{
  conn.Close();
}


Como se vé si hay un fallo, igual se cierra la conexión.

Teniendo en cuenta: http://www.15seconds.com/issue/040830.htm

"Close and Dispose methods of Connection object are equivalent. Neither one gives you any specific advantages over the other. "

Por tanto se puede usar también de una forma más elegante y ostentosa:
 

using (SqlConnection conn = new SqlConnection(myConnectionString))
{
  conn.Open();
  doSomething(conn);
}


El using en este contexto libera todos recursos declarados, por medio de Dispose.