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:
Также нет ничего плохого в сгенерированном операторе 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?