Как увеличить идентификатор перед любой вставкой с помощью NHibernate

#nhibernate #auto-increment

#nhibernate #автоматическое увеличение

Вопрос:

Похоже, что NH получает MAX (ID) только один раз, сначала вставляет, а затем сохраняет это значение внутри, это вызывает у меня некоторые проблемы, когда другие процессы вставляют данные. Тогда у меня нет фактического идентификатора, и возникает исключение с дубликатом ключа.

Давайте представим, что у нас есть таблица Cats

 CREATE TABLE Cats(ID int, Name varchar(25))
  

Затем мы выполняем соответствующее сопоставление с помощью FluentNHibernate

 public class CatMap : ClassMap<Cat>
{
    public CatMap()
    {
      Id(m=>m.ID).GeneratedBy.Increment();
      Map(m=>.Name);
    }
}
  

Все, чего я хочу добиться, это вставить мои Cat записи с идентификаторами, сгенерированными с помощью NHibernate, используя SELECT MAX(ID) FROM Cats перед любой вставкой. Выполнение сеанса.Сброс после любой фиксации не работает. Я провел некоторое расследование с помощью профилировщика SQL Server, и этот sql stetement выполняется только один раз (при первой вставке) — другие вставки не заставляют восстанавливать фактический MAX (ID). Я знаю, что другие алгоритмы, такие как HiLo, лучше, но я не могу его заменить.

Ответ №1:

Как вы выяснили, генератор идентификаторов приращения NHibernate не был предназначен для использования в многопользовательской среде. Вы заявляете, что использование генератора HiLo не является вариантом, поэтому у вас остаются следующие параметры:

  • используйте собственный генератор и измените столбец id, чтобы использовать механизм идентификации, поддерживаемый базой данных

  • используйте назначенный генератор и напишите код для определения следующего допустимого идентификатора

  • создайте пользовательский генератор, в котором вы реализуете интерфейс IIdentifierGenerator, чтобы делать то, что вам нужно

Ниже приведен пример кода для пользовательского генератора, который использует обобщенную процедуру для получения идентификатора для данной таблицы. Основная проблема с этим подходом заключается в том, что вы должны обернуть код во что-то вроде шаблона единицы работы, чтобы гарантировать, что ‘select max (id) …» и вставка покрываются одной и той же транзакцией базы данных. Ссылка IIdentifierGenerator содержит сопоставление XML, необходимое для подключения этого пользовательского генератора.

 using System;
using System.Collections.Generic;
using System.Data;
using NHibernate.Dialect;
using NHibernate.Engine;
using NHibernate.Id;
using NHibernate.Persister.Entity;
using NHibernate.Type;

namespace YourCompany.Stuff
{
    public class IdGenerator : IIdentifierGenerator, IConfigurable
    {
        private string _tableName;
        // The "select max(id) ..." query will go into this proc:
        private const string DefaultProcedureName = "dbo.getId";

        public string ProcedureName { get; protected set; }
        public string TableNameParameter { get; protected set; }
        public string OutputParameter { get; protected set; }

        public IdGenerator()
        {
            ProcedureName = DefaultProcedureName;
            TableNameParameter = "@tableName";
            OutputParameter = "@newID";
        }

        public object Generate(ISessionImplementor session, object obj)
        {
            int newId;
            using (var command = session.Connection.CreateCommand())
            {
                var tableName = GetTableName(session, obj.GetType());

                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = ProcedureName;

                // Set input parameters
                var parm = command.CreateParameter();
                parm.Value = tableName;
                parm.ParameterName = TableNameParameter;
                parm.DbType = DbType.String;

                command.Parameters.Add(parm);

                // Set output parameter
                var outputParameter = command.CreateParameter();
                outputParameter.Direction = ParameterDirection.Output;
                outputParameter.ParameterName = OutputParameter;
                outputParameter.DbType = DbType.Int32;

                command.Parameters.Add(outputParameter);

                // Execute the stored procedure
                command.ExecuteNonQuery();

                var id = (IDbDataParameter)command.Parameters[OutputParameter];

                newId = int.Parse(id.Value.ToString());

                if (newId < 1)
                    throw new InvalidOperationException(
                        string.Format("Could not retrieve a new ID with proc {0} for table {1}",
                                      ProcedureName,
                                      tableName));
            }

            return newId;
        }

        public void Configure(IType type, IDictionary<string, string> parms, Dialect dialect)
        {
            _tableName = parms["TableName"];
        }

        private string GetTableName(ISessionImplementor session, Type objectType)
        {
            if (string.IsNullOrEmpty(_tableName))
            {
                //Not set by configuration, default to the mapped table of the actual type from runtime object:
                var persister = (IJoinable)session.Factory.GetClassMetadata(objectType);

                var qualifiedTableName = persister.TableName.Split('.');
                _tableName = qualifiedTableName[qualifiedTableName.GetUpperBound(0)]; //Get last string
            }

            return _tableName;
        }
    }
}
  

Комментарии:

1. Сделано путем реализации пользовательского генератора идентификаторов.

2. @Dariusz Я столкнулся с аналогичной проблемой, не могли бы вы предоставить дополнительную информацию о том, как вы это реализовали?

3. Как я уже сказал, я внедрил свой пользовательский IdGenerator @nozzleman