I ended up writing a little wrapper method that tries to execute a trivial statement as part of the transaction right before committing, which is effective in detecting the problem.
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();
}
The fix is either of Morg.'s or Ryan Culpepper's answers: either run the statement outside of the transaction or create a SAVEPOINT beforehand and ROLLBACK to it on error.