2008-11-15 12 views
34

¿Por lo general, un ORM no implica hacer algo como un select *?¿Por qué el ORM se considera bueno pero el "seleccionar *" se considera malo?

Si tengo una mesa, MYTHing, con la columna A, B, C, D, etc, entonces hay típicamente sería un objeto, MYTHing con propiedades A, B, C, D.

Sería mal si ese objeto fue instanciado de forma incompleta por una declaración de selección que se veía así, solo buscando el A, B, no el C, D:

seleccione A, B de MyThing/* no obtenga C y D, porque que no los necesitamos */

pero también estaríamos mal siempre hacer esto:

seleccione A, B, C, D/* obtenga todas las columnas para que podamos crear una instancia completa del objeto MyThing */

¿Asume ORM que el acceso a la base de datos es tan rápido que ya no tiene que preocuparse y así siempre puedes buscar todas las columnas?

O, ¿tiene diferentes objetos MyThing, uno para cada combinación de columnas que pueda estar en una instrucción select?

EDITAR: Antes de responder a la pregunta, lea las respuestas de Nicholas Piasecki y Bill Karwin. Supongo que hice mi pregunta mal porque muchos la entendieron mal, pero Nicholas la entendió al 100%. Al igual que él, estoy interesado en otras respuestas.


editar # 2: Los enlaces que se relacionan con esta pregunta:

Why do we need entity objects?

http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx, especialmente la sección "El problema parcial a objetos y el tiempo de carga de Paradox"

http://groups.google.com/group/comp.object/browse_thread/thread/853fca22ded31c00/99f41d57f195f48b?

http://www.martinfowler.com/bliki/AnemicDomainModel.html

http://database-programmer.blogspot.com/2008/06/why-i-do-not-use-orm.html

+0

tal vez el problema es el título - "seleccionar *" en lugar de "seleccionar x, y, z"? – gbjbaanb

+2

¿Qué ORM estás usando que hace un SELECT *? He usado llblgen y nhibernate, ambos deletrean cada columna para buscar – Bob

Respuesta

65

En mi limitada experiencia, las cosas son como usted la describe - es una situación complicada y la habitual salida fácil "depende" se aplica respuesta.

Un buen ejemplo sería la tienda en línea para la que trabajo. Tiene un objeto Brand, y en la página principal del sitio web, todas las marcas que vende la tienda se enumeran en el lado izquierdo. Para mostrar este menú de marcas, todas las necesidades del sitio son el número entero BrandId y la cadena BrandName. Pero el objeto Brand contiene un bote entero de otras propiedades, la más notable es una propiedad Description que puede contener una gran cantidad de texto sobre el Brand. No hay dos formas de hacerlo, cargar toda esa información adicional sobre la marca solo para escupir su nombre en una lista desordenada es (1) lento y medible, generalmente debido a los grandes campos de texto y (2) bastante ineficiente cuando se trata al uso de la memoria, construyendo cadenas grandes y sin siquiera mirarlas antes de tirarlas.

Una opción proporcionada por muchos ORM es la carga perezosa una propiedad. Entonces podríamos tener un objeto Brand devuelto a nosotros, pero ese campo Description que consume mucho tiempo y pierde memoria no es hasta que intentamos invocar su accessor get. En ese punto, el objeto proxy interceptará nuestra llamada y absorberá la descripción de la base de datos justo a tiempo. Esto a veces es lo suficientemente bueno, pero me ha quemado suficientes veces que yo personalmente no lo recomiendo:

  • Es fácil olvidar que la propiedad es-cargado ligeramente, la introducción de un SELECT N + 1 problema con sólo escribir un foreach lazo. Quién sabe qué sucede cuando LINQ se involucra.
  • ¿Qué sucede si falla la llamada a la base de datos just-in-time porque el transporte se desconcertó o la red se apagó? Casi puedo garantizar que cualquier código que esté haciendo algo tan inocuo como string desc = brand.Description no esperaba esa simple llamada para lanzar un DataAccessException. Ahora que acaba de estrellarse de una manera desagradable e inesperada. (Sí, he visto que mi aplicación se estropea por eso. ¡Aprendí de la manera difícil!)

Entonces, lo que terminé haciendo es que en escenarios que requieren rendimiento o son propensos a bloqueos de la base de datos , Creo una interfaz separada que el sitio web o cualquier otro programa puede llamar para obtener acceso a trozos de datos específicos que han tenido sus planes de consulta cuidadosamente examinados. La arquitectura termina buscando algo así como este (valga la arte ASCII):

 
Web Site:   Controller Classes 
        | 
        |---------------------------------+ 
        |         | 
App Server:  IDocumentService    IOrderService, IInventoryService, etc 
        (Arrays, DataSets)    (Regular OO objects, like Brand) 
        |         | 
        |         | 
        |         | 
Data Layer:  (Raw ADO.NET returning arrays, ("Full cream" ORM like NHibernate) 
        DataSets, simple classes) 

Yo solía pensar que se trataba de hacer trampa, subvertir el modelo de objetos OO. Pero en un sentido práctico, siempre que haga este atajo para mostrar datos, creo que está bien. Las actualizaciones/inserciones y lo que todavía tiene que pasar por el modelo de dominio lleno de ORM totalmente hidratado, y eso es algo que sucede con mucha menos frecuencia (en la mayoría de mis casos) que mostrar subconjuntos particulares de los datos. Los ORM como NHibernate te permitirán hacer proyecciones, pero en ese momento no veo el sentido del ORM. Probablemente este sea un procedimiento almacenado, escribir el ADO.NET toma dos segundos.

Esto es sólo mis dos centavos. Espero leer algunas de las otras respuestas.

0

SELECT * no es malo. ¿Le preguntaste a quien lo consideró malo "por qué?".

+0

Me pregunto: ¿es select * bad? Sí, si solo necesita las columnas A y B, no pague el costo de buscar columnas C y D también. Obtenga solo lo que necesita. –

+0

Sí, pero no es universalmente "malo". ¡Es "malo" en algunos casos! ¿Qué pasa si más adelante quieres C y D, en caso de que no los obtengas y los guardes en la memoria caché? Ningún caso es cortante y seco, por lo que no podemos decir "Select * is bad". Podemos decir que "Select * es a veces malo" –

+1

La peor consecuencia de traer a C y D es que quienquiera que venga después de ti no tiene idea de qué intentabas hacer con ellos. Dicho de otra manera, aquí también hay un principio de YAGNI que esto viola. – dkretz

3

Incluso ORM necesidad de evitar SELECT * para ser eficaz, mediante el uso de la carga diferida, etc.

Y sí, SELECT * es generalmente una mala idea si usted no está consumiendo todos los datos.

Entonces, ¿tiene diferentes tipos de objetos MyThing, uno para cada columna combinada? - Corey Trager (15 de noviembre a las 0:37)

No, tengo objetos de solo lectura (que solo contienen información importante) para cosas como búsquedas y colecciones masivas y los convierto en objetos completamente hidratados bajo demanda. - Cade Roux (15 de noviembre a la 1:22)

+0

Entonces, ¿tiene diferentes tipos de objetos MyThing, uno para cada columna combinada? –

+0

No, tengo objetos de resumen de solo lectura (que solo contienen información importante) para cosas como búsquedas y colecciones masivas y los convierto en objetos completamente hidratados bajo demanda. –

+0

+1 para usted respuesta comentario Cade, este bit sobre objetos de resumen debería estar en la respuesta. :) – BobbyShaftoe

4

No estoy seguro de por qué querría un objeto parcialmente hidratado. Dada una clase de Cliente con propiedades de Nombre, Dirección, Id. Quisiera que todos ellos crearan un objeto Cliente completamente poblado.

La lista que cuelga de Clientes llamada Órdenes se puede cargar de forma diferida cuando se accede a través de la mayoría de los ORM. Y NHibernate de todos modos le permite hacer proyecciones en otros objetos. Por lo tanto, si dijera una lista de clientes simple donde muestra el ID y el Nombre, puede crear un objeto de tipo CustomerListDisplay y proyectar su consulta HQL en ese conjunto de objetos y obtener solo las columnas que necesita de la base de datos.

Los amigos no permiten que los amigos optimicen prematuramente. Totalmente hidratar su objeto, cargar sus asociaciones perezoso. Luego, perfile su aplicación buscando problemas y optimice las áreas problemáticas.

+0

Un objeto parcialmente hidratado en los casos en que solo está interesado en un subconjunto de las columnas, y no desea pagar la penalización de rendimiento de recuperar todas las columnas. –

+0

entonces mira en proyecciones ... – NotMyself

+0

a menudo el 'golpe de rendimiento' de ir a buscar todas las columnas es insignificante. El rendimiento alcanzado de repetidos viajes de ida y vuelta al DB es muy significativo. La carga lenta solo ayudará en algunos escenarios, no es una bala mágica. – gbjbaanb

1

ORM en general no se basan en SELECT *, pero se basan en mejores métodos para encontrar columnas como archivos de mapa de datos definidos (Hibernate, las variantes de hibernación, y Apache iBATIS hacer esto). Algo más automático podría configurarse al consultar el esquema de la base de datos para obtener una lista de columnas y sus tipos de datos para una tabla. La forma en que se llenan los datos es específica para el ORM particular que está utilizando, y debe estar bien documentado allí.

Nunca es una buena idea seleccionar datos que no use en absoluto, ya que puede crear una dependencia de código innecesario que puede ser molesto mantener más adelante. Para tratar con datos internos de la clase, las cosas son un poco más complicadas.

Una regla breve sería buscar siempre todos los datos que la clase almacena de forma predeterminada. En la mayoría de los casos, una pequeña cantidad de sobrecarga no hará una gran diferencia, por lo que su objetivo principal es reducir los gastos generales de mantenimiento. Más tarde, cuando perfile el rendimiento del código, y tenga motivos para creer que puede beneficiarse ajustando el comportamiento, ese es el momento de hacerlo.

Si vi un ORM hacer declaraciones SELECT *, ya sea de forma visible o debajo de sus cubiertas, entonces buscaría en otra parte para satisfacer las necesidades de integración de mi base de datos.

+0

No creo que su respuesta aborde la pregunta. Aun así, ORM puede emplear métodos para tener conocimiento de los datos solicitados de la base de datos, ¿de qué otra manera puede poblar de manera concluyente su modelo de objetos ORM sin una declaración SELECT ya sea una declaración preparada u otra cosa? – Florin

+0

La forma en que se cargan los datos en la clase es un detalle específico de la implementación del ORM. En iBATIS, por ejemplo, tiene archivos XML que contienen sentencias o procedimientos SQL, tipos de datos de columnas y, a continuación, asignaciones de columna a clase para especificar qué columna coincide con qué variable específica de la clase. –

+0

@Florin, te entiendo. @doktaru, no te entiendo. Olvídese de la parte de la pregunta "select *". En su lugar, explique cómo se comporta MyThing cuando a veces está completamente hidratado y, a veces parcialmente. –

4

Hay dos cuestiones diferentes a considerar.

Para comenzar, es bastante común cuando se utiliza un ORM para la tabla y el objeto tener "formas" bastante diferentes, esta es una razón por la que muchas herramientas ORM admiten asignaciones bastante complejas.

Un buen ejemplo es cuando una tabla está parcialmente denormalizada, con columnas que contienen información redundante (a menudo, esto se hace para mejorar la consulta o el rendimiento de informes). Cuando esto ocurre, es más eficiente para el ORM solicitar solo las columnas que requiere, que tener todas las columnas extra traídas de vuelta e ignoradas.

La pregunta de por qué "Seleccionar *" es malo está separada, y la respuesta se divide en dos mitades.

Al ejecutar "SELECT *" el servidor de base de datos no tiene la obligación de devolver las columnas en un orden particular, y de hecho podría razonablemente devolver las columnas en un orden diferente cada vez, aunque casi no hay bases de datos hacen esto.

El problema es que, cuando un desarrollador típica observa que las columnas devueltas parecen estar en un orden coherente, se hace la suposición de que las columnas se siempre ser en ese orden, y entonces usted tiene código de hacer suposiciones injustificadas, justo esperando fallar Peor aún, esa falla puede no ser fatal, pero puede involucrar, por ejemplo, usar Año de nacimiento en lugar de Saldo de cuenta.

El otro problema con "Seleccionar *" gira en torno a la propiedad de la tabla: en muchas empresas grandes, el DBA controla el esquema y realiza los cambios necesarios según los sistemas principales.Si su herramienta está ejecutando "select *", solo obtendrá las columnas actuales: si el DBA ha eliminado una columna redundante que necesita, no obtendrá ningún error y su código puede equivocarse y causar todo tipo de daños. Al solicitar explícitamente los campos que necesita, se asegura de que su sistema fragmente en lugar de procesar la información incorrecta.

+0

Bevan - ¿Qué significa eso concretamente, que un "objeto" tiene diferentes "formas"? Por ejemplo, si el lenguaje es Java/C#/C++, ¿hay solo una "clase MyThing" pero a veces su GetSomeStringAttr() devuelve null? –

+1

Para mí, suena como un Objeto que finalmente tiene una definición claramente definida con respecto a un cierto contexto; y diferentes contextos en la aplicación pueden necesitar diferentes formas (conjuntos de propiedades y métodos) para diferentes contextos. – dkretz

+0

Me refería a la forma en que un buen diseño puede terminar en resultados bastante diferentes para objetos y tablas. Un ejemplo trivial: un objeto puede terminar con una propiedad Fecha, donde la tabla tiene Fecha, día, mes, año, día de la semana, día de año calendario y día de año financiero para los informes. – Bevan

21

La gente usa ORM de una mayor productividad de desarrollo, no para la optimización del rendimiento en tiempo de ejecución.Depende del proyecto si es más importante maximizar la eficiencia del desarrollo o la eficiencia del tiempo de ejecución.

En la práctica, se podría usar el ORM para una mayor productividad, y luego perfilar la aplicación para identificar los cuellos de botella una vez que haya terminado. Reemplace el código ORM con consultas SQL personalizadas solo donde obtenga la mejor inversión para el dinero.

SELECT * no está mal si normalmente necesita todas las columnas de una tabla. No podemos generalizar que el comodín siempre es bueno o siempre malo.

editar: Re: doofledorfer's comment ... Personalmente, siempre nombro las columnas en una consulta explícitamente; Nunca uso el comodín en el código de producción (aunque lo uso cuando hago consultas ad hoc). La pregunta original es sobre ORM: de hecho, no es raro que los marcos ORM emitan un SELECT * uniformemente, para llenar todos los campos en el modelo de objetos correspondiente.

Ejecutar una consulta SELECT * puede no indicar necesariamente que necesita todas esas columnas, y no significa necesariamente que sea negligente con respecto a su código. Podría ser que el marco ORM esté generando consultas SQL para asegurarse de que todos los campos estén disponibles en el caso de que los necesite,.

+0

Has entendido por completo la intención de mi pregunta. Gracias. –

+0

Creo que usar "SELECCIONAR *" indica su decisión de que, de hecho, necesita todas las columnas de la tabla. Si eso no es cierto, entonces es engañoso. También sugiere la posibilidad de que no lo hayas pensado muy bien (a menos que dejes un comentario que afirme). – dkretz

6

Linq to Sql, o cualquier implementación de IQueryable, utiliza una sintaxis que, en última instancia, le permite controlar los datos seleccionados. La definición de una consulta también es la definición de su conjunto de resultados.

Esto evita claramente el problema select * al eliminar las responsabilidades de forma de datos del ORM.

Por ejemplo, para seleccionar todas las columnas:

from c in data.Customers 
select c 

Para seleccionar un subconjunto:

from c in data.Customers 
select new 
{ 
    c.FirstName, 
    c.LastName, 
    c.Email 
} 

Para seleccionar una combinación:

from c in data.Customers 
join o in data.Orders on c.CustomerId equals o.CustomerId 
select new 
{ 
    Name = c.FirstName + " " + c.LastName, 
    Email = c.Email, 
    Date = o.DateSubmitted 
} 
+0

Pero ... ¿qué demonios es el objeto que has creado? ¿Son ambos objetos de dominio "Cliente", a pesar de que tienen diferentes propiedades? –

+0

El nuevo objeto es un tipo anónimo, generado por el compilador con el esquema definido. Es como si lo hubieras definido tú mismo, excepto que no tienes que administrar el artefacto de la clase. Consulte http://msdn.microsoft.com/en-us/library/bb397696.aspx para obtener más información. –

+0

El objeto representa una proyección, que por definición no es un objeto de dominio y no se puede confundir con uno. Si necesita un cliente completo, selecciónelo, de lo contrario, seleccione otra cosa. Los "objetos escasamente poblados" parecen un anti-patrón que compromete el dominio por cuestiones técnicas. –

0

SELECT * es una fuerte indicación de que no tiene control de diseño sobre el alcance de su aplicación y sus módulos. Una de las principales dificultades para limpiar el trabajo de otra persona es cuando hay material que no sirve para nada, pero no indica qué se necesita y se usa, y qué no.

Cada dato y código en su aplicación debe estar ahí para un propósito, y el propósito debe especificarse o detectarse fácilmente.

Todos conocemos, y despreciamos, a los programadores que no se preocupan demasiado por por qué funcionan las cosas, solo quieren probar cosas hasta que sucedan las cosas esperadas y cerrarlas para el siguiente tipo. SELECCIONAR * es una forma realmente buena de hacer eso.

2

El caso que describe es un gran ejemplo de cómo ORM no es una panacea. Las bases de datos ofrecen acceso flexible y basado en las necesidades a sus datos principalmente a través de SQL. Como desarrollador, puedo obtener todos los datos (SELECCIONAR *) o algunos de los datos (SELECCIONAR COL1, COL2) según sea necesario. Mi mecanismo para hacerlo será fácilmente comprendido por cualquier otro desarrollador que se haga cargo del proyecto.

Para obtener la misma flexibilidad de ORM, debe hacer mucho más trabajo (ya sea por usted o por los desarrolladores de ORM) solo para volver al lugar bajo el capó donde está obteniendo todo o algunas de las columnas de la base de datos según sea necesario (consulte las excelentes respuestas anteriores para tener una idea de algunos de los problemas). Y todas estas cosas adicionales son solo más cosas que pueden fallar, lo que hace que un sistema ORM intrínsecamente sea menos confiable que las llamadas SQL directas.

Esto no quiere decir que no deba usar ORM (mi descargo de responsabilidad estándar es que todas las opciones de diseño tienen costos y beneficios, y la elección de una u otra depende) - noquear si funciona para usted . Diré que realmente no entiendo la popularidad de ORM, dada la cantidad de trabajo extra no divertido que parece crear para sus usuarios. Me quedaré con el uso de SELECT * cuando (espere) Necesito obtener cada columna de una tabla.

0

Si siente la necesidad de encapsular todo dentro de un objeto, pero necesita algo con un pequeño subconjunto de lo que está contenido en una tabla, defina su propia clase. Escriba sql directamente (dentro o fuera del ORM, la mayoría permite SQL recta para eludir las limitaciones) y llene su objeto con los resultados.

Sin embargo, solo usaría la representación ORM de una tabla en la mayoría de las situaciones a menos que los perfiles me dijeran que no lo haga.

0

Si está utilizando el caché de consultas, seleccione * puede ser bueno. Si está seleccionando un surtido diferente de columnas cada vez que golpea una mesa, podría ser simplemente seleccionar en caché * para todas esas consultas.

Creo que confunde el propósito de ORM. ORM está destinado a mapear un modelo de dominio o similar a una tabla en una base de datos o alguna convención de almacenamiento de datos. No está destinado a hacer que su aplicación sea más eficiente desde el punto de vista informático o incluso esperada.

Cuestiones relacionadas