确保在调用自定义NHibernate IIIdentifierGenerator后插入 设置

确保在调用自定义NHibernate IIIdentifierGenerator后插入 设置,nhibernate,nhibernate-mapping,legacy,Nhibernate,Nhibernate Mapping,Legacy,我们数据库中的一些“旧”表使用了一种奇特的主键生成方案[1],我试图用NHibernate覆盖数据库的这一部分。这个生成方案主要隐藏在一个名为ShootMeInTheFace.getnextseedided的存储过程中 我编写了一个调用此存储过程的IIdentifierGenerator: public class LegacyIdentityGenerator : IIdentifierGenerator, IConfigurable { // ... snip ... pub

我们数据库中的一些“旧”表使用了一种奇特的主键生成方案[1],我试图用NHibernate覆盖数据库的这一部分。这个生成方案主要隐藏在一个名为ShootMeInTheFace.getnextseedided的存储过程中

我编写了一个调用此存储过程的
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尝试批处理插入时,例如在级联中,或者每次调用依赖于此生成器的瞬态实体上的
Save()
后会话未被
Flush()
调用时,它不起作用

那是因为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只会得到多次生成的相同ID,因为它实际上还没有保存任何内容

其他NHibernate生成器(如
IncrementGenerator
)似乎通过向数据库询问种子值一次,然后在同一会话中的后续调用中增加内存中的值来解决这个问题。如果必须的话,我宁愿在我的实现中不这样做,因为我需要的所有代码都已经存在于数据库中,只是等待我正确地调用它

  • 有没有办法让NHibernate在每次调用生成特定类型实体的ID后实际发出INSERT?摆弄批量大小设置似乎没有帮助
  • 除了在内存中重新实现生成代码或在遗留数据库上附加一些触发器之外,您还有什么建议/其他解决方法吗?我想我总是可以将这些作为“指定的”生成器,并试图在域模型的内部隐藏这一事实
谢谢你的建议

更新时间:2个月后 下面的答案建议我使用
IPreInsertEventListener
来实现此功能。虽然这听起来很合理,但也存在一些问题

第一个问题是,将实体的
id
设置为
AssignedGenerator
,然后不在代码中实际分配任何内容(因为我希望我的新
IPreInsertEventListener
实现能够完成这项工作),导致
AssignedGenerator
引发异常,因为它的
Generate()
方法基本上什么也不做,只是检查以确保
id
不是null,否则会引发异常。通过创建我自己的
IIdentifierGenerator
(类似于
AssignedGenerator
),这非常容易解决

第二个问题是从新的
IIdentifierGenerator
(我为解决
AssignedGenerator
的问题而编写的那篇文章导致NHibernate的内部抛出异常,抱怨生成了空id。好吧,好吧,我更改了我的
IIdentifierGenerator
以返回一个哨兵字符串值,比如说,“NOT-REAL-the-REAL-id”,知道my
IPreInsertEventListener
将用正确的值替换它

第三个问题,也是最终的交易破坏者,是
IPreInsertEventListener
在流程中运行得太晚,以至于您需要更新实际的实体对象以及NHibernate使用的状态值数组。但是
id
字段与
IPreInsertEventListeners相关有三个问题e> :

  • 属性不在
    @event.State
    数组中,而是在其自己的
    Id
    属性中
  • Id
    属性没有公共
    set
    访问器
  • 仅更新实体而不更新
    Id
    属性会导致将“not-REAL-the-REAL-Id”sentinel值传递到数据库,因为
    IPreInsertEventListener
    无法插入正确的位置
所以在这一点上,我的选择是使用反射来获得NHibernate属性,或者真正坐下来说“看,这个工具不应该以这种方式使用。”

因此,我回到了我最初的
IIdentifierGenreator
并使其适用于惰性刷新:它在第一次调用时从数据库中获得了较高的值,然后我在C#中为后续调用重新实现了ID生成函数,在
增量
生成器之后对此进行建模:

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
种子值,从而产生重复的标识

@#$@#$@

此时,我的解决方案是将生成器缓存为
ISessions
及其
lastGenerated
值的
WeakReference
字典。这样一来,
lastGenerated
实际上是特定
IIdentifierGenerator
生存期的局部>,并且因为我在每次调用
Generate()
的开始处保留
WeakReferences
并剔除它们,所以这不会导致内存消耗激增。而且由于每个
ISession
在第一次调用时都会命中数据库表,我们将获得必要的行锁(假设我们在事务中)我们需要防止重复身份的发生(如果重复身份发生,例如来自幻影行,则只有
i会话
需要重复身份)