I'm doing some maintenance on a legacy app uses OracleConnection and OracleCommand to manage our data. I'm Having an issue where a specific update isn't working when I use parameters, but if I convert the same statement to an interpolated string, it's working fine. I'm not getting any exceptions, the update just doesn't happen, and returns a 0 for rows updated. I'm doing other updates with parameters, so I'm curious if anyone sees anything that I might have missed with this one. I've tried with/without the transaction as well as explicitly creating OracleParameter objects to no effect.
The method is below. I've left the parameterized version and the parameter setting commented out for reference.
public int UpdateBusinessEntitlement(int appId, int businessId, int entitlementTypeId, string sso)
{
// Non-Working Parameterized Version
//var sql = "UPDATE APD.APD_BUS_TO_APP_MAP " +
// "SET ENTITLEMENT_TYPE_SEQ_ID = :entitlementTypeId, " +
// "LAST_UPDATE_DATE = SYSDATE, " +
// "LAST_UPDATED_BY = :lastUpdatedBy " +
// "WHERE APP_SEQ_ID = :appId AND BUSINESS_SEQ_ID = :businessId";
var sql = "UPDATE APD.APD_BUS_TO_APP_MAP " +
$"SET ENTITLEMENT_TYPE_SEQ_ID = {entitlementTypeId}, " +
"LAST_UPDATE_DATE = SYSDATE, " +
$"LAST_UPDATED_BY = {sso} " +
$"WHERE APP_SEQ_ID = {appId} AND BUSINESS_SEQ_ID = {businessId}";
using (var cn = _connectionBuilder.GetUpdaterConnection())
{
using (var cmd = _connectionBuilder.GetCommand(sql, cn))
{
cn.Open();
var transaction = cn.BeginTransaction(IsolationLevel.ReadCommitted);
cmd.Transaction = transaction;
//cmd.Parameters.Add("appId", appId);
//cmd.Parameters.Add("businessId", businessId);
//cmd.Parameters.Add("entitlementTypeId", entitlementTypeId);
//cmd.Parameters.Add("lastUpdatedBy", sso);
var rows = cmd.ExecuteNonQuery();
transaction.Commit();
return rows;
}
}
}
I suspect you are binding parameters by position rather than name.
By position, you'd be putting appId first into entitlement_type_seq_id. Then BusinessId into last_Updated_By, entitlementTypeId into app_seq_id and lastUpdatedBy into business_seq_id.
https://docs.oracle.com/cd/B19306_01/win.102/b14307/OracleCommandClass.htm#i997666
Either you have to set
cmd.BindByName = true;
because default value for BindByName property is false, which means the parameters are bound by position.
Or you have to use the same order of parameters as they appear in your statement, i.e.
cmd.Parameters.Add("entitlementTypeId", entitlementTypeId);
cmd.Parameters.Add("lastUpdatedBy", sso);
cmd.Parameters.Add("appId", appId);
cmd.Parameters.Add("businessId", businessId);
btw, usually OracleParameter are added like this:
cmd.Parameters.Add("entitlementTypeId", OracleDbType.Int32, ParameterDirection.Input).Value = entitlementTypeId;
cmd.Parameters.Add("lastUpdatedBy", OracleDbType.Varchar2, ParameterDirection.Input).Value = entitlementTypeId;
cmd.Parameters.Add("appId", OracleDbType.Int32, ParameterDirection.Input).Value = appId;
cmd.Parameters.Add("businessId", OracleDbType.Int32, ParameterDirection.Input).Value = businessId;
I assume for simple data types like numbers of string the syntax does not matter, however other data type (e.g. Date) may fail if you simply use cmd.Parameters.Add(string name, object val).
Related
I have a cell value in a column that is "12,000". And I want to change to "11,000" and display it... but it only displays a blank space. And in the database the value appears NULL.
In the database the type is Decimal(18,3).
My code in C# is like this:
decimal dec = Convert.ToDecimal(dgvRow.Cells[16].Value.ToString());
string query = "UPDATE cabecdoc SET CDU_Peso = TRY_CONVERT(DECIMAL(18,3),'" + dec + "' ) WHERE Id = '" + idDoc + "'";
If I do the query:
UPDATE CabecDoc
SET CDU_Peso = TRY_CONVERT(DECIMAL(18,3), '11.000')
WHERE Id = 'fb9668a9-46fa-11ec-9494-00155d01b010'
in Microsoft SQL Server - it works... but in my program in C# it displays a blank space value.
Ok, assuming the text value will NOT have the $ (or currency character), then this will work:
string strSQL = "UPDATE cabecdoc " +
"SET CDU_Peso = #Peso " +
"WHERE Id = #ID";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
{
conn.Open();
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = dgvRow.Cells[16].Text;
cmdSQL.Parameters.Add("#ID", SqlDbType.NVarChar).Value = idDoc;
cmdSQL.ExecuteNonQuery();
}
So if the cell has:
12000
12,000
12,000.00
Then the above will work fine.
However, if the cell is to have:
$12,000.00
Then you need to use the globalization converters for this.
Say like this:
// -- using System.Globalization;
Decimal TestNum = 0;
decimal.TryParse(TextBox1.Text,
NumberStyles.Currency,
CultureInfo.CurrentCulture.NumberFormat, out TestNum);
Now, if the converter fails, then the TestNum will not be changed (the "out" return value in above), and then we now have this:
string strSQL = "UPDATE cabecdoc " +
"SET CDU_Peso = #Peso " +
"WHERE Id = #ID";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
{
conn.Open();
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = TestNum;
cmdSQL.Parameters.Add("#ID", SqlDbType.NVarChar).Value = idDoc;
cmdSQL.ExecuteNonQuery();
}
And while the first will work as long as no currency character such as "$"?
Well, it will work, but first example does not handle "" (empty string).
so, you could say use this:
string MyPeso = dgvRow.Cell[16].Text;
if (MyPeso == "")
MyPeso = "0";
....
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = MyPeso;
Also, as noted, not only is using paramters a lot easier - you don't even have to know (or think) if you need to surround the values - so the sql is much easier to read, and as noted, it is sql injection safe.
eg this:
I have a Window Application in ado.net with adding, modifying and deleting rows for a few tables. My problem is that after modifying a table with money type, the money value is much bigger after the operation.
String sql = "UPDATE kierowca SET imie='" + txtImie.Text + "',nazwisko='" + txtNazwisko.Text + "',data_zatrudnienia='" + txtData.Text + "',pensja='" + Convert.ToDecimal(txtPensja.Text) + "' WHERE imie='" + listBox2.SelectedItem.ToString() + "' AND nazwisko='" + listBox3.SelectedItem.ToString() + "';";
conn.Open();
cmd = new SqlCommand(sql, conn);
cmd.ExecuteNonQuery();
cmd.Clone();
conn.Close();
pensja in my table is type of money. What am I dong wrong?
I would give you an example of a parameterized query using your data
String sql = #"UPDATE kierowca SET imie=#imie,nazwisko=#nazwisko,
data_zatrudnienia=#data_zatrudnienia,pensja=#pensja
WHERE imie=#search1 AND nazwisko=#search2";
using(SqlConnection con = new SqlConnection(......))
using(SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.Parameters.Add("#imie", SqlDbType.NVarChar).Value = txtImie.Text;
cmd.Parameters.Add("#nazwisko", SqlDbType.NVarChar).Value = txtNazwisko.Text;
cmd.Parameters.Add("#data_zatrudnienia", SqlDbType.NVarChar).Value = txtData.Text;
cmd.Parameters.Add("#pensja", SqlDbType.Decimal).Value = Convert.ToDecimal(txtPensja.Text);
cmd.Parameters.Add("#search1", SqlDbType.Decimal).Value = listBox2.SelectedItem.ToString();
cmd.Parameters.Add("#search2", SqlDbType.Decimal).Value = listBox3.SelectedItem.ToString();
con.Open();
int rowsChanged = cmd.ExecuteNonQuery();
MessageBox.Show("Updated " + rowsChanged + " rows");
}
Notice that I assume two things here.
The Convert.ToDecimal doesn't fails (better use decimal.TryParse to test if the input is indeed a decimal value).
The other fields involved in your query are all of type text (nvarchar on db)
Why this should work? Because with this code a parameter of type decimal and whose value is a decimal value is passed to the database engine. So the engine don't need to convert a string back to a decimal. This conversion could easily fails or give incorrect results if the a locale decimal point (comma) is not interpreted correctly by the database conversion code
Of course if your fields are of different type you should change the SqlDbType value in all the affected parameters
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.
I have this SQL that works if i just execute on Oracle SQL Developer:
SELECT * FROM MYTABLE
WHERE LOWER(TRANSLATE(DESCRIPTION, 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc'))
LIKE LOWER(TRANSLATE('%são paulo%', 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc'))
But when is execute on C# code, wont work. The result always is 0.
string translate = "'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc'";
string query = string.Format("SELECT * FROM {0}
WHERE LOWER(TRANSLATE(DESCSITE, {2}))
LIKE LOWER(TRANSLATE({1}, {2}))",
TABLE, string.Format("'%{0}%'", str.ToLower()), translate);
UPDATE
This is how show in the breakpoint:
SELECT * FROM PROD
WHERE TRANSLATE(LOWER(DESCSITE), 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc')
LIKE TRANSLATE(LOWER('%macarrão%'), 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc')
And the same problem. Works on Oracle SQL Developer bu wont on C# code.
UPDATE
I tried this, but but in this case is not working to.
string query = string.Format("SELECT * FROM {0}
WHERE LOWER(TRANSLATE(DESCSITE, {1}))
LIKE LOWER(TRANSLATE(:DESCSITE, {1}))", TABLE, translate);
List<OracleParameter> parameters = new List<OracleParameter>();
parameters.Add(new OracleParameter(":DESCSITE", string.Format("'%{0}%'", str)));
If I only try this, i know tha will work, but I have to check the others things.
string query = string.Format(#"SELECT * FROM {0} WHERE CODIPROD = :CODIPROD", TABLE);
List<OracleParameter> parameters = new List<OracleParameter>();
parameters.Add(new OracleParameter(":CODIPROD", id));
UPDATE
I'm using for retun:
OracleCommand command;
command.ExecuteReader();
UPDATE
I tried put Unicode=True on the ConnectionString but nothing
UPDATE
This is how I execute the query. Everything works fine with characters without accents:
OracleConnection connection = new OracleConnection();
connection.Open();
OracleTransaction transaction;
transaction = connection.BeginTransaction();
OracleCommand command;
command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = commandText;
OracleParameter parameter;
command.Parameters.Add(parameter);
reader = command.ExecuteReader();
while (reader.Read())
{
// Get data
}
So, the last try (yesterday 5 pm) I made this:
String x = "SELECT * FROM PROD WHERE TRANSLATE(LOWER(DESCSITE), 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc') LIKE LOWER(TRANSLATE('%"+str+"%', 'âáàãêéèîíìôóòõûúùç', 'aaaaeeeiiioooouuuc'))";
And works fine. But this way I know that is not right.
You are calling the functions in the wrong order. The TRANSLATE function is case-sensitive. Therefore you must make the strings lower case before translating.
SELECT * FROM {0}
WHERE TRANSLATE(LOWER(DESCSITE), {2}) LIKE TRANSLATE({1}, {2})
Also, the second LOWER is superfluous, since you do with str.ToLower() already.
UPDATE
It is still not clear how you really execute the query. Here is an example of how it can be done
string connectionString = "...";
string query = "...";
using (var connection = new OracleConnection(connectionString)) {
var command = new OracleCommand(query);
command.Parameters.Add(":DESCSITE", OracleType.NVarChar);
connection.Open();
using (OracleDataReader reader = command.ExecuteReader()) {
int descSiteOrdinal = reader.GetOrdinal("DESCSITE");
while (reader.Read()) {
Console.WriteLine(reader.GetString(descSiteOrdinal));
}
}
}
I've resolved it adding this in my connectionString like this (Unicode=true;):
protected string conexionOraclePruebas = #"Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=xxx)(HOST=xxx)(PORT=xxx)))(CONNECT_DATA=(SERVER=xxx)(SERVICE_NAME=xxx)));"
+ " User Id=xxx;Password=xxx;Min Pool Size=x;Connection Lifetime=x; "
+ " Unicode=true;";
My query was:
query += " AND translate(UPPER(" + field + "), 'ÁÉÍÓÚÀÈÌÒÙÃÊÎÕÛÂÄËÏÖÔÜÇÑ', 'AEIOUAEIOUAEIOUAAEIOOUCN') "
+"LIKE translate(UPPER('%" + this.value.ToString() + "%'), 'ÁÉÍÓÚÀÈÌÒÙÃÊÎÕÛÂÄËÏÖÔÜÇÑ', 'AEIOUAEIOUAEIOUAAEIOOUCN')";
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);