Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF + ODP.NET + CLOB = Value Cannot be Null - Parameter name: byteArray?

Our project recently updated to the newer Oracle.ManagedDataAccess DLL's (v 4.121.2.0) and this error has been cropping up intermittently. We've fixed it a few times without really knowing what we did to fix it.

I'm fairly certain it's caused by CLOB fields being mapped to strings in Entity Framework, and then being selected in LINQ statements that pull entire entities instead of just a limited set of properties.

Error:

Value cannot be null.
Parameter name: byteArray

Stack Trace:

   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()

Suspect Entity Properties:

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

But I'm confident this is the proper way to map the fields per Oracle's matrix:

ODP.NET Types Overview

There is nothing obviously wrong with the generated SQL statement either:

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

I have also tried this Fluent code per Ozkan's suggestion, but it does not seem to affect my case.

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

Troubleshooting Update:

After extensive testing, we are quite certain this is actually a bug, not a configuration problem. It appears to be the contents of the CLOB that cause the problem under a very specific set of circumstances. I've cross-posted this on the Oracle Forums, hoping for more information.

like image 949
Tom Halladay Avatar asked Feb 18 '15 20:02

Tom Halladay


4 Answers

After installation of Oracle12 client we ran into the same problem. In machine.config (C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config) I removed all entries with Oracle.ManagedDataAccess. In directory C:\Windows\Microsoft.NET\assembly\GAC_MSIL I removed both Oracle.ManagedDataAccess and Policy.4.121.Oracle.ManagedDataAccess. Then my C# program started working as usually, using the Oracle.ManagedDataAccess dll in it's own directory.

like image 80
CasaSpider Avatar answered Oct 24 '22 07:10

CasaSpider


I spent a lot of time trying to decipher this and found bits of this and that here and there on the internet, but no where had everything in one place, so I'd like to post what I've learned, and how I resolved it, which is much like Ragowit's answer, but I've got the C# code for it.

Background

The error: So I had this error on my while (dr.Read()) line:

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

I ran into very little on the internet about this, except that it was an error with the CLOB field when it was null, and was supposedly fixed in the latest ODAC release, according to this: https://community.oracle.com/thread/3944924

My take on this -- NOT TRUE! It hasn't been updated since October 5, 2015 (http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html), and the 12c package I'm using was downloaded in April 2016.

Full stack trace by someone else with the error that pretty much mirrored mine: 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()

How I encountered it: It was while reading an 11-column table of about 3000 rows. One of the columns was actually an NCLOB (so apparently this is just as susceptible as CLOB), which allowed nulls in the database, and some of its values were empty - it was an optional "Notes" field, after all. It's funny that I didn't get this error on the first or even second row that had an empty Notes field. It didn't error until row 768 finished and it was about to start row 769, according to an int counter variable that started at 0 that I set up and saw after checking how many rows my DataTable had thus far. I found I got the error if I used:

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

as well if I used:

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

or if I used:

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

where cmd is the OracleCommand, so it made no difference.

Resolution

The following is basically the code I used to parse through an OracleDataReader values in order to assign them to a DataTable. It's actually not as refined as it could be - I am using it to just return dr[i] to datarow in all cases except when the value is null, and when it is the eleventh column (index = 10, because it starts at 0) and a particular query has been executed so that I know where my NCLOB column is.

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;

}

In the same way that I have it assigning specific types of null based on the column type found in the schema table loop, you could add the conditions to the "not null" side of the if...then and do various GetOracle... statements there. I found it was only necessary for this NCLOB instance, though.

To give credit where credit is due, the original codebase is based on the answer given by sarathkumar at Populate data table from data reader .

like image 30
vapcguy Avatar answered Oct 24 '22 07:10

vapcguy


We met this problem in our project an hour ago and found a solution. It is generating this error because of null values in CLOB caolumn. We have a CLOB column and it is Nullable in database. In EntityFramework model it is String but not Nullable. We changed column's Nullable property to True in EF model and it fixed problem.

like image 33
Ozkan Avatar answered Oct 24 '22 08:10

Ozkan


We have this problem as well on some computers, and we are running the latest Oracle.ManagedDataAccess.dll (4.121.2.20150926 ODAC RELEASE 4).

We found a solution to our problem, and I just wanted to share.

This was our problem that occurred some computers.

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

And here's the solution that made it work on all computers.

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
like image 1
Ragowit Avatar answered Oct 24 '22 07:10

Ragowit