I have an issue where I am trying to re-insert an entity into my database. It is illustrated with the following unit test:
// entity mapped to dbo.IdentityInsertTest table
// dbo.IdentityInsertTest has an IDENTITY Primary Key, Id
var id = (long)NHibernateSession1.Save(new IdentityInsertTest());
NHibernateSession1.Flush();
// delete previously created row
ExecuteNonQuery("DELETE FROM dbo.IdentityInsertTest");
try
{
// set entity insert off so that I can re-insert
NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON").UniqueResult();
// re-create deleted row with explicit Id
NHibernateSession2.Save(new IdentityInsertTest { Id = id });
NHibernateSession2.Flush();
Assert.AreEqual(1, ExecuteScalar("SELECT COUNT(1) FROM dbo.IdentityInsertTest"));
// this assert fails: expected 1, actual 2
Assert.AreEqual(id, ExecuteScalar("SELECT TOP 1 [Id] FROM dbo.IdentityInsertTest"));
}
finally
{
NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest OFF").UniqueResult();
}
My mapping is quite simple:
<class name="IdentityInsertTest" table="IdentityInsertTest">
<id name="Id" type="long">
<generator class="native" />
</id>
<property name="Data" type="int" not-null="false" />
</class>
The issue as far as I can see it is that the NHibernate generator is still somehow invoking the identity generation from SQL, even though I have switched it off. Is there any way around this?
Edit: I had originally forgotten to execute "UniqueResult()" when setting IDENTITY_INSERT, but this does not seem to be the root of the error. Still getting the same results
you are not actually executing your SQLQuery, this should do the trick
IQuery sqlQry = NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON");
object ret = sqlQry.UniqueResult();
Just wondering your logic here regarding deleting/re-adding as opposed to NOT deleting but just updating....
However if NHibernate is getting in your way and you can't change remove Identity column then there are some hideous work arounds...
If you want to add a record at the bottom
then you could try this:-
var sql = "DECLARE @id long = 0;
SELECT @id = MAX(Id) + 1 FROM IdentityInsertTest;
DBCC CHECKIDENT(IdentityInsertTest, RESEED, @id);";
NHibernateSession2.CreateSqlQuery(sql).ExecuteUpdate();
... now save the entity normally
OR If you want to add a record somewhere in the middle
of the table then you will have to build the SQL by hand:-
var sql = "SET IDENTITY_INSERT dbo.IdentityInsertTest ON;
INSERT INTO IdentityInsertTest(Id, Data) Values (:id, :data)
VALUES (:id, :data);
SET IDENTITY_INSERT dbo.IdentityInsertTest OFF;";
NHibernateSession2.CreateSqlQuery(sql)
.SetInt64("id", id)
.SetInt32("data", data)
.ExecuteUpdate();
NOTE: I have marked this as the answer as it directly answers the question, however, in the end I went with a soft delete option as commented above
The problem was that
- I wasn't specifying the Id explicitly in the save method
even if I had, the set identity_insert would have been executed in another query. That one is fixed by using a transaction
// entity mapped to dbo.IdentityInsertTest table
// dbo.IdentityInsertTest has an IDENTITY Primary Key, Id
var id = (long)NHibernateSession1.Save(new IdentityInsertTest());
NHibernateSession1.Flush();
// delete previously created row
ExecuteNonQuery("DELETE FROM dbo.IdentityInsertTest");
try
{
NHibernate.ITransaction txn;
using (txn = SelectSession1.BeginTransaction())
{
// set entity insert off so that I can re-insert
NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON").UniqueResult();
// re-create deleted row with explicit Id
NHibernateSession2.Save(new IdentityInsertTest(), id);
NHibernateSession2.Flush();
txn.Commit();
}
Assert.AreEqual(1, ExecuteScalar("SELECT COUNT(1) FROM dbo.IdentityInsertTest"));
// this assert fails: expected 1, actual 2
Assert.AreEqual(id, ExecuteScalar("SELECT TOP 1 [Id] FROM dbo.IdentityInsertTest"));
}
finally
{
NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest OFF").UniqueResult();
}
Choose another key generation strategy, what you're attempting to do is a really bad idea. An identity column is an artificial primary key and it should not have any meaning.
I have to say, this issue blocked me long time. Even through I exec sql "SET IDENTITY_INSERT dbo.IdentityInsertTest ON" before and then run Nihbernate code, it was stil does not work. there are 2 points need to take more attation.
First, you must use Transaction in you code.
NHibernate.ITransaction txn;
using (txn = SelectSession1.BeginTransaction())
{
NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON").UniqueResult();
...
NHibernateSession2.Flush();
txn.Commit();
}
Second, you must use "Id(x => x.Id).GeneratedBy.Assigned().Column("Id");" in your mapping section.