sentencias resueltos ejercicios ejemplos datos consultas complejas .net sql code-review three-tier

.net - resueltos - Mejorando la legibilidad del código para comandos SQL



select sql ejemplos (8)

En una de mis clases para una aplicación web que estoy desarrollando, tengo algunas consultas SQL razonablemente largas.

Al desarrollar una aplicación de tres niveles, ¿cuáles son las mejores prácticas para hacer que este tipo de código sea más ordenado?

Dim dc As New SqlCommand("INSERT INTO Choices VALUES (''" + _ SanitizeInput(strUser) + "'', ''" + _ SanitizeInput(strFirstHalfDay) + "'', ''" + _ SanitizeInput(strSecondHalfDay) + "'', ''" + _ SanitizeInput(strFullDay) + "'', " + _ SanitizeInput(Convert.ToInt32(firstHalfPaid).ToString()) + ", " + _ SanitizeInput(Convert.ToInt32(secondHalfPaid).ToString()) + ", " + _ SanitizeInput(Convert.ToInt32(fullPaid).ToString()) + ")", cn)

¿Consideras que este tipo de código es aceptable o apestoso?


Stinky: ese SanitizeInput es casi seguro vulnerable a la inyección de SQL.

Utilizaría un SP parametrizado (código generado a partir del esquema DB, posiblemente ajustado manualmente), con un contenedor .NET (también generado a partir del esquema DB).


Consideraría esto como más legible y seguro, es decir, menos propenso a la inyección de SQL:

Dim dc As New SqlCommand("INSERT INTO Choices VALUES (@UserName, @FirstHalfDay, @SecondHalfDay, @FullDay, @FirstHalfPaid, @SecondHalfPaid, @FullPaid)", cn) dc.Parameters.AddWithValue("@UserName", strUsername) dc.Parameters.AddWithValue("@FirstHalfDay", strFirstHalfDay) dc.Parameters.AddWithValue("@SecondHalfDay", strSecondHalfDay) dc.Parameters.AddWithValue("@FullDay", strFullDay) dc.Parameters.AddWithValue("@FirstHalfPaid", Convert.ToInt32(firstHalfPaid)) dc.Parameters.AddWithValue("@SecondHalfPaid", Convert.ToInt32(secondHalfPaid)) dc.Parameters.AddWithValue("@FullPaid", Convert.ToInt32(fullPaid))

Al hacer esto para sentencias seleccionadas obtendrá el beneficio adicional si está usando SQL Server, que podrá guardar en caché el plan de consulta, ya que será la misma cadena que se acaba de paramterizar. Si no está utilizando SQL Server, pueden ser necesarios pequeños cambios. También obtienes otro beneficio gratis que es un rendimiento ligeramente mejor a través de menos concatenación de cadenas.

Yo personalmente agregaría las columnas después del nombre de la tabla de opciones, que sería menos propenso a la rotura al agregar nuevas columnas en la tabla de la base de datos (asumiendo los valores predeterminados).


Este enfoque está bien para un proyecto pequeño, sin embargo, para cualquier tamaño significativo, debe considerar una biblioteca de terceros para construir sus consultas o desarrollar la suya propia.

El patrón general que utilizo va más o menos así: (Estoy usando la sintaxis de Java porque es a lo que estoy más acostumbrado).

Archivo de propiedades de plantilla de consulta

some.query=INSERT INTO Choices VALUES ( ''{0}'', ''{1}'', ''{2}'', ''{3}'' )

Código de marco

public class SqlUtil { static Properties queries; static { queries = <LOAD QUERY PROPS> } public static SqlCommand createCommand(String propName, Object params ... ) throws SqlException { String cmd = queries.getString(propName) if ( propName == null || "".equals(propName) ) throw new SqlException("Query not found"); int i=0; for ( Object param : params ) { cmd = cmd.replace("{" + i + "}", param) i++; } return new SqlCommand(cmd); } }

Código de uso:

SqlCommand dc = SqlUtil.createCommand("some.query", SanitizeInput(strUser), SanitizeInput(strFirstHalfDay) )


Haga que el comando SQL sea una cadena separada antes de llamar al constructor. Será más fácil de leer

En lugar de:

Dim dc As New SqlCommand("INSERT INTO SomeTable....", Arg( blah blah blah ) )

Hacer esto:

Dim sqlstr = "INSERT INTO SomeTable...." Dim dc As New SqlCommand(sqlstr, Arg(blah blah blah))

(Perdonar que no sepa la sintaxis correcta de VB)

La cuestión es que el vertido de la cadena literal en el constructor SqlCommand () es solo un montón de cosas en un gran comando que alguien tiene que romper en su cabeza más adelante.

Además, cuando desee imprimir el SQL que está ejecutando durante la depuración, será mucho más fácil incluir un simple

Print "Now executing: " + sqlstr

No te preocupes porque estás creando una variable "extra". La computadora no le importa.


La reescritura directa de eso en C # sería:

SqlCommand dc = new SqlCommand(String.Format(@" INSERT INTO Choices VALUES (''{0}'', ''{1}'', ''{2}'', {3}, {4}, {5}) ", SanitizeInput(strUser), SanitizeInput(strFirstHalfDay), SanitizeInput(strFullDay), SanitizeInput(Convert.ToInt32(firstHalfPaid).ToString()), SanitizeInput(Convert.ToInt32(secondHalfPaid).ToString()), SanitizeInput(Convert.ToInt32(fullPaid).ToString())), cn);

Uso string.format mucho en lugar de concatenación de cadenas. Mejora la legibilidad también. No estoy familiarizado con VB, lo siento.

Estoy de acuerdo en que usar parámetros es una mejor solución.


Podría utilizar una abstracción de datos de terceros como Hibernate (en Java: no sé qué hay disponible para .NET).

O podría escribir sus propias clases de ayuda que permiten la sintaxis como:

Dim insHelper As New InsertionHelper(conn, "Choices"); insHelper.appendValue(strUser); insHelper.appendValue(strFirstHalfDay); insHelper.appendValue(strSecondHalfDay); insHelper.appendValue(strFullDay); insHelper.appendValue(firstHalfPaid); // overloaded for different data types insHelper.appendValue(secondHalfPaid); insHelper.appendValue(fullPaid); insHelper.execute();

Incluso podría agregar el método de encadenamiento a la mezcla:

Dim insHelperAs New InsertionHelper(conn, "Choices"); insHelper .appendValue(strUser) .appendValue(strFirstHalfDay) .appendValue(strSecondHalfDay) .appendValue(strFullDay) .appendValue(firstHalfPaid) .appendValue(secondHalfPaid) .appendValue(fullPaid) .execute();


Si bien creo que su código es maravilloso, tengo una lista no exhaustiva de posibles mejoras:

  • Está escribiendo SQL en lugar de utilizar una tecnología de mapeo (NHibernate, Linq a SQL, EF, SubSonic, LLBLGenPro, etc.)
  • Dado que estás escribiendo SQL, no estás usando parámetros. Al menos estás desinfectando correctamente la entrada como lo haces concatenación de cadenas ... con suerte ...
  • Concedido que está escribiendo SQL sin parámetros, no está usando String.Format para crear cadenas de caracteres y formatear cadenas.
  • Tus nombres de variables son desordenados. Usted está nombrando sus variables con una convención húngara de notación húngara. La notación húngara fue originalmente pensada para ayudar a que sus nombres de variables transmitan cómo se usan estas variables, no para transmitir sus tipos.
  • Está codificando la implementación, no la interfaz. Su variable de conexión debe escribirse IDbConnection y su variable de comando debe escribirse IDbCommand. No SqlConnection y SqlCommand. Use IDbConnection.CreateCommand ().
  • SanitizeInput debe renombrarse como EscapeStringForSQL o algo que indique su intención real. No necesitas escapar de los números.

DETÉNGASE , no haga eso, use staments preparados, obtendrá seguridad y legibilidad.

Use esto en su lugar:

Dim dc As New SqlCommand("INSERT INTO Choices VALUES (@User, @FirstHalfDay, @SecondHalfDay, @FullDay, @FirstHalfPaid, @SecondHalfPaid, @FullPaid''", cn) dc.Parameters.Add (new SqlParameter ("User", strUser)) dc.Parameters.Add (new SqlParameter ("FirstHalfDay", strFirstHalfDay)) dc.Parameters.Add (new SqlParameter ("SecondHalfDay", strSecondHalfDay)) dc.Parameters.Add (new SqlParameter ("FullDay", strFullDay)) dc.Parameters.Add (new SqlParameter ("FirstHalfPaid", firstHalfPaid)) dc.Parameters.Add (new SqlParameter ("SecondHalfPaid", secondHalfPaid)) dc.Parameters.Add (new SqlParameter ("FullPaid", fullPaid))