c# - tipos - try catch definicion
¿Cómo puedo detectar una condición que causa una excepción antes de que suceda? (8)
No tuve suerte con esta pregunta, así que he producido este caso de prueba tan simple como sea posible para demostrar el problema.
En el siguiente código, ¿es posible detectar que la conexión no se puede usar antes de intentar usarla?
SqlConnection c = new SqlConnection(myConnString);
c.Open(); // creates pool
setAppRole(c); // OK
c.Close(); // returns connection to pool
c = new SqlConnection(myConnString); // gets connection from pool
c.Open(); // ok... but wait for it...
// ??? How to detect KABOOM before it happens?
setAppRole(c); // KABOOM
El KABOOM se manifiesta como un error en el registro de eventos de Windows;
La conexión se ha eliminado porque el principal que la abrió posteriormente asumió un nuevo contexto de seguridad y luego trató de restablecer la conexión en su contexto de seguridad suplantado. Este escenario no es compatible. Consulte "Descripción general de la personificación" en Libros en línea.
... más una excepción en el código.
setAppRole es un método simple para establecer un rol de aplicación en la conexión. Es similar a esto ...
static void setAppRole(SqlConnection conn) {
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "exec sp_setapprole ";
cmd.CommandText += string.Format("@rolename=''{0}''",myUser);
cmd.CommandText += string.Format(",@password=''{0}''",myPassword);
cmd.ExecuteNonQuery();
}
}
En el código real, se intenta utilizar sp_unsetapprole antes de cerrar la conexión, pero no siempre se puede garantizar (aplicación heredada de subprocesos con errores). En cualquier caso, parece razonable esperar poder detectar el kaboom antes de causarlo.
¿No hay una forma de borrar un grupo de todas las conexiones? SqlPools.Clear o algo así.
Simplemente podría tratar de atrapar la excepción y crear una nueva conexión, que debería obligar al grupo a crear una nueva conexión completa.
@edg: Usted dice en un comentario, "... es solo cuando golpea el servidor real y se encuentra con un problema de seguridad como se describe en la cita del mensaje".
Esto apunta al origen de su problema: se encuentra con un problema de seguridad, y esto parece inevitable porque el código de llamada asume otra identidad que la utilizada para abrir la conexión. Esto, naturalmente, hace una entrada de registro de seguridad.
Dado que el cambio de identidad es por diseño, tal vez la solución es filtrar el registro seguro. El visor de eventos tiene una acción de filtro de registro actual que puede filtrar por palabra clave o evento.
+ tom
Intente mover sp_unsetapprole
(¿es realmente el nombre del sproc? Probablemente, sp_dropapprole
es el correcto?) Para setAppRole()
y ejecútelo antes de agregar un rol de aplicación.
No estoy seguro de su problema, pero creo que lo evitaría si crea nuevos objetos de conexión en lugar de reutilizarlos. Entonces, en lugar de hacer
c.Open();
blabla;
c.Close();
c.Open();
kaboom...
Harías lo siguiente:
using (new SqlConnection ...)
{
c.Open();
blabla;
}
using (new SqlConnection ... )
{
c.Open();
no kaboom?
}
(Por favor perdone el pseudocódigo ... El teclado en mi eeepc es imposible de usar ...)
Puede verificar c.State (objeto ConnectionState), que debería ser uno de los siguientes:
System.Data.ConnectionState.Broken
System.Data.ConnectionState.Closed
System.Data.ConnectionState.Connecting
System.Data.ConnectionState.Executing
System.Data.ConnectionState.Fetching
System.Data.ConnectionState.Open
En resumen, no parece que puedas de ninguna manera simple.
Mi primer pensamiento fue ejecutar este SQL:
SELECT CASE WHEN USER = ''MyAppRole'' THEN 1 ELSE 0 END
Esto funciona si usa SQL Server Management Studio, pero falla cuando lo ejecuta desde el código C #. El problema es que el error que está recibiendo no ocurre cuando se realiza la llamada a sp_setapprole, en realidad ocurre cuando la agrupación de conexiones llama a sp_reset_connection. La agrupación de conexiones llama a esto cuando usa por primera vez una conexión y no hay forma de entrar antes.
Entonces supongo que tienes cuatro opciones:
- Desactive la agrupación de conexiones agregando "Pooling = false;" a su cadena de conexión.
- Utilice otra forma de conectarse a SQL Server. Hay API de nivel inferior a ADO.Net, pero, francamente, probablemente no valga la pena.
- Como CasperOne dice que podrías arreglar tu código para cerrar la conexión correctamente.
- Captura la excepción y reinicia el grupo de conexiones. Aunque no estoy seguro de lo que esto hará a otras conexiones abiertas. Ejemplo de código a continuación:
class Program
{
static void Main(string[] args)
{
SqlConnection conn = new SqlConnection("Server=(local);Database=Test;UID=Scrap;PWD=password;");
setAppRole(conn);
conn.Close();
setAppRole(conn);
conn.Close();
}
static void setAppRole(SqlConnection conn)
{
for (int i = 0; i < 2; i++)
{
conn.Open();
try
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "exec sp_setapprole ";
cmd.CommandText += string.Format("@rolename=''{0}''", "MyAppRole");
cmd.CommandText += string.Format(",@password=''{0}''", "password1");
cmd.ExecuteNonQuery();
}
}
catch (SqlException ex)
{
if (i == 0 && ex.Number == 0)
{
conn.Close();
SqlConnection.ClearPool(conn);
continue;
}
else
{
throw;
}
}
return;
}
}
}
en realidad ocurre cuando la agrupación de conexiones llama a sp_reset_connection. La agrupación de conexiones llama a esto cuando usa por primera vez una conexión y no hay forma de entrar antes.
Según la respuesta de Martin Brown , podría intentar agregar "Reinicio de conexión = Falso" a la cadena de conexión como una forma de "entrar antes" de sp_reset_connection. (Consulte " Trabajar con" conexiones sucias " para obtener una explicación de muchos de los inconvenientes de esta configuración).
Su problema es un problema conocido con la agrupación de conexiones . La alternativa sugerida es deshabilitar la agrupación de conexiones ... si se trata de una aplicación de escritorio, puede valer la pena considerar mantener abiertas algunas conexiones (también se explica en el artículo vinculado anteriormente ).
Hoy en día (SQL 2005+) la recomendación (en Roles de aplicación y agrupación de conexiones ) es "aprovechar los nuevos mecanismos de seguridad que puede usar en lugar de roles de aplicación", como EJECUTAR AS.
También publiqué esto en respuesta a su pregunta anterior. Cuando llame a sp_setapprole, debe llamar a sp_unsetapprole cuando haya terminado, y la solución que propuse allí lo ayudará a:
Detectando SqlConnections agrupadas inutilizables
Parece que estás llamando a sp_setapprole pero no llamando a sp_unsetapprole y luego dejando que la conexión solo se devuelva al grupo.
Sugeriría usar una estructura (o una clase, si tiene que usar esto en todos los métodos) con una implementación de IDisposable que se encargará de esto:
public struct ConnectionManager : IDisposable
{
// The backing for the connection.
private SqlConnection connection;
// The connection.
public SqlConnection Connection { get { return connection; } }
public void Dispose()
{
// If there is no connection, get out.
if (connection == null)
{
// Get out.
return;
}
// Make sure connection is cleaned up.
using (SqlConnection c = connection)
{
// See (1). Create the command for sp_unsetapprole
// and then execute.
using (SqlCommand command = ...)
{
// Execute the command.
command.ExecuteNonQuery();
}
}
}
public ConnectionManager Release()
{
// Create a copy to return.
ConnectionManager retVal = this;
// Set the connection to null.
retVal.connection = null;
// Return the copy.
return retVal;
}
public static ConnectionManager Create()
{
// Create the return value, use a using statement.
using (ConnectionManager cm = new ConnectionManager())
{
// Create the connection and assign here.
// See (2).
cm.connection = ...
// Create the command to call sp_setapprole here.
using (SqlCommand command = ...)
{
// Execute the command.
command.ExecuteNonQuery();
// Return the connection, but call release
// so the connection is still live on return.
return cm.Release();
}
}
}
}
- Creará el SqlCommand que corresponde a llamar al procedimiento almacenado sp_setapprole. También puede generar la cookie y almacenarla en una variable miembro privada.
- Aquí es donde creas tu conexión.
El código del cliente se ve así:
using (ConnectionManager cm = ConnectionManager.Create())
{
// Get the SqlConnection for use.
// No need for a using statement, when Dispose is
// called on the connection manager, the connection will be
// closed.
SqlConnection connection = cm.Connection;
// Use connection appropriately.
}