personaje - ¿Por qué el ORM se considera bueno, pero "select*" se considera malo?
orm personaje (12)
Incluso los ORM necesitan evitar que SELECT * sea efectivo, usando cargas perezosas, etc.
Y sí, SELECCIONAR * generalmente es una mala idea si 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 resumen de solo lectura (que solo contienen información importante) para cosas como búsquedas y colecciones masivas, y los convierto en objetos totalmente hidratados bajo demanda. - Cade Roux (15 de noviembre a la 1:22)
¿Un ORM no implica generalmente hacer algo como un select *?
Si tengo una tabla, MyThing, con la columna A, B, C, D, etc., entonces normalmente habría un objeto, MyThing con las propiedades A, B, C, D.
Sería malo si ese objeto fuera instanciado de forma incompleta por una instrucción select que se viera así, solo buscando el A, B, no el C, D:
seleccione A, B de MyThing / * no obtenga C y D, porque no los necesitamos * /
pero también sería malo hacer siempre 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 por eso y siempre puede buscar todas las columnas?
O bien, ¿tiene diferentes objetos MyThing, uno para cada combinación de columnas que podría estar en una declaración seleccionada?
EDITAR: Antes de responder la pregunta, lea las respuestas de Nicholas Piasecki y Bill Karwin. Supongo que hice mi pregunta mal porque muchos la entendieron mal, pero Nicholas lo entendió al 100%. Al igual que él, estoy interesado en otras respuestas.
EDIT # 2: Enlaces que se relacionan con esta pregunta:
¿Por qué necesitamos objetos de entidad?
http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx , especialmente la sección "El problema de objeto parcial y la paradoja del tiempo de carga"
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
El caso que describes 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, se debe trabajar mucho más (ya sea por usted o por los desarrolladores de ORM) solo para regresar al lugar bajo el capó donde obtendrá todas 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 sea intrínsecamente 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 elecciones de diseño tienen costos y beneficios, y la elección de una u otra depende) - no sé si funciona para ti. 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.
En mi experiencia limitada, las cosas son como usted describe: es una situación complicada y se aplica la respuesta habitual de "depende".
Un buen ejemplo sería la tienda en línea para la que trabajo. Tiene un objeto de Brand
, y en la página principal del sitio web, todas las marcas que vende la tienda están enumeradas 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 completo de otras propiedades, en particular una propiedad de Description
que puede contener una gran cantidad de texto sobre la 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 provista por muchos ORM es cargar una propiedad de forma perezosa. Así que podríamos tener un objeto de Brand
que nos sea devuelto, pero ese campo de Description
pérdida de memoria y que consume mucho tiempo no es hasta que intentemos invocar su acceso de 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 las veces que personalmente no lo recomiendo:
- Es fácil olvidar que la propiedad tiene una carga lenta, presentando un problema de SELECT N + 1 simplemente escribiendo un ciclo foreach. 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 unaDataAccessException
. Ahora que acaba de estrellarse de una manera desagradable e inesperada. (Sí, he visto a mi aplicación sufrir daños por eso. ¡Aprendí de la manera más difícil!)
Entonces, lo que terminé haciendo es que en escenarios que requieren rendimiento o son propensos a interbloqueos de bases de datos, creo una interfaz separada que el sitio web o cualquier otro programa puede llamar para obtener acceso a trozos específicos de datos que han tenido su consulta planes cuidadosamente examinados. La arquitectura termina pareciéndose a esto (perdona el 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)
Solía pensar que esto era 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 a través del modelo de dominio lleno de ORM lleno de hidratación, 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.
Estos son mis únicos dos centavos. Espero leer algunas de las otras respuestas.
Hay dos cuestiones separadas para considerar.
Para comenzar, es bastante común cuando se usa un ORM para la tabla y el objeto tener "formas" bastante diferentes, esta es una de las razones por las cuales muchas herramientas ORM soportan mapeos bastante complejos.
Un buen ejemplo es cuando una tabla está parcialmente denormalizada, con columnas que contienen información redundante (a menudo, esto se hace para mejorar el rendimiento de las consultas o 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 "seleccionar *", el servidor de la base de datos no tiene la obligación de devolver las columnas en un orden particular, y de hecho podría devolver razonablemente las columnas en un orden diferente cada vez, aunque casi ninguna base de datos hace esto.
El problema es que cuando un desarrollador típico observa que las columnas devueltas parecen estar en un orden consistente, se asume que las columnas siempre estarán en ese orden, y luego tiene un código que hace suposiciones injustificadas, esperando a fallar. Peor aún, esa falla puede no ser fatal, sino que simplemente puede implicar, por ejemplo, utilizar el Año de nacimiento en lugar del Saldo de la 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.
Las personas usan los ORM para una mayor productividad de desarrollo, no para la optimización del rendimiento en el 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, uno 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 es malo si normalmente necesita todas las columnas en 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 completar todos los campos en el modelo de objeto correspondiente.
Ejecutar una consulta SELECT *
puede no necesariamente indicar que necesita todas esas columnas, y no necesariamente significa que usted es negligente con respecto a su código. Podría ser que el marco ORM genere consultas SQL para asegurarse de que todos los campos estén disponibles en caso de que los necesite.
Los ORM en general no confían en SELECT *, sino que se basan en mejores métodos para encontrar columnas como archivos de mapas de datos definidos (Hibernate, variantes de Hibernate y Apache iBATIS hacen 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 innecesaria del código que puede ser desagradable para mantener en el futuro. Para tratar con datos internos de la clase, las cosas son un poco más complicadas.
Una regla corta 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 veo que un ORM hace declaraciones SELECT *, ya sea visiblemente o bajo sus coberturas, entonces buscaría en otra parte para cumplir con las necesidades de integración de mi base de datos.
No estoy seguro de por qué querrías 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 llamados Órdenes se puede cargar de forma perezosa 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.
SELECCIONAR * no está mal. ¿Le preguntaste a quien lo consideró malo "por qué?".
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 pieza de datos y código en su aplicación debe estar ahí para un propósito, y el propósito debe ser especificado o fácil de detectar.
Todos sabemos, y despreciamos, que los programadores que no se preocupan demasiado por el funcionamiento de las cosas, simplemente quieren probar cosas hasta que sucedan las cosas esperadas y cerrarlas para el siguiente tipo. SELECCIONAR * es una forma realmente buena de hacer eso.
Si siente la necesidad de encapsular todo dentro de un objeto, pero necesita algo con un pequeño subconjunto de lo que está contenido dentro de 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, usaría la representación ORM de una tabla en la mayoría de las situaciones a menos que el perfil me dijera que no lo haga.
Linq to Sql , o cualquier implementación de IQueryable , usa 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 cuidadosamente el problema de 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
}
Si está utilizando el almacenamiento en 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 estás confundiendo 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.