#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