Хотел сделать сравнение двух 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 есть очень удобный профайлер. Рекомендую!
Мне написал Frans Bouma, создатель LLBLGen. Франс перевел этот пост и написал мне письмо. Он заверил, что в 3-ей версии его ORM проблем, описанных мною, уже не будет. Могу только пожелать ему удачи! Ну, а как 3-ая версия выйдет, там и посмотрим ;)
Наш архитектор написал свои мысли по этому поводу.