Почему ORM считается хорошим, а «select *» - плохим? - PullRequest
35 голосов
/ 15 ноября 2008

Разве ORM обычно не включает в себя что-то вроде выбора *?

Если у меня есть таблица MyThing со столбцами A, B, C, D и т. Д., То обычно будет объект MyThing со свойствами A, B, C, D.

Было бы злом, если бы этот объект был создан не полностью с помощью оператора select, который выглядел следующим образом, получая только A, B, а не C, D:

выберите A, B из MyThing / * не получите C и D, потому что они нам не нужны * /

но было бы также плохо всегда делать это:

выберите A, B, C, D / * получить все столбцы, чтобы мы могли полностью создать экземпляр объекта MyThing * /

Предполагает ли ORM, что доступ к базе данных является таким быстрым, что вам не нужно об этом беспокоиться, и вы всегда можете выбрать все столбцы?

Или у вас есть разные объекты MyThing, по одному для каждой комбинации столбцов, которые могут оказаться в операторе select?

РЕДАКТИРОВАТЬ: Прежде чем ответить на вопрос, пожалуйста, прочитайте ответы Николаса Пиасеки и Билла Карвина. Наверное, я плохо задал вопрос, потому что многие его неправильно поняли, но Николай понял это на 100%. Как и он, меня интересуют другие ответы.


РЕДАКТИРОВАТЬ # 2: Ссылки, связанные с этим вопросом:

Зачем нам нужны объекты сущностей?

http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx, особенно раздел «Проблема частичных объектов и парадокс времени загрузки»

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 / почему-я-ду-не-потребительная orm.html

Ответы [ 12 ]

65 голосов
/ 15 ноября 2008

По моему ограниченному опыту, все так, как вы описываете - это беспорядочная ситуация, и применяется обычный ответ «все зависит».

Хорошим примером будет интернет-магазин, в котором я работаю. У него есть объект Brand, и на главной странице веб-сайта все бренды, которые продает магазин, перечислены на левой стороне. Чтобы отобразить это меню брендов, все, что нужно сайту, это целое число BrandId и строка BrandName. Но объект Brand содержит целый набор других свойств, в частности, свойство Description, которое может содержать существенно больший объем текста о Brand. Нет двух способов: загрузить всю эту дополнительную информацию о бренде, просто чтобы выложить его название в неупорядоченном списке, (1) измеримо и значительно медленно, обычно из-за больших текстовых полей и (2) довольно неэффективно, когда оно появляется использования памяти, создания больших строк и даже не глядя на них, прежде чем выбросить их.

Одна опция, предоставляемая многими ORM, - это ленивая загрузка свойства. Таким образом, мы могли бы получить объект Brand, возвращенный нам, но это трудоемкое и бесполезное для памяти поле Description недоступно, пока мы не попытаемся вызвать его средство доступа get. В этот момент прокси-объект перехватит наш вызов и высосет описание из базы данных как раз вовремя. Иногда это достаточно хорошо, но я сжигаю столько раз, что лично не рекомендую:

  • Легко забыть, что свойство загружается лениво, создавая проблему SELECT N + 1, просто написав цикл foreach. Кто знает, что происходит, когда LINQ вмешивается.
  • Что, если вызов базы данных «точно вовремя» завершится неудачно из-за сбоя транспорта или выхода из сети? Я могу почти гарантировать, что любой код, который делает что-то столь же безобидное, как string desc = brand.Description, не ожидал, что этот простой вызов бросит DataAccessException. Теперь вы только что разбились противным и неожиданным способом. (Да, я видел, как мое приложение упало из-за этого. Изучил трудный путь!)

Итак, что я в итоге сделал, так это то, что в сценариях, требующих производительности или склонных к взаимоблокировке базы данных, я создаю отдельный интерфейс, который веб-сайт или любая другая программа могут вызывать для получения доступа к определенным порциям данных, которые имеют тщательно изучили свои планы запросов. Архитектура выглядит примерно так (простите за искусство 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)

Раньше я думал, что это обман, подрывая объектную модель ОО. Но в практическом смысле, пока вы делаете этот ярлык для отображения данных, я думаю, что все в порядке. Обновления / вставки и то, что вы по-прежнему проходите через полностью гидратированную, заполненную ORM модель домена, и это происходит гораздо реже (в большинстве моих случаев), чем отображение определенных подмножеств данных. ORM, такие как NHibernate, позволят вам делать прогнозы, но к этому моменту я просто не вижу смысла ORM. В любом случае это, вероятно, будет хранимая процедура, написание ADO.NET занимает две секунды.

Это всего лишь два моих цента. Я с нетерпением жду, чтобы прочитать некоторые другие ответы.

21 голосов
/ 15 ноября 2008

Люди используют ORM для повышения производительности разработки, а не для оптимизации производительности во время выполнения. От проекта зависит, насколько важнее максимизировать эффективность разработки или эффективность времени выполнения.

На практике можно использовать ORM для максимальной производительности, а затем профилировать приложение для выявления узких мест, как только вы закончите. Замените ORM-код пользовательскими SQL-запросами только там, где вы получаете наибольшую отдачу.

SELECT * неплохо, если вам обычно нужны все столбцы в таблице. Мы не можем обобщить, что подстановочный знак всегда хорош или всегда плох.

edit: Re: комментарий doofledorfer ... Лично я всегда называю столбцы в запросе явно; Я никогда не использую подстановочный знак в производственном коде (хотя я использую его при выполнении специальных запросов). Первоначальный вопрос касается ORM - на самом деле нередко среды ORM выдают SELECT * равномерно, чтобы заполнить все поля в соответствующей объектной модели.

Выполнение запроса SELECT * может не обязательно указывать на то, что вам нужны все эти столбцы, и это не обязательно означает, что вы пренебрегаете своим кодом. Возможно, среда ORM генерирует SQL-запросы, чтобы убедиться, что все поля доступны , если они вам нужны.

6 голосов
/ 15 ноября 2008

Linq to Sql или любая реализация IQueryable использует синтаксис, который в конечном итоге позволяет вам контролировать выбранные данные. Определение запроса также является определением его результирующего набора.

Это позволяет аккуратно избежать проблемы select *, удалив обязанности по формированию данных из ORM.

Например, чтобы выбрать все столбцы:

from c in data.Customers
select c

Чтобы выбрать подмножество:

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

Чтобы выбрать комбинацию:

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
}
4 голосов
/ 15 ноября 2008

Необходимо рассмотреть два отдельных вопроса.

Начнем с того, что при использовании ORM для таблицы и объекта очень разные «формы», это одна из причин, по которой многие инструменты ORM поддерживают довольно сложные отображения.

Хорошим примером является частичная денормализация таблицы со столбцами, содержащими избыточную информацию (часто это делается для повышения производительности запросов или отчетов). Когда это происходит, ORM эффективнее запрашивать только те столбцы, которые ему требуются, чем возвращать и игнорировать все дополнительные столбцы.

Вопрос о том, почему «Выбрать *» является злом, является отдельным, и ответ делится на две половины.

При выполнении команды "select *" сервер базы данных не обязан возвращать столбцы в каком-либо конкретном порядке и фактически может разумно возвращать столбцы в другом порядке каждый раз, хотя практически ни одна база данных не делает этого.

Проблема в том, что когда типичный разработчик замечает, что возвращаемые столбцы выглядят в согласованном порядке, предполагается, что столбцы будут всегда в таком порядке, и тогда у вас будет код, который делает необоснованным предположения, просто жду, чтобы потерпеть неудачу. Хуже того, эта неудача не может быть фатальной, но может просто включать, скажем, использование Год рождения вместо Баланс счета .

Другая проблема с «Select *» связана с владением таблицами - во многих крупных компаниях администратор базы данных контролирует схему и вносит изменения в соответствии с требованиями основных систем. Если ваш инструмент выполняет «select *», вы получаете только текущие столбцы - если администратор базы данных удалил лишний столбец, который вам нужен, вы не получите ошибки, и ваш код может ошибиться, что приведет к всевозможным повреждениям. Явно запрашивая необходимые поля, вы гарантируете, что ваша система сломается, а не обработает неправильную информацию.

4 голосов
/ 15 ноября 2008

Я не уверен, почему вы хотите частично гидратированный объект. Дан класс Customer со свойствами Name, Address, Id. Я бы хотел, чтобы все они создали полностью заполненный объект Customer.

Список клиентов, называемых заказами, можно загружать при доступе через большинство ORM. И NHibernate в любом случае позволяет вам делать проекции на другие объекты. Таким образом, если вы произвели простой список клиентов, в котором отображали идентификатор и имя, вы можете создать объект типа CustomerListDisplay и спроецировать свой HQL-запрос на этот набор объектов и получать только нужные столбцы из базы данных.

Друзья не позволяют друзьям преждевременно оптимизировать. Полностью увлажняйте свой объект, лениво загружайте его ассоциации. А затем профилируйте свое приложение в поисках проблем и оптимизируйте проблемные области.

3 голосов
/ 15 ноября 2008

Даже ORM необходимо избегать, чтобы SELECT * был эффективным, используя ленивую загрузку и т. Д.

И да, SELECT * вообще плохая идея, если вы не используете все данные.

Итак, у вас есть разные типы объектов MyThing, по одному для каждой колонки? - Кори Трейгер (15 ноября в 0:37)

Нет, у меня есть дайджест-объекты только для чтения (которые содержат только важную информацию) для таких вещей, как поиск и массивные коллекции, и по требованию они преобразуются в полностью гидратированные объекты. - Кейд Ру (15 ноября в 1:22)

2 голосов
/ 15 ноября 2008

Случай, который вы описываете, является отличным примером того, как ORM не является панацеей. Базы данных предлагают гибкий, основанный на потребностях доступ к своим данным в основном через SQL. Как разработчик, я могу легко и просто получить все данные (SELECT *) или некоторые данные (SELECT COL1, COL2) по мере необходимости. Мой механизм для этого будет легко понят любому другому разработчику, взявшему на себя управление проектом.

Чтобы получить такую ​​же гибкость от ORM, нужно проделать гораздо больше работы (либо вами, либо разработчиками ORM), чтобы вернуть вас в то место, где вы либо получаете все, либо некоторые столбцов из базы данных по мере необходимости (см. превосходные ответы выше, чтобы понять некоторые проблемы). И все эти дополнительные вещи - просто больше вещей, которые могут дать сбой, делая систему ORM по своей природе менее надежной, чем прямые вызовы SQL.

Это не означает, что вы не должны использовать ORM (мой стандартный отказ от ответственности заключается в том, что все варианты дизайна имеют свои издержки и выгоды, а выбор того или другого просто зависит) - вырубите себя, если это работает для вас. Я скажу, что я действительно не понимаю популярность ORM, учитывая количество лишней забавной работы, которую он, кажется, создает для своих пользователей. Я буду использовать SELECT *, когда (подожду) я должен получить каждый столбец из таблицы.

1 голос
/ 15 ноября 2008

ORM в целом не полагаются на SELECT *, но полагаются на лучшие методы для поиска столбцов, таких как определенные файлы карты данных (Hibernate, варианты Hibernate и Apache iBATIS делают это). Что-то более автоматическое можно настроить, запросив схему базы данных, чтобы получить список столбцов и их типов данных для таблицы. То, как данные заполняются, зависит от конкретной ORM, которую вы используете, и это должно быть там хорошо документировано.

Никогда не рекомендуется выбирать данные, которые вы вообще не используете, так как это может создать ненужную зависимость кода, которую впоследствии будет неудобно поддерживать. Для работы с данными, внутренними для класса, все немного сложнее.

Краткое правило: всегда выбирать все данные, которые класс хранит по умолчанию. В большинстве случаев небольшие накладные расходы не будут иметь большого значения, поэтому ваша главная цель - сократить накладные расходы на обслуживание. Позже, когда вы профилируете производительность кода и у вас есть основания полагать, что он может извлечь пользу из настройки поведения, самое время это сделать.

Если бы я увидел, как ORM делает операторы SELECT *, видимые или скрытые, то я бы посмотрел в другом месте, чтобы удовлетворить мои потребности в интеграции с базой данных.

0 голосов
/ 22 октября 2011

Если вы используете кеширование запросов, выбор * может быть хорошим. Если вы выбираете различный ассортимент столбцов каждый раз, когда вы нажимаете на таблицу, это может быть просто получение кэшированного выбора * для всех этих запросов.

Я думаю, вы путаете цель ORM. ORM предназначен для отображения модели домена или аналога таблицы в базе данных или некоторого соглашения о хранении данных. Оно не предназначено для того, чтобы сделать ваше приложение более вычислительным или даже ожидаемым.

0 голосов
/ 15 ноября 2008

Если вы чувствуете необходимость инкапсулировать все в объекте, но вам нужно что-то с небольшим подмножеством того, что содержится в таблице - определите свой собственный класс. Напишите прямой sql (в пределах или без ORM - большинство разрешают прямой sql обойти ограничения) и заполните ваш объект результатами.

Однако я бы просто использовал представление таблицы ORM в большинстве ситуаций, если только профилирование не скажет мне об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...