C# NHibernate HiLo生成器生成重复Id';s

C# NHibernate HiLo生成器生成重复Id';s,c#,sql-server-2008,nhibernate,C#,Sql Server 2008,Nhibernate,我有一个在nHibernate v4.0.4.4000上运行的应用程序-它在三个独立的Web服务器上运行。对于ID生成,我使用默认的HiLo实现(跨表的唯一ID) 有时,在使用以下堆栈跟踪保存新实体时,它会生成重复的Id: at NHibernate.AdoNet.SqlClientBatchingBatcher.DoExecuteBatch(IDbCommand ps) at NHibernate.AdoNet.AbstractBatcher.ExecuteBatchWithTiming(ID

我有一个在nHibernate v4.0.4.4000上运行的应用程序-它在三个独立的Web服务器上运行。对于ID生成,我使用默认的HiLo实现(跨表的唯一ID)

有时,在使用以下堆栈跟踪保存新实体时,它会生成重复的Id:

at NHibernate.AdoNet.SqlClientBatchingBatcher.DoExecuteBatch(IDbCommand ps)
at NHibernate.AdoNet.AbstractBatcher.ExecuteBatchWithTiming(IDbCommand ps)
at NHibernate.AdoNet.AbstractBatcher.ExecuteBatch()
at NHibernate.AdoNet.AbstractBatcher.PrepareCommand(CommandType type, SqlString sql, SqlType[] parameterTypes)
at NHibernate.AdoNet.AbstractBatcher.PrepareBatchCommand(CommandType type, SqlString sql, SqlType[] parameterTypes)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session)
at NHibernate.Action.EntityInsertAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at Xena.Database.Main.Listeners.Strategies.CreateEntityAuditTrailStrategy.Execute(Object criteria) in K:\Projects\Xena\WorkDir\src\Xena.Database.Main\Listeners\Strategies\CreateEntityAuditTrailStrategy.cs:line 41
at Xena.Domain.Rules.Strategies.StrategyExtensions.Execute[TCriteria](IEnumerable`1 strategies, TCriteria criteria) in K:\Projects\Xena\WorkDir\src\Xena.Domain\Rules\Strategies\RelayStrategy.cs:line 55
at NHibernate.Action.EntityInsertAction.PostInsert()
at NHibernate.Action.EntityInsertAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultAutoFlushEventListener.OnAutoFlush(AutoFlushEvent event)
at NHibernate.Impl.SessionImpl.AutoFlushIfRequired(ISet`1 querySpaces)
at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results)
at NHibernate.Impl.CriteriaImpl.List(IList results)
at NHibernate.Impl.CriteriaImpl.UniqueResult[T]()
at Xena.Web.EntityUpdaters.LedgerPostPreviewUpdater.TryCreateNewFiscalEntity(ISession session, FiscalSetup fiscalSetup, LedgerPostPreview& entity, IEnumerable`1& errors) in K:\Projects\Xena\WorkDir\src\Xena.Web\EntityUpdaters\LedgerPostPreviewUpdater.cs:line 52
at Xena.Web.SecurityContext.<>c__DisplayClass8_0`1.<TrySaveUpdate>b__0(ISession session, TEntity& entity, IEnumerable`1& errors) in K:\Projects\Xena\WorkDir\src\Xena.Web\SecurityContext.cs:line 235
at Xena.Web.SecurityContext.<>c__DisplayClass41_0`1.<TrySave>b__0(ITransaction tx) in K:\Projects\Xena\WorkDir\src\Xena.Web\SecurityContext.cs:line 815
at Xena.Web.SecurityContext.TryWrapInTransaction[T](Func`2 action, T& result, IEnumerable`1& errors, Boolean alwaysCommit) in K:\Projects\Xena\WorkDir\src\Xena.Web\SecurityContext.cs:line 804
at Xena.Web.SecurityContext.TrySave[TEntity](IEntityUpdater`1 entityUpdater, EntityCreate`1 create) in K:\Projects\Xena\WorkDir\src\Xena.Web\SecurityContext.cs:line 812
at Xena.Web.SecurityContext.TrySaveUpdate[TEntity](IFiscalEntityUpdater`1 entityUpdater) in K:\Projects\Xena\WorkDir\src\Xena.Web\SecurityContext.cs:line 236
at Xena.Web.Api.XenaFiscalApiController.WrapSave[TEntity,TDto](IFiscalEntityUpdater`1 updater, Func`2 get, Action`2 postGet) in K:\Projects\Xena\WorkDir\src\Xena.Web\Api\Abstract\XenaFiscalApiController.cs:line 35
at Xena.Web.Api.ApiLedgerPostPreviewController.Post(LedgerPostPreviewDto ledgerPostPreview) in K:\Projects\Xena\WorkDir\src\Xena.Web\Api\ApiLedgerPostPreviewController.cs:line 79
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
SessionFactory设置为使用快照隔离,DB设置为compability level 2008(100)

据我所知,hilo值的更新是在一个与“正常”事务不同的事务中运行的(我尝试过引发一个异常-hilo值没有回滚(这是有意义的))

根据NHibernate探查器,针对服务器运行的hilo值SQL为:

Reading high value: 
select next_hi
from   hibernate_unique_key with (updlock, rowlock)
Updating high value: 
update hibernate_unique_key
set    next_hi = 5978 /* @p0 */
where  next_hi = 5977 /* @p1 - next_hi */
我错过了什么?HiLo不应该防止重复吗

编辑:重复ID不仅发生在一个表上,而且发生在插入和删除非常频繁的表中。上面的代码是嫌疑犯中最简单的,而且非常简单-它只需要
.Get()
一个父级来检查它是否存在,然后在新实体上创建并调用
.Save()
,以及一个审计跟踪行(使用nHibernate中的PostInsert eventlistener)

EDIT2:上述类型的Id映射(用于所有实体):

publicstaticvoidmapid(此TMapping映射)
其中t映射:类映射
其中TType:class,IHasId
{
mapping.Id(m=>m.Id,m=>m.Generator(Generators.HighLow,g=>g.Params(new{max_lo=100}));
}
奇怪的是(由于@Dexions注释),当我检查审计跟踪和表时,没有任何内容被持久化。用于持久化的代码如下所示:

using (var tx = Session.BeginTransaction())
{
    try
    {
        var voucherPreview = Session.Get<VoucherPreview>(voucherPreviewId); //Parent
        var postPreview = //Factory create with the voucherPreview;
        var index = Session.QueryOver<LedgerPostPreview>()
            .Where(lpp => lpp.VoucherPreview == voucherPreview)
            .SelectList(l => l.SelectMax(lpp => lpp.Index))
            .SingleOrDefault<int>() + 1
        postPreview.Index = index;
        // Set a few other properties and check validity
        Session.SaveOrUpdate(postPreview);
    }
    catch(Exception ex)
    {
        //Errorhandling leading to the above stacktrace
    }
}
使用(var tx=Session.BeginTransaction())
{
尝试
{
var voucherPreview=Session.Get(voucherPreviewId);//父
var postReview=//使用voucherPreview创建工厂;
var index=Session.QueryOver()
.Where(lpp=>lpp.VoucherPreview==VoucherPreview)
.SelectList(l=>l.SelectMax(lpp=>lpp.Index))
.SingleOrDefault()+1
指数=指数;
//设置一些其他属性并检查有效性
会议.保存或更新(后期审查);
}
捕获(例外情况除外)
{
//导致上述堆栈跟踪的错误处理
}
}

鉴于对这个问题的评论链,目前我可以想到两种可能的情况

当实际的ID生成发生在给定实例上时(因为数据库上的ID序列是事务隔离的),您可能会错误地处理nhibernate会话,并出现隐藏的竞争条件。这假定相同的应用程序实例成功插入了{ID=123},然后尝试插入另一个具有{ID=123}的对象。您可以将插入跟踪回应用程序实例,以验证插入的重复是否发生在同一实例上。我不太确定这种情况在整个NHibernate管道链上是否可行,但ISession不是线程安全的(这是一个已知的事实)。您确实说过它已经运行了4年了(尽管您没有提到该bug是否存在这么久),所以最近的一次提交可能引入了这种行为(我相信一个collection.AsParallel()就足以触发它了)


问题的另一个角度是,假设已插入的对象已加载,然后从ISession上分离,但(由于设计或意外)重新连接到(相同/不同)ISession上,然后立即尝试插入对象。这可能会发生,一个假设的场景可能会发生

  • var entity123=Get(123)
  • var entity123=entity123.Clone()或ISession.execute(entity123)
  • 在调用SaveOrUpdate(entity123)的过程中的某个地方(或者更糟糕的是,如果您使用级联保存规则将其添加到引用的集合中,则跟踪会更糟)
  • NHibernate看到一个带有标识符的托管对象,尝试 插入它
  • 在NHibernate的一些早期版本中,我确实看到了非身份插入的这种行为

    上述情况也可能发生在复制标识符的坏/哑工厂方法中


    要跟踪此项,请检查插入SQL参数(对于log4net,它将是一个带有debug的NHibernate.SQL条目,尽管我认为NHibernate探查器也将公开它)是否与现有行的列值匹配。如果它们完全匹配,则可能会发生类似上述情况。如果它们部分匹配,可能是您创建了实体的部分副本,并且它也错误地复制了ID。

    如果您只是更改为:

    postPreview.Index = index+1;
    

    我解决了这个问题。事实证明,这与身份证无关


    作为insert语句的一部分,我们更新一个控制数字序列的辅助表。如果辅助表遇到快照隔离故障,则会出现问题-因为所有内容都在nHibernate的SqlCommandSet内部处理-错误会在链中冒泡,并带有错误描述。

    是否所有应用程序实例上的hi/lo配置值都设置为完全相同的值?是-所有设置为相同的值-我们在所有三个实例上运行相同的代码,并且值是在代码中设置的。因此,考虑到您提供的SQL(来自profiler),是什么阻止两个事务读取相同的下一个高值?并发插入?您能否找到重复项是否由同一应用程序插入?请注意。ISession不是线程安全的,但这里的一切都发生在一个线程中的一个事务中。这是一个原始的插入,因此没有。获取所讨论的对象(因为我们在插入之后首先知道Id,所以不可能猜测)。我们在代码库的这一特定部分中不使用.Merge或.execute,对象既没有集合,也不是集合的一部分。Id属性仅由
    using (var tx = Session.BeginTransaction())
    {
        try
        {
            var voucherPreview = Session.Get<VoucherPreview>(voucherPreviewId); //Parent
            var postPreview = //Factory create with the voucherPreview;
            var index = Session.QueryOver<LedgerPostPreview>()
                .Where(lpp => lpp.VoucherPreview == voucherPreview)
                .SelectList(l => l.SelectMax(lpp => lpp.Index))
                .SingleOrDefault<int>() + 1
            postPreview.Index = index;
            // Set a few other properties and check validity
            Session.SaveOrUpdate(postPreview);
        }
        catch(Exception ex)
        {
            //Errorhandling leading to the above stacktrace
        }
    }
    
    postPreview.Index = index+1;