ODP.Net Managed API - Array Binding with strings > 1000 characters - c#

When using the ODP.Net managed API, when using array binding to insert data into a column of type VARCHAR2(4000), and a string length of a row value in our array is greater than 1000 characters, the following exception is thrown:
ORA-01461: can bind a LONG value only for insert into a LONG column
string sql = "INSERT INTO STAGING(\"COLUMN1\") VALUES (:COLUMN1)";
using (OracleCommand cmd = connection.CreateCommand())
{
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
cmd.BindByName = true;
cmd.ArrayBindCount = dt.Rows.Count;
var p = new OracleParameter { ParameterName = parameterName.ToUpper() };
p.OracleDbType = OracleDbType.Varchar2;
p.Value = dt.AsEnumerable().Select(c => (!c.IsNull(fieldName) ? c.Field<T>(fieldName) : default(T))).ToArray();
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
We currently define our parameters as:
p.OracleDbType = OracleDbType.Varchar2;
I tried to use this instead, but still run into the same issue:
p.OracleDbType = OracleDbType.Clob;
Also tried to set a size on the length of the Varchar2 as follows, but still have the same issue.
p.OracleDbType = OracleDbType.Varchar2;
p.Size = 4000;
Also tried this, with no luck:
string sql = "INSERT INTO STAGING(\"COLUMN1\") VALUES (CAST(:COLUMN1 AS VARCHAR2(4000))";
Any ideas?
This appears to be a similiar issue: https://community.oracle.com/thread/3649551
Update
I suspected that maybe there was some sort of character set issue, which made the length longer than expected, so to rule this out, I reduced the column length of the table that we're trying to insert data into down to VARCHAR2(1000), assuming that this would make the maximum allowable character length to be 250 - this is not the case though. The maximum that is working before this exception is thrown is still 1000.
Update 2
I've found an oracle patch which may resolve this issue. I will try and get this patch and verify. https://support.oracle.com/epmos/faces/PatchDetail?patchId=20361140&requestId=18735492
Update 3
The oracle patch didn't fix the issue for me. I tried to iterate through all of the parameter bind statuses, but they all indicate a success.
catch (Exception ex)
{
foreach (OracleParameter p in cmd.Parameters)
{
foreach (var s in p.ArrayBindStatus)
{
if (s != OracleParameterStatus.Success)
{
}
}
}
}
Update 4
Seems that this is a bug in the Oracle Managed API, here's a sample class that can reproduce the issue.
namespace OracleBindError
{
using Oracle.ManagedDataAccess.Client;
using System.Data;
using System.Linq;
class Program
{
static void Main(string[] args)
{
string testTable = "BIND_TEST_TABLE";
string connString = "[conn string here]";
string dropTable =
#"DECLARE pEXISTS NUMBER;
BEGIN
SELECT COUNT(*) INTO pEXISTS FROM USER_TABLES WHERE TABLE_NAME = '" + testTable + #"';
IF(pEXISTS > 0) THEN
EXECUTE IMMEDIATE 'DROP TABLE " + testTable + #"';
END IF;
EXECUTE IMMEDIATE 'CREATE TABLE " + testTable + #" (COLUMN1 VARCHAR2(4000), COLUMN2 VARCHAR2(4000))';
END;";
string[] greaterThanOneThousand = new string[] {
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkldfdffd",
};
string insertStatement = "INSERT INTO " + testTable + "(\"COLUMN1\",\"COLUMN2\") VALUES (:COLUMN1,:COLUMN2)";
using (OracleConnection conn = new OracleConnection(connString))
{
conn.Open();
OracleCommand dropCmd = new OracleCommand(dropTable, conn);
dropCmd.ExecuteNonQuery();
using (OracleCommand cmd = conn.CreateCommand())
{
cmd.CommandText = insertStatement;
cmd.CommandType = CommandType.Text;
cmd.BindByName = true;
cmd.ArrayBindCount = greaterThanOneThousand.Length;
var p = new OracleParameter { ParameterName = "COLUMN1" };
p.OracleDbType = OracleDbType.Varchar2;
p.Value = greaterThanOneThousand.ToArray();
cmd.Parameters.Add(p);
var p2 = new OracleParameter { ParameterName = "COLUMN2" };
p2.OracleDbType = OracleDbType.Varchar2;
p2.Value = new string[] { null };
cmd.Parameters.Add(p2);
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
}
}
Found work-around
If I change the OracleDbType from Varchar2 to NVarchar2 in my parameters it works.
var p = new OracleParameter { ParameterName = "COLUMN1" };
p.OracleDbType = OracleDbType.NVarchar2;
p.Value = greaterThanOneThousand.ToArray();
cmd.Parameters.Add(p);
var p2 = new OracleParameter { ParameterName = "COLUMN2" };
p2.OracleDbType = OracleDbType.Varchar2;
p2.Value = new string[] { " " };
cmd.Parameters.Add(p2);
Work-Around
The work-around is to use NVARCHAR2 in the parameter on the .net side.

You are assigning array of type "T" into value. Whereas, the database expects it to be VARCHAR2(4000), which is equivalent to string. Try converting the value to string.

Related

SqlBulkCopy got exception : "Received an invalid column length from the bcp client for colid ."

I have code using SqlBulkCopy to clone a lot of tables, it used to work before, but very weird, recently got exception
Received an invalid column length from the bcp client for colid
I have search this exception and still not solve my problem.
sqlBulkCopy.WriteToServer(reader) will raise this exception if a table has two continous columns which are both of type Char(1) or nvarchar(nn), and both have NULL. Sometime, changing the SqlBulkCopy.BatchSize makes it work, but many times, it will not.
After simplify, I have test case as follow, and it is reproduceable on two servers:
Create a table like below: (tested on SQL Server 2012 SP 4 and SQL Server 2016 SP2)
IF OBJECT_ID('dbo.TestTable', 'U') IS NOT NULL
DROP TABLE dbo.TestTable;
CREATE TABLE [dbo].[TestTable]
(
[value2] [char](1) NULL,
[value1] [char](1) NULL
) ON [PRIMARY]
GO
DECLARE #i int = 0
WHILE #i < 262
BEGIN
SET #i = #i + 1
INSERT INTO [dbo].[TestTable]([value2], [value1])
VALUES (null, null)
END
C# console (.net framework 4.7) code as below
class Program
{
// [change here]
static string sourceConn = #"Server={YourServer};Database={YourDatabase};User ID={userYourName};Password={yourPassword};connect timeout=15";
static void Main(string[] args)
{
CopyTable(sourceConn, sourceConn, "TestTable", "testTableBAK");
Console.ReadLine();
}
static void CopyTable(string sConnSource, string sConnDest, string sTableSource, string sTableDest)
{
if (IsTableExist(sConnDest, sTableDest))
{
RunNonQuerySQL(sConnDest, "DROP TABLE " + sTableDest);
Console.WriteLine($"existing table {sTableDest} dropped");
}
CopySchema(sConnDest, sTableSource, sTableDest);
using (SqlConnection connSource = new SqlConnection(sConnSource))
{
connSource.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = connSource;
cmd.CommandText = "SELECT * FROM " + sTableSource;
// using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sConnDest, SqlBulkCopyOptions.KeepNulls | SqlBulkCopyOptions.KeepIdentity))
using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sConnDest))
{
// sqlBulkCopy.BatchSize = 1380; // this optional setting will work if set value smaller than 1397 for testTable on my new server (SQL server 13.0.5102.14)
// sqlBulkCopy.BatchSize = 261; // this optional setting will work if set value smaller than 261 for testTable on 2 older server (SQL server 11.0.7001)
sqlBulkCopy.DestinationTableName = sTableDest;
SqlDataReader reader = cmd.ExecuteReader();
try
{
// exception here
sqlBulkCopy.WriteToServer(reader);
Console.WriteLine("table copied");
}
catch (SqlException ex)
{
Console.WriteLine(ex.Message);
}
sqlBulkCopy.Close();
}
}
}
static bool IsTableExist(string sConn, string sTableName)
{
bool result = false;
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
string[] s = sTableName.Split('.');
if (s.Length > 1)
{
cmd.CommandText = "select count (*) as counter from information_schema.tables where table_name = '" + s[1] + "' and TABLE_SCHEMA='" + s[0] + "'";
}
else
{
cmd.CommandText = "select count (*) as counter from information_schema.tables where table_name = '" + sTableName + "'";
}
cmd.Connection = conn;
var count = Convert.ToInt32(cmd.ExecuteScalar());
result = count > 0;
}
return result;
}
static bool RunNonQuerySQL(string sConn, string sSQL)
{
bool result = false;
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sSQL;
cmd.Connection = conn;
var count = cmd.ExecuteNonQuery();
result = true;
}
return result;
}
static public bool CopySchema(string sConn, string sTableSource, string sTableDest)
{
return RunQuerySQL(sConn, "select * into " + sTableDest + " from " + sTableSource + " where 1=2");
}
static public bool RunQuerySQL(string sConn, string sSQL)
{
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sSQL;
cmd.Connection = conn;
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
return true;
}
else
{
return false;
}
}
}
}
I just experienced this error.
I tried to insert a string with a lenght of 5 into a table column with a definition of "varchar(4)".
The error message in my case was:
"Received an invalid column length from the bcp client for colid 2".
"Colid 2" refered to the second column of the row (DataRow) that was part of the DataTable which I used as parameter for the call to the SqlBulkCopy.WriteToServer(DataTable table) method.
The solution in my case was to add validation code that checks the lenght of the strings in my input data before trying to call SqlBulkCopy.WriteToServer().

ORA-06550 wrong number or types of arguments when calling Oracle stored procedure

procedure select_card_transaction(trans_id nvarchar2,
usr_id number,
Quantity out number) is
begin
select count(*)
into Quantity
from user_cards u
where u.transaction_id = trans_id
and u.user_id = usr_id;
end;
and Consuming it:
using(var conn = new OracleConnection(Settings.Default.OraWUConnString))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "for_temporary_testing.select_card_transaction";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("trans_id", TransactionID);
cmd.Parameters.AddWithValue("usr_id", UserID);
var q = new OracleParameter("Quantity", OracleType.Number);
q.Direction = ParameterDirection.Output;
cmd.Parameters.Add(q);
//cmd.Parameters[0].OracleType = OracleType.NVarChar;
//cmd.Parameters[1].OracleType = OracleType.Number;
conn.Open();
var obj = cmd.ExecuteNonQuery();
conn.Close();
return (int)q.Value == 1;
}
It returns the following error.
ORA-06550 wrong number or types of arguments when calling Oracle stored procedure...
ANY IDEA?
I have had the same problem before. Are you using the ODP.Net drivers? I was able to solve the problem by adding the output parameter first. This needs to be done before the input parameters. In your case it would look like
using(var conn = new OracleConnection(Settings.Default.OraWUConnString))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "for_temporary_testing.select_card_transaction";
cmd.CommandType = CommandType.StoredProcedure;
// Return value parameter has to be added first !
var Quantity = new OracleParameter();
Quantity.Direction = ParameterDirection.ReturnValue;
Quantity.OracleDbType = OracleDbType.Int32;
cmd.Parameters.Add(Quantity);
//now add input parameters
var TransID = cmd.Parameters.Add("trans_id", TransactionID);
TransID.Direction = ParameterDirection.Input;
TransID.OracleDbType = OracleDbType.NVarchar2;
var UsrID = cmd.Parameters.Add("usr_id", UserID);
UsrID.Direction = ParameterDirection.Input;
UsrID.OracleDbType = OracleDbType.Int32;
cmd.ExecuteNonQuery();
conn.Close();
return Convert.ToInt32(Quantity.Value);
}
The problem was in the parameter. It was null and oracle returned error. I got that if argument is null, it should be sent as DBNULL

Why do I error out when trying to retrieve a result set with DotConnect components?

With the code below I get, "ORA-01036: illegal variable name/number" on the call to ExecuteReader:
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
Devart.Data.Oracle.OracleCursor oraCursor =
(Devart.Data.Oracle.OracleCursor)cmd.Parameters["cur"].Value;
Devart.Data.Oracle.OracleDataReader odr = cmd.ExecuteReader();
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
What I want to do is populate a List with the result of the query. I don't see any examples for that in DevArt's documentation (or googling). I had it working with Oracle's ODP components with:
OracleDataReader odr = cmd.ExecuteReader();
while (odr.Read())
{
ACurrentUserRoles.Add(odr.GetString(0));
}
...but can't find the parallel working with DotConnect components.
Updated:
Okay, here's the entire method (ACurrentUserRoles is a List of Strings):
public void PopulateCurrentUserRoles(String AUserName, List<String> ACurrentUserRoles) {
_UserName = AUserName;
String query = "select roleid from ABCrole where ABCid = :ABCID";
Devart.Data.Oracle.OracleCommand cmd = new Devart.Data.Oracle.OracleCommand(query, con);
cmd.CommandType = CommandType.Text;
int _ABCID = GetABCIDForUserName();
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
//cmd.ExecuteNonQuery(); blows up: "illegal variable name/number"
//cmd.ExecuteCursor(); " "
//cmd.ExecuteReader(); " "
Devart.Data.Oracle.OracleCursor oraCursor =
(Devart.Data.Oracle.OracleCursor)cmd.Parameters["cur"].Value;
Devart.Data.Oracle.OracleDataReader odr = oraCursor.GetDataReader(); // "Object reference not set to an instance of an object"
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
}
The err msgs I'm getting are appended as comments to the lines where they occur.
First, why are you adding a cursor type parameter and then totally ignore it?.
Second, I have never seen this use of cursor with the ExecuteReader but with the ExecuteNonQuery.
For example:
string cmdText = "begin open :cur for select * from dept; end;";
OracleCommand oraCommand = new OracleCommand(cmdText, oraConnection);
oraCommand.Parameters.Add("cur", OracleDbType.Cursor);
oraCommand.Parameters["cur"].Direction = ParameterDirection.Output;
oraCommand.ExecuteNonQuery();
OracleCursor oraCursor = (OracleCursor)oraCommand.Parameters["cur"].Value;
oraDataAdapter.Fill(dataSet, "Table", oraCursor);
So probably your exception derives from the use of ExecuteReader
This is another example taken directly from the site of DevArt:
string cmdText = "begin open :cur1 for select * from dept;" +
"open :cur2 for select * from emp; end;";
OracleCommand oraCommand = new OracleCommand(cmdText, oraConnection);
oraCommand.Parameters.Add("cur1", OracleDbType.Cursor);
oraCommand.Parameters["cur1"].Direction = ParameterDirection.Output;
oraCommand.Parameters.Add("cur2", OracleDbType.Cursor);
oraCommand.Parameters["cur2"].Direction = ParameterDirection.Output;
oraDataAdapter.SelectCommand = oraCommand;
oraDataAdapter.Fill(dataSet);

Stored Procedures with Mysql connector

I'm trying to call a simple stored procedure in c# 2010.
With only a IN argument it's ok, but now with a OUT argument it's not working.
In phpmyadmin :
drop procedure if exists insert_artist;
delimiter $$
create procedure insert_student(IN name VARCHAR(100), OUT id INT)
begin
insert into student(name) values(name);
set id = last_insert_id();
end$$
delimiter ;
Then using
call insert_student("toto",#id);
select #id;
It's working fine.
Now, in c# :
using (MySqlConnection connection = new MySqlConnection(connectionString))
{
connection.Open();
using (MySqlCommand command = connection.CreateCommand())
{
command.CommandText = "insert_student";
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.AddWithValue("#name", "xxxx");
command.Parameters.AddWithValue("#id",MySqlDbType.Int32);
command.ExecuteNonQuery();
Console.WriteLine("**** " + command.Parameters["#id"].Value);
}
}
Gives me an exception when executing ExecuteNonQuery() :
OUT or INOUT argument 2 for routine insert_student is not a variable or NEW pseudo-variable in BEFORE trigger
The same thing without the out argument in the stored procedure is working fine.
Where is my mistake?
A fuller example:
if (this.OpenConnection() == true)
{
MySqlCommand cmd = new MySqlCommand(nameOfStoredRoutine, connection);
cmd.CommandType = CommandType.StoredProcedure;
//input parameters
for (int i = 0; i < (parameterValue.Length / 2); i++)
{
cmd.Parameters.AddWithValue(parameterValue[i, 0], parameterValue[i, 1]);
cmd.Parameters[parameterValue[i, 0]].Direction = ParameterDirection.Input;
parameterList = parameterList + parameterValue[i,0] + " " + parameterValue[i,1] + " ";
}
//single output parameter
cmd.Parameters.AddWithValue("#output", MySqlDbType.Int32);
cmd.Parameters["#output"].Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery(); //Execute command
this.CloseConnection(); //close connection
return Convert.ToInt32(cmd.Parameters["#output"].Value.ToString());
my below code works
pls check if it's ok for you.
InsertQuery = New MySqlCommand("xxxxxx")
InsertQuery.Connection = Connection
InsertQuery.CommandType = Data.CommandType.StoredProcedure
InsertQuery.Parameters.AddWithValue("IN_xxx", str_xxxx)
InsertQuery.Parameters.Add("OUT_LastID", MySqlDbType.Int32).Direction = ParameterDirection.Output
IQ = InsertQuery.ExecuteReader()
IQ.Read()
LASTID = InsertQuery.Parameters("OUT_LastID").Value

SCOPE_IDENTITY is not working in asp.net?

I am trying to insert a record and get its newly generated id by executing two queries one by one, but don't know why its giving me the following error.
Object cannot be cast from DBNull to other types
My code is as below: (I don't want to use sql stored procedures)
SqlParameter sqlParam;
int lastInsertedVideoId = 0;
using (SqlConnection Conn = new SqlConnection(ObjUtils._ConnString))
{
Conn.Open();
using (SqlCommand sqlCmd = Conn.CreateCommand())
{
string sqlInsertValues = "#Name,#Slug";
string sqlColumnNames = "[Name],[Slug]";
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + ");";
sqlCmd.CommandText = sqlQuery;
sqlCmd.CommandType = CommandType.Text;
sqlParam = sqlCmd.Parameters.Add("#Name", SqlDbType.VarChar);
sqlParam.Value = txtName.Text.Trim();
sqlParam = sqlCmd.Parameters.Add("#Slug", SqlDbType.VarChar);
sqlParam.Value = txtSlug.Text.Trim();
sqlCmd.ExecuteNonQuery();
//getting last inserted video id
sqlCmd.CommandText = "SELECT SCOPE_IDENTITY() AS [lastInsertedVideoId]";
using (SqlDataReader sqlDr = sqlCmd.ExecuteReader())
{
sqlDr.Read();
lastInsertedVideoId = Convert.ToInt32(sqlDr["lastInsertedVideoId"]);
}
}
}
//tags insertion into tag table
if (txtTags.Text.Trim().Length > 0 && lastInsertedVideoId > 0)
{
string sqlBulkTagInsert = "";
string[] tags = txtTags.Text.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tag in tags)
{
sqlBulkTagInsert += "INSERT INTO tags(VideoId, Tag) VALUES(" + lastInsertedVideoId + ", " + tag.Trim().ToLowerInvariant()+ "); ";
}
using (SqlConnection Conn = new SqlConnection(ObjUtils._ConnString))
{
Conn.Open();
using (SqlCommand sqlCmd = Conn.CreateCommand())
{
string sqlQuery = sqlBulkTagInsert;
sqlCmd.CommandText = sqlQuery;
sqlCmd.CommandType = CommandType.Text;
sqlCmd.ExecuteNonQuery();
}
}
}
And also if possible, please check is the above code coded well or we can optimize it more for improve performance?
Thanks
The call to SCOPE_IDENTITY() is not being treated as being in the same "scope" as the INSERT command that you're executing.
Essentially, what you need to do is change the line:
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + ");";
to:
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + "); SELECT SCOPE_IDENTITY() AS [lastInsertedVideoId]";
and then call
int lastVideoInsertedId = Convert.ToInt32(sqlCmd.ExecuteScalar());
instead of .ExecuteNonQuery and the code block following the "//getting last inserted video id" comment.
The SCOPE_IDENTITY() should be extracted from the first command (SELECT, RETURN or OUT) and passed into the next command. By that, I mean that the SELECT_IDENTITY() should be at the end of the first command. In SQL 2008 there is additional syntax for bring values back as part of the INSERT, which makes this simpler.
Or more efficiently: combine the commands into one to avoid round-trips.

Categories