40

This is an absolute howler. I cannot believe my own eyes, and I cannot believe nobody before me would have discovered this if it was a genuine bug in C#, so I'm putting it out for the rest of the developer community to tell me what I am doing wrong. I'm sure this question is going to involve me saying "DOH!" and smacking my head very hard with the palm of my hand - but here goes, anyway...

For the sake of testing, I have created a table Test_1, with script as follows:

CREATE TABLE TEST_1 (
  COLUMN1 NUMBER(12) NOT NULL,
  COLUMN2 VARCHAR2(20),
  COLUMN3 NUMBER(12))
TABLESPACE USERS
STORAGE (
  INITIAL 64K
  MAXEXTENTS UNLIMITED
)
LOGGING;

Now I execute the following code:

var conn = new OracleConnection("connectionblahblah");
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = 
  "insert into Test_1(Column1, Column2, Column3) " +
  "values(:Column1, :Column2, :Column3)";
var p = cmd.Parameters;
p.Add("Column1", 1);
p.Add("Column3", null);
p.Add("Column2", "record 1");
cmd.ExecuteNonQuery();

Whoa! I get an ORA-01722 error - "invalid number"! What's wrong, though? Column1 is numeric, and has a value of 1, so that's fine; Column2 is a string, and Column3 is a nullable column, so that shouldn't cause any trouble...

Now sit down for this one... the problem here is that Column3 and Column2 are transposed in the order in which they are added to the OracleParameterCollection. Switch them around, and presto! It works!

This, of course, leads me to the next obvious experiment... let's change that block of code for adding parameters like so:

p.Add("Foo", 1);
p.Add("Bar", "record 1");
p.Add("hahahahahahaha", null);

You think that'll work? Well guess what - it does!

I am sitting here absolutely stunned. I cannot believe what I am seeing, and I likewise cannot believe that nobody before me has discovered this behavior (unless I don't know how to use Google properly).

This is not just an annoyance - it is seriously dangerous. What would have happened if I'd transposed two columns of the same data type? I wouldn't have even got an error - I would have simply inserted the wrong data into the wrong columns, and been none the wiser.

Does anyone have any ideas for a workaround - other than just being careful not to add parameters in the wrong order?

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387

3 Answers3

49

This is not a bug but explicitly mentioned in Oracle ODP.Net documentation. In a OracleCommand class the parameters are bound by position as default. If you want to bind by name then set the property cmd.BindByName = true; explicitly.

Reference to Oracle documentation. http://download.oracle.com/docs/cd/E11882_01/win.112/e12249/OracleCommandClass.htm#i997666

softveda
  • 10,858
  • 6
  • 42
  • 50
  • 5
    Exactly. That's not a bug, just a stupid "feature". All providers bind parameters by name, but not ODP.NET, perhaps because Oracle doesn't like to do like everyone else... I used to bitch at this when I was working with an Oracle DB, there is apparently no simple way to bind by name by default... See [this question](http://stackoverflow.com/questions/1046632/binding-query-parameters-by-name-with-odp-net) for more on the subject – Thomas Levesque Oct 06 '10 at 22:48
  • 7
    Well, what can I say? Your answer is correct - but I have to concur with @Thomas that this is a stupid "feature". Stronger: it's a feature so mindbogglingly stupid, misleading and dangerous that it should be classified as a bug. Why on earth would **anyone** want to bind their parameters in order, rather than by name? And to have that behavior by default?! This is absolutely outrageous! – Shaul Behr Oct 07 '10 at 07:46
  • 2
    @Shaul: I agree with you. But even then the bug is with Oracle not C#. You can post this to ODP.Net forum at oracle's website and see if someone responds with a reason. But after working with Oracle you will find many little gems like this. – softveda Oct 07 '10 at 08:30
  • 1
    My guess is it's a properly stupid "optimization". It would save a few cycles to bind by index. It cost me multiple hours to work out what was going on for those cycles' sake, and I don't think I'm alone. (Of course it is also dangerous as already pointed out!) – The Dag Nov 14 '11 at 10:50
  • 2
    **Fine print trap:** You expect something (using parameter names means named parameters), but when you run it, you get an error (if you are lucky!). Then you get told: "Surprise, look at the fine print, your own fault if you didn't read!". If Oracle uses positional parameters by default, they should allow question marks only in SQL and value assignment only by order or index! If they let users specify parameter names and assign values by names, that is a contract for named parameters which has to be served! I think this bug is already quite old. – Erik Hart May 26 '15 at 08:53
  • It is definitely a bug if you can give `Add` a name and it isn't respected. I don't care how Oracle tries to spin it; this is broken. – jpmc26 Jan 20 '17 at 18:18
  • Umm., OleDbCommand always binds by position FWIW AFAICT. The parameter names are ignored but I would offer that they are still useful as documentation. – Allen Apr 03 '20 at 15:15
  • The same bug / stupid feature seems to exist in the .Net core provider as well. Had to waste couple of hours before this fixed my issue! – CodeNinja May 28 '21 at 07:13
  • These two providers are both used in my application. After long painful experience, I have learned to NEVER create a parameterized Oracle command without BindByName=true. I always (or at least I intend to ) use it. For ODP.NET, that seems to sort this issue. What is truly nauseating is the behavior of the OleDb provider, which does not care one whit about the names you give parameters AFAICT - it is ALWAYS positional. If someone has figured out how to get around that, please post! I initialize the command like this: { CommandText= Connection = BindByName = true } – Allen Aug 13 '21 at 11:18
5

Is that a typo that you have column3 being added before column2?

Because the colon syntax signifies a bind variable--name doesn't matter to BIND variables in PLSQL, they're populated in order of submission. Which would mean you'd be attempting to set column2 value as "record 1", which would explain the invalid number error...

You currently have:

p.Add("Column1", 1);
p.Add("Column3", null);
p.Add("Column2", "record 1");

...see if this alteration fixes your issue:

p.Add("Column1", 1);
p.Add("Column2", "record 1");
p.Add("Column3", null);

Getting Named Parameters to Work?

I have to defer to someone with more C# experience to explain how to get named parameters working. But I'm glad we confirmed that the colon appears to be interpreting as an Oracle BIND variable.

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
  • So then what is the correct way of passing named parameters instead of ordered? – Shaul Behr Oct 06 '10 at 21:08
  • @OMG: You haven't really answered my question; you've just restated my discovery that the parameter names are ignored, and it only uses the order in which they're added. I want to be able to add parameters by **name**, and **not care** about what order I add them in. How do I do that? – Shaul Behr Oct 06 '10 at 21:12
  • @OMG: In any case, this is what I call a "stumbling block in the public domain" - why bother having a parameter name in the interface if you're just going to ignore it anyway? All it does is confuse the programmers and leads them to making possibly very severe mistakes, such as transposing values in fields. What if it hadn't thrown an exception? – Shaul Behr Oct 06 '10 at 21:15
  • @Shaul: Sorry about that. [Based on the documentation, the colon should work but the examples don't reinforce that](http://msdn.microsoft.com/en-us/library/ebxy9a8b%28VS.71%29.aspx). It looks like you should change the ":" to "@".for named parameters to work. Odd, considering @ is TSQL notation. – OMG Ponies Oct 06 '10 at 21:18
  • @Shaul: Regarding "pit in the public domain" -- I agree, it's an odd choice but sadly the likelihood of changing it is small for sake of backwards compatibility :/ – OMG Ponies Oct 06 '10 at 21:21
  • @Shaul: Just to confirm -- the example did work if parameters were provided in order rather than by name? – OMG Ponies Oct 06 '10 at 21:23
  • @OMG: The "@" syntax there refers to MS SQL, not Oracle. I can't see any other syntax for named parameters in Oracle other than the ":" syntax. (And just to be sure, I tried the "@" syntax - it doesn't work.) – Shaul Behr Oct 06 '10 at 21:23
  • @OMG: Yes, the example works if you provide the parameters in the same order in which they appear in the query. (Actually I mistranslated the expression - should be "Pit in the public domain".) – Shaul Behr Oct 06 '10 at 21:25
  • @Shaul: Thx, that makes sense about ":" vs "@". Sorry, I don't have the C# background to help you further. Cool, so it is interpreting the variables as BIND variables... – OMG Ponies Oct 06 '10 at 21:25
  • 1
    @OMG: **"Cool"!!?** I've got other words to describe this that don't bear publishing...! ;) – Shaul Behr Oct 06 '10 at 21:42
  • 3
    @Shaul: I'm as guilty as the next person for schadenfreude in situations like these. If only because it'll be *me* next time :) – OMG Ponies Oct 06 '10 at 21:51
0
p.Add(":Column1", 1);
p.Add(":Column2", "record 1");
p.Add(":Column3", null);

//NOTE i have added : to the parameter names to be recognised by oracle data client

Luke
  • 1
  • Does this mean the bug has finally been fixed? I found reports on it from 2003. To call this a bug is really understated, I would prefer terms like fine-print trap or plain sabotage! If you specify parameter names in both SQL and parameter list, the driver must either use named parameters or throw an exception, but not secretly handle them as positional. No average developer will read through such details when told "make it run with Oracle". That's just like signing a contract to buy a computer now, but in fine print, you'll buy a new washing machine every month in a 2 year subscription! – Erik Hart Dec 07 '13 at 16:56