Reading data off SQL Rows sequentially - c#

I am working on a process where I basically need to read a lot of customer data, apply some calculations and processes to the data and then save them elsewhere. I know how to do what I want to do, but what I don't know is how to read the data sequentially (row by row). Right now, I am reading the number of accounts so I can make a loop. Here's what I have:
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Connection"].ConnectionString);
conn.Open();
SqlCommand comm = new SqlCommand("SELECT COUNT(*) FROM Accounts WHERE ID = #ID AND Open = 1", conn);
comm.Parameters.AddWithValue("#ID", Session["ID"]);
Int32 count = (Int32)comm.ExecuteScalar();
//testing purposes
Label3.Text = count.ToString();
int i = 0;
while (i <= count)
{
//READ COMMAND HERE?
i = i + 1;
}
if (i == count)
{
//loading bar
UpdateProgress1.Visible = false;
}
Now Inside the loop (I guess!) I need to execute a code to read data from each row where ID = #ID, read the data, use some processes (i know how to work them) and update the data (also know how to do this). I just need to be able to read the data and I don't know how. Help would be appreciated!

Instead of ExecuteScalar, you will want to use ExecuteReader.
Something like:
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Connection"].ConnectionString);
conn.Open();
SqlCommand comm = new SqlCommand("SELECT * FROM Accounts WHERE ID = #ID AND Open = 1", conn);
comm.Parameters.AddWithValue("#ID", Session["ID"]);
using(var dataReader = comm.ExecuteReader())
{
if(dataReader.HasRows)
{
while(dataReader.Read())
{
var myRowColumn1 = dataReader["NameOfColumnInDataBase"].ToString();
}
}
}

Related

How to run multiple data readers on single my sql connection

MySqlConnection con = GetConnection();
string sql = "SELECT COUNT(ID) FROM booking WHERE DATE(Booking_Date) = CURDATE()";
string sql2 = "SELECT SUM(Fare) FROM booking WHERE DATE(Booking_Date) = CURDATE()";
MySqlCommand cmd = new MySqlCommand(sql, con);
cmd.CommandType = System.Data.CommandType.Text;
MySqlDataReader BookingToday = cmd.ExecuteReader();
while (BookingToday.Read())
{
label6.Text = BookingToday.GetValue(0).ToString();
}
This is my C# code and I want to run both the given queries to get result at once. But I don't know how to run multiple data readers on single connection or run 2 connections at once. Anyone please help me in this regard
You can use one query:
string sql = "SELECT COUNT(ID), SUM(Fare) FROM booking WHERE DATE(Booking_Date) = CURDATE()";
In this specific case: you don't need to - you can use the code shown in slaakso's answer to perform both aggregates in one query.
In the more general case:
using (var reader = cmd1.ExecuteReader())
{
// while .Read(), etc
}
using (var reader = cmd2.ExecuteReader())
{
// while .Read(), etc
}
i.e. sequentially and not overlapping (unless your provider supports the equivalent of "MARS").
You can also often issue multiple queries (select) in a single command; you use NextResult() to move between them:
using (var reader = cmd.ExecuteReader())
{
// while .Read(), etc, first grid
if (reader.NextResult())
{
// while .Read(), etc, second grid
}
}
You can't open multiple data readers simultaneously from a single connection.
If you want a single result, you can use ExecuteScalar.
var result1 = (DateTime)new MySqlCommand("select now()", con).ExecuteScalar();
Console.WriteLine(result1);
var result2 = (DateTime)new MySqlCommand("select date_add(now(), interval 10 day)", con).ExecuteScalar();
Console.WriteLine(result2);
For multi-row results, you can store them in a DataTable.
var dt1 = new DataTable();
dt1.Load(new MySqlCommand("select id from table1", con).ExecuteReader());
var dt2 = new DataTable();
dt2.Load(new MySqlCommand("select name from table1", con).ExecuteReader());
foreach(var row in dt1.AsEnumerable())
{
Console.WriteLine($"id:{row["id"]}");
}
foreach (var row in dt2.AsEnumerable())
{
Console.WriteLine($"name:{row["name"]}");
}

sql query to show data from price between two columns C#

EDIT: I am not able to format my code below, if any one can fix it.
I am new to sql queries and still learning.
Table Name: CommissionSetupTable.
I want to display #Paisa if gross_amount is between the range of #FromRate and #ToRate
Below is my code:
string paisa;
private void load_commission_setup()
{
SqlCeConnection conn = null;
SqlCeCommand cmd = null;
SqlCeDataReader rdr = null;
try
{
conn =
new SqlCeConnection(
#"Data Source=|DataDirectory|\Database.sdf;Persist Security Info=False");
conn.Open();
int rowindex = purchaseBillTableDataGridView.Rows.Count - 1;
gross_amount = double.Parse(purchaseBillTableDataGridView[10, rowindex].Value.ToString());
// Gross Amount is between the ranges of FromRate and ToRate.
cmd = new SqlCeCommand("SELECT Paisa FROM CommissionSetupTable WHERE='" + gross_amount.ToString() + "' BETWEEN #FromRate AND #ToRate;", conn);
rdr = cmd.ExecuteReader();
if (rdr == null)
{
}
else
{
while (rdr.Read())
{
paisa = rdr["Paisa"].ToString();
}
rdr.Close();
cmd.Dispose();
}
}
finally
{
conn.Close();
int rowindex = purchaseBillTableDataGridView.Rows.Count - 1;
purchaseBillTableDataGridView[11, rowindex].Value = paisa;
}
}
The correct syntax to use here is the following
cmd = new SqlCeCommand(#"SELECT Paisa FROM CommissionSetupTable
WHERE #gross BETWEEN FromRate AND ToRate;", conn);
Notice that the two field names should not be prefixed with #, otherwise they will be considered parameters placeholders.
And now, before executing the command, add the parameter for the #gross placeholder
cmd.Parameters.Add("#gross", SqlDbType.Decimal).Value = gross_amount;
I don't know what is the exact datatype of the columns FromRate and EndRate, but
note that you should use the correct datatype for your parameter. Do not pass a string and expect the database engine do the conversion for you. (or worse concatenate your value to the rest of the sql using ToString()). This is always wrong also if sometime the database engine could understand your values.
EDIT
Also, following your comments below, it appears that this line is wrong
int rowindex = purchaseBillTableDataGridView.Rows.Count - 1;
If your DataGridView has the property AllowUserToAddRow set to True then you want to use
int rowindex = purchaseBillTableDataGridView.Rows.Count - 2;
because the first line points to the empty row added to the DataGridView for inserting a new record.

How to check if the primary key exists in my database from a textbox (windows form), c#

I have a textbox and a button in a windows form application.
I want to check if the primary key (persId) exists in my sql database/dataset (made with Visual studio) when I enter a number in the textbox and press the button. I dont know how to compare the text with persId from the database.
If the persId exists I want to fill two textboxes in a new form and show the persId and persName.
I am new to programming in C# so I have probably missed something. I looked at how to check if value exists in database from textbox c# but could not find an answer.
Thanks in advance!
public void searchPersId(string persId)
{
SqlConnection conn = new SqlConnection();
SqlCommand myCommand = new SqlCommand("SELECT persId FROM Customers WHERE persId = #persId", conn);
myCommand.Parameters.AddWithValue("#persId", persId);
if (textBox1.Text = myCommand ) //I dont know how to compare the values of textbox with myCommand..
{
//Show values (persId and persName) in two textBoxes in a new form.
}
else
{
MessageBox.Show("The ID does not exist.");
}
}
First, use the using-statement for everything implementing IDisposable like the connection to dispose unmanaged resources and to close the connection, even in case of an error.
Then you have to open the connection and to use ExecuteReader to get a datareader to check if there's at least one record with that ID, you can use reader.HasRows. You also have to select the persName if you want it as mentioned.
using(var conn = new SqlConnection())
using(var myCommand = new SqlCommand("SELECT persId, persName FROM Customers WHERE persId = #persId", conn))
{
myCommand.Parameters.AddWithValue("#persId", persId);
conn.Open();
using(var rd = myCommand.ExecuteReader())
{
bool personExists = rd.HasRows;
if(personExists)
{
// advance the reader to the first record, presuming there is only one, otherwise use a loop while(rd.Read)
rd.Read();
string persName = rd.GetString(1); // second field
// ...
}
else
{
MessageBox.Show("The ID does not exist.");
}
}
}
You can also use ExecuteScalar
public void searchPersId(string persId)
{
SqlConnection conn = new SqlConnection();
SqlCommand myCommand = new SqlCommand("SELECT persName FROM Customers WHERE persId = #persId", conn);
myCommand.Parameters.AddWithValue("#persId", persId);
object personName = myCommand.ExecuteScalar();
if(!string.IsNullOrEmpty(personName.ToString()))
//if (textBox1.Text = myCommand) //I dont know how to compare the values of textbox with myCommand..
{
//Show values (persId and persName) in two textBoxes in a new form.
textBox2.Text = personName.ToString();
}
else
{
MessageBox.Show("The ID does not exist.");
}
}
First you have to Execute the command.
SqlDataReader dr = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
if (dr.HasRows)
{
// ... if it has rows then you know it match
}
else
{
// ... data doesn't exists
}
Then you can compare the result.

Login page in asp.net. Issue with query?

I would like to create a simple login page in asp.net. here is my code:
protected void Button1_Click(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
conn.Open();
string query = "SELECT COUNT(*) FROM Account WHERE acc_username= '" + TextBox1.Text + "' AND acc_password= '" + TextBox2.Text + "'";
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader myreader = cmd.ExecuteReader();
int count = 0;
while(myreader.Read())
{
count = count + 1;
}
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
conn.Close();
}
I set a counter in order to know if the credentials entered are present in the DB. If the value of the counter goes to 1, the login is successful. Otherwise, the label with a message error is displayed!
However, whatever I enter as input in the username and login textboxes, it always redirect me to the other page !
For now, my concern is not the security aspects, I just want to test this simple code, I don't see any problem with the code, but still it doesnt work, it is driving me crazy...
The reason that you are always redirecting is that your reader always returns 1 row, whether there is a match or not. If there is a match in your database, then the query will return
(no column name)
---------------
1
If there is not a match then it will return:
(no column name)
---------------
0
Either way, myreader.Read() will return true, and you will increment count in this part:
while(myreader.Read())
{
count = count + 1;
}
if(count==1)
{
Response.Redirect("page2.aspx");
}
Rather than checking the if the query returns rows you can retrieve the value of the count using SqlCommand.ExecuteScalar(). In addition to this I would make three more changes:
1. Use parameterised queries
This is not just a security concern, parameterised queries are able to use cached plans, whereas if you concatenate the parameters into the query then a new plan is compliled for each new variable value. In addition, parameterised queries are more strongly typed, and you don't need to escape things like O'shea to ensure that your extra quote doesn't mess up the query.
2. Encrypt the passwords
This is directly to do with security so should really be overlooked as per your request to not comment on security, HOWEVER, this answer is not just for your benefit, and a half answer is likely to be read by someone in the future who may or may not be aware of the risks of storing plain text passwords. There is a simple encryption method in this answer.
3. Add using blocks to your code
A minor change, but when you have objects that implement IDisposable it is a good idea to use a using block to esnure they are disposed of properly.
So you might end up with:
string password = SomeStaticClass.Encrypt(TextBox2.Text);
string connectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
string query = "SELECT UserCount = COUNT(*) FROM Account WHERE acc_username= #UserName AND acc_password= #Password";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(query, connection))
{
connection.Open();
command.Parameters.Add("#UserName", SqlDbType.VarChar, 50).Value = TextBox1.Text;
command.Parameters.Add("#Password", SqlDbType.VarChar, 50).Value = password;
int count = Convert.ToInt32(command.ExecuteScalar());
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
}
The problem you are experiencing is because the followinq query Always returns one row even if there isn't a match in the database:
SELECT COUNT(*) FROM Account WHERE acc_username=....
If there is no match, you get a row with one column, value 0.
You are checking the number of rows returned when you should just be checking the return value.
Use this instead
int count = Convert.ToInt32(cmd.ExecuteScalar());
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
I know you said you don't want advice on security but just to be sure:
Don't store passwords plain text in a database. Always hash them using a salt.
Don't use string concatenation when building sql. Use parameters.
don't use ExecuteReader when you want to return a single value, use ExecuteScalar:
int count = int.Pares(cmd.ExecuteScalar().toString());
if(count >= 1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
You should always use Paremeterized queries Using parameters in SQL statements
string username=TextBox1.Text;
string password=TextBox2.Text;
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM Account WHERE acc_username=#username and
AND acc_password=#password", conn);
cmd.Parameters.AddWithValue("#username",username);
cmd.Parameters.AddWithValue("#password",password);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
Try adding if (myreader.HasRows) before while(myreader.Read())

Error "There is already an open DataReader associated with this Command which must be closed first" when using 2 distinct commands

I have this legacy code :
private void conecta()
{
if (conexao.State == ConnectionState.Closed)
conexao.Open();
}
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
conecta();
sql =
#"SELECT *
FROM historico_verificacao_email
WHERE nm_email = '" + email + #"'
ORDER BY dt_verificacao_email DESC, hr_verificacao_email DESC";
com = new SqlCommand(sql, conexao);
SqlDataReader dr = com.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
sql =
#"SELECT COUNT(e.cd_historico_verificacao_email) QT
FROM emails_lidos e
WHERE e.cd_historico_verificacao_email =
'" + dr["cd_historico_verificacao_email"].ToString() + "'";
tipo_sql = "seleção";
conecta();
com2 = new SqlCommand(sql, conexao);
SqlDataReader dr3 = com2.ExecuteReader();
while (dr3.Read())
{
//quantidade de emails lidos naquela verificação
dados_historico[4] = dr3["QT"].ToString();
}
dr3.Close();
conexao.Close();
//login
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
dr.Close();
}
else
{
dr.Close();
}
conexao.Close();
return historicos;
}
I have created two separates commands to correct the issue, but it still continues: "There is already an open DataReader associated with this Command which must be closed first".
An additional info: the same code is working in another app.
Just add the following in your connection string:
MultipleActiveResultSets=True;
The optimal solution could be to try to transform your solution into a form where you don't need to have two readers open at a time. Ideally it could be a single query. I don't have time to do that now.
If your problem is so special that you really need to have more readers open simultaneously, and your requirements allow not older than SQL Server 2005 DB backend, then the magic word is MARS (Multiple Active Result Sets). http://msdn.microsoft.com/en-us/library/ms345109%28v=SQL.90%29.aspx. Bob Vale's linked topic's solution shows how to enable it: specify MultipleActiveResultSets=true in your connection string. I just tell this as an interesting possibility, but you should rather transform your solution.
in order to avoid the mentioned SQL injection possibility, set the parameters to the SQLCommand itself instead of embedding them into the query string. The query string should only contain the references to the parameters what you pass into the SqlCommand.
You can get such a problem when you are two different commands on same connection - especially calling the second command in a loop. That is calling the second command for each record returned from the first command. If there are some 10,000 records returned by the first command, this issue will be more likely.
I used to avoid such a scenario by making it as a single command.. The first command returns all the required data and load it into a DataTable.
Note: MARS may be a solution - but it can be risky and many people dislike it.
Reference
What does "A severe error occurred on the current command. The results, if any, should be discarded." SQL Azure error mean?
Linq-To-Sql and MARS woes - A severe error occurred on the current command. The results, if any, should be discarded
Complex GROUP BY on DataTable
I suggest creating an additional connection for the second command, would solve it. Try to combine both queries in one query. Create a subquery for the count.
while (dr3.Read())
{
dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
Why override the same value again and again?
if (dr3.Read())
{
dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
Would be enough.
I bet the problem is being shown in this line
SqlDataReader dr3 = com2.ExecuteReader();
I suggest that you execute the first reader and do a dr.Close(); and the iterate historicos, with another loop, performing the com2.ExecuteReader().
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
conecta();
sql = "SELECT * FROM historico_verificacao_email WHERE nm_email = '" + email + "' ORDER BY dt_verificacao_email DESC, hr_verificacao_email DESC";
com = new SqlCommand(sql, conexao);
SqlDataReader dr = com.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
//System.Windows.Forms.MessageBox.Show(dados_historico[1]);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
dr.Close();
sql = "SELECT COUNT(e.cd_historico_verificacao_email) QT FROM emails_lidos e WHERE e.cd_historico_verificacao_email = '" + dr["cd_historico_verificacao_email"].ToString() + "'";
tipo_sql = "seleção";
com2 = new SqlCommand(sql, conexao);
for(int i = 0 ; i < historicos.Count() ; i++)
{
SqlDataReader dr3 = com2.ExecuteReader();
while (dr3.Read())
{
historicos[i][4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
dr3.Close();
}
}
return historicos;
Add MultipleActiveResultSets=true to the provider part of your connection string. See the example below:
<add name="DbContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=dbName;Persist Security Info=True;User ID=userName;Password=password;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
Try to combine the query, it will run much faster than executing an additional query per row.
Ik don't like the string[] you're using, i would create a class for holding the information.
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
using (SqlConnection conexao = new SqlConnection("ConnectionString"))
{
string sql =
#"SELECT *,
( SELECT COUNT(e.cd_historico_verificacao_email)
FROM emails_lidos e
WHERE e.cd_historico_verificacao_email = a.nm_email ) QT
FROM historico_verificacao_email a
WHERE nm_email = #email
ORDER BY dt_verificacao_email DESC,
hr_verificacao_email DESC";
using (SqlCommand com = new SqlCommand(sql, conexao))
{
com.Parameters.Add("email", SqlDbType.VarChar).Value = email;
SqlDataReader dr = com.ExecuteReader();
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
//System.Windows.Forms.MessageBox.Show(dados_historico[1]);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
dados_historico[4] = dr["QT"].ToString();
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
}
}
return historicos;
}
Untested, but maybee gives some idea.

Categories

Resources