Обеспечение вставки после вызова пользовательского NHibernate IIdentifierGenerator - PullRequest
5 голосов
/ 02 апреля 2010

Настройка

Некоторые из «старых, старых» таблиц нашей базы данных используют экзотическую схему генерации первичного ключа [1], и я пытаюсь наложить эту часть базы данных с помощью NHibernate. Эта схема генерации в основном скрыта в хранимой процедуре, которая называется, скажем, ShootMeInTheFace.GetNextSeededId '.

Я написал IIdentifierGenerator, который вызывает этот сохраненный процесс:

public class LegacyIdentityGenerator : IIdentifierGenerator, IConfigurable
{
    // ... snip ...
    public object Generate(ISessionImplementor session, object obj)
    {
        var connection = session.Connection;

        using (var command = connection.CreateCommand())
        {
            SqlParameter param;

            session.ConnectionManager.Transaction.Enlist(command);

            command.CommandText = "ShootMeInTheFace.GetNextSeededId";
            command.CommandType = CommandType.StoredProcedure;

            param = command.CreateParameter() as SqlParameter;
            param.Direction = ParameterDirection.Input;
            param.ParameterName = "@sTableName";
            param.SqlDbType = SqlDbType.VarChar;
            param.Value = this.table;
            command.Parameters.Add(param);

            // ... snip ...

            command.ExecuteNonQuery();

            // ... snip ...

            return ((IDataParameter)command
                .Parameters["@sTrimmedNewId"]).Value as string);
     }
}

Проблема

Я могу отобразить это в файлах сопоставления XML, и это прекрасно работает, НО ....

Не работает, когда NHibernate пытается пакетировать вставки, например, в каскаде, или когда сеанс не обрабатывается Flush() после каждого вызова Save() для временной сущности, которая зависит на этом генераторе.

Это потому, что NHibernate, похоже, делает что-то вроде

for (each thing that I need to save)
{
    [generate its id]
    [add it to the batch]
}

[execute the sql in one big batch]

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

Другие генераторы NHibernate, такие как IncrementGenerator, похоже, обходят это, запрашивая у базы данных начальное значение один раз, а затем увеличивая значение в памяти во время последующих вызовов в том же сеансе. Я бы предпочел не делать этого в моей реализации, если бы пришлось, поскольку весь код, который мне нужен, уже находится в базе данных, просто ожидая, чтобы я его правильно вызвал.

  • Есть ли способ заставить NHibernate фактически выдавать INSERT после каждого вызова для генерации идентификатора для сущностей определенного типа? Вращение с настройками размера пакета, похоже, не помогает.
  • Есть ли у вас какие-либо предложения / другие обходные пути, кроме повторной реализации кода генерации в памяти или использования некоторых триггеров для устаревшей базы данных? Я думаю, я всегда мог бы рассматривать их как «назначенные» генераторы и пытаться как-то скрыть этот факт в рамках модели домена ....

Спасибо за любой совет.

Обновление: через 2 месяца

В ответах ниже было предложено использовать IPreInsertEventListener для реализации этой функции. Хотя это звучит разумно, с этим было несколько проблем.

Первая проблема заключалась в том, что установка id сущности на AssignedGenerator и затем фактически не присваивание чего-либо в коде (поскольку я ожидал, что моя новая реализация IPreInsertEventListener выполнит эту работу) привела к исключению выдается AssignedGenerator, так как его метод Generate() по сути не делает ничего, кроме проверки, чтобы убедиться, что id не равен NULL, в противном случае выдается исключение. Это достаточно легко обойти, создав мой собственный IIdentifierGenerator, который похож на AssignedGenerator без исключения.

Вторая проблема заключалась в том, что возвращение нулевого значения из моего нового IIdentifierGenerator (тот, который я написал для преодоления проблем с AssignedGenerator, привел к тому, что внутренности NHibernate выдавали исключение, жалуясь на то, что был сгенерирован нулевой идентификатор. хорошо, я изменил свой IIdentifierGenerator, чтобы он возвращал строковое значение, скажем, "NOT-REALLY-THE-REAL-ID", зная, что мой IPreInsertEventListener заменит его на правильное значение.

Третья проблема и окончательное прерывание сделки заключалось в том, что IPreInsertEventListener выполняется настолько поздно, что вам необходимо обновить как реальный объект сущности, так и массив значений состояний, которые использует NHibernate. Обычно это не проблема, и вы можете просто следовать примеру Айенде. Но есть три проблемы с полем id, относящиеся к IPreInsertEventListeners:

  • Свойство находится не в массиве @event.State, а в собственном свойстве Id.
  • Свойство Id не имеет общедоступного set метода доступа.
  • Обновление только сущности, но не свойства Id, приводит к тому, что дозорное значение NOT-REALLY-THE-REAL-ID передается в базу данных, поскольку IPreInsertEventListener не удалось вставить в нужных местах.

Таким образом, в этот момент я решил использовать рефлексию, чтобы получить доступ к этому свойству NHibernate, или по-настоящему сесть и сказать: «Посмотрите, инструмент просто не должен был использоваться таким образом».

Итак, я вернулся к своему исходному IIdentifierGenreator и заставил его работать для отложенных сбросов: он получил высокое значение из базы данных при первом вызове, а затем я повторно реализовал эту функцию генерации идентификатора в C # для последующих вызовов, моделируя это после генератора Increment:

private string lastGenerated;

public object Generate(ISessionImplementor session, object obj)
{
    string identity;

    if (this.lastGenerated == null)
    {
         identity = GetTheValueFromTheDatabase();
    }
    else
    {
         identity = GenerateTheNextValueInCode();
    }

    this.lastGenerated = identity;

    return identity;
}

Некоторое время это работает нормально, но, как и генератор increment, мы могли бы также назвать его TimeBombGenerator. Если есть несколько рабочих процессов, выполняющих этот код в несериализуемых транзакциях, или если есть несколько сущностей, сопоставленных с одной и той же таблицей базы данных (это старая база данных, это произошло), то мы получим несколько экземпляров этого генератора с одинаковым lastGenerated начальное значение, что приводит к дублированию идентификаторов.

@ # $ @ # $ @. * * 1092

На этом этапе я решил сделать кэш генератора словарём от WeakReference s до ISessions и их lastGenerated значений. Таким образом, lastGenerated эффективно зависит от времени жизни конкретного ISession, а не от времени жизни IIdentifierGenerator, и потому что я держу WeakReferences и отбираю их в начале каждого Generate() вызов, это не взорвется в потреблении памяти. И так как каждый ISession будет попадать в таблицу базы данных при первом вызове, мы получим необходимые блокировки строк (при условии, что мы находимся в транзакции), нам нужно предотвратить возникновение дублирующихся идентификаторов (и если они это сделают, такие как из фантомного ряда, нужно выбросить только ISession, а не весь процесс).

Это некрасиво, но более осуществимо, чем изменение схемы первичного ключа 10-летней базы данных. FWIW.

<ч />

[1] Если вы хотите узнать о генерации идентификатора, вы берете подстроку (len - 2) из ​​всех значений, находящихся в данный момент в столбце PK, приводите их к целым числам и находите максимум, добавляете одно к этому числу добавьте все цифры этого числа и добавьте сумму этих цифр в качестве контрольной суммы. (Если в базе данных есть одна строка, содержащая «1000001», то мы получим максимум 10000, +1 равно 10001, контрольная сумма - 02, в результате новый PK будет «1000102». Не спрашивайте меня, почему.

Ответы [ 2 ]

1 голос
/ 02 апреля 2010

Возможный обходной путь - генерировать и назначать идентификатор в прослушивателе событий, а не использовать реализацию IIdentifierGenerator. Слушатель должен реализовать IPreInsertEventListener и назначить идентификатор в OnPreInsert.

0 голосов
/ 28 ноября 2011

Почему бы вам не сделать приватную строку lastGenerated; Статическая

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