¿Se considera un anti patrón para escribir SQL en el código fuente?

87

¿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:

  1. Use LINQ
    o
  2. 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?

w0051977
fuente
31
"Usar Linq" y "Usar procedimientos almacenados" no son razones; son solo sugerencias. Espere dos semanas y pregúntele por razones.
Robert Harvey
47
La red de Stack Exchange usa un micro-ORM llamado Dapper. Creo que es razonable decir que la gran mayoría del código Dapper es "SQL codificado" (más o menos). Entonces, si es una mala práctica, entonces es una mala práctica adoptada por una de las aplicaciones web más destacadas del planeta.
Robert Harvey
16
Para responder a su pregunta sobre repositorios, el SQL codificado sigue siendo SQL codificado sin importar dónde lo coloque. La diferencia es que el repositorio le brinda un lugar para encapsular el SQL codificado. Es una capa de abstracción que oculta los detalles del SQL del resto del programa.
Robert Harvey
26
No, SQL dentro del código no es un antipatrón. Pero esa es una gran cantidad de código de placa de caldera para una simple consulta SQL.
GrandmasterB
45
Hay una diferencia entre 'en el código fuente' y 'extendido por todo el código fuente'
hasta

Respuestas:

112

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 SQLCustomerRepositorypero no en un MongoCustomerRepository). 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ó.

marstato
fuente
80
La idea de que "podemos cambiar la tecnología de persistencia" es poco realista e innecesaria en la gran mayoría de los proyectos del mundo real. Una base de datos SQL / relacional es una bestia completamente diferente a las bases de datos NoSQL como MongoDB. Vea este artículo detallado al respecto. A lo sumo, un proyecto que comenzó a usar NoSQL eventualmente se daría cuenta de su error y luego cambiaría a un RDBMS. En mi experiencia, permitir el uso directo de un lenguaje de consulta orientado a objetos (como JPA-QL) del código comercial proporciona los mejores resultados.
Rogério
66
¿Qué pasa si hay muy pocas posibilidades de que se cambie la capa de persistencia? ¿Qué pasa si no hay un mapeo uno a uno al cambiar la capa de persistencia? ¿Cuál es la ventaja del nivel extra de abstracción?
pllee
19
@ Rogério La migración de una tienda NoSQL a un RDBMS, o viceversa, es, como usted dice, probablemente no realista (suponiendo que la elección de la tecnología fuera apropiada para empezar). Sin embargo, he estado involucrado en varios proyectos del mundo real donde migramos de un RDBMS a otro; en ese escenario, una capa de persistencia encapsulada es definitivamente una ventaja.
David
66
"La idea de que" podemos cambiar la tecnología de persistencia "es poco realista e innecesaria en la gran mayoría de los proyectos del mundo real". Mi compañía previamente escribió código junto con esa suposición. Tuvimos que refactorizar toda la base de código para admitir no uno, sino tres sistemas de almacenamiento / consulta completamente diferentes y estamos considerando un tercero y un cuarto. Peor aún, incluso cuando teníamos una sola base de datos, la falta de consolidación llevó a todo tipo de pecados de código.
NPSF3000
3
@ Rogério ¿Conoces la "gran mayoría de los proyectos del mundo real"? En mi empresa, la mayoría de las aplicaciones pueden funcionar con uno de los muchos DBMS comunes, y esto es muy útil, ya que la mayoría de nuestros clientes tienen requisitos no funcionales al respecto.
BartoszKP
55

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.

Doc Brown
fuente
1
He editado la pregunta. No estoy seguro si arroja algo de luz.
w0051977
18
@ w0051977: el enlace que publicó no nos dice nada sobre su aplicación, ¿verdad? Parece que está buscando una regla simple y mental donde colocar SQL o no, que se ajuste a cada aplicación. No hay ninguno. Esta es una decisión de diseño que debe tomar sobre su aplicación individual (probablemente junto con su equipo).
Doc Brown
77
+1. En particular, me gustaría señalar que no hay una diferencia real entre SQL en un método y SQL en un procedimiento almacenado, en términos de cuán "codificado" es algo, o cuán reutilizable, mantenible, etc. Cualquier abstracción que pueda hacer con un El procedimiento se puede hacer con un método. Por supuesto, los procedimientos pueden ser útiles, pero una regla de memoria "no podemos tener SQL en los métodos, así que vamos a ponerlo todo en los procedimientos" probablemente solo producirá una gran cantidad de problemas con poco beneficio.
1
@ user82096 Yo diría que, en general, poner código de acceso db en SP ha sido perjudicial en casi todos los proyectos que he visto. (Pila de MS) Además de las degradaciones reales del rendimiento (cachés del plan de ejecución rígido), el mantenimiento del sistema se hizo más difícil al dividir la lógica y los SP mantenidos por un conjunto diferente de personas que los desarrolladores de lógica de la aplicación. Especialmente en Azure, donde los cambios de código entre las ranuras de implementación son como segundos de trabajo, pero las migraciones de esquemas sin código primero / db-primero entre ranuras son un poco de un dolor de cabeza operativo.
Centinela
33

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:

En Hibernate estoy iterando a través de 250,000 registros comprobando los valores de un par de propiedades y actualizando objetos que coinciden con ciertas condiciones. Funciona un poco lento, ¿qué puedo hacer para acelerarlo?

¿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.

mcottle
fuente
55
No dije que el "bloqueo de proveedores" es malo. Dije que viene con compensaciones. En el mercado actual con db como xaaS, estos viejos contratos y licencias para ejecutar db autohospedado serán cada vez más raros. Hoy es bastante más fácil cambiar la base de datos del proveedor A a B en comparación con cómo era hace 10 años. Si lees los comentarios, estoy a favor de aprovechar cualquier característica del almacenamiento de datos. Por lo tanto, es fácil poner algunos detalles específicos del proveedor en algo (aplicación) destinado a ser independiente del proveedor. Nos esforzamos por seguir SOLiD hasta el final, bloquear el código en un único almacenamiento de datos. No es gran cosa
Laiv
2
Esta es una respuesta genial. A menudo, el enfoque correcto es el que conduce a la situación menos mala si se escribe el peor código posible. El peor código ORM posible podría ser mucho peor que el peor SQL incrustado posible.
jwg
@Laiv puntos positivos PaaS cambia un poco el juego: tendré que pensar más en las implicaciones.
mcottle
3
@jwg Me inclinaría a decir que el código ORM por encima del promedio es peor que el SQL incorporado por debajo del promedio :)
mcottle
@macottle Suponiendo que el SQL incrustado por debajo del promedio fue escrito por ppl por encima del promedio de quien escribe un ORM. Lo dudo mucho. Muchos desarrolladores por ahí no son capaces de escribir uniones izquierdas sin verificar SO primero.
Laiv
14

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.

Código limpio por Robert C. Martin, pág.  288 (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:

  • La lógica de la base de datos es difícil de localizar. ¿Qué buscas para encontrar todas tus declaraciones SQL? ¿Cadenas con "SELECCIONAR", "ACTUALIZAR", "COMBINAR", etc.?
  • Refactorizar los usos del mismo SQL o uno similar se vuelve difícil.
  • Agregar soporte para otras bases de datos es difícil. ¿Cómo se lograría esto? Agregue if..then declaraciones para cada base de datos y almacene todas las consultas como cadenas en el método?
  • Los desarrolladores leen una declaración en otro idioma y se distraen por el cambio de enfoque del propósito del método a los detalles de implementación del método (cómo y de dónde se recuperan los datos).
  • Si bien las frases simples pueden no ser un gran problema, las cadenas SQL en línea comienzan a desmoronarse a medida que las declaraciones se vuelven más complejas. ¿Qué haces con una declaración de 113 líneas? Pon las 113 líneas en tu método?
  • ¿Cómo mueve el desarrollador eficientemente las consultas de un lado a otro entre su editor de SQL (SSMS, SQL Developer, etc.) y su código fuente? El @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 caracteres de sangría utilizados para alinear el SQL con el código de la aplicación circundante se transmiten a través de la red con cada ejecución. Esto es probablemente insignificante para aplicaciones a pequeña escala, pero puede acumularse a medida que crece el uso del software.

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 ?:

using(DbConnection connection = Database.SystemConnection()) {
    var eyesoreSql = @"
    SELECT
        Viewable.ViewId,
        Viewable.HelpText,
        PageSize.Width,
        PageSize.Height,
        Layout.CSSClass,
        PaginationType.GroupingText
    FROM Viewable
    LEFT JOIN PageSize
        ON PageSize.Id = Viewable.PageSizeId
    LEFT JOIN Layout
        ON Layout.Id = Viewable.LayoutId
    LEFT JOIN Theme
        ON Theme.Id = Viewable.ThemeId
    LEFT JOIN PaginationType
        ON PaginationType.Id = Viewable.PaginationTypeId
    LEFT JOIN PaginationMenu
        ON PaginationMenu.Id = Viewable.PaginationMenuId
    WHERE Viewable.Id = @Id
    ";
    var results = connection.Query<int>(eyesoreSql, new { Id });
}

Se convierte

using(DbConnection connection = Database.SystemConnection()) {
    var results = connection.Query<int>(sql.GetViewable, new { Id });
}

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 :

SQL en un recurso

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:

  1. El código de la base de datos es fácil de localizar porque ya está centralizado. En proyectos más grandes, agrupe like-SQL en archivos separados, tal vez en una carpeta llamada SQL.
  2. El soporte para una segunda, tercera, etc. bases de datos es más fácil. Haga una interfaz (u otro lenguaje de abstracción) que devuelva las declaraciones únicas de cada base de datos. La implementación para cada base de datos se convierte en poco más que declaraciones similares a: 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.
  3. La refactorización es simple: simplemente reutilice el mismo recurso. Incluso puede usar la misma entrada de recursos para diferentes sistemas DBMS la mayor parte del tiempo con unas pocas declaraciones de formato. Hago esto a menudo.
  4. El uso del idioma secundario puede usar nombres descriptivos , por ejemplo, en sql.GetOrdersForAccountlugar de más obtusosSELECT ... FROM ... WHERE...
  5. Las sentencias SQL se invocan con una línea, independientemente de su tamaño y complejidad.
  6. SQL se puede copiar y pegar entre herramientas de base de datos como SSMS y SQL Developer sin modificación o copia cuidadosa. Sin comillas Sin barras invertidas finales. En el caso del editor de recursos de Visual Studio específicamente, un clic resalta la instrucción SQL. CTRL + C y luego péguelo en el editor de SQL.

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.

Charles Burns
fuente
13
Creo que esto se centra demasiado en una mala crítica de tener SQL en el código fuente. Tener dos idiomas diferentes en el mismo archivo no es necesariamente terrible. Depende de muchas cosas, como cómo se relacionan y cómo se presentan.
jwg
99
"mezclar idiomas completamente diferentes con sintaxis diferente en el mismo archivo" Este es un argumento terrible. También se aplicaría al motor de vista Razor, y HTML, CSS y Javascript. ¡Me encantan tus novelas gráficas!
Ian Newson
44
Esto simplemente empuja la complejidad en otro lugar. Sí, su código original es más limpio, a expensas de agregar una dirección indirecta que el desarrollador ahora tiene que navegar para fines de mantenimiento. La verdadera razón por la que haría esto es si cada instrucción SQL se usara en varios lugares del código. De esa manera, realmente no es diferente de cualquier otra refactorización ordinaria ("Método de extracción").
Robert Harvey
3
Para ser más claros, esta no es una respuesta a la pregunta "qué hago con mis cadenas SQL", sino una respuesta a la pregunta "qué hago con cualquier colección de cadenas suficientemente complejas", una pregunta que Su respuesta se dirige elocuentemente.
Robert Harvey
2
@RobertHarvey: Estoy seguro de que nuestras experiencias difieren, pero en la mía he encontrado que la abstracción en cuestión es una victoria en la mayoría de los casos. Seguramente refinaré mis compensaciones de abstracción como lo he hecho arriba y abajo durante años, y algún día podría volver a poner SQL en el código. YMMV. :) Creo que los microservicios pueden ser geniales, y también generalmente separan los detalles de SQL (si se usa SQL) del código de la aplicación principal. Estoy menos a favor de "usar mi método específico: recursos" y más a favor de "Generalmente no mezcle idiomas de petróleo y agua".
Charles Burns
13

Depende. Hay varios enfoques diferentes que pueden funcionar:

  1. Modularice el SQL y aíslelo en un conjunto separado de clases, funciones o cualquier unidad de abstracción que utilice su paradigma, luego instálelo con la lógica de la aplicación.
  2. Mueva todo el SQL complejo a vistas, y luego solo haga un SQL muy simple en la lógica de la aplicación, para que no necesite modularizar nada.
  3. Use una biblioteca de mapeo relacional de objetos.
  4. YAGNI, solo escribe SQL directamente en la lógica de la aplicación.

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 decirCREATE 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 una INSERT, UPDATEo DELETE, 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.

Kevin
fuente
2 tal como está escrito esencialmente supone una base de datos de solo lectura. Los procedimientos almacenados solían ser frecuentes para consultas que modifican.
jpmc26
@ jpmc26: Cubro los escritos entre paréntesis ... ¿Tuviste una recomendación específica para mejorar esta respuesta?
Kevin
Hm. Disculpas por comentar antes de completar la respuesta y luego distraerse. Pero las vistas actualizables hacen que sea bastante difícil rastrear dónde se actualiza una tabla. Restringir las actualizaciones directamente en las tablas, independientemente del mecanismo, tiene sus ventajas en términos de comprender la lógica del código. Si está restringiendo la lógica a la base de datos, entonces, no puede aplicar eso sin procedimientos almacenados. También tienen la ventaja de permitir múltiples operaciones con una sola llamada. En pocas palabras: no creo que debas ser tan duro con ellos.
jpmc26
@ jpmc26: Eso es justo.
Kevin
8

¿Se considera un antipatrón para escribir SQL en el código 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.

supongamos que quiso decir; ya sea:

1) Use LINQ

o

2) Use procedimientos almacenados para el SQL

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 el beneficio de los procedimientos almacenados es que los desarrolladores de SQL pueden participar en el proceso de desarrollo (escribir procedimientos almacenados, etc.)

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.

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?

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í.

Laiv
fuente
1
Gracias. +1 para "Las instrucciones SQL de codificación rígida pueden llevarlo a acoplar estrechamente su aplicación al motor db". Me pregunto si se refería a SQL Server en lugar de SQL, es decir, usando SQLConnection en lugar de dbConnection; SQLCommand en lugar de dbCommand y SQLDataReader en lugar de dbDataReader.
w0051977
No. Me refería al uso de expresiones que no cumplen con ANSI. Por ejemplo: 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 ...
Laiv
14
"Bloqueo de proveedores" ... ¿Con qué frecuencia las personas / empresas realmente migran la base de datos de sus productos existentes a otro RDBMS? Incluso en proyectos que usan ORM exclusivamente, nunca vi que eso sucediera: por lo general, el software también se reescribe desde cero, porque las necesidades han cambiado en el medio. Por lo tanto, pensar en esto me parece una "optimización prematura". Sí, es totalmente subjetivo.
Olivier Grégoire
1
No me importa con qué frecuencia sucede. Me importa Puede suceder. En proyectos greenfield, el costo de implementar un DAL extraído de la base de datos es casi 0. Una posible migración no lo es. Los argumentos en contra de ORM son bastante débiles. Los ORM no son como hace 15 años cuando Hibernate era bastante verde. Lo que he visto es una gran cantidad de configuraciones "por defecto" y diseños de modelos de datos bastante más malos. Es como muchas otras herramientas, muchas personas hacen el tutorial de "inicio" y no les importa lo que sucede debajo del capó. La herramienta no es el problema. El problema es quién no sabe cómo y cuándo usarlo.
Laiv
Por otro lado, el vendedor encerrado no es necesariamente malo. Si me preguntaran, diría que no me gusta . Sin embargo, ya estoy bloqueado para Spring, Tomcat o JBoss o Websphere (aún peor). Los que puedo evitar, lo hago. Aquellos que no puedo, simplemente los vivo. Es cuestión de preferencias.
Laiv
3

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.

Sava B.
fuente
Permitir que los DBA cambien su SQL sin actualizar la aplicación efectivamente divide su aplicación en dos entidades desarrolladas por separado y desplegadas por separado. Ahora necesita administrar el contrato de interfaz entre ellos, sincronizar la publicación de cambios interdependientes, etc. Esto puede ser algo bueno o malo, pero es mucho más que "poner todo su SQL en un solo lugar", está muy lejos -alcanzando la decisión arquitectónica.
IMSoP
2

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

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";
rleir
fuente
77
El problema más evidente con esto es la cuestión de dónde proviene ese valor 42. ¿Estás concatenando ese valor en la cadena? Si es así, ¿de dónde vino? ¿Cómo se ha fregado para mitigar los ataques de inyección SQL? ¿Por qué este ejemplo no muestra una consulta parametrizada?
Craig
Solo quería mostrar el formato. Lo siento, olvidé parametrizarlo. Ahora corregido después de consultar msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir
+1 por la importancia del formato, aunque mantengo que las cadenas SQL son un olor a código independientemente del formato.
Charles Burns
1
¿Insiste en el uso de un ORM? Cuando un ORM no está en uso, para un proyecto más pequeño, uso una clase 'shim' que contiene SQL incorporado.
Rleir
Las cadenas SQL definitivamente no son un olor a código. Sin un ORM, como gerente y arquitecto, los alentaría sobre los SP por razones de mantenimiento y, a veces, de rendimiento.
Sentinel
1

No puedo decirte lo que querías decir colega. Pero puedo responder esto:

¿Hay algún beneficio de preparar una declaración SQL?

. 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:

List<UUID> ids = select(person.id).from(person).fetch();

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 personse 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 ...

meriton - en huelga
fuente
Para ser claros: el uso de declaraciones preparadas no impide la inyección de SQL. El uso de declaraciones preparadas con variables enlazadas sí. Es posible utilizar declaraciones preparadas que se crean a partir de datos no confiables.
Andy Lester
1

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.

Pieter B
fuente
+1 Buen punto. La compatibilidad es más importante que la mayoría de las otras consideraciones, así que no seas demasiado inteligente :) Dicho esto, si eres una tienda de ORM, no olvides que hay casos en los que ORM no es la respuesta y necesitas escribir SQL directo. No optimice prematuramente, pero habrá ocasiones en cualquier aplicación no trivial en las que tendrá que seguir esta ruta.
mcottle
0

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í.

h22
fuente
0

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.

var query = "select * from accounts";

if(users.IsInRole('admin'))
{
   query += " join secret_balances";
}

Sugiero usar un generador de consultas sql

amd
fuente
Puede ser que solo estés usando la taquigrafía, pero es una muy mala idea usar ciegamente "SELECCIONAR *" porque recuperarás los campos que no necesitas (¡ancho de banda!) Además, aunque no tengo ningún problema con la cláusula de administración condicional conduce por una pendiente muy resbaladiza a una cláusula condicional "DÓNDE" y ese es un camino que conduce directamente al infierno del rendimiento.
mcottle
0

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.

bikeman868
fuente
Incorrecto ...........
Centinela
¿Crees que tu comentario es útil para alguien?
bikeman868
Esto es simplemente incorrecto. Digamos que tenemos un proyecto Web API2 con EF como acceso a datos y Azure Sql como almacenamiento. No, eliminemos EF y usemos SQL artesanal. El esquema db se almacena en un proyecto SSDT SQL Server. La migración del esquema DB implica asegurarse de que la base de código se sincronice con el esquema db y se pruebe por completo, antes de enviar el cambio a una prueba db en Azure. Un cambio de código implica pasar a un espacio de implementación de App Service, que demora 2 segundos. Además, el SQL artesanal tiene ventajas de rendimiento sobre los procedimientos almacenados, en general, a pesar de la "sabiduría" de lo contrario.
Centinela
En su caso, la implementación puede llevar segundos, pero este no es el caso para muchas personas. No dije que los procedimientos almacenados son mejores, solo que tienen algunas ventajas en algunas circunstancias. Mi declaración final es que si estas circunstancias no se aplican, entonces colocar SQL en la aplicación es muy válido. ¿Cómo contradice esto lo que estás diciendo?
bikeman868
Los procedimientos almacenados son SQL hechos a mano ???
bikeman868