NHibernate - Insert with identity_insert ON

2019-06-02 21:59发布

问题:

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

回答1:

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();


回答2:

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();


回答3:

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

  1. I wasn't specifying the Id explicitly in the save method
  2. 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();
    }
    


回答4:

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.



回答5:

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.