【问题标题】:NHibernate - Insert with identity_insert ONNHibernate - 插入与 identity_insert ON
【发布时间】:2012-11-16 13:52:01
【问题描述】:

我在尝试将实体重新插入数据库时​​遇到问题。用下面的单元测试来说明:

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

我的映射很简单:

<class name="IdentityInsertTest" table="IdentityInsertTest">
    <id name="Id" type="long">
        <generator class="native" />
    </id>

    <property name="Data" type="int" not-null="false" />
</class>

据我所知,问题是 NHibernate 生成器仍然以某种方式从 SQL 调用身份生成,即使我已将其关闭。有没有办法解决这个问题?

编辑:我最初在设置 IDENTITY_INSERT 时忘记执行“UniqueResult()”,但这似乎不是错误的根源。仍然得到相同的结果

【问题讨论】:

  • 您是想在表格中间某处红色添加一个实体,还是想在最后添加一个实体?我真的不明白你的逻辑,你为什么需要这样做?
  • 正如另一条评论中所说,这是在云应用程序中实现撤消/重做逻辑,缺少自然键
  • 如果所有表都是标识列并且您具有数据库完整性,那么为什么密钥必须回到原始状态?我的评论也是在你澄清之前......对不起,但我还是不这样做;'明白你的逻辑
  • 如果我更改了 ID,我必须将其传回给客户端,事情可能会变得非常混乱。只读客户端也无法执行工作

标签: nhibernate identity-insert


【解决方案1】:

您实际上并没有执行您的 SQLQuery,这应该可以解决问题

 IQuery sqlQry = NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON");
 object ret = sqlQry.UniqueResult();

【讨论】:

  • 我已经更改了我的代码以包含它,但它并没有改变结果。不过谢谢指出,以后会出问题的。
【解决方案2】:

只是想知道您在这里关于删除/重新添加而不是不删除而只是更新的逻辑......

但是,如果 NHibernate 妨碍了您并且您无法更改删除身份列,那么有一些可怕的变通方法...

如果你想在bottom 添加一条记录,那么你可以试试这个:-

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

如果您想在表的middle 某处添加一条记录,那么您将不得不手动构建 SQL:-

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

【讨论】:

  • 这个答案为我指明了正确的方向。问题(或其中一个问题)是 NHibernate 并没有像上面那样在并发语句中执行整个事情
【解决方案3】:

注意:我已将此标记为答案,因为它直接回答了问题,但是,最后我使用了上面评论的软删除选项

问题是

  1. 我没有在保存方法中明确指定 Id
  2. 即使我有,set identity_insert 也会在另一个查询中执行。那是通过使用事务来修复的

    // 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】:

    选择另一种密钥生成策略,您尝试做的是一个非常糟糕的主意。标识列是一个人为的主键,它不应该有任何意义。

    【讨论】:

    • 这是现阶段迈出的一大步,我不完全理解您的论点“身份列是人工主键”。您是否知道我可以阅读任何关于此的文章以进行澄清?
    • 谷歌代理与自然主键。自然主键,例如社会安全号码,在现代系统中很少使用。 SQL 标识列是代理键;除了唯一标识记录之外,这种类型的密钥没有任何意义。一个设计良好的系统不应该需要插入具有特定代理键的记录。您要解决的问题是什么?
    • 我明白你在说什么,但这个数据没有自然键。整个问题源于在云(基于浏览器)应用程序中实现备忘录撤消模式。我使用身份作为自然键,因为我没有其他的
    • 好的,那么我建议使用软删除,或者在用户不再有可能撤消操作之前不要实际删除。
    【解决方案5】:

    不得不说,这个问题困扰了我很久。即使通过我之前执行 sql "SET IDENTITY_INSERT dbo.IdentityInsertTest ON" 然后运行 ​​Nihbernate 代码,它仍然无法正常工作。有 2 点需要采取更多的 attation。

    首先,您必须在代码中使用 Transaction。

    NHibernate.ITransaction txn;
    using (txn = SelectSession1.BeginTransaction())
    {
        NHibernateSession2.CreateSQLQuery("SET IDENTITY_INSERT dbo.IdentityInsertTest ON").UniqueResult();
    
        ...
    
        NHibernateSession2.Flush();
        txn.Commit();
    }
    

    其次,必须使用“Id(x => x.Id).GeneratedBy.Assigned().Column("Id");”在您的映射部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-30
      • 1970-01-01
      • 2013-06-25
      • 1970-01-01
      • 2015-07-02
      • 1970-01-01
      • 2010-11-17
      相关资源
      最近更新 更多