¿Se considera un anti patrón para codificar SQL en una aplicación como esta:
public List<int> getPersonIDs()
{
List<int> listPersonIDs = new List<int>();
using (SqlConnection connection = new SqlConnection(
ConfigurationManager.ConnectionStrings["Connection"].ConnectionString))
using (SqlCommand command = new SqlCommand())
{
command.CommandText = "select id from Person";
command.Connection = connection;
connection.Open();
SqlDataReader datareader = command.ExecuteReader();
while (datareader.Read())
{
listPersonIDs.Add(Convert.ToInt32(datareader["ID"]));
}
}
return listPersonIDs;
}
Normalmente tendría una capa de repositorio, etc., pero la he excluido en el código anterior por simplicidad.
Recientemente recibí algunos comentarios de un colega que se quejó de que SQL estaba escrito en el código fuente. No tuve la oportunidad de preguntar por qué y ahora está fuera por dos semanas (tal vez más). Supongo que quiso decir:
- Use LINQ
o - Usar procedimientos almacenados para el SQL
¿Estoy en lo correcto? ¿Se considera un anti patrón para escribir SQL en el código fuente? Somos un pequeño equipo trabajando en este proyecto. Creo que el beneficio de los procedimientos almacenados es que los desarrolladores de SQL pueden participar en el proceso de desarrollo (escribir procedimientos almacenados, etc.).
Editar El siguiente enlace habla sobre instrucciones SQL codificadas: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . ¿Hay algún beneficio de preparar una declaración SQL?
Respuestas:
Excluyó la parte crucial para la simplicidad. El repositorio es la capa de abstracción para la persistencia. Separamos la persistencia en su propia capa para que podamos cambiar la tecnología de persistencia más fácilmente cuando sea necesario. Por lo tanto, tener SQL fuera de la capa de persistencia frustra completamente el esfuerzo de tener una capa de persistencia separada.
Como resultado: SQL está bien dentro de la capa de persistencia que es específica de una tecnología SQL (por ejemplo, SQL está bien en un
SQLCustomerRepository
pero no en unMongoCustomerRepository
). Fuera de la capa de persistencia, SQL rompe su abstracción y, por lo tanto, se considera una práctica muy mala (por mí).En cuanto a herramientas como LINQ o JPQL: esas pueden simplemente abstraer los sabores de SQL. Tener consultas LINQ-Code o JPQL fuera de un repositorio rompe la abstracción de persistencia tanto como lo haría SQL sin formato.
Otra gran ventaja de una capa de persistencia separada es que le permite probar su código de lógica de negocios sin tener que configurar un servidor de base de datos.
Obtiene pruebas rápidas de bajo perfil de memoria con resultados reproducibles en todas las plataformas que admite su idioma.
En una arquitectura de servicio MVC +, esta es una tarea simple de burlarse de la instancia del repositorio, crear algunos datos simulados en la memoria y definir que el repositorio debe devolver esos datos simulados cuando se llama a un determinado captador. Luego puede definir los datos de prueba por unidad de prueba y no preocuparse por limpiar la base de datos después.
Probar las escrituras en la base de datos es tan simple: verifique que se hayan llamado a los métodos de actualización relevantes en la capa de persistencia y afirme que las entidades estaban en el estado correcto cuando eso sucedió.
fuente
La mayoría de las aplicaciones comerciales estándar actuales usan diferentes capas con diferentes responsabilidades. Sin embargo, qué capas usa para su aplicación, y qué capa tiene qué responsabilidad depende de usted y su equipo. Antes de que pueda tomar una decisión sobre si es correcto o incorrecto colocar SQL directamente en la función que nos ha mostrado, necesita saber
qué capa de su aplicación tiene qué responsabilidad
de qué capa la función anterior es de
No hay una solución única para todo esto. En algunas aplicaciones, los diseñadores prefieren usar un marco ORM y dejar que el marco genere todo el SQL. En algunas aplicaciones, los diseñadores prefieren almacenar dicho SQL exclusivamente en procedimientos almacenados. Para algunas aplicaciones hay una capa de persistencia (o repositorio) escrita a mano donde vive el SQL, y para otras aplicaciones está bien definir excepciones bajo ciertas circunstancias de colocar estrictamente SQL en esa capa de persistencia.
Así que lo que hay que pensar: las capas que es lo que desea o necesita en su aplicación particular, y cómo se desea que las responsabilidades? Escribiste "Normalmente tendría una capa de repositorio" , pero ¿cuáles son las responsabilidades exactas que quieres tener dentro de esa capa y qué responsabilidades quieres poner en otro lugar? Responda eso primero, luego puede responder su pregunta usted mismo.
fuente
Marstato da una buena respuesta, pero me gustaría agregar más comentarios.
SQL en la fuente NO es un antipatrón, pero puede causar problemas. Recuerdo cuando solía tener que poner consultas SQL en las propiedades de los componentes que se soltaron en cada formulario. Eso hizo que las cosas fueran realmente feas muy rápido y tuviste que saltar a través de los aros para localizar consultas. Me convertí en un firme defensor de centralizar el acceso a la base de datos tanto como sea posible dentro de las limitaciones de los idiomas con los que estaba trabajando. Es posible que su colega tenga recuerdos de estos días oscuros.
Ahora, algunos de los comentarios hablan sobre el bloqueo de proveedores como si automáticamente fuera algo malo. No lo es Si estoy firmando un cheque de seis cifras cada año para usar Oracle, puede apostar que quiero que cualquier aplicación que acceda a esa base de datos use la sintaxis Oracle adicional de manera apropiadapero al máximo. No estaré contento si mi brillante base de datos está paralizada por codificadores que escriben mal ANSI vanilla cuando hay una "forma Oracle" de escribir el SQL que no paraliza la base de datos. Sí, cambiar las bases de datos será más difícil, pero solo lo he visto en un sitio de un gran cliente un par de veces en más de 20 años y uno de esos casos fue mudarse de DB2 -> Oracle porque el mainframe que alojaba DB2 era obsoleto y estaba fuera de servicio . Sí, eso es un bloqueo de proveedores, pero para los clientes corporativos es realmente deseable pagar un RDBMS capaz y costoso como Oracle o Microsoft SQL Server y luego utilizarlo en toda su extensión. Tiene un acuerdo de soporte como su manta de confort. Si estoy pagando por una base de datos con una implementación rica de procedimientos almacenados,
Esto lleva al siguiente punto, si está escribiendo una aplicación que accede a una base de datos SQL , tiene que aprender SQL , así como el otro idioma y, por aprender, me refiero también a la optimización de consultas; Me enojaré contigo si escribes un código de generación de SQL que vacía el caché de SQL con un aluvión de consultas casi idénticas cuando podrías haber usado una consulta inteligentemente parametrizada.
Sin excusas, sin esconderse detrás de una pared de Hibernate. Los ORM mal utilizados realmente pueden afectar el rendimiento de las aplicaciones. Recuerdo haber visto una pregunta sobre Stack Overflow hace unos años en la línea de:
¿Qué tal "ACTUALIZAR tabla SET field1 = donde field2 es True y Field3> 100". Crear y deshacerse de 250,000 objetos puede ser su problema ...
es decir, ignorar Hibernate cuando no es apropiado usarlo. Comprender la base de datos.
Entonces, en resumen, incrustar SQL en el código puede ser una mala práctica, pero hay cosas mucho peores que puedes hacer tratando de evitar incrustar SQL.
fuente
Sí, la codificación de cadenas SQL en el código de la aplicación generalmente es un antipatrón.
Intentemos dejar de lado la tolerancia que hemos desarrollado tras años de ver esto en el código de producción. Mezclar idiomas completamente diferentes con una sintaxis diferente en el mismo archivo generalmente no es una técnica de desarrollo deseable. Esto es diferente a los lenguajes de plantilla como Razor, que están diseñados para dar significado contextual a múltiples idiomas. Como Sava B. menciona en un comentario a continuación, SQL en su C # u otro lenguaje de aplicación (Python, C ++, etc.) es una cadena como cualquier otra y carece de sentido semántico. Lo mismo se aplica al mezclar más de un idioma en la mayoría de los casos, aunque obviamente hay situaciones en las que hacerlo es aceptable, como el ensamblaje en línea en C, fragmentos pequeños y comprensibles de CSS en HTML (teniendo en cuenta que CSS está diseñado para mezclarse con HTML ), y otros.
(Robert C. Martin sobre la mezcla de idiomas, Clean Code , Capítulo 17, "Código de olores y heurística", página 288)
Para esta respuesta, enfocaré SQL (como se preguntó en la pregunta). Los siguientes problemas pueden ocurrir al almacenar SQL como un conjunto a la carta de cadenas disociadas:
@
prefijo de C # hace que esto sea más fácil, pero he visto mucho código que cita cada línea SQL y escapa a las nuevas líneas."SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
Los ORM completos (mapeadores relacionales de objetos como Entity Framework o Hibernate) pueden eliminar SQL salpicado al azar en el código de la aplicación. Mi uso de SQL y archivos de recursos es solo un ejemplo. Los ORM, las clases auxiliares, etc. pueden ayudar a lograr el objetivo de un código más limpio.
Como dijo Kevin en una respuesta anterior, SQL en el código puede ser aceptable en proyectos pequeños, pero los proyectos grandes comienzan como proyectos pequeños, y la probabilidad de que la mayoría de los equipos regresen y lo hagan bien es a menudo inversamente proporcional al tamaño del código.
Hay muchas formas simples de mantener SQL en un proyecto. Uno de los métodos que uso a menudo es colocar cada instrucción SQL en un archivo de recursos de Visual Studio, generalmente llamado "sql". Un archivo de texto, documento JSON u otra fuente de datos puede ser razonable dependiendo de sus herramientas. En algunos casos, una clase separada dedicada a instalar cadenas SQL puede ser la mejor opción, pero podría tener algunos de los problemas descritos anteriormente.
Ejemplo de SQL: ¿Cuál se ve más elegante ?:
Se convierte
El SQL siempre está en un archivo fácil de ubicar o en un conjunto de archivos agrupados, cada uno con un nombre descriptivo que describe lo que hace en lugar de cómo lo hace, cada uno con espacio para un comentario que no interrumpirá el flujo del código de la aplicación :
Este método simple ejecuta una consulta solitaria. En mi experiencia, el beneficio aumenta a medida que el uso del "idioma extranjero" se vuelve más sofisticado. Mi uso de un archivo de recursos es solo un ejemplo. Los diferentes métodos pueden ser más apropiados según el lenguaje (SQL en este caso) y la plataforma.
Este y otros métodos resuelven la lista anterior de la siguiente manera:
SQL
.return SqlResource.DoTheThing;
Cierto, estas implementaciones pueden omitir el recurso y contener el SQL en una cadena, pero algunos (no todos) problemas anteriores aparecerían.sql.GetOrdersForAccount
lugar de más obtusosSELECT ... FROM ... WHERE...
La creación de SQL en un recurso es rápida, por lo que hay poco ímpetu para mezclar el uso de recursos con SQL en código.
Independientemente del método elegido, descubrí que mezclar idiomas generalmente reduce la calidad del código. Espero que algunos problemas y soluciones descritos aquí ayuden a los desarrolladores a eliminar este olor a código cuando sea apropiado.
fuente
Depende. Hay varios enfoques diferentes que pueden funcionar:
Como suele ser el caso, si su proyecto ya ha elegido una de estas técnicas, debe ser coherente con el resto del proyecto.
(1) y (3) son relativamente buenos para mantener la independencia entre la lógica de la aplicación y la base de datos, en el sentido de que la aplicación continuará compilando y pasando pruebas de humo básicas si reemplaza la base de datos con un proveedor diferente. Sin embargo, la mayoría de los proveedores no se ajustan completamente al estándar SQL, por lo que reemplazar cualquier proveedor con cualquier otro proveedor probablemente requiera pruebas exhaustivas y búsqueda de errores, independientemente de la técnica que utilice. Soy escéptico de que esto sea tan importante como la gente cree que es. Cambiar las bases de datos es básicamente un último recurso cuando no puede obtener la base de datos actual para satisfacer sus necesidades. Si eso sucede, probablemente eligió mal la base de datos.
La elección entre (1) y (3) depende principalmente de cuánto le gustan los ORM. En mi opinión están sobreutilizados. Son una representación pobre del modelo de datos relacionales, porque las filas no tienen identidad en la forma en que los objetos tienen identidad. Es probable que encuentre puntos débiles en torno a restricciones únicas, uniones, y puede tener dificultades para expresar algunas consultas más complicadas dependiendo del poder del ORM. Por otro lado, (1) probablemente requerirá significativamente más código que un ORM.
(2) rara vez se ve, en mi experiencia. El problema es que muchas tiendas prohíben que los SWE modifiquen directamente el esquema de la base de datos (porque "ese es el trabajo del DBA"). Esto no es necesariamente una mala cosa en sí misma; Los cambios de esquema tienen un potencial significativo para romper cosas y es posible que sea necesario implementarlos con cuidado. Sin embargo, para que (2) funcione, las SWE deberían al menos poder introducir nuevas vistas y modificar las consultas de respaldo de las vistas existentes con una burocracia mínima o nula. Si este no es el caso en su lugar de trabajo, (2) probablemente no funcionará para usted.
Por otro lado, si puede hacer que (2) funcione, es mucho mejor que la mayoría de las otras soluciones porque mantiene la lógica relacional en SQL en lugar del código de la aplicación. A diferencia de los lenguajes de programación de propósito general, SQL está específicamente diseñado para el modelo de datos relacionales y, en consecuencia, es mejor para expresar consultas y transformaciones de datos complicados. Las vistas también se pueden portar junto con el resto de su esquema al cambiar las bases de datos, pero harán que tales movimientos sean más complicados.
Para las lecturas, los procedimientos almacenados son básicamente una versión más desagradable de (2). No los recomiendo en esa capacidad, pero es posible que aún los desee para escrituras, si su base de datos no admite vistas actualizables , o si necesita hacer algo más complejo que insertar o actualizar una sola fila a la vez (p. Ej. transacciones, leer y escribir, etc.). Puede acoplar su procedimiento almacenado a una vista usando un disparador (es decir
CREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;
), pero las opiniones varían considerablemente en cuanto a si esta es realmente una buena idea. Los proponentes le dirán que mantiene el SQL que su aplicación ejecuta de la manera más simple posible. Los detractores le dirán que este es un nivel inaceptable de "magia" y que simplemente debe ejecutar el procedimiento directamente desde su aplicación. Yo diría que esto es una mejor idea de si el procedimiento almacenado se ve o actúa muy parecido a unaINSERT
,UPDATE
oDELETE
, y una idea peor si se está haciendo algo más. En última instancia, tendrá que decidir por sí mismo qué estilo tiene más sentido.(4) es la no solución. Puede valer la pena para proyectos pequeños o grandes que solo interactúan esporádicamente con la base de datos. Pero para proyectos con una gran cantidad de SQL, no es una buena idea, ya que puede tener duplicados o variaciones de la misma consulta dispersos al azar en su aplicación, lo que interfiere con la legibilidad y la refactorización.
fuente
No necesariamente. Si lee todos los comentarios aquí, encontrará argumentos válidos para codificar sentencias SQL en el código fuente.
El problema viene con dónde coloca las declaraciones . Si coloca las declaraciones SQL en todo el proyecto, en todas partes, probablemente ignorará algunos de los principios SÓLIDOS que generalmente nos esforzamos por seguir.
No podemos decir lo que quiso decir. Sin embargo, podemos adivinar. Por ejemplo, lo primero que me viene a la mente es el bloqueo de proveedores . Las instrucciones SQL de codificación rígida pueden llevarlo a acoplar estrechamente su aplicación al motor de base de datos. Por ejemplo, el uso de funciones específicas del proveedor que no cumplen con ANSI.
Esto no es necesariamente incorrecto ni malo. Solo estoy señalando el hecho.
Ignorar los principios SÓLIDOS y los bloqueos de proveedores tienen posibles consecuencias adversas que usted puede ignorar. Es por eso que generalmente es bueno sentarse con el equipo y exponer sus dudas.
Creo que no tiene nada que ver con las ventajas de los procedimientos almacenados. Además, si a su colega no le gusta SQL codificado, es probable que mover el negocio a los procedimientos almacenados no le guste también.
Si. La publicación enumera las ventajas de las declaraciones preparadas . Es una especie de plantilla SQL. Más seguro que la concatenación de cadenas. Pero la publicación no lo alienta a seguir este camino ni confirma que tiene razón. Simplemente explica cómo podemos usar SQL codificado de manera segura y eficiente.
En resumen, intente preguntarle a su colega primero. Envíe un correo, llámelo, ... Responda o no, siéntese con el equipo y exponga sus dudas. Encuentre la solución que mejor se adapte a sus necesidades. No hagas suposiciones falsas basadas en lo que lees por ahí.
fuente
select GETDATE(), ....
GETDATE () es la función de fecha de SqlServer. En otros motores, la función tiene un nombre diferente, una expresión diferente ...Creo que es una mala práctica, sí. Otros han señalado las ventajas de mantener todo su código de acceso a datos en su propia capa. No tiene que buscarlo, es más fácil optimizarlo y probarlo ... Pero incluso dentro de esa capa, tiene algunas opciones: usar un ORM, usar sprocs o incrustar consultas SQL como cadenas. Yo diría que las cadenas de consulta SQL son, con mucho, la peor opción.
Con un ORM, el desarrollo se vuelve mucho más fácil y menos propenso a errores. Con EF, usted define su esquema simplemente creando sus clases de modelo (de todos modos, habría necesitado crear estas clases). Consultar con LINQ es muy sencillo: a menudo te saldrás con 2 líneas de C # donde de lo contrario necesitarías escribir y mantener un sproc. En mi opinión, esto tiene una gran ventaja cuando se trata de productividad y mantenibilidad: menos código, menos problemas. Pero hay una sobrecarga de rendimiento, incluso si sabe lo que está haciendo.
Sprocs (o funciones) son la otra opción. Aquí, escribe sus consultas SQL manualmente. Pero al menos obtienes alguna garantía de que son correctos. Si está trabajando en .NET, Visual Studio incluso arrojará errores de compilación si el SQL no es válido. Esto es genial. Si cambia o elimina alguna columna, y algunas de sus consultas se vuelven inválidas, al menos es probable que lo descubra durante el tiempo de compilación. También es mucho más fácil mantener sprocs en sus propios archivos: probablemente obtendrá resaltado de sintaxis, autocompletar ... etc.
Si sus consultas se almacenan como sprocs, también puede cambiarlas sin volver a compilar y volver a implementar la aplicación. Si nota que algo en su SQL está roto, un DBA puede solucionarlo sin necesidad de tener acceso al código de su aplicación. En general, si sus consultas están en literales de cadena, dbas no puede hacer su trabajo tan fácilmente.
Las consultas SQL como literales de cadena también harán que su código de acceso a datos C # sea menos legible.
Como regla general, las constantes mágicas son malas. Eso incluye literales de cadena.
fuente
No es un antipatrón (esta respuesta es peligrosamente cercana a la opinión). Pero el formato del código es importante, y la cadena SQL debe formatearse para que esté claramente separada del código que la usa. Por ejemplo
fuente
No puedo decirte lo que querías decir colega. Pero puedo responder esto:
SÍ . El uso de declaraciones preparadas con variables enlazadas es la defensa recomendada contra la inyección de SQL , que sigue siendo el mayor riesgo de seguridad para las aplicaciones web durante más de una década . Este ataque es tan común que apareció en los cómics web hace casi diez años, y es hora de que se implementen defensas efectivas.
... y la defensa más efectiva contra una mala concatenación de cadenas de consulta no es representar consultas como cadenas en primer lugar, sino utilizar una API de consulta de tipo seguro para generar consultas. Por ejemplo, así es como escribiría su consulta en Java con QueryDSL:
Como puede ver, aquí no hay un solo literal de cadena, lo que hace que la inyección de SQL sea casi imposible. Además, tuve que completar el código al escribir esto, y mi IDE puede refactorizarlo si elijo cambiar el nombre de la columna de identificación. Además, puedo encontrar fácilmente todas las consultas para la tabla de personas preguntando a mi IDE dónde
person
se accede a la variable. Ah, y ¿notaste que es un poco más corto y más fácil para la vista que tu código?Solo puedo suponer que algo así está disponible para C # también. Por ejemplo, escucho grandes cosas sobre LINQ.
En resumen, representar las consultas como cadenas SQL dificulta que el IDE ayude de manera significativa a escribir y refactorizar consultas, difiere la detección de errores de sintaxis y tipo desde el tiempo de compilación hasta el tiempo de ejecución, y es una causa que contribuye a las vulnerabilidades de inyección SQL [1]. Entonces, sí, hay razones válidas por las que uno podría no querer cadenas SQL en el código fuente.
[1]: Sí, el uso correcto de las declaraciones preparadas también evita la inyección de SQL. Pero esa otra respuesta en este hilo era vulnerable a una inyección de SQL hasta que un comentador señaló que esto no inspira confianza en que los programadores junior los usen correctamente siempre que sea necesario ...
fuente
Muchas respuestas geniales aquí. Además de lo que ya se ha dicho, me gustaría agregar otra cosa.
No considero que usar SQL en línea sea un antipatrón. Sin embargo, lo que sí considero un antipatrón es que cada programador haga lo suyo. Tienes que decidir como equipo en un estándar común. Si todos usan linq, entonces usas linq, si todos usan vistas y procedimientos almacenados, entonces lo haces.
Sin embargo, siempre mantén una mente abierta y no caigas en los dogmas. Si su tienda es una tienda "linq", use eso. Excepto por aquel lugar donde linq no funciona en absoluto. (Pero no deberías 99.9% del tiempo).
Entonces, lo que haría es investigar el código en la base del código y verificar cómo trabajan sus colegas.
fuente
El código se ve desde la capa que interactúa con la base de datos que se encuentra entre la base de datos y el resto del código. Si realmente es así, esto no es antipatrón.
Este método ES la parte de la capa, incluso si OP piensa de manera diferente (acceso simple a la base de datos, sin mezclarlo con nada más). Quizás esté mal colocado dentro del código. Este método pertenece a la clase separada, "capa de interfaz de base de datos". Sería antipatrón ubicarlo dentro de la clase ordinaria junto a los métodos que implementan actividades que no son de base de datos.
El antipatrón sería llamar a SQL desde algún otro lugar de código aleatorio. Debe definir una capa entre la base de datos y el resto del código, pero no es que deba usar absolutamente alguna herramienta popular que pueda ayudarlo allí.
fuente
En mi opinión, no no es un antipatrón, pero te encontrarás lidiando con el espaciado de formato de cadena, especialmente para consultas grandes que no pueden caber en una línea.
Otra ventaja es que se vuelve mucho más difícil cuando se trata de consultas dinámicas que deben construirse de acuerdo con ciertas condiciones.
Sugiero usar un generador de consultas sql
fuente
Para muchas organizaciones modernas con canalizaciones de despliegue rápido donde los cambios de código pueden compilarse, probarse y llevarse a producción en minutos, tiene mucho sentido incorporar sentencias SQL en el código de la aplicación. Esto no es necesariamente un antipatrón dependiendo de cómo lo hagas.
Un caso válido para usar procedimientos almacenados hoy en día es en organizaciones donde hay muchos procesos alrededor de implementaciones de producción que pueden involucrar semanas de ciclos de control de calidad, etc. Las consultas en los procedimientos almacenados.
A menos que se encuentre en esta situación, le recomiendo que incruste su SQL en el código de su aplicación. Lea otras respuestas en este hilo para obtener consejos sobre la mejor manera de hacerlo.
Texto original a continuación para el contexto
La principal ventaja de los procedimientos almacenados es que puede optimizar el rendimiento de la base de datos sin volver a compilar y volver a implementar su aplicación.
En muchas organizaciones, el código solo se puede enviar a producción después de un ciclo de control de calidad, etc., que puede llevar días o semanas. Si su sistema desarrolla un problema de rendimiento de la base de datos debido a patrones de uso cambiantes o aumentos en el tamaño de las tablas, etc., entonces puede ser bastante difícil rastrear el problema con consultas codificadas, mientras que la base de datos tendrá herramientas / informes, etc., que identificarán los procedimientos almacenados del problema . Con los procedimientos almacenados, las soluciones se pueden probar / comparar en producción y el problema se puede solucionar rápidamente sin tener que empujar una nueva versión del software de la aplicación.
Si este problema no es importante en su sistema, entonces es una decisión perfectamente válida codificar las instrucciones SQL en la aplicación.
fuente