пятница, 25 декабря 2009 г.

ORM-style IdentityGenerator or impossible is nothing

То, что я сейчас собираюсь описать, иначе как грязный хак назвать нельзя. Но жизнь сложная штука, и иногда приходится изворачиваться как уж, чтобы добиться своего. А добиться я пытаюсь вот чего: чтобы NHibernate не ломал семантику Unit of Work при работе с классами, Id которых генерится Identity счетчиком в SQL Server.

Прежде всего, советую ознакомиться с историей вопроса:
о вреде Identity генераторов, еще о вреде и мнение в законе. Проблема Identyity генератора в том, что он не может получить Id, не вставив запись в таблицу, а без Id сущность нельза ассоциировать с NHibernate сессией, поэтому как только мы пытаемся связать объект с сессией, он тут же попадает в базу. Отсюда вырастают огромные проблемы в длинных бизнес транзакциях, когда все действия с БД атомарны и выполняются в отдельных БД транзакциях, а сама бизнес операция растянута по времени. Так вот откат бизнес транзакции будет невозможен в случае использования Identity генератора. А это как раз мой случай. Но, как я уже написал выше решение есть, но это грязный хак.

Итак. Прекратив ныть, про хак, перехожу к сути. Мне надо:

  • вставлять свои значения в поля Identity;
  • получать адекватные значения для Identity поля;
В SQL Server можно отключить автоинкрементацию Identity поля и вставить свое значение, например, пропущенное в последовательности (при удалении). А получить его можно, вставив в таблицу запись и удалив ее. Это возможно, потому что при откате транзакции значение счетчика Identity назад не откатывается, таким образом, изменив таблицу, мы перекидываем счетчик, а затем откатив транзакцию, убираем изменения.

Вооружившись этим знанием я нарисовал следующую схему:

Решение, как следует из картинки состоит из 3х частей:
  • Custom Id generator, который я назвал ORMIdentityGenerator;
  • PreInsert listener, для отключения автоинкрементного поля;
  • PostInsert listener, для его включения назад;
ORMIdentityGenerator

Следуя agile принципу KISS - keep it simple, stupid! я в
сего лишь конфигурирую его минимальным INSERT выражением, которое можно исполнить, не повредив базу и не вызвав нарушений целостности.

PreInsert


PostInsert


Вот и все. Теперь я могу спокойно редактировать объект в длинной сессии и откатывать изменения, когда потребуется. PS Простите ребята, гаджетом для вставки кода пока не обзавелся. Удачи!