确保在调用自定义NHibernate IIIdentifierGenerator后插入 设置
我们数据库中的一些“旧”表使用了一种奇特的主键生成方案[1],我试图用NHibernate覆盖数据库的这一部分。这个生成方案主要隐藏在一个名为ShootMeInTheFace.getnextseedided的存储过程中 我编写了一个调用此存储过程的确保在调用自定义NHibernate IIIdentifierGenerator后插入 设置,nhibernate,nhibernate-mapping,legacy,Nhibernate,Nhibernate Mapping,Legacy,我们数据库中的一些“旧”表使用了一种奇特的主键生成方案[1],我试图用NHibernate覆盖数据库的这一部分。这个生成方案主要隐藏在一个名为ShootMeInTheFace.getnextseedided的存储过程中 我编写了一个调用此存储过程的IIdentifierGenerator: public class LegacyIdentityGenerator : IIdentifierGenerator, IConfigurable { // ... snip ... pub
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?摆弄批量大小设置似乎没有帮助
- 除了在内存中重新实现生成代码或在遗留数据库上附加一些触发器之外,您还有什么建议/其他解决方法吗?我想我总是可以将这些作为“指定的”生成器,并试图在域模型的内部隐藏这一事实
IPreInsertEventListener
来实现此功能。虽然这听起来很合理,但也存在一些问题
第一个问题是,将实体的id
设置为AssignedGenerator
,然后不在代码中实际分配任何内容(因为我希望我的新IPreInsertEventListener
实现能够完成这项工作),导致AssignedGenerator
引发异常,因为它的Generate()
方法基本上什么也不做,只是检查以确保id
不是null,否则会引发异常。通过创建我自己的IIdentifierGenerator
(类似于AssignedGenerator
),这非常容易解决
第二个问题是从新的IIdentifierGenerator
(我为解决AssignedGenerator
的问题而编写的那篇文章导致NHibernate的内部抛出异常,抱怨生成了空id。好吧,好吧,我更改了我的IIdentifierGenerator
以返回一个哨兵字符串值,比如说,“NOT-REAL-the-REAL-id”,知道myIPreInsertEventListener
将用正确的值替换它
第三个问题,也是最终的交易破坏者,是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会话
需要重复身份)