2011-09-23 125 views
3

我在.NET 4.0下使用Npgsql 2.0.11来修改PostgreSQL 9.0数据库。该程序对数据库进行了许多修改,全部在单个事务中进行。Npgsql不会在失败的命令后提交事务

在提交之前,我运行SELECT语句,有时会失败(例如,超时)。我吞下异常并继续并提交交易。没有错误,所以看起来好像一切正​​常,但实际上数据库根本没有修改!

我的猜测是失败的SELECT回滚了整个事务。我是否可以防止这种情况(即让事务仍然提交)或者至少检测到这种情况并抛出异常,以便用户知道提交失败?

我知道在这种特殊情况下,我可以将SELECT移到事务之外,但我更关心解决这个问题的一般情况。提交不提交是一个非常严重的问题,我想确保它不会被检测到。

+0

如果发生错误,事务回滚并AFAIK(我使用另一个ADO.NET提供程序 - 不确定这适用于Npgsql,但是)连接显示“InTransaction == false”之后...所以被调用提交只是在一个“空的交易”... – Yahia

+0

没有'InTransaction',我可以在Npgsql中看到。你使用什么提供商? – EMP

+0

我使用Devart(商业版)中的一个...方法InTransaction是我的代码中的东西...它基于Connection对象的TransactionStateChanged事件的实现... – Yahia

回答

6

我一无所知Npgsql的,但我可以到PostgreSQL的行为说话。当PostgreSQL事务发生任何错误时,事务被标记为无效直到它关闭。 (他们的用语是“流产”,我认为这是误导性的。)此外,这是恕我直言,疯狂,如果你COMMIT无效交易,它“成功”,但具有相同的效果ROLLBACK。您可以在psql REPL中观察到这一点;它将打印ROLLBACK以响应您的COMMIT命令,但不会发出错误信号。

您可以在最终的SELECT之前创建SAVEPOINT。如果失败,则将ROLLBACK保存到保存点名称;这将使您脱离无效状态并允许您提交事务的前一部分。

+0

感谢您的解释,+1。如果没有真正考虑这个问题,我期待PostgreSQL能给我提供和我自动获得SAVEPOINT一样的行为,也就是说。如果任何给定的语句失败,我期望数据库的状态与该语句之前的状态相同,并被允许执行进一步的命令。在另一种情况下,我实现了SAVEPOINT/ROLLBACK机制,但在这种情况下,我只是简单地将最后一个SELECT移到了事务之外。我还为其他类似案例添加了一个检查(代码在我的答案中)。 – EMP

-1

在事务中发生某些事务失败,但事务完成并不会非常事务正确吗?因此,基本上,如果它可能失败,并且你不关心它,那么不要把它放在事务中,而必须不会失败。因为他们注定要被使用,你不会有任何问题,

使用交易;)

+0

这对客户端来说是完全合法的应用程序(而不是ADO.NET提供程序)来解码失败是否需要完全回滚。这是关于特定领域知识的问题,客户端应用程序只是具有这些知识的应用程序。 –

+0

这就是为什么你正确使用交易,保存点和所有。 –

+0

但严重的是,您的评论在这里无效。交易将被用作交易,而不是作为“一揽子查询”,您在其中放置随机无关的东西。这是一项交易,其目的是将包含在一起并且不能以其他方式存在的事情包装在一起。任何应用程序都不会要求在同一个事务中打包一个不重要的选择,因为这是非常重要的插入/更新。 –

0

我最终编写了一个小封装方法,它试图在提交之前执行一个简单的语句作为事务权限的一部分,这对检测问题是有效的。

public static void CommitTransaction(NpgsqlConnection conn, NpgsqlTransaction tran) 
    { 
     using (var command = new NpgsqlCommand("SELECT 1", conn, tran)) 
     { 
      try 
      { 
       command.ExecuteScalar(); 
      } 
      catch (NpgsqlException ex) 
      { 
       if (ex.Code == "25P02") 
        throw new Exception("The transaction is invalid..."); 
       throw; 
      } 
     } 

     tran.Commit(); 
    } 

该修复方法是Morg。's或Ryan Culpepper的回答:或者在事务之外运行语句,或者事先创建SAVEPOINT,并在错误时创建SAVEPOINT。