10

I've been using a parameterized query to insert values into an Oracle table, like so:

var q = "insert into MyTable(Field1, Field2...) values(:Field1, :Field2...)";
var cmd = new OracleCommand(q, conn); // conn is a pre-existing connection
cmd.Parameters.Add("Field1", field1Val); 
cmd.Parameters.Add("Field2", field2Val);
// etc...
cmd.ExecuteNonQuery();

This has been working fine, but suddenly this has stopped working, and I am getting Oracle error ORA-01722 (invalid number). I have checked the parameters, and all numbers are unquestionably valid numbers. I even substituted dummy values for any nulls, and I still get the error. I tried the same query in direct sql (using OraDeveloper Studio), and it works, even with the identical parameters.

How do I track this one down?

EDIT: per request in the comments, here's the create table statement:

CREATE TABLE ALPHA.VISITFINDINGS (
  ID NUMBER(12),
  VISITID NUMBER(12) NOT NULL,
  DESCRIPTION VARCHAR2(100),
  CUSTOMIMAGE CLOB,
  VISUALFINDINGSSECTIONMAPID NUMBER(12),
  FINDINGSID NUMBER(12),
  CONSTRAINT FK_VISITFINDINGS_AREA FOREIGN KEY (VISUALFINDINGSSECTIONMAPID)
    REFERENCES ALPHA.VISUALFINDINGSSECTIONMAP(VISUALFINDINGSSECTIONMAPID),
  CONSTRAINT FK_VISITFINDINGS_FINDINGS FOREIGN KEY (FINDINGSID)
    REFERENCES ALPHA.FINDINGS(FINDINGSID),
  CONSTRAINT FK_VISITFINDINGS_VISIT FOREIGN KEY (VISITID)
    REFERENCES ALPHA.VISITS(VISITID),
  CONSTRAINT PK_VISITFINDINGS PRIMARY KEY (ID))
TABLESPACE USERS
STORAGE (
  INITIAL 64K
  MAXEXTENTS UNLIMITED
)
LOGGING;
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387
  • 2
    Please post the CREATE TABLE statement for the table. My guess is the values you're inserting are larger than what's allocated for the data type. IE: The column is NUMBER(3), and you're attempting to insert 1,000. – OMG Ponies Oct 05 '10 at 18:07
  • create table statement posted. – Shaul Behr Oct 06 '10 at 08:59

3 Answers3

49

I have given answer credit already, but I think it's worth mentioning here exactly what the root of my problems was, in case anyone else finds this item while looking for an answer to their own problem.

The problem is that the C# implementation of parameterized queries for Oracle contains a serious and potentially dangerous bug - a real "pit in the public domain":

It doesn't matter what you name your parameters; they have to be added in the order in which they appear in the query.

See more here.

Community
  • 1
  • 1
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387
  • 2
    Since this is more accurate you should probably remove check from my answer and mark this one instead! – Rup Oct 20 '10 at 12:49
  • 4
    It's claimed in the other thread this isn't a bug, but a feature. Of course, it'd be a feature so dangerous and useless as to be BOTH a feature and a bug then. But, they say if you set OracleCommand.BindByName = true, it'll work as it should. – The Dag Nov 14 '11 at 10:54
  • That was a very frustrating bug! Thanks for the tip! – Jason Turan Feb 18 '16 at 22:15
  • I spent all afternoon tracking down this issue! This is a huge bug! – Dennis Kiesel Aug 25 '16 at 21:38
  • 2
    This is not a bug, you can force Oracle to match the parameters by name by setting the property command.BindByName = true; – user3163495 Apr 24 '18 at 22:29
  • Not available when using DbProviderFactory and the Oracle.DataAccess.Client -in this case I had to choose the right order – Peter M. Jul 26 '18 at 08:44
5

When you say you checked the parameters do you mean the Parameters collection on the SqlCommand class? You might be falling foul of this note on the SqlParameter page:

Use caution when you use this overload of the SqlParameter constructor to specify integer parameter values. Because this overload takes a value of type Object, you must convert the integral value to an Object type when the value is zero, as the following C# example demonstrates. Copy

Parameter = new SqlParameter("@pname", Convert.ToInt32(0));

If you do not perform this conversion, the compiler assumes that you are trying to call the SqlParameter (string, SqlDbType) constructor overload.

I'd suggest you use something like

cmd.Parameters.Add(
   new SqlParameter("Field1", SqlDbType.Int32) { Value = field1Val });

instead to explicitly set the type.

Rup
  • 33,765
  • 9
  • 83
  • 112
  • 3
    The OP is using Oracle, not SQL Server, so that would be OracleParameter... but your answer still stands. However it doesn't explain why it used to work... – Thomas Levesque Oct 05 '10 at 19:58
  • D'oh, sorry. I couldn't remember what the Oracle version was called so I'd meant to use the base class name, DbParameter. True, I'm not sure why that might have changed things unless he's only just started passing zero parameters. – Rup Oct 06 '10 at 08:59
  • +1 It was a good guess, but no - I iterated through the actual parameters in debug mode and checked out all their values, and they are all correct. (It was interesting to note that all the values get converted to strings - seems absurd to convert a number to a string on the client, just so you can convert it back again on the server side!) – Shaul Behr Oct 06 '10 at 09:43
  • Interestingly, though, I also checked the DbType of all the parameters - and they're all of type String! That could be the problem, no? – Shaul Behr Oct 06 '10 at 09:53
  • DOH! I found the problem - clue came from my previous comment ... I recently introduced a change that inadvertently caused all the parameters to convert to strings! I'll give you answer credit for having helped point me in the right direction... :) – Shaul Behr Oct 06 '10 at 09:54
  • Didn't ANYONE spot a problem there? I for one do NOT believe that "converting" an int to an int can somehow cause the compiler to bind to a different overload of the parameter constructor. – The Dag Nov 14 '11 at 09:49
  • @TheDag I quoted that from MSDN so I guess that really is how it works. But it's fairly counter-intuitive, yes, and if they really meant pass-an-int-as-an-object then I'd have thought there would be better ways to do it. – Rup Nov 14 '11 at 10:21
1
command.BindByName = true;

Please see this explination.

Dennis Kiesel
  • 1,902
  • 3
  • 15
  • 20