I am having the hardest time trying to neutralize this oracle not all variables bound. I tried the usual suggestions I got from some Google searches, but nothing seemed to help.
Eventually, for testing purposes, I reduced my query and code to a simple
public override List<DiscountList> GetDiscountList(string name)
{
string cmdText = "select discount from users where name = :Name";
DbParameters prms = new DbParameters(Ado.AdoTemplate.DbProvider);
prms.AddWithValue("Name", name);
List<DiscountList> list = Ado.AdoTemplate.QueryWithRowMapperDelegate<DiscountList>(CommandType.Text, cmdText,
new RowMapperDelegate<DiscountList>((reader, rowNum) =>
{
DiscountList item = new DiscountList();
item.Discount = reader.GetString(0, string.Empty);
return item;
})).ToList();
return list;
}
But I still receive the Oracle error, I even hardcoded the second parameter in AddWithValue to make sure it is not passing a null issue, but still the same error.
EDIT: To include whole function
Not sure why you are using DbParameters instead of OracleParameter like
command.Parameters.Add(New OracleParameter("Name", name));
(OR)
command.Parameters.AddWithValue("Name", name);
Without a bigger code snippet, it's hard to say if what you have done should work or not. To cut to the chase, this should be a working example of what I believe you are trying to do.
Note that the AddWithValue is very convenient and will nearly always result in the proper datatype mapping. If you really want to be iron-clad explicit, you can use the overload that specifies the datatype. If you ever start passing weird datatypes like Blobs, this might become important.
OracleCommand cmd = new OracleCommand("select discount from users where name = :Name",
conn);
cmd.Parameters.Add("Name", OracleDbType.VarChar);
cmd.Parameters[0].Value = name;
OracleDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
object discount = reader.GetValue(0);
}
reader.Close();
Turns out it was a silly mistake by me and I forgot an important piece of code:
})).ToList();
should be
}),prms).ToList();
I think the query should be like this:
string cmdText = "select discount from users where name = #Name";
Related
Before you mark this question as a duplicate, here is the tricky part I don't understand. This error is sporadic, I believe the code is correct and it's always working and I'm handling the possible mistakes with an if else condition inside the Reader part. Here is the code:
public static Tuple<int, string> GetIDAndString(string term)
{
try
{
using (SqlConnection con = GetConnection())
using (cmd = new SqlCommand())
using (myReader)
{
int ID = 0;
string status = string.Empty;
cmd.Connection = con;
con.Open();
cmd.CommandText = #"SELECT t.TableID, t.Status
FROM Table t WITH (NOLOCK) /* I know NOLOCK is not causing the mistake as far as I know */
WHERE t.Term = #term";
cmd.Parameters.AddWithValue("#term", term);
myReader = cmd.ExecuteReader();
while(myReader.Read())
{
ID = myReader.IsDBNull(0) ? 0 : myReader.GetInt32(0);
status = myReader.IsDBNull(1) ? string.Empty : myReader.GetString(1).Trim();
}
myReader.Close();
return new Tuple<int, string>(ID, status);
}
}
catch (Exception)
{
throw;
}
}
I know I should be using a class instead of a Tuple, but I can't change that existing code and as you can see. So the main problem is that in the production server there was a Index out of bounds array exception in that method but I can't identify what's the problem.
Even if the term is not found in the query, the myReader will not enter and I'll return the ID = 0, status = string.Empty. Sometimes when I'm debugging code and working on the develpment server, my code starts to crash everywhere, showing me exceptions where is tested code and I have to reopen the solution to avoid that (I haven't found a solution to that, not even cleaning the solution).
So I hope someone have experience with something like that in a production server. I don't have specifications to the production server so I don't know anything about the server.
First you don't need the try/catch block, you don't do anything with it. After that don't share SqlDataReader in the class, this could bring problems and probably the problem comes from this. You are overwriting the value of ID and Status all the time in your while. Probably a good idea will be to call Top 1 on your query and order it by with correct field. Also there is no need to Dispose() the SqlCommand, the Constructor of SqlCommand is calling SupressFinalization().
Why this problem can happen: Imagine your query returns 1000 records with TableID and Status column and you are entering the while loop. In this moment some other user is going in your application and executing another method which overwrites the SqlDataReader and return 5 records with only one column. On the next iteration of you while loop you will receive your exception. Because of that you should never define your Readers as static for the whole class. Static variables are shared between all of application users.
public static Tuple<int, string> GetIDAndString(string term)
{
int ID = 0;
string status = string.Empty;
using (SqlConnection con = GetConnection())
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
con.Open();
cmd.CommandText = #"SELECT t.TableID, t.Status
FROM Table t WITH (NOLOCK) /* I know NOLOCK is not causing the mistake as far as I know */
WHERE t.Term = #term";
cmd.Parameters.AddWithValue("#term", term);
using(SqlDataReader myReader = cmd.ExecuteReader())
{
while(myReader.Read())
{
ID = myReader.IsDBNull(0) ? 0 : myReader.GetInt32(0);
status = myReader.IsDBNull(1) ? string.Empty : myReader.GetString(1).Trim();
}
}
}
return new Tuple<int, string>(ID, status);
}
this probably happens when you do ID = myReader.IsDBNull(0) ? 0 : myReader.GetInt32(0); or status = myReader.IsDBNull(1) ? string.Empty : myReader.GetString(1).Trim(); because the result set does not conform to your expectations. You should add logging of the reader's row before actually reading it, might help you pinpoint the issue
I guess the problem is caused by the myReader field which I suppose is static. If you look at the SqlDataReader (I suppose that's the field's type) documentation at https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader(v=vs.110).aspx, you'll find that instance methods are not thread safe, hence you must synchronize access to that field.
using (myReader) captures the value that the reader had at that time and disposes that later. It does not remember the variable. This has to be so as you can see from this example: using (Random() ? myReader : null). Clearly, the C# language will not reexecute that expression at dispose time. It runs it just once.
So you're disposing some old/other reader.
In case you are sharing objects between threads (maybe using static variables) this trivially is a race condition. Don't do that. Use locals. There is no need/advantage to use static variables here.
I have a list Called ListTypes that holds 10 types of products. Below the store procedure loops and gets every record with the product that is looping and it stores it in the list ListIds. This is killing my sql box since I have over 200 users executing this constantly all day.
I know is not a good architecture to loop a sql statement, but this the only way I made it work. Any ideas how I can make this without looping? Maybe a Linq statement, I never used Linq with this magnitude. Thank you.
protected void GetIds(string Type, string Sub)
{
LinkedIds.Clear();
using (SqlConnection cs = new SqlConnection(connstr))
{
for (int x = 0; x < ListTypes.Count; x++)
{
cs.Open();
SqlCommand select = new SqlCommand("spUI_LinkedIds", cs);
select.CommandType = System.Data.CommandType.StoredProcedure;
select.Parameters.AddWithValue("#Type", Type);
select.Parameters.AddWithValue("#Sub", Sub);
select.Parameters.AddWithValue("#TransId", ListTypes[x]);
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
ListIds.Add(Convert.ToInt32(dr["LinkedId"]));
}
cs.Close();
}
}
}
Not a full answer, but this wouldn't fit in a comment. You can at least update your existing code to be more efficient like this:
protected List<int> GetIds(string Type, string Sub, IEnumerable<int> types)
{
var result = new List<int>();
using (SqlConnection cs = new SqlConnection(connstr))
using (SqlCommand select = new SqlCommand("spUI_LinkedIds", cs))
{
select.CommandType = System.Data.CommandType.StoredProcedure;
//Don't use AddWithValue! Be explicit about your DB types
// I had to guess here. Replace with the actual types from your database
select.Parameters.Add("#Type", SqlDBType.VarChar, 10).Value = Type;
select.Parameters.Add("#Sub", SqlDbType.VarChar, 10).Value = Sub;
var TransID = select.Parameters.Add("#TransId", SqlDbType.Int);
cs.Open();
foreach(int type in types)
{
TransID.Value = type;
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
result.Add((int)dr["LinkedId"]);
}
}
}
return result;
}
Note that this way you only open and close the connection once. Normally in ADO.Net it's better to use a new connection and re-open it for each query. The exception is in a tight loop like this. Also, the only thing that changes inside the loop this way is the one parameter value. Finally, it's better to design methods that don't rely on other class state. This method no longer needs to know about the ListTypes and ListIds class variables, which makes it possible to (among other things) do better unit testing on the method.
Again, this isn't a full answer; it's just an incremental improvement. What you really need to do is write another stored procedure that accepts a table valued parameter, and build on the query from your existing stored procedure to JOIN with the table valued parameter, so that all of this will fit into a single SQL statement. But until you share your stored procedure code, this is about as much help as I can give you.
Besides the improvements others wrote.
You could insert your ID's into a temp table and then make one
SELECT * from WhatEverTable WHERE transid in (select transid from #tempTable)
On a MSSQL this works really fast.
When you're not using a MSSQL it could be possible that one great SQL-Select with joins is faster than a SELECT IN. You have to test these cases by your own on your DBMS.
According to your comment:
The idea is lets say I have a table and I have to get all records from the table that has this 10 types of products. How can I get all of this products? But this number is dynamic.
So... why use a stored procedure at all? Why not query the table?
//If [Type] and [Sub] arguments are external inputs - as in, they come from a user request or something - they should be sanitized. (remove or escape '\' and apostrophe signs)
//create connection
string queryTmpl = "SELECT LinkedId FROM [yourTable] WHERE [TYPE] = '{0}' AND [SUB] = '{1}' AND [TRANSID] IN ({2})";
string query = string.Format(queryTmpl, Type, Sub, string.Join(", ", ListTypes);
SqlCommand select = new SqlCommand(query, cs);
//and so forth
To use Linq-to-SQL you would need to map the table to a class. This would make the query simpler to perform.
OK, nobody seems to know how to solve the problem I'm having looping through a cursor/result set for storage into a List, so I'm going to break it down into pieces and try to slog through it that way. So, first of all:
I add SQL Parameters to an OracleCommand object this way (works fine):
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
IOW, when I add the param, I pass the name of the parameterized portion of the SQL ("ABCID" above) and a value to give it (_ABCID is a variable that has been assigned, let's say, "42").
However, when adding a Cursor (output) param, it seems to want, not a value (such as an initialized cursor object), but simply the data type:
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
(I tried both ways, and neither one works, so...?)
Verily/thus, my question is: Is this really the correct way of declaring a Cursor parameter to be outputted back for traversal/access?
I'm using the brand new version of DevArt DotConnect components (6.80.332), VS 2010, .NET 4
Updated:
Here's the code in more context:
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("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
//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 following is from the Oracle Data Provider for .NET Developer's Guide. yes, I know, "Devart". Nonetheless, It suggests the following:
Be careful with your parameter typing declaration.
add that cursor/output parameter to the Parameters collection before
any others.
As a long shot... my guide shows a OracleDbType.RefCursor but not a OracleDbType.Cursor. If DevArt has RefCursor, try that. In visual studio, what type does .NET think that parameter is? This question is not as dumb as I used to think.
... On the other hand, if the parameter is set as an OracleDbType.Char type by setting the OracleDbType property, the output data is returned
as an OracleString type. If both DbType and OracleDbType properties
are set before the command execution, the last setting takes affect.
. . .
"An application should not bind a value for output parameters; it is
the responsibility of ODP.NET to create the value object and populate
the OracleParameter Value property with the object. When binding by
position (default) to a function, ODP.NET expects the return value to
be bound first, before any other parameters."
EDIT:
Based on #Clay's self-answer... so there is no parameter specified for the output, rather one simply does this: OracleDataReader odr = cmd.ExecuteReader();
Straight from the horse's mouth (the DevArt folks):
_UserName = AUserName;
// From the DevArtisans:
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;
Devart.Data.Oracle.OracleDataReader odr = cmd.ExecuteReader();
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
To quote Casey and the Sonshine Banned, "That's the way, Uh huh Uh huh, I like it, Uh huh Uh huh"; actually, I can't stand that crap, but I do kind of relate to that sentiment right about now.
i have design an access query which seem like this:
SELECT Replace(names,'lion','kiss') AS Expr1
FROM table1;
the two values that is lion and kiss, which are temporary, now i want these to be two variables, so that i can pass value to it from c#.
how to call this query from c#, and pass it two values.
I am using access 2007.
Thanks for your help :)
Try something like this (I found this on the subject):
public void ReplaceColumnA(string oldvalue, string newvalue)
{
using(OleDbConnection connection1 = (OleDbConnection)DatabaseConnection.Instance.GetConnection())
{
connection1.Open();
using(OleDbCommand sqlcmd2 = new OleDbCommand("queryname", connection1))
{
sqlcmd2.Parameters.AddWithValue("param1", newvalue);
sqlcmd2.Parameters.AddWithValue("param2", oldvalue);
sqlcmd2.ExecuteNonQuery();
}
}
}
The Access query would look like this:
UPDATE [t]
SET [a] = ?
WHERE [a] = ?
The names of the parameters you pass on don't matter, it's the order you pass them as.
By using the "using" statement you are ensure .NET is properly releasing the connections and resources.
Additionally I STRONGLY recommend switching to SQL Server Express Edition. It's free and a LOT more potent than what you can cook up in Access. Really, you're just shooting yourself in the foot continuing in Access...
You execute it like this:
sqlcmd2.ExecuteNonQuery();
I think you need to modify code like this
string q = "SELECT Replace(names,'{0}','{1}') AS Expr1 FROM table1";
//You can provide any values instead of LION and KISS
string query = string.format(q,"LION","KISS")
using(OleDbConnection cnn = (OleDbConnection)DatabaseConnection.Instance.GetConnection())
{
cnn.Open();
using(OleDbCommand cmd = new OleDbCommand(query, cnn))
{
OleDbDataReader reader = cmd.ExecuteReader();
while(reader.Read())
{
Console.WriteLine(reader["Expr1"].toString());
}
}
}
// Add into DB
using (tblArtworkTemplatesTableAdapter tblAdapter = new tblArtworkTemplatesTableAdapter())
{
tblAdapter.Insert(DateTime.Now, "#specID");
"#specID" = int.Parse(lstChooseSpec.SelectedValue)
}
I know the code is wrong, just for illustration of my objective, how do I paramatise the input?
Generally it depends. If You are using any kind of ORM like LINQ to SQL or NHibernate, it will do it for You no questions asked. If YOu are doing it using Plain ADO objects (which I suppose is the case) then You will have to comeup with the Command (or SQLCommand or any other ICommand implementation) object and use SQLParameter class (or other parameter classes).
ICommand has the collection of parameters that You can arbitralily edit.
SqlCommand cmd = new SqlCommand(
"select * from STH where column = #SpecID", conn);
//it might be useful to specify a type as well
SqlParameter param = new SqlParameter();
param.ParameterName = "#SpecID";
//I woudl use the TryParse method though
param.Value = int.Parse(lstChooseSpec.SelectedValue);
cmd.Parameters.Add(param);
This line
"#specID" = int.Parse(lstChooseSpec.SelectedValue)
Is incorrect. You can't assign a value to a constant. You might mean something like
specId = int.Parse(lstChooseSpec.SelectedValue);
The rest of the code is confusing. Why are you parsing lstChooseSpec.SelectedValue to an integer, then trying to add it to the adapter as a DateTime? C# is strongly-typed: something is either an int or a DateTime, but cannot be both.
It might help if you could post the rest of the method.
Also, have a look at this overview on MSDN.