EF + ODP.NET + CLOB =值不能为空 - 参数名称:byteArray?

时间:2015-02-18 20:43:03

标签: oracle entity-framework entity-framework-6 odp.net odp.net-managed

我们的项目最近更新到更新的Oracle.ManagedDataAccess DLL(v 4.121.2.0),并且此错误已经间歇性地出现。我们已经修了好几次而不知道我们做了什么来修复它。

我相当肯定它是由CLOB字段映射到实体框架中的字符串引起的,然后在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 Types Overview

生成的SQL语句没有明显错误:

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

我也按照Ozkan的建议尝试了这个Fluent代码,但它似乎并没有影响我的情况。

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

排查更新:

经过大量测试后,我们确信这实际上是一个错误,而不是配置问题。它似乎是CLOB的内容在非常特定的情况下导致问题。我在Oracle Forums上交叉发布了此信息,希望了解更多信息。

6 个答案:

答案 0 :(得分:1)

我们一小时前在项目中遇到了这个问题并找到了解决方案。由于CLOB caolumn中的空值,它正在生成此错误。我们有一个CLOB列,它在数据库中是Nullable。在EntityFramework模型中,它是String但不是Nullable。我们在EF模型中将列的Nullable属性更改为True并修复了问题。

答案 1 :(得分:1)

我们在某些计算机上也遇到此问题,我们正在运行最新的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

答案 2 :(得分:1)

我花了很多时间试图破译这个并在互联网上找到了这个和那里的一些东西,但是没有哪里有一切都在一个地方,所以我想发布我学到的东西,并且我如何解决它,这很像Ragowit的答案,但我已经得到了C#代码。

<强>背景

错误:所以我在while (dr.Read())行上出现了此错误:

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

我在互联网上遇到的情况很少,除了当CLOB字段为空时它是错误的,并且据说在最新的ODAC版本中被修复了,根据这个:{{3 }}

我对此的看法 - 不是真的!它自2015年10月5日(https://community.oracle.com/thread/3944924)以来未更新,我正在使用的12c软件包已于2016年4月下载。

其他人的完整堆栈跟踪,错误几乎反映了我的错误:http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html

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

我是怎么遇到的:这是在阅读一个大约3000行的11列表时。其中一列实际上是一个NCLOB(显然这和CLOB一样容易受到影响),它允许数据库中的空值,并且它的一些值是空的 - 它是一个可选的“注释”毕竟,田野。有趣的是,我没有在第一行或第二行有一个空的Notes字段时得到此错误。在行768完成并且它即将开始第769行之前没有错误,根据我设置的int计数器变量开始,并且在检查了我的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())
     {
         ....
     }
}

其中cmdOracleCommand,所以没有区别。

解决

以下基本上是我用于解析OracleDataReader值的代码,以便将它们分配给DataTable。它实际上并不像它那样精致 - 我使用它在所有情况下只返回dr[i]datarow,除非值为null,当它是第11列时(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一样,您可以将条件添加到if...then的“非空”一侧并执行各种操作那里有GetOracle...个陈述。我发现这只是NCLOB实例所必需的。

为了在信用到期时给予信用,原始代码库基于sarathkumar在http://pastebin.com/24AfFDnq给出的答案。

答案 3 :(得分:1)

安装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#程序开始正常工作,在它自己的目录中使用Oracle.ManagedDataAccess dll。

答案 4 :(得分:0)

对我来说很简单!我在odac v 4.121.1.0上遇到了这个错误。我刚刚用Nuget将Oracle.ManagedDataAccess更新到4.121.2.0,现在它正在运行。

您是否尝试使用Nugget卸载并重新安装Oracle.ManagedDataAccess?

答案 5 :(得分:0)

将Oracle.ManagedDataAccess.dll升级到版本4.122.1.0解决了。 如果您使用的是vs 2017,我们可以通过NuGet进行更新。

enter image description here