C# parameterized queries for Oracle - serious & dangerous bug! - c#

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?

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

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.

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

Related

C# & SQL Server Insert ExecuteNonQuery: which column is causing the error "String or Binary Data Would Be Truncated?"

I'm trying to insert into a table in a C# program. I have this insert command:
var insertSql = #"INSERT INTO dbo.[Case]
VALUES (#Id, #IsDeleted, #CaseNumber, #ContactId, #AccountId, #ParentId, #SuppliedName, #SuppliedEmail, #SuppliedPhone, #SuppliedCompany, #Type, #RecordTypeId, #Status, #Reason, #Origin...
And then I've got many lines adding in the parameters like so:
var command = new SqlCommand(insertSql, easySoftConn);
if (case2.Id != null)
command.Parameters.AddWithValue("#Id", case2.Id);
else
command.Parameters.AddWithValue("#Id", DBNull.Value);
if (case2.IsDeleted != null)
{
if (case2.IsDeleted == "true")
command.Parameters.AddWithValue("#IsDeleted", 1);
else
command.Parameters.AddWithValue("#IsDeleted", 0);
}
else
command.Parameters.AddWithValue("#IsDeleted", DBNull.Value);
if (case2.CaseNumber != null)
command.Parameters.AddWithValue("#CaseNumber", case2.CaseNumber);
else
command.Parameters.AddWithValue("#CaseNumber", DBNull.Value);
if (case2.ContactId != null)
command.Parameters.AddWithValue("#ContactId", case2.ContactId);
else
command.Parameters.AddWithValue("#ContactId", DBNull.Value);
...
When I finally execute the insert:
try
{
command.ExecuteNonQuery();
}
catch (System.Data.SqlClient.SqlException e)
{
CLog.Write(e.Message.ToString(), CLog.ErrLvl.Error);...
}
I get the error:
String or binary data would be truncated
My issue is, the error doesn't tell me which column would be truncated. I've got 80 columns I'm inserting into, and I'd rather not go through them one-by-one. Is there a way to get the error handling to tell me exactly which field is throwing the error?
EDIT: I have a full stack trace in my log file but it still doesn't tell me which column, I just shortened it to the actual error here.
Switching to using strongly typed data access would head this one off sooner:
Add a dataset file to your project
Open it, right click the surface, add tableadapter, set connection parameters, add a query of SELECT * FROM [Case]
FInish the wizard, a datatable and adapter are generated. The DB is used to drive the creation, so all the string columns have a MaxLength property in the dataset that comes from the DB
Attempting to add a row to this table will now cause an error like "unable to set column XYZ, the value violates the MaxLength limit for the column"
Data access code looks like:
var dt = new YourDataSetNameHere.CaseDataTable();
dt.AddCaseRow(put, your, values, here, you , dont, need, to worry, about, null, this, or, data, type, that, because, VS, handles, it, all, for, you, in, the, DataSet.Designer.cs, file);
new YourDataSetNameHereTableAdapters.CaseTableAdapter().Update(dt); //save the new row;
So it'll save you a boatload of time writing boring data access code too
Depending on your SQL version you can apply a KB to get this to show more data as stated here - Link
It effectively starts to show messages like the following
Msg 2628, Level 16, State 6, Procedure ProcedureName, Line Linenumber
String or binary data would be truncated in table '%.*ls', column
'%.*ls'. Truncated value: '%.*ls'.
This came from this great post Link which goes much further to explain how you can try and search for the column should this not be possible. The post also talks about how you can do manual searching although I'd imagine if the list of columns is too large that may be something you want to avoid.
Looks like the value of one or more of your parameters has more length than the table cell can contain.
You should to look at table column definitions.

System.Data.Linq.ChangeConflictException? Row not found or changed? But, I'm inserting, not updating

My program is writing records to a db table. So far, it has written about 58 new records to this table. All of a sudden, I get an error message saying "Row not found or changed." Which is odd, because I'm inserting a new record, not trying to find one or update an existing one. Here's the small bit of code that I'm using to create an object and then insert to the table:
// create new comment object
var comment = new Comment
{
TableName = "Circuit",
TableKey = circuitId,
Text = remarks,
CreatedOn = DateTime.Now,
CreatedByName = "loadCC03Circuits",
CreatedByUupic = "000000000"
};
cimsContext.Comments.InsertOnSubmit(comment);
cimsContext.SubmitChanges();
I'm not quite sure what to do, at this point. Each field has a value, there are no nulls. And, as I said, 58 records have already been written out by this very same bit of code before this happens so, other than the data being off (which, according to the field values in my debugger session, are not) I'm not quite sure what else to check. Any advice?
EDIT: I added an answer below that made this problem go away. But, I'm not sure why this solution worked.
I found a solution, but not the "answer". The solution, in this case, was to make a variable that contained the DateTime.Now value:
var dateNow = DateTime.Now;
And I changed the affected line of code to look like this:
CreatedOn = dateNow,
Wonders of wonders, I no longer received the error. I'm not sure why this fixed the problem, I only tried this on a suggestion from a co-worker. He theorizes that the sandbox database that I'm working is sluggish and could be affecting the DateTime.Now function. Regardless, this made that issue go away. I wish I had a definitive answer, though. I hate making a problem go away when I don't understand why the solution worked.

I Want to know the exact datatype(dbtype) of the ms access database table column in c#

need to know the exact info of database and containing tables using c#.
database is MS access.i want to full info of the tables in it like primary key,max length,not null of the columns in tables in ms access database,etc..
so whats the best way of doing it....
advanced thanx for any kind of help.
another issue is getschema gives me datatypes in numeric way like 130,131..
so how can i use them in create table query they give error
let me explain what i am trying to do.i want to recreate the database about which i have no information.i don't know about its size,tables,data or any thing.
actually i have succeeded to an extent.what i have done is i get the db name and create it with CatalogClass and with getschema(tables) i get all the table names and create them with create table from C#.then column names with alter table.and now i have to give it constraints which are in the DB which have been provided.
so,other then this method i have used is there any thing else which i am missing.any easy or better way available to do this.so, it can go faster
question is still open
I believe everything is documented at the link below, try to run it step by step with debug and then u can inspect the element and display every value you want.
http://msdn.microsoft.com/en-us/library/system.data.datatable.aspx
Primary Key:
DataTable.PrimaryKey
Max Length, of what? Records?
DataTable.Rows.Count
Columns?
DataTable.Columns.Rows
It appears that you are using a schema to return the field types. I have been testing, and something on these lines appears to return what you want.
ADODB.Connection cn = new ADODB.Connection();
ADODB.Recordset rs = new ADODB.Recordset();
string cnStr;
cnStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Z:\\Docs\\Test.accdb";
string ssql = "Select * From Table1 where 1=2";
cn.Open(cnStr, null, null, 0);
rs.Open(ssql, cn, ADODB.CursorTypeEnum.adOpenKeyset,
ADODB.LockTypeEnum.adLockOptimistic, -1);
foreach (ADODB.Field fld in rs.Fields)
{
Console.WriteLine(fld.Type);
}
Console.Read();
rs.Close();
cn.Close();
For various types this returns:
adInteger
adVarWChar = Text
adDate
adInteger
adLongVarWChar = Memo
adVarWChar
adDate
adBoolean

Exception on ExecuteNonQuery method about unsuccessful value convertion

i am using OleDbCommand.ExecuteNonQuery() to insert data into the database:
ObjCommand = New OleDbCommand
ObjCommand.CommandType = CommandType.StoredProcedure
ObjCommand.CommandText = StrSQL
ObjCommand.Parameters.Add("field1", OleDbType.VarChar).Value = <something1>
ObjCommand.Parameters.Add("field", OleDbType.VarChar).Value = <something2>
(...)
(...)
ObjCommand.Parameters.Add("field50", OleDbType.VarChar).Value = <something50>
ObjCommand.Connection = GetDBConnection(StrConnectionString)
ObjCommand.Connection.Open()
<some integer> = ObjCommand.ExecuteNonQuery()
And there is a conversion exception that only shows up in the last line:
error converting datatype varchar to smallint
I'd like to know if it's the normal behavior, and how do i know where the conversion problem occurs.
update:
ObjCommand.Parameters.Add("#IdMunFatoGerador", OleDbType.VarChar).Value
= ObjNFe.idMunFatoGerador
i've found this line through commenting every line and uncommenting some, this one gives me the above said exception.
ObjNFe.idMunFatoGerador is a string and gives me "error converting datatype varchar to smallint" too
That implies that one of the parameters of the query is of the wrong type. Namely you are passing varchar when you should be passing a smallint (short in c#).
Without the definition of the stored procedure there's no way we can guess which one it is..
One of the parameters you are pasing to the stored procedure as a varChar is typed in the stored procedure as an smallint. And, in this case the value you are passing in cannot be converted implicitly by the server to an integer value. Look at the stored proc definition, Either lala, or lulu is typed as an smallint. Then look at actual values you are sending it...
If you use the DataSet designer, it will generate everything for you and you'll get a compiler error instead of a run-time error. Add a new DataSet to your project then add a Query to the DataSet.
You end up with something like this:
QueriesTableAdapter ta = new QueriesTableAdapter();
ta.Connection = myConnection;
ta.MySeveralParameterStoredProc(x0, x1, ..., xN);
I guess you could loop through the parameter collection and look at the value and see if it can be numberic (string.isnumeric). The use debug.assert to output a message that the parameter value is too big to be a small int as well as the parameter name. Even better is for you to set the parameter type to be oledbtype.smallint and then only look at those. Ultimately, you need to know your parameters and how they correspond to the underlying SQL. I would just narrow my search by typing my parameters correctly and then ensure I never passed anything to the command object that wouldn't work. HTH.
Possible code:
For each parameter as SqlParameter in mycommandobject.parameters
if isnumeric(parameter.value) then
debug.assert(convert.int32(parameter.value) <= 32,767,"This parameter could have an issue - " & parameter.parametername & " value = " & parameter.value)
end if
loop
I haven't tested the code, but i believe this will work.
I've finally found it.
It was everything ok with the formats of the values.
The problem was: one of the parameters was missing. I still didn't understand it completely, but the issue was that the missing parameter (smallint) was interpreted in the following one (varchar) and so the error i found was in the second one.
In other words, field~35 was missing (haha)
So the thing is: when mounting a command to a procedure, remember to always put the fields in the exact amount and order. =)
Thank you guys!

Why do I need OleDbCommand.Prepare()?

I'm working with a datagrid and adapter that correspond with an MSAccess table through a stored query (named "UpdatePaid", 3 paramaters as shown below) like so:
OleDbCommand odc = new OleDbCommand("UpdatePaid", connection);
OleDbParameter param;
odc.CommandType = CommandType.StoredProcedure;
param = odc.Parameters.Add("v_iid", OleDbType.Double);
param.SourceColumn = "I";
param.SourceVersion = DataRowVersion.Original;
param = odc.Parameters.Add("v_pd", OleDbType.Boolean);
param.SourceColumn = "Paid";
param.SourceVersion = DataRowVersion.Current;
param = odc.Parameters.Add("v_Projected", OleDbType.Currency);
param.SourceColumn = "ProjectedCost";
param.SourceVersion = DataRowVersion.Current;
odc.Prepare();
myAdapter.UpdateCommand = odc;
...
myAdapter.Update();
It works fine...but the really weird thing is that it didn't until I put in the odc.Prepare() call.My question is thus: Do I need to do that all the time when working with OleDb stored procs/queries? Why? I also have another project coming up where I'll have to do the same thing with a SqlDbCommand... do I have to do it with those, too?
This is called, oddly enough, a prepared statement, and they're actually really nice. Basically what happens is you either create or get a sql statement (insert, delete, update) and instead of passing actual values, you pass "?" as a place holder. This is all well and good, except what we want is our values to get passed in instead of the "?".
So we prepare the statement so instead of "?", we pass in parameters as you have above that are going to be the values that go in in place of the place holders.
Preparing parses the string to find where parameters can replace the question marks so all you have to do is enter the parameter data and execute the command.
Within oleDB, stored queries are prepared statements, so a prepare is required. I've not used stored queries with SqlDB, so I'd have to defer to the 2 answers previous.
I don't use it with SqlDbCommand. It seems as a bug to me that it's required. It should only be nice to have if you're going to call a procedure multiple times in a row. Maybe I'm wrong and there's a note in documentation about providers that love this call too much.
Are you using the JET OLEDB Provider? or MSDASQL + JET ODBC?
You should not need to call Prepare(), but I believe that's driver/provider dependent.
You definitely don't need to use Prepare() for System.Data.SqlClient.

Categories

Resources