【问题标题】:Force query execution without flush/commit在没有刷新/提交的情况下强制执行查询
【发布时间】:2010-02-16 17:51:33
【问题描述】:

您好,我正在为 asp.net Web 应用程序使用每请求事务(视图中的会话)模式。我在应用程序中有几点要保存 NHibernate 托管实体,然后使用通用 sql 进行更多插入和更新。这些插入/更新取决于 NH 保存的实体将采用的 ID。

问题是生成的 id 不存在于事务的范围内。如果我强制刷新/提交,则 id 将保留,但如果插入/更新失败,我必须回滚,但刷新/提交的实体不会。目前我正在为这些情况进行手动插入,但这是我想要改变的。那么,有没有办法在 Save() 之后执行 SQL 语句(在已经打开的事务中)但不强制刷新/提交?

编辑:我正在添加一个半伪代码示例,我得到了 4 个错误答案,所以我认为人们不明白(NHibernate 是如何工作的) 在开始请求我发出一个

nhsession.BeginTransaction()

然后在某个时候我会这样做

FooClass fc = new FooClass("value");
nhsession.Save(fc);
ITransaction trans = nhsession.Transaction;
SqlCommand sc = new SqlCommand("some insert/update query that depends on fc's id", (SqlConnection)nhsession.Connection);
sc.Parameters.Add("id", fc.Id); //NHibernate generates the id, note i'm using assigned/hi-lo so no round trip to the db takes place
transaction.Enlist(sc);
try {
    sc.ExecuteNonQuery();
}
catch (SqlException ex){
    transaction.RollBack();
    nhsession.Close();
}

在请求结束时我发出CommitTransaction()nhsession.Close()

现在这将完全没有任何作用:FooClass (fc) 尚未刷新/提交到数据库。 NH 已经完成的Save() 操作在内存中达到了这一点。这意味着 nhibernate 没有发出任何 sql 命令,这意味着我之后触发的 SqlCommand (sc) 将失败,因为 id 不存在。

如果我在Save()SqlCommandFooClass(fc) _cannot_be_rolled_back_ 之间进行刷新/提交,这是一件坏事。 目前,为了让它工作,我使用SqlCommand 进行 vanila sql 插入,我想改变它。 (为什么?因为我不想制作香草插入,它们容易因架构/模型更改而出错,我为此得到了 OR/M)

怎么样?我想通知 NHibernate 以某种方式执行 SqlCommand 以对应于 Save() 插入(地狱,它可以执行它收集的所有 SqlCommands)但没有它提交或刷新!。

目前我还在搜索 nhibernate 在刷新/提交保存的对象时生成的准备好的 sql 语句。也许我可以只使用该字符串并在事务中登记的 SqlCommand 中运行它。

【问题讨论】:

  • @Jaguar:我想我对这种情况的理解存在差距。我猜 Save() 方法为您提供了由 NHibernate 生成的 Id,无论它是 hi/lo 还是身份或其他。你不能只使用这个返回值吗?我不记得了,但我认为它是返回的持久对象。然后,调用 Save() 将提供您需要的 Id 值,然后在您的其他 SQL 语句 (sc) 中使用该值。否则,使用 ISession.CreateSQLQuery() 方法代替这个 SqlCommand 不是更好吗?这种方法可以让你直接对 DB 运行 SQL 语句
  • 将您的 Save() 语句包含在您的事务中是否会有所帮助?那么当 Save() 返回 Id 时,您可以将其用于其他 SQL DML 语句吗?然后 Flush() 会话中的所有内容,因此如果您的 Insert (Save()) 失败,这将由您的事务的 Rollback() 方法自动回滚?

标签: c# asp.net nhibernate transactions


【解决方案1】:

也许我不明白,但你不能这样做......

using(var tx = session.BeginTransaction())
{
    session.Save(entity);
    session.Flush();

    //perform non NH operations with entity.id

    tx.Commit();
}//uncommitted transaction would be rolled back on dispose()

【讨论】:

  • 我仍然认为 Flush 会在这里做你想做的事。将插入语句显式刷新到数据库,而不是等待提交调用刷新。
  • 你不能回滚刷新的对象
  • 插入将被回滚。回滚刷新的对象是什么意思?
  • 在您的示例中,如果非 NH 操作由于某种原因失败,则 tx.Rollback() 调用不会删除您保存的实体,因为您已刷新它。并且(这么说)至少这是我尝试过的 NH2.0 及更高版本的行为:一旦刷新/提交,您将无法恢复更改
  • 我不知道该告诉你什么,除了 session.Flush 没有提交事务并且插入确实会回滚。
【解决方案2】:

您不能使用嵌套事务或事务范围来支持这一点吗?

本质上,您要求 nh 在使用之前分配 id,生成的 id 仅在刷新更改时分配,更改仅在事务边界处刷新,因此您不能创建内部事务范围来包装 nh相关的持久性强制刷新和 id 生成,但仍然允许外部事务失败并在其中一个存储过程失败时回滚所有工作。

How do I do nested transactions in NHibernate? 有一些示例代码演示了使用范围的方法。

【讨论】:

  • 非常有趣,乍一看它似乎符合要求,我给它+1,直到我可以测试它是否真的有效
【解决方案3】:

听起来您在事务期间需要“脏读”隔离级别。这将允许您阅读“未提交”但“已保存”的内容。但是,我在 NHibernate 中不够流利,无法提供有关如何在那里进行操作的任何见解。

我确信其他人可以了解如何为 NH 设置“脏读”隔离级别。

【讨论】:

  • 其实没有。在事务中完成的任何操作在该事务中都是可见的。我的隔离级别是 ReadCommited(并且会保持这种状态)。除此之外,请阅读问题:NH 的 Save() 在 NH 的会话被刷新/提交之前不会执行插入,所以即使我有脏读,我仍然不会得到任何结果。
【解决方案4】:

我在 NHibernate 之上使用 Castle ActiveRecord,但原理应该是相同的,我的 DTC 已禁用,所以我不肯定这可行,但这应该是正确的方向。

Imports Castle.ActiveRecord
imports NHibernate.Expression

<ActiveRecord> _
Public Class Test1
    Inherits ActiveRecordBase(Of Test1)

    Private _id As Guid
    <PrimaryKeyAttribute(PrimaryKeyType.Guid)> _
    Public overridable Property Id as Guid
        Get
            return _id
        End Get

        Set(value As guid)
            _id = value
        End Set
    End Property


    Private _prop1 As String
    <[Property]> _
    Public overridable Property prop1 as String
        Get
            return _prop1
        End Get

        Set(value As String)
            _prop1 = value
        End Set
    End Property

End Class

<ActiveRecord> _
Public Class Test2
    Inherits ActiveRecordBase(Of Test2)

    Private _id As Guid
    <PrimaryKey(PrimaryKeyType.Guid)> _
    Public overridable Property Id as Guid
        Get
            return _id
        End Get

        Set(value As guid)
            _id = value
        End Set
    End Property

    Private _prop1 As String
    <[Property]> _
    Public overridable Property prop1 as String
        Get
            return _prop1
        End Get

        Set(value As String)
            _prop1 = value
        End Set
    End Property

    Private _t1 As Test1
    <BelongsTo()> _
    Public overridable Property t1 as Test1
        Get
            return _t1
        End Get

        Set(value As test1)
            _t1 = value
        End Set
    End Property


End Class

Imports Castle.ActiveRecord
Imports NHibernate.Expression
Imports System.Data.SqlClient
Imports System.Transactions
imports System.Configuration

Public Module Module1
    Public Class modMain
        Public Shared Sub Main()
            Castle.ActiveRecord.ActiveRecordStarter.Initialize()
            Using T As New System.Transactions.TransactionScope(ondispose.Rollback)
            Dim x As New Test1()
            x.prop1 = "Hello"
            x.Save

            using c As New SqlConnection()
                c.ConnectionString = ConfigurationManager.ConnectionStrings("Development").ConnectionString
                Dim cmd As SqlCommand = c.CreateCommand()
                cmd.CommandText = "insert into test2(prop1, t1) values(@prop1, @t1)"
                Dim p As SqlParameter = cmd.CreateParameter()
                p.Direction = parameterdirection.Input
                p.DbType = dbtype.Guid
                p.ParameterName = "@prop1"
                p.Value = "Test"

                cmd.Parameters.Add(p)
                p = cmd.CreateParameter()
                p.Direction = parameterdirection.Input
                p.DbType = dbtype.Guid
                p.ParameterName = "@t1"
                p.Value = x.Id

                c.Open
                cmd.ExecuteNonQuery
            end using

            t.Complete
            end using

        End Sub
    End Class
End Module

【讨论】:

  • 我不确定 NHibernate 事务,但从我理解的方式来看,你正在做的事情应该可以正常工作。你可以在一个简单的查询窗口中有效地做同样的事情,它会起作用,我不确定到底是什么让你的失败。我会启动并运行您的示例以复制问题,看看我能找到什么。您是否尝试过使用 System.Transactions.Transactionscope 而不是 NHibernate 事务?
  • 再次,ISession.Save() 方法不进行任何插入(它只是下令引擎对传递的对象执行保存)。只有当我们调用 ISession.Flush() 时它才会这样做。但是,刷新的对象不能回滚。
【解决方案5】:

您是否尝试过使用 NHibernate 的直接 SQL 功能?可以使用 ISession.CreateSQLQuery("native SQL here"),而不是闯入 NHibernate 对您一无所知的 SQLCommand。如果您在您的 Save 方法之后在事务中执行此操作,NHibernate 应该在 Commit 发生时按顺序执行所有语句。因此:

using(var tx = session.BeginTransaction())
{
    try
    {
        FooClass fc = new FooClass("value");
        nhsession.Save(fc);

        var nativeQuery = nhsession.CreateSQLQuery("some insert/update query that depends on fc's id");

        // Add parameters to nativeQuery using SetXXX methods
        nativeQuery.SetXXX(...);

        // Save nativeQuery
        nativeQuery.Save(); // I think...

        tx.Commit();
    }
    catch (...)
    {
        tx.Rollback();
    }
}

有关使用 NH 本地查询的另一个类似问题,请参阅此内容:Hibernate NHibernate - Native SQL

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-01
    • 2015-09-28
    • 2015-12-29
    • 2017-01-12
    • 2018-11-10
    • 2019-09-19
    • 1970-01-01
    • 2015-11-28
    相关资源
    最近更新 更多