почему я получаю исключение OracleTruncateException с ODP.NET OracleDataAdapter, но не с System.Data.Адаптер OracleClient при DbDataAdapter.Вызвано обновление?

#c# #.net #oracle #ado.net #odp.net

#c# #.net #Oracle #ado.net #odp.net

Вопрос:

Я делаю следующее:

 protected int CreateComponent(DbConnection cnctn, string tableName)
{
    int newId;

    DbCommand selectCmd = _provFactory.CreateCommand();
    selectCmd.Connection = cnctn;
    selectCmd.CommandText = string.Format(
            "SELECT * FROM {0} WHERE ID = (SELECT MAX(ID) FROM {0})", tableName);

    DbDataAdapter dataAdapter = _provFactory.CreateDataAdapter();
    dataAdapter.SelectCommand = selectCmd;

      ...
    // create Insert/Update/Delete commands with a builder for the data adapter
      ...

    dataAdapter.Fill(_dataSet, tableName);      

    newId = Convert.ToInt32(_dataSet.Tables[tableName].Rows[0]["id"])   1000000;

    DataRow newRow = _dataSet.Tables[tableName].NewRow();
    newRow.ItemArray = _dataSet.Tables[tableName].Rows[0].ItemArray;
    newRow["ID"] = newId;

    _dataSet.Tables[tableName].Rows.Add(newRow); 
}
  

Это отлично работает для OleDb и System.Data.OracleClient. Однако с Oracle.Доступ к данным.Поставщик клиента, которого я получаю:

 Oracle.DataAccess.Types.OracleTruncateException (16550) 
  

с усеченным текстом результата, полученного из:

 at System.Data.Common.DbDataAdapter.UpdatedRowStatusErrors  
at System.Data.Common.DbDataAdapter.UpdatedRowStatus  
at System.Data.Common.DbDataAdapter.Update
at Oracle.DataAccess.Client.OracleDataAdapter.Update  
at System.Data.Common.DbDataAdapter.UpdateFromDataTable  
at System.Data.Common.DbDataAdapter.Update
  

Таблицы, которые я получаю, это большие таблицы, которые содержат 61 поле. Типы всех полей ограничены:

 VARCHAR2(different lenghts)
VARCHAR2(different lenghts) NOT NULL
FLOAT(126) NOT NULL     
NUMBER NOT NULL
DATE
  

Отредактируйте, чтобы предотвратить слишком много комментариев:

-Я не могу изменить тип данных или что-либо еще в базе данных.

-В DataRow эти столбцы с плавающей точкой (126) имеют тип данных System.Десятичный (как при использовании других поставщиков)

-В отличие от того, что я говорил ранее: ID не является первичным ключом. Это уникальный индекс. Таблица не имеет первичного ключа (как определение Oracle) Я должен признать, что я думал, что уникальный индекс является первичным ключом, что может показаться нелепым для людей, знакомых с Oracle. В любом случае я делаю вставку только из 1 строки. Я не пытался вручную создать Insert-command, что я сделаю немного позже. Разработчики команд должны обрабатывать таблицы без PK (http://msdn.microsoft.com/en-us/library/tf579hcz.aspx : «Команда SelectCommand также должна возвращать хотя бы один первичный ключ или уникальный столбец».)

-Это работает также с ODP.NET/Oracle.Доступ к данным.Клиент, если:

  • Я присваиваю всем столбцам с плавающей точкой (126) значение 0 перед последней строкой метода. Даже при присвоении значения 1 или 2 любому возникает такое же исключение, когда DbDataAdapter.Вызывается обновление.

или

  • Я создаю DbDataAdapter.Insertommand сам, и есть только вставка (как код выше), когда DbDataAdapter.Вызывается обновление. Когда я сам создаю cmd, я даю DbParameter.DbType = DbType.Дважды для столбцов с плавающей точкой (126). Если я создаю его сам, принимаются все обычные двойные значения.

app.config:

 <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
  <system.data>
  <DbProviderFactories>
    <add name="Oracle Data Provider for .NET"
            invariant="Oracle.DataAccess.Client"
            description="Oracle Data Provider for .NET"
            type="Oracle.DataAccess.Client.OracleClientFactory,
                  Oracle.DataAccess,
                  Version=2.112.1.0,
                  Culture=neutral,
                  PublicKeyToken=89b483f429c47342" />
  </DbProviderFactories>
  </system.data>
  

есть идеи, в чем причина и как я собираюсь заставить это работать для всех 3 поставщиков?

Спасибо и с наилучшими пожеланиями — Matti

Комментарии:

1. Из моего опыта ODP.NET имеет какое-то странное поведение, а иногда и странные ошибки в зависимости от того, какую версию вы используете и какой клиент Oracle вы используете и т.д. К сожалению, на данный момент System.Data.OracleClient это устарело… Эти вещи приводят меня к использованию коммерческого провайдера… довольно рад, что пока одна ошибка (которая была исправлена немедленно) и никогда не оглядывалась назад…. Хотя я не знаю, подходит ли вам этот вариант…

2. Имеет ли таблица, показывающая это поведение, первичный ключ?

3. да, это «ID». я дополнительно протестировал его и извлек строку maxid, как указано выше, но затем изменил значения новой строки из 1-го столбца с плавающей точкой (126) на последний столбец (NUMBERs и FLOAT (126) s получают 0, VARCHAR2s получают короткую бессмысленную строку, а DATEs получают DateTime. Теперь) и проблем нет! когда я оставляю 1-е поле с плавающей запятой таким, каким оно было, возникает исключение, поэтому оно не допускает значений, которые извлекаются из базы данных!!!

4. Я только что заметил FLOAT (126) — это, конечно, странно, особенно. в базе данных Oracle … вы можете уточнить?

5. попробуйте использовать NUMBER вместо этого — это гораздо более «естественно» для базы данных Oracld и имеет немного более высокую точность (38 вместо 37,9).

Ответ №1:

Во-первых, я думаю, вам следует сообщить об этом Oracle как об ошибке. Ошибка возникает, даже если таблица действительно маленькая. Это не имеет ничего общего с индексами или первичными ключами, ошибка возникает, даже если таблица не имеет индекса. Если вы установите значение ID равным 0, вставка будет выполнена нормально.

Мне удалось создать обходной путь, хотя он и не очень хороший, но может быть достаточно хорошим для вашего случая.

Обходной путь заключается в использовании собственных клиентских классов Oracle для ODP.Net поэтому вам придется проверить, было ли ваше приложение настроено для ODP или одного из других, и соответствующим образом выбрать свой код.

Версия вашей функции «Только для ODP» может выглядеть следующим образом:

     protected void CreateComponentODPOnly(Oracle.DataAccess.Client.OracleConnection cntn, string tableName)
    {
        int newId;

        System.Data.DataSet _dataSet = new DataSet();

        Oracle.DataAccess.Client.OracleCommand selectCmd = new Oracle.DataAccess.Client.OracleCommand();
        selectCmd.Connection = cntn;
        selectCmd.CommandText = string.Format(
                "SELECT * FROM {0} WHERE ID = (SELECT MAX(ID) FROM {0})", tableName);

        Oracle.DataAccess.Client.OracleDataAdapter dataAdapter = new Oracle.DataAccess.Client.OracleDataAdapter();
        Oracle.DataAccess.Client.OracleCommandBuilder cmdBuilder = new Oracle.DataAccess.Client.OracleCommandBuilder();
        dataAdapter.SelectCommand = selectCmd;
        cmdBuilder.DataAdapter = dataAdapter;

        dataAdapter.Fill(_dataSet, tableName);

        newId = Convert.ToInt32(_dataSet.Tables[tableName].Rows[0]["id"])   1000000;

        DataRow newRow = _dataSet.Tables[tableName].NewRow();
        newRow.ItemArray = _dataSet.Tables[tableName].Rows[0].ItemArray;
        newRow["ID"] = (Decimal)newId;

        _dataSet.Tables[tableName].Rows.Add(newRow);
        dataAdapter.InsertCommand = cmdBuilder.GetInsertCommand();
        dataAdapter.Update(_dataSet.Tables[tableName]);
    }
  

Комментарии:

1. Спасибо GTG! Я также сам нашел обходной путь, который я отредактировал в вопросе (создавая сам). Ваш обходной путь достаточно хорош, если он работает! Код уже полон условий, проверяющих поставщика, потому что параметры, схемы и т.д. Обрабатываются по-разному. Я попытаюсь протестировать это сегодня и определенно соглашусь, если это сработает. Еще раз спасибо!

2. еще раз спасибо! это работает, что странно, потому что адаптер данных и конструкторы являются экземплярами OracleDataAdapter и OracleCommandBuilder. есть какие-либо объяснения? Я еще немного протестирую, прежде чем принять.

3. Я провел еще несколько тестов и обнаружил, что проблема, похоже, в commandbuilder. GetInsertCommand() . Мой тест заключался в использовании _provFactory . Создать… для всех объектов и для использования системы. Типы данных данных. Это отлично работает, если вы делаете это следующим образом: DataAdapter. InsertCommand = ((Oracle. Доступ к данным. Клиент. OracleCommandBuilder)cmdBuilder). GetInsertCommand(); Это кажется странным — можно было бы предположить, что это виртуальная функция, поэтому это приведение к типу не должно иметь никакого эффекта.

4. действительно странно. в любом случае проблема «решена». действительно странно, что ошибка в этой обычной операции остается незамеченной (согласно моим поискам в Интернете). возможно, никто на самом деле не использует ODP.NET потому что там полно ошибок.

Ответ №2:

Вы пробовали добавлять PK после заполнения таблицы?

 _dataSet.Tables[tableName].PrimaryKey = (new List<DataColumn>() { _dataSet.Tables[0].Columns["ID"] }).ToArray();
  

Комментарии:

1. что такое CurrentDataSet . Таблицы [0]? но если вы имеете в виду попытку добавить столбец ID как PK, я этого не сделал. Я пытаюсь это сделать.

2. добавление «ID» в качестве PK не имело никакого эффекта : (

3. извините, это должно было быть _dataSet . Я обновил свой ответ, хотя, похоже, вы уже поняли это и попробовали это.