我花了很多时间试图破译这一点,并在互联网上到处找到一些这样和那样的东西,但没有一个地方能把所有东西都放在一个地方,所以我想发布我学到的东西,以及我是如何解决的它,这很像 Ragowit 的答案,但我有它的 C# 代码。
背景
错误: 所以我的while (dr.Read())
线路上有这个错误:
Value cannot be null. \r\nParmeter name: byteArray
我在互联网上遇到的这个问题很少,除了CLOB
当它为空时该字段出现错误,并且据推测在最新的 ODAC 版本中已修复,据此: https ://community.oracle.com/thread /3944924
我对此的看法——不正确!它自 2015 年 10 月 5 日 ( http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html ) 以来一直没有更新,我使用的 12c 软件包是在 2016 年 4 月下载的。
其他人的完整堆栈跟踪几乎反映了我的错误: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()
我是怎么遇到的: 那是在阅读一个大约 3000 行的 11 列表时。其中一列实际上是一个NCLOB
(显然这与 一样容易受到影响CLOB
),它允许数据库中的空值,并且它的一些值是空的 - 毕竟它是一个可选的“Notes”字段。有趣的是,在注释字段为空的第一行甚至第二行上我都没有收到此错误。根据从 0 开始的计数器变量,直到第 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())
{
....
}
}
cmd
是哪里OracleCommand
,所以没有区别。
分辨率
以下基本上是我用来解析OracleDataReader
值以便将它们分配给DataTable
. 它实际上并没有像它可能的那样精致 - 我使用它只是在所有情况下都返回dr[i]
,datarow
除了当值为空时,当它是第十一列(索引 = 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;
}
就像我让它根据在模式表循环中找到的列类型分配特定类型的空值一样,您可以将条件添加到“非空”端if...then
并在那里执行各种GetOracle...
语句。不过,我发现它仅在这种NCLOB
情况下是必需的。
为了在信用到期时给予信用,原始代码库基于 sarathkumar 在Populate data table from data reader 中给出的答案。