I am connecting to an SQL Server 2012 database to query for a single value based on an ID. (It may be worth mentioning that this database is on a server on another continent from my development machine, and so latencies are quite high. Somewhere around 100ms).
The query appears to execute successfully. The HasRows property of the SqlDataReader object is set to true, so I try to use the value to assign a variable. When I run the program normally, I encounter an exception with message 'Given key was not present in the dictionary'. If I stop the execution and inspect the SqlDataReader object, and enumerate the results. Firstly I am told 'enumeration yielded no results' and then when I continue execution I get a different exception with the message 'invalid attempt to read when no data is present'
Here is the code in question:
SqlConnection sql_conn = new SqlConnection(ConnectionString);
SqlCommand sql_cmd = new SqlCommand(String.Format("select ItemType from ItemTable where ItemID='{0}'", item_id), sql_conn);
Console.WriteLine(sql_cmd.CommandText);
sql_conn.Open();
SqlDataReader rdr = sql_cmd.ExecuteReader();
rdr.Read();
if (rdr.HasRows) //True
{
item_type= TypesMap[rdr["ItemType"].ToString()]; //Either 'given key not found in dictionary' or 'invalid attempt to read when no data is present'
}
I have executed the SQL statement in SQL Server Management Studio and it is successful. I have tried hardcoding an ItemID into the statement in the C# code, and the same errors exist.
What more can I do to debug this? Everything appears to be okay, until I try to access the results of the query.
You have to debug: it seems that the TypesMap doesn't have the key read from the database:
// Wrap IDisposable into using
using (SqlConnection sql_conn = new SqlConnection(ConnectionString)) {
// Make SQL readable
// Make SQL parametrized (and not formatted) when it's possible
String sql =
#"select ItemType
from ItemTable
where ItemID = #prm_ItemId";
// Wrap IDisposable into using
using (SqlCommand sql_cmd = new SqlCommand(sql, sql_conn)) {
// I don't know ItemID's type that's why I've put AddWithValue
sql_cmd.Parameters.AddWithValue("#prm_ItemId", item_id);
// Wrap IDisposable into using
using (SqlDataReader rdr = sql_cmd.ExecuteReader()) {
// rdr.HasRows is redundant - rdr.Read() returns true if record has been read
if (rdr.Read()) {
String key = Convert.ToString(rdr.GetValue(0));
// Put break point here: what is the "key" value?
item_type = TypesMap[key];
}
}
}
}
Edit: as Luke has mentioned in the comment, the cause of the error was that key comparison is expected to be case insensitive, so the amendment is to explain .Net how to compare keys:
var TypesMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
...
TypesMap.Add("aBc", "xyz");
String test = TypesMap["Abc"]; // return "xyz"; notice "aBc" and "Abc"
As Dmitry pointed out the 'given key not found...' is not a DB thing but a dictionary thing.
Below I've added a simple check to ensure the key is in the dictionary - if it is then we can assigned to item_type.
Also, if HasRows() isn't doing what you expect, try the following. It's the standard way I read from DB:
using (SqlDataReader results = sql_cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (results.Read())
{
string Key = rdr["ItemType"].ToString();
if (TypesMap.ContainsKey(Key))
item_type = TypesMap[Key];
}
}
I Converted:
dto.Id = (int)record["Id"];
To:
dto.Id = (int)record[0];
This worked for me.
Related
I have a Table in my local database Ships(HistID,ShipName,ShipLength). I am polling the database for all ships with HistID == theme, but while(reader.Read()){} is never entered. Also, my Ships table has more than one row (saw this problem in another SO question) so I'm not sure why I cant store the results into List of Tuples. Executing the query alone in Visual Studio 2015 yields the correct results.
public List<Tuple<String, int>> getHistoricalShipList(int theme)
{
List<Tuple<String, int>> list = new List<Tuple<string, int>>();
using (db)
{
cmd = new SqlCommand(#"Select ShipName, ShipLength from Ships Where HistID=#theme", db);
cmd.Parameters.AddWithValue("#theme", theme);
db.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows) // always returning false
{
//Loop through results
while (reader.Read())
{
String shipName = reader[0].ToString();
int shipLength = Convert.ToInt32(reader[1]);
list.Add(Tuple.Create(shipName, shipLength));
}
}
db.Close();
}
return list;
}
EDIT:: removed the single quotes from the query as suggested, but still having the same issue.
Your theme is of type int, and you are enclosing it in single quotes like it is a string value. Remove the quotes, but more importantly, use Parameters
cmd = new SqlCommand(string.Format(#"Select ShipName, ShipLength from Ships Where HistID={0}", theme), db);
Never use string concatenation/string format to build SQL statements, your code is prone to SQL injection.
cmd = new SqlCommand(#"Select ShipName, ShipLength from Ships Where HistID=#theme", db);
cmd.Parameters.AddWithValue("#theme", theme);
//Or more precise
//cmd.Parameters.Add(new SqlParameter("#theme", SqlDbType.Int) {Value = theme});
The reason you are not getting any rows back is, that your field HistID is of numeric type and you are trying to compare it with a string value (by enclosing the value in single quote).
Remove HasRows check and just use the .Read while loop; there's various bug reports on HasRows not being entirely accurate in some cases.
Other than that, it's likely you're making a mistake somewhere. Either theme isn't what you expect it to be, or some environment error like hitting the wrong database.
How would I take info stored in a Select method and transfer it to a string? I'm trying to get the max value from the match_id column and get its value from command.CommandText into the matchCode string. Where would I go from here?
string connectString = "Server=myServer;Database=myDB;Uid=myUser;Pwd=myPass;";
string matchCode = "";
MySqlConnection connect = new MySqlConnection(connectString);
MySqlCommand command = connect.CreateCommand();
command.CommandText = "SELECT MAX(VAL(match_id)) FROM `data`";
try
{
connect.Open();
command.ExecuteNonQuery();
matchCode = "??";
connect.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
I'm new to C#, as it's like my fourth day trying it out. Thanks for the help!
The ExecuteNonQuery() method is for INSERT/UPDATE/DELETE queries. If you're just getting a single value back, use ExecuteScalar(). If you're getting a whole result set back, use ExecuteReader() or Fill() a DataSet object.
Also, there are some things that are idiomatic to C# that you should be doing:
public int GetMatchCode()
{
//this could be loaded from config file or other source
string connectString = "Server=myServer;Database=myDB;Uid=myUser;Pwd=myPass;";
string sql = "SELECT MAX(VAL(match_id)) FROM `data`";
using (var connect = new MySqlConnection(connectString))
using (var command = new MySqlCommand(sql, connect))
{
connect.Open();
var result = command.ExecuteScalar();
if (result == DBNull.Value)
{
//what you do here depends on your application
// if it's impossible for the query to return NULL, you can even skip this
}
return (int)result;
}
}
Some of the changes need explanation:
I don't ever call .Close(). The using block takes care of that for me, even if an exception was thrown. The old code would have left the connection hanging if an exception occured.
.Net developers tend to believe in very small methods. More than that, this method ought to be part of a class that has nothing but other simple public data access methods and maybe a few private helper methods or properties for abstracting common code in the class.
There is no exception handling code here. If you have small methods that are part of a generic database access class, exception handling should be at higher level, where you are better positioned to make decisions about how to proceed.
I am trying to retrieve list of records from one table , and write to another table. I've used a simple query to retrieve the values to SqlDataReader,then load them to a DataTable. Using the DataTableReader , I am going through the entire data set which is Saved in DataTable. The problem is, while reading each and every record I am trying to insert those values to another table using a Stored Procedure.But it only insert the first row of values,and for the second row onward giving some Exception saying."procedure or function has too many arguments specified".
string ConStr = ConfigurationManager.ConnectionStrings["ConString"].ConnectionString;
SqlConnection NewCon = new SqlConnection(ConStr);
NewCon.Open();
SqlCommand NewCmd3 = NewCon.CreateCommand();
NewCmd3.CommandType = CommandType.Text;
NewCmd3.CommandText ="select * from dbo.Request_List where group_no ='" +group_no+ "'";
NewCon.Close();
NewCon.Open();
SqlDataReader dr = (SqlDataReader)NewCmd3.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
DataTableReader reader = new DataTableReader(dt);
NewCmd.Dispose();
NewCon.Close();
NewCon.Open();
SqlCommand NewCmdGrpReqSer = NewCon.CreateCommand();
NewCmdGrpReqSer.CommandType = CommandType.StoredProcedure;
NewCmdGrpReqSer.CommandText = "Voucher_Request_Connection";
if (reader.HasRows)
{
int request_no = 0;
while (reader.Read())
{
request_no = (int)reader["request_no"];
NewCmdGrpReqSer.Parameters.Add("#serial_no", serial_no);
NewCmdGrpReqSer.Parameters.Add("#request_no", request_no);
try
{
NewCmdGrpReqSer.ExecuteNonQuery();
MessageBox.Show("Connection Updated");//just to check the status.tempory
}
catch (Exception xcep)
{
MessageBox.Show(xcep.Message);
}
MessageBox.Show(request_no.ToString());//
}
NewCmdGrpReqSer.Dispose();
NewCon.Close();
}
Any Solutions ?
As #Sparky suggests, the problem is that you continue to add parameters to the insertion command. There are several other ways in which the code could be improved, however. These improvements would remove the need to clear the parameters and would help to make sure you don't leave disposable resources undisposed.
First - use the using statement for your disposable objects. This removes the need for the explicit Close (btw, only one of Close/Dispose is needed for the connection as I believe Dispose calls Close). Second, simply create a new command for each insertion. This will prevent complex logic around resetting the parameters and, possibly, handling error states for the command. Third, check the results of the insertion to make sure it succeeds. Fourth, explicitly catch a SqlException - you don't want to accidentally hide unexpected errors in your code. If it's necessary to make sure all exceptions don't bubble up, consider using multiple exception handlers and "doing the right thing" for each case - say logging with different error levels or categories, aborting the entire operation rather than just this insert, etc. Lastly, I would use better variable names. In particular, avoid appending numeric identifiers to generic variable names. This makes the code harder to understand, both for others and for yourself after you've let the code sit for awhile.
Here's my version. Note there are several other things that I might do such as make the string literals into appropriately named constants. Introduce a strongly-typed wrapper around the ConfigurationManager object to make testing easier. Remove the underscores from the variable names and use camelCase instead. Though those are more stylistic in nature, you might want to consider them as well.
var connectionString = ConfigurationManager.ConnectionStrings["ConString"].ConnectionString;
using (var newConnection = new SqlConnection(connectionString))
{
newConnection.Open();
using (var selectCommand = newConnection.CreateCommand())
{
selectCommand.CommandType = CommandType.Text;
select.CommandText ="select request_no from dbo.Request_List where group_no = #groupNumber";
selectCommand.Parameters.AddWithValue("groupNumber", group_no);
using (dataReader = (SqlDataReader)newCommand.ExecuteReader())
{
while (reader.HasRows && reader.Read())
{
using (var insertCommand = newConnection.CreateCommand())
{
insertCommand.CommandType = CommandType.StoredProcedure;
insertCommand.CommandText = "Voucher_Request_Connection";
var request_no = (int)reader["request_no"];
insertCommand.Parameters.Add("#serial_no", serial_no);
insertCommand.Parameters.Add("#request_no", request_no);
try
{
if (insertCommand.ExecuteNonQuery() == 1)
{
MessageBox.Show("Connection Updated");//just to check the status.tempory
}
else
{
MessageBox.Show("Connection was not updated " + request_no);
}
}
catch (SqlException xcep)
{
MessageBox.Show(xcep.Message);
}
MessageBox.Show(request_no.ToString());//
}
}
}
}
}
Try clearing your parameters each time...
while (reader.Read())
{
request_no = (int)reader["request_no"];
// Add this line
NewCmdGrpReqSer.Parameters.Clear();
NewCmdGrpReqSer.Parameters.Add("#serial_no", serial_no);
NewCmdGrpReqSer.Parameters.Add("#request_no", request_no);
try
{
My C# code below checks a SQL database to see if a record matches a ClientID and a User Name. If more than 15 or more matching records are found that match, the CPU on my Windows 2008 server peaks at about 78% while the 15 records are found while the below C# code executes. The SQL Server 2008 database and software is located on another server so the problem is not with SQL Server spiking the CPU. The problem is with my C# software that is executing the code below. I can see my software executable that contains the C# code below spike to 78% while the database query is executed and the records are found.
Can someone please tell me if there is something wrong with my code that is causing the CPU to spike when 15 or more matching records are found? Can you also please tell/show me how to optimize my code?
Update: If it finds 10 records, the CPU only spikes at 2-3 percent. It is only when it finds 15 or more records does the CPU spike at 78% for two to three seconds.
//ClientID[0] will contain a ClientID of 10 characters
//output[0] will contain a User Name
char[] trimChars = { ' ' };
using (var connection = new SqlConnection(string.Format(GlobalClass.SQLConnectionString, "History")))
{
connection.Open();
using (var command = new SqlCommand())
{
command.CommandText = string.Format(#"SELECT Count(*) FROM Filelist WHERE [ToAccountName] = '" + output[0] + #"'");
command.Connection = connection;
var rows = (int) command.ExecuteScalar();
if (rows >= 0)
{
command.CommandText = string.Format(#"SELECT * FROM Filelist WHERE [ToAccountName] = '" + output[0] + #"'");
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
//Make sure ClientID does NOT exist in the ClientID field
if (reader["ClientID"].ToString().TrimEnd(trimChars).IndexOf(ClientID[0]) !=
-1)
{
//If we are here, then do something
}
}
}
reader.Close();
reader.Dispose();
}
}
// Close the connection
if (connection != null)
{
connection.Close();
}
}
}
You can decrease the number of database access from 2 to 1 if will remove first query, it is not necessary.
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT ClientID FROM dbo.Filelist WHERE ToAccountName = #param"; // note single column in select clause
command.Parameters.AddWithValue("#param", output[0]); // note parameterized query
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read()) // reader.HasRow is doubtfully necessary
{
// logic goes here
// but it's better to perform it on data layer too
// or return all clients first, then perform client-side logic
yield return reader.GetString(0);
}
} // note that using block calls Dispose()/Close() automatically
}
Change this:
SELECT * FROM Filelist
To this:
SELECT ClientID FROM Filelist
And check for performance.
I suspect there is a blob field on your select.
Also select * is not recommended, write your exact interested fields in your query.
Nothing looks obviously CPU intensive, but one problem does stand out.
You are running a query to count how many records there are
"SELECT Count(*) FROM Filelist WHERE [ToAccountName] = '" + output[0] + #"'"
Then, if more than 0 is returned, you are running another query to get the data.
"SELECT * FROM Filelist WHERE [ToAccountName] = '" + output[0] + #"'"
This is redundant. Get rid of the first query, and just use the second one, checking to see if the reader has data. You can also get rid of the HasRows call and just do
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
}
}
Please consider what already said about parametrized queries.
Beside that, I think that the only big issue could arise in the following block:
while (reader.Read())
{
//Make sure ClientID does NOT exist in the ClientID field
if (reader["ClientID"].ToString().TrimEnd(trimChars).IndexOf(ClientID[0]) != -1)
{
//If we are here, then do something
}
}
So try to just cache your reader.Read() data in some local variable, releasing the SQL resources asap, then you can work on the data you just retrieved. Eg:
List<string> myRows = new List<string>();
while (reader.Read())
{
myRows.Add(reader["ClientID"].ToString();
}
/// quit the using clause
/// now elaborate what you got in myRows
There is nothing in the code to indicate a performance problem.
What does SQL Profiler show?
(Both in terms of query plan, and server resources used.)
Edit: To make this clearer: you have one measurement that might indicate an issue. You now need to measure more deeply to understand if it really is a problem, only you can do this (no one else has access to the hardware).
I strongly recommend that you get a copy of dotTrace from JetBrains.
At the very least, profiling the client code will help you identify/eliminate the source of the CPU spike.
I recommend using parameters as suggested, however, I have seen performance problems where the type of the string column does not match the C# string. In these cases, I suggest specifying the type explicitly.
Like this:
command.CommandText = "SELECT ClientID FROM dbo.Filelist WHERE ToAccountName = #accountName";
command.Parameters.Add("#accountName", SqlDbType.NVarChar, 16, output[0]);
Or this:
SqlParameter param = command.Parameters.Add(
"#accountName", SqlDbType.NVarChar);
param.Size = 16; //optional
param.Value = output[0];
Updated Question: Is there a way to force dataadapter accept only commands which do not include any update/drop/create/delete/insert commands other than verifying the command.text before sending to dataadapter (otherwise throw exception). is there any such built-in functionality provided by dot net in datareader dataadapter or any other?
Note: DataReader returns results it also accepts update query and returns result. (I might be omitting some mistake but I am showing my update command just before executing reader and then show message after its success which is all going fine
Could you search the string for some keywords? Like CREATE,UPDATE, INSERT, DROP or if the query does not start with SELECT? Or is that too flimsy?
You might also want to create a login for this that the application uses that only has read capability. I don't know if the object has that property but you can make the server refuse the transaction.
All you need to do is ensure there are no INSERT, UPDATE, or DELETE statements prepared for the DataAdapter. Your code could look something like this:
var dataAdapter = new SqlDataAdapter("SELECT * FROM table", "connection string");
OR
var dataAdapter = new SqlDataAdapter("SELECT * FROM table", sqlConnectionObject);
And bam, you have a read-only data adapter.
If you just wanted a DataTable then the following method is short and reduces complexity:
public DataTable GetDataForSql(string sql, string connectionString)
{
using(SqlConnection connection = new SqlConnection(connectionString))
{
using(SqlCommand command = new SqlCommand())
{
command.CommandType = CommandType.Text;
command.Connection = connection;
command.CommandText = sql;
connection.Open();
using(SqlDataReader reader = command.ExecuteReader())
{
DataTable data = new DataTable();
data.Load(reader);
return data;
}
}
}
}
usage:
try{
DataTable results = GetDataForSql("SELECT * FROM Table;", ApplicationSettings["ConnectionString"]);
}
catch(Exception e)
{
//Logging
//Alert to user that command failed.
}
There isn't really a need to use the DataAdapter here - it's not really for what you want. Why even go to the bother of catching exceptions etc if the Update, Delete or Insert commands are used? It's not a great fit for what you want to do.
It's important to note that the SelectCommand property doesn't do anything special - when the SelectCommand is executed, it will still run whatever command is passed to it - it just expects a resultset to be returned and if no results are returned then it returns an empty dataset.
This means that (and you should do this anyway) you should explicitly grant only SELECT permissions to the tables you want people to be able to query.
EDIT
To answer your other question, SqlDataReader's are ReadOnly because they work via a Read-Only firehose style cursor. What this effectively means is:
while(reader.Read()) //Reads a row at a time moving forward through the resultset (`cursor`)
{
//Allowed
string name = reader.GetString(reader.GetOrdinal("name"));
//Not Allowed - the read only bit means you can't update the results as you move through them
reader.GetString(reader.GetOrdina("name")) = name;
}
It's read only because it doesn't allow you to update the records as you move through them. There is no reason why the sql they execute to get the resultset can't update data though.
If you have a read-only requirement, have your TextBox use a connection string that uses an account with only db_datareader permissions on the SQL database.
Otherwise, what's stopping the developer who is consuming your control from just connecting to the database and wreaking havoc using SqlCommand all on their own?