代码之家  ›  专栏  ›  技术社区  ›  Nicholas Piasecki

确保在调用自定义nhibernate iIdentifier生成器后插入

  •  5
  • Nicholas Piasecki  · 技术社区  · 15 年前

    设置

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

    我写了一篇 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最终会多次生成相同的id, 因为它还没有真正保存任何东西。

    其他的纤维发电机像 IncrementGenerator 似乎可以通过向数据库请求seed值一次,然后递增该值来解决这个问题 在记忆中 在同一会话中的后续调用期间。如果必须的话,我宁愿不在实现中这样做,因为我需要的所有代码都已经在数据库中了,只是等待我正确地调用它。

    • 有没有办法让nhibernate在每次调用后发出insert,为某个类型的实体生成id?修改批大小设置似乎没有帮助。
    • 除了在内存中重新实现生成代码或将某些触发器固定到遗留数据库之外,您还有什么建议/其他解决方法吗?我想我可以一直把它们当作“分配的”生成器,并试图在域模型的内部隐藏这个事实。

    谢谢你的建议。

    更新:2个月后

    下面的答案建议我使用 IPreInsertEventListener 以实现此功能。虽然这听起来很合理,但也有一些问题。

    第一个问题是设置 id 一个实体的 AssignedGenerator 然后不在代码中实际分配任何内容(因为我期待新的 IPReinsertEventListener 执行此项工作的实现)导致 分配发电机 ,因为它 Generate() 方法实际上只做检查,以确保 身份证件 不为空,否则引发异常。通过创建自己的 识别发生器 这就像 分配发电机 无一例外。

    第二个问题是从新的 识别发生器 (我写这封信是为了克服 分配发电机 导致nhibernate内部抛出异常,抱怨生成了空id。好吧,好吧,我换了我的 识别发生器 要返回一个sentinel字符串值,请说“not-real-the-real-id”,知道 IPReinsertEventListener 会用正确的值替换它。

    第三个问题,也是最终的交易破坏者,是 IPReinsertEventListener 在这个过程中运行得太晚,以至于您需要更新实际的实体对象以及nhibernate使用的状态值数组。 Typically this is not a problem and you can just follow Ayende's example. 但有三个问题 身份证件 IPreInsertEventListeners :

    • 财产不在 @event.State 但在它自己的数组中 Id 财产。
    • 这个 身份证件 属性没有public set 存取器。
    • 只更新实体,而不更新 身份证件 属性导致“not-real-the-real-id”sentinel值传递到数据库,因为 IPReinsertEventListener 无法插入正确的位置。

    所以我在这一点上的选择是使用反射来获取nhibernate属性,或者是坐下来说“看,这个工具并不是这样使用的。”

    所以我回到了我原来的 IIdentifierGenreator 并使其适用于惰性刷新:它在第一次调用时从数据库中获得了较高的值,然后我在c中重新实现了用于后续调用的id生成函数,在 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 发电机,我们不妨称之为定时炸弹发电机。如果有多个工作进程在非序列化事务中执行此代码,或者如果有多个实体映射到同一个数据库表(这是一个旧数据库,已发生),则我们将获得具有相同 lastGenerated 种子值,导致重复的标识。

    “@”$ $@ $ @。

    此时,我的解决方案是使生成器缓存成为 WeakReference S to ISessions 及其 上次生成 价值观。这边,那个 上次生成 在一个特定的 ISession ,而不是 识别发生器 ,因为我拿着 WeakReferences 在每一个开始的时候 广义() 呼叫,这不会在内存消耗爆炸。从每一个 遗赠 在第一次调用数据库表时,我们将获得必要的行锁(假设我们在事务中),我们需要防止重复的标识发生(如果它们发生了,比如从虚行中,只有 遗赠 需要扔掉,而不是整个过程)。

    这很难看,但比改变一个10年前的数据库的主键方案更可行。FWW。


    [1]如果您想知道id的生成,您可以将当前pk列中的所有值的一个子串(len-2)转换为整数并找到最大值,向该数字中添加一个,添加该数字的所有数字,然后将这些数字的和作为校验和追加。(如果数据库有一行包含“1000001”,那么我们将得到最大10000,+1等于10001,校验和是02,结果新的主键是“1000102”。 别问我为什么。

    2 回复  |  直到 14 年前
        1
  •  1
  •   Jamie Ide    15 年前

    一种可能的解决方法是在事件侦听器中生成和分配id,而不是使用iIdentifierGenerator实现。监听器应该实现ipreinserteventlistener并在onpreinsert中分配id。

        2
  •  -2
  •   Dan    14 年前

    为什么不把私有字符串设为lastGenerated;static?