Hello,
In converting a legacy database application to use NHibernate, I have to cope with its identity generation. It's a MSSQL2000 database, and it uses a one-record table called MISeq to record the last ID generated (very similar to an Oracle sequence), which is a sequentially-incremented integer. This generated ID guarantees uniqueness across several tables, which are distinctly different objects in my new model.
What is the best approach for me to do this? Is there any way to get one of the built-in generators to do this for me?
I tried writing a custom IIdentifierGenerator so that new instances of my objects can get the next value from the MISeq table, but by the time my IIdentifierGenerator.Generate() method gets called, I'm already inside a pending transaction (an ITransaction). This causes an exception in the Generate() method, which is attempting to execute a stored procedure (MISEQ_U) that does the work of incrementing the ID for me. Here's the VB.NET code I'm using to execute the stored procedure:
Code:
Public Class MISeqIdentifierGenerator
Implements NHibernate.Id.IIdentifierGenerator
Public Function Generate(ByVal session As NHibernate.Engine.ISessionImplementor, ByVal entity As Object) As Object Implements NHibernate.Id.IIdentifierGenerator.Generate
If (entity Is Nothing) Then Throw New ArgumentNullException("entity")
If (Not TypeOf entity Is IMISeqIdentifier) Then Throw New ArgumentException("must be IMISeqIdentifier", "entity")
MsgBox("generating MISeq ID")
Dim conn As IDbConnection = session.Connection
Dim cmd As IDbCommand = conn.CreateCommand()
cmd.CommandText = "EXEC MISEQ"
Dim param As IDbDataParameter = cmd.CreateParameter()
param.Direction = ParameterDirection.Output
param.DbType = DbType.Int32
cmd.Parameters.Add(param)
cmd.ExecuteNonQuery()
Dim miseq As Integer = CInt(param.Value)
cmd.Dispose()
Return miseq
End Function
End Class
And here's the logged exception and stack trace:
Code:
2007-07-11 18:19:46,317 [1796] WARN NHibernate.Util.ADOExceptionReporter - System.InvalidOperationException: Execute requires the command to have a transaction object when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.
at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean executing)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at NHibernateTest.MISeqIdentifierGenerator.Generate(ISessionImplementor session, Object entity) in C:\data\NHibernateTest\core\MISeqIdentifierGenerator.vb:line 21
at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)
In case you need it, here is the mapping file:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernateTest" assembly="NHibernateTest"
default-access="nosetter.camelcase-underscore" default-lazy="false">
<class name="MITask" table="TASK">
<id name="TaskSeq" type="Int32">
<generator class="NHibernateTest.MISeqIdentifierGenerator, NHibernateTest" />
</id>
<property name="TaskDescription" type="String" />
<property name="TaskResults" type="String" />
<many-to-one name="Recommendation" class="MIRecommendation" column="RecommendationSeq" />
</class>
</hibernate-mapping>
Am I going about this the right way? Does one of the built-in generators do what I need (and I missed it!)? Thanks for your help!
David McClelland