Ошибка выбора столбца, недавно вставленного с помощью ALTER TABLE в ODP.NET

1- Подключитесь к Oracle XE с помощью Oracle.DataAccess.

2- Выполните команду для добавления столбца в таблицу: alter table TABLE add COLUMN b int;

3- Выполните команду, чтобы выбрать этот столбец

4- Чтение с помощью DataReader. Приложение вызывает IndexOutOfRangeException: невозможно найти указанный столбец в наборе результатов

5- Перезапустите приложение, и запрос будет выполнен правильно.

Почему DataReader не может получить доступ к столбцу, который я только что создал?

Вот большой, но простой код для тестирования:

private void button1_Click(object sender, EventArgs e)
{
    using (OracleConnection con = new OracleConnection(Settings.Default.CS))
    {
        con.Open();
        try
        {
            using (OracleCommand com = new OracleCommand())
            {
                com.Connection = con;

                // Create a test table
                com.CommandText = "CREATE TABLE Test (a int)";
                com.ExecuteNonQuery();

                // Add one column
                com.CommandText = "ALTER TABLE Test ADD b int";
                com.ExecuteNonQuery();

                com.CommandText = "SELECT * FROM Test";
                using (DbDataReader dr = com.ExecuteReader())
                {
                    MessageBox.Show(dr.FieldCount.ToString());
                    // Here is showing "2", thats ok
                }
            }
        }
        finally
        {
            con.Close();
        }
    }
}

private void button2_Click(object sender, EventArgs e)
{
    using (OracleConnection con = new OracleConnection(Settings.Default.CS))
    {
        con.Open();
        try
        {
            using (OracleCommand com = new OracleCommand())
            {
                OracleTransaction trans = con.BeginTransaction();
                try
                {
                    // Add a column to table already created
                    com.Connection = con;
                    com.CommandText = "ALTER TABLE Test ADD c int";
                    com.ExecuteNonQuery();

                    // Insert a value, ok
                    com.CommandText = "INSERT INTO TEST (a, b, c) VALUES (1, 2, 3)";
                    com.ExecuteNonQuery();

                    trans.Commit();
                }
                catch
                {
                    trans.Rollback();
                    throw;
                }

                // Selecting only "c" column
                com.CommandText = "SELECT c FROM Test";
                using (DbDataReader dr = com.ExecuteReader())
                {
                    if (dr.Read())
                        MessageBox.Show(Convert.ToInt32(dr["c"]).ToString());
                        // Showing correct value, ok
                }

                // Uncomment these lines to solve problem
                //con.Close();
                //OracleConnection.ClearAllPools();
                //con.Open();

                // Selecting all fields * from table
                com.CommandText = "SELECT * FROM Test";
                using (DbDataReader dr = com.ExecuteReader())
                {
                    MessageBox.Show(dr.GetSchemaTable().Rows.Count.ToString() + " / " + dr.FieldCount.ToString());
                    // HERE IS THE PROBLEM: message are showing 2/2, but table haves 3 fields

                    if (dr.Read())
                        MessageBox.Show(Convert.ToInt32(dr["c"]).ToString());
                    // Here throws IndexOutOfRangeException: Unable to find specified column in result set
                }
            }
        }
        finally
        {
            con.Close();
        }
    }
}

person rkawano    schedule 17.04.2012    source источник
comment
Для меня это звучит как какое-то кэширование метаданных. Может, поможет метод OracleConnection.PurgeStatementCache()?   -  person Randy supports Monica    schedule 18.04.2012
comment
Я пробовал PurgeStatementCache после добавления столбца и перед выбором, но не работал.   -  person rkawano    schedule 18.04.2012
comment
OracleTransaction должен быть удален и поэтому должен иметь свой собственный блок using. Вы можете использовать OracleCommand.CreateCommand, так как это избавит вас от необходимости назначьте соединение команде самостоятельно.   -  person WhiteKnight    schedule 19.04.2012


Ответы (2)


Похоже, что транзакция вставки еще не зафиксирована, когда вы выполняете считыватель. Я думаю, вы могли бы проверить это, закрыв соединение сразу после вставки и снова открыв его непосредственно перед запуском считывателя (вам может потребоваться использовать OracleConnection.ClearAllPools)

person Ulises    schedule 17.04.2012
comment
В нашем реальном приложении мы используем транзакции, и поведение точно такое же. Повторное открытие соединения не разрешается, но разрешается перезапуск приложения. - person rkawano; 18.04.2012
comment
Включает ли это вызов OracleConnection.ClearAllPools? Если вы, ребята, используете пул соединений, фреймворк сохранит объект соединения в пуле, даже когда вы вызываете close - person Ulises; 18.04.2012
comment
ClearAllPools сработал! Я выполняю команду, чтобы добавить столбец, закрыть соединение, выполнить clearAllPools и снова открыть соединение. Работал нормально, и мне не нужно перезапускать все приложения. Спасибо! - person rkawano; 18.04.2012
comment
Я рад это слышать, пул соединений — это прекрасно, но иногда это может быть болью в заднице :) - person Ulises; 18.04.2012

Попробуйте использовать OracleConnection.BeginTransaction для создания транзакции, а затем Commit перед выбором, как показано в пример в документации.

person WhiteKnight    schedule 18.04.2012
comment
Я пытался начать транзакцию перед добавлением столбца и зафиксировать после вставки значений, но тоже не работал. - person rkawano; 18.04.2012
comment
Не могли бы вы отредактировать свой пример, чтобы показать использование транзакций? - person WhiteKnight; 18.04.2012
comment
Спасибо. Это может не иметь никакого значения, но не могли бы вы попробовать использовать объект OracleTransaction, возвращенный BeginTransaction, и, возможно, обновить его в OracleCommand, например. com.Transaction = con.BeginTransaction();? - person WhiteKnight; 18.04.2012