LLBLGen vs. NHibernate

18 декабря 2009 г.

Хотел сделать сравнение двух ORM ответом на пост «Встреча казанской UserGroup 9.12.2009». Выписал все плюсы и минусы в таблицу. Взвесил все «за» и «против» и сделал вывод, какая ORM все-таки лучше.

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

Сильные недостатки
Бинарный .lgp

LLBLGen использует редактор с графическим интерфейсом для задания маппингов. Этот редактор создает свой файл проекта (файл с расширением .lgp), в котором хранит все настройки. По неудачному стечению обстоятельств этот файл бинарный. Системы контроля версий не умеют сливать бинарные файлы. Поэтому, если вам надо изменить какую-то настройку в маппинге, придется предупредить всю команду, что вы забираете файл .lgp на редактирование. Когда проектом занималось 2 человека, это не вызывало проблем. Но когда в проекте участвует 5 программистов, это становится реальным ограничением. Приходится ждать, пока файл освободится.

Медленное считывание/записывание свойств

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

   1:  return product.Price > 0;

Обычный вызов get-функции для decimal-значения. Что тут может тормозить? Смотрю код, который сгенерировал LLBLGen для сущности Product:

   1:  public virtual System.Decimal Price
   2:  {
   3:      get { return (System.Decimal)GetValue((int)ProductFieldIndex.Price, true); }
   4:      set { SetValue((int)ProductFieldIndex.Price, value); }
   5:  }

Вместо того, чтобы просто вернуть значение переменной, происходят какие-то шаманские действия. Остальные get/set-функции в свойствах всех сгенерированных объектов выглядят точно также.

Сложное внутренне устройство сущностей

Сгенерированный класс с 10 полями в базе данных и двумя связанными коллекциями обычно содержит 700-900 строк кода. Все это нужно только для того, чтобы LLBLGen мог нормально работать.

К сожалению, особенности внутреннего устройства LLBLGen постепенно переползают за сгенерированный проект модели. Например, вы изменили поле в сущности Product. Теперь, чтобы узнать изменилось ли поле, надо обратиться к свойству IsDirty. Зачем рефлексия, говорят нам на форуме LLBLGen, используйте список Fields. Он содержит все поля сущности с их названиями, текущими и измененными значениями. В конце концов в коде, то там то тут, вылезают различные особенности устройства LLBLGen сущностей.

И это плохо, потому что весь наш проект узнал про ORM.

В добавок к этому приведу список публичных свойств/методов, которые доступны для сгенерированной сущности:

Этот длинный список вообще никакой смысловой нагрузки не несет. Чтобы просто найти свойства класса Product придется потратить изрядно времени.

А если вы захотите передать такой объект сериализованным в JSON, то он придет с полями:

  • CustomProperties
  • FieldsCustomProperties
  • IsDirty
  • IsNew
  • ...

Клиенту, который принимает JSON эти данные не нужны. Все эти публичные поля нужны только для внутренней работы LLBLGen.

Неприятные мелочи

Кроме этого, есть недостатки, с которыми можно было бы продолжать работать. Тем не менее они вызывают раздражение.

Не просто ORM

LLBLGen – это не просто ORM, это целый слой доступа к данным. Вот как они позиционируют свою утилиту:

«LLBLGen Pro generates a complete data-access tier and business objects tier for you.»

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

Тонна кода

Последние подсчеты NDepend показали, что 2/3 нашего проекта состоит из кода сгенерированного LLBLGen.

Вместо LLBLGen

После 1,5 лет использования LLBLGen мы решили перейти на NHibernate. Одна из самых банальная причин перехода – у него нет недостатков LLBLGen'а:

  • Маппинги хранятся прямо в коде. Изменять маппинги можно без проблем всей командой, т.к. это текстовые файлы, они очень просто сливаются системой контроля версий. К счастью, теперь можно задавать маппинги не через XML, а прямо на языке C#;
  • NHibernate использует «чистые» доменные объекты. Соответственно, обращаться к полю через get-функцию ничего не стоит;
  • Опять же «чистые» доменные объекты очень хорошо сериализуются и имеют простой набор публичных свойств/методов.

Плюс ко всему у NHibernate есть очень удобный профайлер. Рекомендую!


Дополнение 1

Мне написал Frans Bouma, создатель LLBLGen. Франс перевел этот пост и написал мне письмо. Он заверил, что в 3-ей версии его ORM проблем, описанных мною, уже не будет. Могу только пожелать ему удачи! Ну, а как 3-ая версия выйдет, там и посмотрим ;)

Дополнение 2

Наш архитектор написал свои мысли по этому поводу.

Моя книга «Антихрупкость в IT»

Как достигать результатов в IT-проектах в условиях неопределённости. Подробнее...