EF + ODP.NET + CLOB = Значение не может быть пустым - Имя параметра: byteArray?

Наш проект недавно обновлен до более новых библиотек DLL Oracle.ManagedDataAccess (v 4.121.2.0), и эта ошибка периодически возникала. Мы исправили это несколько раз, даже не зная, что мы сделали, чтобы это исправить.

Я вполне уверен, что это вызвано тем, что поля CLOB отображаются на строки в Entity Framework, а затем выбираются в операторах LINQ, которые извлекают целые объекты вместо ограниченного набора свойств.

Ошибка:

Value cannot be null.
Parameter name: byteArray

Трассировки стека:

   at System.BitConverter.ToString(Byte[] value, Int32 startIndex, Int32 length)
   at OracleInternal.TTC.TTCLob.GetLobIdString(Byte[] lobLocator)
   at OracleInternal.ServiceObjects.OracleDataReaderImpl.CollectTempLOBsToBeFreed(Int32 rowNumber)
   at Oracle.ManagedDataAccess.Client.OracleDataReader.ProcessAnyTempLOBs(Int32 rowNumber)
   at Oracle.ManagedDataAccess.Client.OracleDataReader.Read()
   at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()

Подозреваемые свойства объекта:

'Mapped to Oracle CLOB Column'
<Column("LARGEFIELD")>
Public Property LargeField As String

Но я уверен, что это правильный способ сопоставить поля в соответствии с матрицей Oracle:

Обзор типов ODP.NET

Также нет ничего плохого в сгенерированном операторе SQL:

SELECT 
...
"Extent1"."LARGEFIELD" AS "LARGEFIELD",
...
FROM ... "Extent1"
WHERE ...

Я также попробовал этот свободный код в соответствии с предложением Озкана, но, похоже, это не влияет на мой случай.

modelBuilder.Entity(Of [CLASS])().Property(
    Function(x) x.LargeField
).IsOptional()

Обновление для устранения неполадок:

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

6 ответов

После установки клиента Oracle12 мы столкнулись с той же проблемой. В machine.config (C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config) я удалил все записи с помощью Oracle.ManagedDataAccess. В каталоге C:\Windows\Microsoft.NET\assembly\GAC_MSIL я удалил как Oracle.ManagedDataAccess, так и Policy.4.121.Oracle.ManagedDataAccess. Затем моя программа на C# начала работать как обычно, используя dll Oracle.ManagedDataAccess в своем собственном каталоге.

Я потратил много времени, пытаясь расшифровать это, и нашел кое-что из этого и этого здесь и там в Интернете, но нигде не было всего в одном месте, поэтому я хотел бы опубликовать то, что я узнал, и как я решил это, что очень похоже на ответ Ragowit, но у меня есть код C# для этого.

Фон

Ошибка: так у меня была эта ошибка на моем while (dr.Read()) линия:

Value cannot be null. \r\nParmeter name: byteArray

Я столкнулся с этим очень мало в интернете, за исключением того, что это была ошибка с CLOB поле, когда оно было нулевым и предположительно было исправлено в последнем выпуске ODAC, в соответствии со следующим: https://community.oracle.com/thread/3944924

Мой взгляд на это - НЕ ПРАВДА! Он не обновлялся с 5 октября 2015 г. ( http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html), а используемый мной пакет 12c был загружен в апреле 2016 г.

Полная трассировка стека кем-то другим с ошибкой, которая в значительной степени отражает мою: http://pastebin.com/24AfFDnq

Value cannot be null.
Parameter name: byteArray

at System.BitConverter.ToString(Byte[] value, Int32 startIndex, Int32 length)
at OracleInternal.TTC.TTCLob.GetLobIdString(Byte[] lobLocator)
at OracleInternal.ServiceObjects.OracleDataReaderImpl.CollectTempLOBsToBeFreed(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.ProcessAnyTempLOBs(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.Read()
at     System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()

'Mapped to Oracle CLOB Column'
<Column("LARGEFIELD")>
Public Property LargeField As String

'Mapped to Oracle BLOB Column'
<Column("IMAGE")>
Public Property FileContents As Byte()

Как я с этим столкнулся: это было при чтении таблицы из 11 столбцов, содержащей около 3000 строк. Одна из колонн была на самом деле NCLOB (по-видимому, это так же восприимчив, как CLOB), которая допускает пустые значения в базе данных, а некоторые ее значения остаются пустыми - это было необязательное поле "Примечания", в конце концов. Забавно, что я не получил эту ошибку в первом или даже втором ряду с пустым полем Notes. Это не ошибка, пока строка 768 не закончилась, и он собирался начать строку 769, в соответствии с int переменная counter, которая началась с 0, которую я установил и увидел после проверки, сколько строк в моей DataTable было до сих пор. Я обнаружил, что получил ошибку, если использовал:

DataSet ds = new DataSet();
OracleDataAdapter adapter = new OracleDataAdapter(cmd);
adapter.Fill(ds);

а также если бы я использовал:

DataTable dt = new DataTable();
OracleDataReader dr = cmd.ExecuteReader();
dt.Load(dr);

или если я использовал:

OracleDataReader dr = cmd.ExecuteReader();
if (dr.HasRows)
{
     while (dr.Read())
     {
         ....
     }
}

где cmd это OracleCommand, так что это не имеет значения.

разрешение

Ниже приведен код, который я использовал для анализа OracleDataReader значения для того, чтобы назначить их DataTable, Это на самом деле не так хорошо, как могло бы быть - я использую его, чтобы просто вернуть dr[i] в datarow во всех случаях, кроме случаев, когда значение равно нулю и когда это одиннадцатый столбец (index = 10, потому что он начинается с 0), и был выполнен определенный запрос, так что я знаю, где мой NCLOB столбец есть.

public static DataTable GetDataTableManually(string query)
{
    OracleConnection conn = null;
    try
    {
        string connString = ConfigurationManager.ConnectionStrings["MyConn"].ConnectionString;
        conn = new OracleConnection(connString);
        OracleCommand cmd = new OracleCommand(query, conn);
        conn.Open();
        OracleDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
        DataTable dtSchema = dr.GetSchemaTable();
        DataTable dt = new DataTable();

        List<DataColumn> listCols = new List<DataColumn>();
        List<DataColumn> listTypes = new List<DataColumn>();

        if (dtSchema != null)
        {
            foreach (DataRow drow in dtSchema.Rows)
            {
                string columnName = System.Convert.ToString(drow["ColumnName"]);
                DataColumn column = new DataColumn(columnName, (Type)(drow["DataType"]));
                listCols.Add(column);
                listTypes.Add(drow["DataType"].ToString());   // necessary in order to record nulls
                dt.Columns.Add(column);
            }
        }

        // Read rows from DataReader and populate the DataTable
        if (dr.HasRows)
        {
            int rowCount = 0;
            while (dr.Read())
            {            
                string fieldType = String.Empty;
                DataRow dataRow = dt.NewRow();

                for (int i = 0; i < dr.FieldCount; i++)
                {
                    if (!dr.IsDBNull[i])
                    {
                        fieldType = dr.GetFieldType(i).ToString(); // example only, this is the same as listTypes[i], and neither help us distinguish NCLOB from NVARCHAR2 - both will say System.String

                        // This is the magic
                        if (query == "SELECT * FROM Orders" && i == 10)
                            dataRow[((DataColumn)listCols[i])] = dr.GetOracleClob(i);  // <-- our new check!!!!
                        // Found if you have null Decimal fields, this is 
                        // also needed, and GetOracleDecimal and GetDecimal
                        // will not help you - only GetFloat does
                        else if (listTypes[i] == "System.Decimal")
                            dataRow[((DataColumn)listCols[i])] = dr.GetFloat(i);
                        else
                            dataRow[((DataColumn)listCols[i])] = dr[i];
                    }
                    else  // value was null; we can't always assign dr[i] if DBNull, such as when it is a number or decimal field
                    {
                        byte[] nullArray = new byte[0];
                        switch (listTypes[i])
                        {
                            case "System.String": // includes NVARCHAR2, CLOB, NCLOB, etc.
                                dataRow[((DataColumn)listCols[i])] = String.Empty;
                            break;
                            case "System.Decimal":
                            case "System.Int16":  // Boolean
                            case "System.Int32":  // Number
                                dataRow[((DataColumn)listCols[i])] = 0;
                            break;
                            case "System.DateTime":
                                dataRow[((DataColumn)listCols[i])] = DBNull.Value;
                            break;
                            case "System.Byte[]":  // Blob
                                dataRow[((DataColumn)listCols[i])] = nullArray;
                            break;
                            default:
                                dataRow[((DataColumn)listCols[i])] = String.Empty;
                            break;
                        }
                   }
               }
               dt.Rows.Add(dataRow);
           }
           ds.Tables.Add(dt);
       }

    }
    catch (Exception ex)
    {
        // handle error
    }
    finally
    {
        conn.Close();
    }

    // After everything is closed
    if (ds.Tables.Count > 0)
        return ds.Tables[0]; // there should only be one table if we got results
    else
        return null;

}

Точно так же, как у меня назначается определенный тип null на основе типа столбца, найденного в цикле таблицы схемы, вы можете добавить условия к стороне "not null" if...then и делать разные GetOracle... заявления там. Я нашел, что это было необходимо только для этого NCLOB экземпляр, хотя.

Чтобы отдать должное, когда это необходимо, исходная кодовая база основана на ответе, данном sarathkumar в таблице Заполнить данные из устройства чтения данных.

Мы встретили эту проблему в нашем проекте час назад и нашли решение. Он генерирует эту ошибку из-за пустых значений в колонке CLOB. У нас есть столбец CLOB, и он может быть пустым в базе данных. В модели EntityFramework это String, но не Nullable. Мы изменили свойство столбца Nullable на True в модели EF, и это устранило проблему.

У нас также есть эта проблема на некоторых компьютерах, и мы используем последнюю версию Oracle.ManagedDataAccess.dll (4.121.2.20150926 ODAC RELEASE 4).

Мы нашли решение нашей проблемы, и я просто хотел поделиться.

Это была наша проблема, возникшая на некоторых компьютерах.

Using connection As New OracleConnection(yourConnectionString)
    Dim command As New OracleCommand(yourQuery, connection)
    connection.Open()

    Using reader As OracleDataReader = command.ExecuteReader()
        Dim clobField As String = CStr(reader.Item("CLOB_FIELD"))
    End Using

    connection.Close()
End Using

И вот решение, которое заставило его работать на всех компьютерах.

Using connection As New OracleConnection(yourConnectionString)
    Dim command As New OracleCommand(yourQuery, connection)
    connection.Open()

    Using reader As OracleDataReader = command.ExecuteReader()
        Dim clobField As String = reader.GetOracleClob(0).Value
    End Using

    connection.Close()
End Using

Решено обновить Oracle.ManagedDataAccess.dll до версии 4.122.1.0. Если вы используете vs 2017, мы можем обновить через NuGet.

Для меня это было просто! У меня была эта ошибка с odac v 4.121.1.0. Я только что обновил Oracle.ManagedDataAccess до 4.121.2.0 с помощью Nuget, и теперь он работает.

Вы пытались удалить и переустановить Oracle.ManagedDataAccess с помощью Nugget?

Другие вопросы по тегам