So, basically what I'm doing is, after adding a diagnosis on the TextBox I'm checking if there is a Diagnosis with the same name already. The connection works fine, however, I'm having difficulties with executing the command in this line here:
var count = (int)cmd.ExecuteNonQuery();
Here's the full method
protected void MesmoDiagnostico_ServerValidate(object source, ServerValidateEventArgs args)
{
string connectionString = ConfigurationManager.ConnectionStrings["BDClinica"].ConnectionString;
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("Select COUNT(*) from Diagnosticos Where Diagnostico_Nome=#Diagnostico_Nome", connection);
connection.Open();
cmd.Parameters.AddWithValue("#Diagnostico_Nome", source);
var count = (int)cmd.ExecuteNonQuery();
if (count > 0)
{
args.IsValid = false;
}
else
{
args.IsValid = true;
}
connection.Close();
}
Am I missing something? Thanks!
According to MSDN, ExecuteNonQuery is for executing catalog or UPDATE/INSERT/DELETE operations and returns the number of rows affected. By using a COUNT, you're still looking for "number of rows" but it's being executed as query, not an update.
Since you only want one piece of data, technically the first column of the first row, you can use ExecutScalar instead.
This is almost the exact code that you need :
SqlConnection con = new SqlConnection(Settings.Default.FrakoConnectionString);
SqlCommand maxcommand = new SqlCommand("SELECT MAX(Counter) AS max FROM ppartikulieren", con);
try
{
con.Open();
max = (int)maxcommand.ExecuteScalar() + 1;
}
catch (Exception ex)
{
MessageBox.Show("Fout bij het plakken:\n" + ex.Message, "Frako planner", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
finally
{
con.Close();
}
you can also use a using statement of course. But the point is that you really need to cast the output of ExecuteScalar.
Related
I have issue when showing the status when data was delete or not. Here's the code
public bool isDelete (String nim, String pass)
{
String query = "delete from dbmahasiswa where NIM=#NIM AND Password=#Password";
class_Mahasiswa cm = new class_Mahasiswa();
try
{
connect.Open();
MySqlCommand cmd = new MySqlCommand(query, connect);
cmd.Parameters.AddWithValue("#NIM", nim);
cmd.Parameters.AddWithValue("#Password", pass);
cmd.ExecuteNonQuery();
MySqlDataReader reader;
reader = cmd.ExecuteReader();
int count = 0;
while (reader.Read())
{
count += 1;
}
if (count == 1)
{
System.Windows.Forms.MessageBox.Show("sukses!", "Status");
return true;
}
else
System.Windows.Forms.MessageBox.Show("akun tidak ditemukan", "Status");
return false;
connect.Close();
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.Message, "Warning");
return false;
}
return true;
}
If I type the wrong username or password, it will show MessageBox "akun tidak ditemukan"(account not found). Also when I type the right username and password to delete it, it will show that MessageBox because the function will read the database after data has been deleted.
My question is, how to show the "Sukses" MessageBox when data has been deleted?
You are calling ExecuteReader. The ExecuteReader is used to read data returning from the query with a SELECT statement. You can't use it to know if a row or more has been deleted. For this task you use just ExecuteNonQuery and get the return value to know the number of rows 'affected' by the query command
String query = "delete from dbmahasiswa where NIM=#NIM AND Password=#Password";
class_Mahasiswa cm = new class_Mahasiswa();
try
{
connect.Open();
MySqlCommand cmd = new MySqlCommand(query, connect);
cmd.Parameters.AddWithValue("#NIM", nim);
cmd.Parameters.AddWithValue("#Password", pass);
int rows = cmd.ExecuteNonQuery();
if (rows > 0)
{
System.Windows.Forms.MessageBox.Show("sukses!", "Status");
return true;
}
else
{
System.Windows.Forms.MessageBox.Show("akun tidak ditemukan", "Status");
return false;
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.Message, "Warning");
return false;
}
finally
{
connect.Close();
}
}
Also, it seems that you are using a global connect object for your connection. This is usually the source of many bugs like the one you have in your catch clause. If your code results in an exception you forgot to close the connection and, in the next call to connect.Open, you will get an error. I have added a finally to ensure proper closure of your connection object. However it is a better practice to keep the connection local to the code where you need it, open inside a using statement block to have it closed and disposed at the end of the block
I want to shift some variables by one. I searched for the command for it but I couldn't find. If anybody knows it please help me.
Here is the code:
private int shiftNumbers(int number)
{
int newNumber = 0;
string stm = "UPDATE devices SET number= #newNumber WHERE number>#number";
try
{
con.Open();
cmd = new MySqlCommand(stm, con);
cmd.Parameters.AddWithValue("#number", number);
}
catch (Exception e)
{
ErrorMessage = e.Message;
con.Close();
return null;
}
try
{
rdr = cmd.ExecuteReader();
while(rdr.Read()) {
newNumber = rdr.GetInt32(1);
cmd.Parameters.AddWithValue("#newNumber ", (newNumber-1));
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
con.Close();
return null;
}
con.Close();
return 1;
}
I know this code useless but I show it for you to get the logic that I want to do.
I think your approach is wrong.
First, you read from the database, using a select statement;
Then you go over that result, your rdr.Read();
Then you create a new command, updating the original record;
Move forward in your reader (rdr) and repeat from 2 until you are done.
What you are doing now is impossible. You can't get a result set from an update, just a count affected.
Or, if you can, let your update statement do the calculation (it seems it is only subtracting one from the original number, so why not do that in SQL?):
string stm = "UPDATE devices SET number = number - 1 WHERE number>#number";
Yes, your code is really useless. In your update statement you are passing a parameter #newNumber bu not providing it. Closing the connection in catch block.
string stm = "UPDATE devices SET number= #newNumber WHERE number>#number";
First decide from where you are going to get the #newNumber value and then add that as parameter and use ExecuteNonQuery() method.
If you want pass the other parameter as well in your method and use it like
private int shiftNumbers(int number, int newNumber)
{
//int newNumber = 0;
string stm = "UPDATE devices SET number= #newNumber WHERE number>#number";
using(SqlConnection con = new SqlConnection(connectionString))
{
cmd = new MySqlCommand(stm, con);
SqlParameter paramNumber = new SqlParameter("#number", SqlDbType.Int);
paramNumber.Value = number;
SqlParameter paramNewNumber = new SqlParameter("#newNumber", SqlDbType.Int);
paramNewNumber.Value = newNumber;
cmd.Parameters.Add(paramNumber);
cmd.Parameters.Add(paramNewNumber);
con.Open();
cmd.ExecuteNonQuery();
}
//Rest of your code logic if any
}
So I have this code that is designed to delete a row in mySQL server database judging by what is selected in my list box. Here is the code I have to remove the rows:
private void remove_btn_Click(object sender, EventArgs e)
{
try
{
if (Calls_lsb.SelectedItem == null)
MessageBox.Show("Please select an item for deletion.");
}
else
{
int i = Calls_lsb.SelectedIndex;
if (i > 0)
{
SqlConnection connection = new SqlConnection(//My Connection String);
string sqlStatement1 = "DELETE FROM Records WHERE CallID = #Id";
string sqlStatement2 = "DELETE FROM Calls WHERE CallID = #Id";
connection.Open();
SqlCommand cmd1 = new SqlCommand(sqlStatement1, connection);
cmd1.Parameters.AddWithValue("#Id", Calls_lsb.Items[i]);
cmd1.ExecuteNonQuery();
SqlCommand cmd2 = new SqlCommand(sqlStatement2, connection);
cmd2.Parameters.AddWithValue("#Id", Calls_lsb.Items[i]);
cmd2.ExecuteNonQuery();
connection.Close();
Calls_lsb.Items.Remove(Calls_lsb.Items[i]);
}
else
{
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
I get no exceptions and I have similar code that adds records that works fine. I tried stepping into the code but it all seemed fine. It simply just does not delete the row from the database. It removes the correct item from the list, just not the database.
If anyone could shine some light on this situation that would be great, thanks!
Edit : Ok, I seem to have fixed the problem. I just removed the whole i = selected index stuff and replace the 'Calls_lsb.Items[i]' with '(Calls_lsb.SelectedIndex + 1)'. I don't really understand why I was getting an exception when I tried to add 1 to i as this is basically doing the same thing.
Replace your below line code.
cmd1.Parameters.AddWithValue("#Id", Calls_lsb.Items[i]);
//with
cmd1.Parameters.AddWithValue("#Id", Calls_lsb.Items[i].Value);
and
cmd2.Parameters.AddWithValue("#Id", Calls_lsb.Items[i]);
// with
cmd2.Parameters.AddWithValue("#Id", Calls_lsb.Items[i].Value);
In my WindowsCE / Compact Framework (.NET1.1) project, I need to create a new table in code. I thought I could do it this way:
if (! TableExists("table42"))
{
CreateTable42();
}
public static bool TableExists(string tableName)
{
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
sqlConn.Open();
string qryStr = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ?";
SqlCeCommand cmd = new SqlCeCommand(qryStr, sqlConn);
cmd.Parameters[0].Value = tableName;
cmd.CommandType = CommandType.Text;
int retCount = (int)cmd.ExecuteScalar();
return retCount > 0;
}
}
catch (Exception ex)
{
MessageBox.Show("TableExists ex.Message == " + ex.Message);
MessageBox.Show("TableExists ex.ToString() == " + ex.ToString());
MessageBox.Show("TableExists ex.GetBaseException() == " + ex.GetBaseException());
return false;
}
}
...but the call to TableExists() fails; and shows me:
TableExists ex.Message ==
TableExists ex.ToString() == System.Data.SqlServerCe.SqlCeException at System.Data.SqlServerCe.SqlConnection.ProcessResults(Int32 hr) at ...at Open(boolean silent) ...
TableExists ex.GetBaseException() == [same as ex.ToString() above]
"Int32 hr" ... ??? What the Hec Ramsey is that?
As documented previously in these environs, I can't step through this projct, so I rely on those calls to MessageBox.Show().
The rest of the related code, if it may be of interest, is:
public static void CreateTable42()
{
try
{
using (SqlCeConnection con = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
con.Open();
using (SqlCeCommand com = new SqlCeCommand(
"create table table42 (setting_id INT IDENTITY NOT NULL PRIMARY KEY, setting_name varchar(40) not null, setting_value(63) varchar not null)", con))
{
com.ExecuteNonQuery();
WriteSettingsVal("table42settingname","table42settingval");
}
}
}
catch (Exception ex)
{
MessageBox.Show("CreateTable42 " + ex.Message);
}
}
public static void WriteSettingsVal(string settingName, string settingVal)
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
sqlConn.Open();
string dmlStr = "insert into tabld42 (setting_name, setting_value) values(?, ?)";
SqlCeCommand cmd = new SqlCeCommand(dmlStr, sqlConn);
cmd.CommandType = CommandType.Text;
cmd.Parameters[0].Value = settingName;
cmd.Parameters[1].Value = settingVal;
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show("WriteSettingsVal " + ex.Message);
}
}
}
UPDATE
Answer to Brad Rem's comment:
I don't think it's necessary to encase the param in quotes, as other working code is like:
cmd.Parameters.Add("#account_id", Dept.AccountID);
-and:
cmd.Parameters[0].Value = Dept.AccountID;
(it does it one way the first time when in a loop, and the other way thereafter (don't ask me why).
Anyway, just for grins, I did change the TableExists() parameter code from this:
cmd.Parameters[0].Value = tableName;
...to this:
cmd.Parameters.Add("#TABLE_NAME", tableName);
...but I still get the exact same result.
UPDATE 2
Here (http://msdn.microsoft.com/en-us/library/aa237891(v=SQL.80).aspx) I found this: "Caution You must specify the SQL Server CE provider string when you open a SQL Server CE database."
They give this example:
cn.ConnectionString = "Provider=Microsoft.SQLSERVER.OLEDB.CE.2.0; data source=\Northwind.sdf"
I'm not doing that; my conn str is:
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\CCRDB.SDF"))
Could that be my problem?
UPDATE 3
I took this gent's advice (http://www.codeproject.com/Answers/629613/Why-is-my-SQLServer-CE-code-failing?cmt=487657#answer1) and added a catch for SqlCeExcpetions so that it is now:
public static bool TableExists(string tableName)
{
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\CCRDB.SDF"))
{
sqlConn.Open();
string qryStr = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #TABLE_NAME";
SqlCeCommand cmd = new SqlCeCommand(qryStr, sqlConn);
cmd.Parameters.Add("#TABLE_NAME", tableName);
cmd.CommandType = CommandType.Text;
int retCount = (int)cmd.ExecuteScalar();
return retCount > 0;
}
}
catch (SqlCeException sqlceex)
{
MessageBox.Show("TableExists sqlceex.Message == " + sqlceex.Message);
MessageBox.Show("TableExists sqlceex.ToString() == " + sqlceex.ToString());
return false;
. . .
The SqlCeException message is: "There is a file sharing violation. A different process might be using the file [,,,,,]" then "...processresults ... open ... getinstance ..."
UPDATE 4
Trying to use ctacke's sample code, but: Is Transaction absolutely necessary? I had to change the code to the following for my scenario/milieu, and don't know what Transaction should be or how to build it:
public static bool TableExists(string tableName)
{
string sql = string.Format("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'", tableName);
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\HHSDB.SDF"))
{
SqlCeCommand command = new SqlCeCommand(sql, sqlConn);
//command.Transaction = CurrentTransaction as SqlCeTransaction;
command.Connection = sqlConn;
command.CommandText = sql;
int count = Convert.ToInt32(command.ExecuteScalar());
return (count > 0);
}
}
catch (SqlCeException sqlceex)
{
MessageBox.Show("TableExists sqlceex.Message == " + sqlceex.Message);
return false;
}
}
UPDATE 5
With this code, the err msg I get is, "An err msg is available for this exception but cannot be displayed because these messages are optional and are not currently insallted on this device. Please install ... NETCFv35.Messages.EN.cab"
UPDATE 6
All too typically, this legacy, ancient-technology project is giving me headaches. It seems that only one connection is allowed to be open at a time, and the app opens one from the outset; so, I have to use that connection. However, it is a DBConnection, not a SqlCeConnection, so I can't use this code:
using (SqlCeCommand com = new SqlCeCommand(
"create table hhs_settings (setting_id int identity (1,1) Primary key, setting_name varchar(40) not null, setting_value(63) varchar not null)", frmCentral.dbconn))
{
com.ExecuteNonQuery();
WriteSettingsVal("beltprinter", "ZebraQL220");
}
...because the already-open connection type passed as an arg to the SqlCeCommand constructor is DBCommand, not the expected/required SqlCeConneection.
The tentacles of this code are far too wide and entrenched to rip out by the roots and refactor to make it more sensible: a single tentative step in the foothills causes a raging avalanche on Everest.
For fun I'd try two things. First, replace the '?' parameter with a named parameter like '#tablename' and see if that changes things. Yes, I know '?' should work, but it's a confusing, ugly precedent and maybe since it's a system table it's wonky. Yes, it's a stretch, but worth a try just to know.
The second thing I'd do is something like this method from the SQLCE implementation of the OpenNETCF ORM:
public override bool TableExists(string tableName)
{
var connection = GetConnection(true);
try
{
using (var command = GetNewCommandObject())
{
command.Transaction = CurrentTransaction as SqlCeTransaction;
command.Connection = connection;
var sql = string.Format("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'", tableName);
command.CommandText = sql;
var count = Convert.ToInt32(command.ExecuteScalar());
return (count > 0);
}
}
finally
{
DoneWithConnection(connection, true);
}
}
Note that I didn't even bother parameterizing, largely because I doubt it will provide any perf benefit (queue the hordes whining about SQL injection). This way definitely works - we've got it deployed and in use in many live solutions.
EDIT
For completeness (though I'm not sure it adds to clarity).
protected virtual IDbConnection GetConnection(bool maintenance)
{
switch (ConnectionBehavior)
{
case ConnectionBehavior.AlwaysNew:
var connection = GetNewConnectionObject();
connection.Open();
return connection;
case ConnectionBehavior.HoldMaintenance:
if (m_connection == null)
{
m_connection = GetNewConnectionObject();
m_connection.Open();
}
if (maintenance) return m_connection;
var connection2 = GetNewConnectionObject();
connection2.Open();
return connection2;
case ConnectionBehavior.Persistent:
if (m_connection == null)
{
m_connection = GetNewConnectionObject();
m_connection.Open();
}
return m_connection;
default:
throw new NotSupportedException();
}
}
protected virtual void DoneWithConnection(IDbConnection connection, bool maintenance)
{
switch (ConnectionBehavior)
{
case ConnectionBehavior.AlwaysNew:
connection.Close();
connection.Dispose();
break;
case ConnectionBehavior.HoldMaintenance:
if (maintenance) return;
connection.Close();
connection.Dispose();
break;
case ConnectionBehavior.Persistent:
return;
default:
throw new NotSupportedException();
}
}
wow... still struggling... I did too when I first got started on a handheld device SQL-CE. My current project is running with C#.Net 3.5 but I think the principles you are running into are the same. Here is what is working for my system in it's close parallels to yours.
First, the connection string to the handheld. It is just
string myConnString = #"Data Source=\MyFolder\MyData.sdf";
no reference to the sql driver
Next, the TableExists
SqlCeCommand oCmd = new SqlCeCommand( "select * from INFORMATION_SCHEME.TABLES "
+ " where TABLE_NAME = #pTableName" );
oCmd.Parameters.Add( new SqlCeParameter( "pTableName", YourTableParameterToFunction ));
The "#pTableName" is to differentiate between the "TABLE_NAME" column and to absolutely prevent any issues about ambiguity. The Parameter does NOT get the extra "#". In SQL, the # indicates to look for a variable... The SqlCeParameter of "pTableName" must match as it is in the SQL Command (but without the leading "#").
Instead of issuing a call to ExecuteScalar, I am actually pulling the data down into a DataTable via
DataTable oTmpTbl = new DataTable();
SqlCeDataAdapter da = new SqlCeDataAdapter( oCmd );
da.Fill( oTmpTbl );
bool tblExists = oTbl.Rows.Count > 0;
This way, I either get records back or I dont... if I do, the number of records should be > 0. Since I'm not doing a "LIKE", it should only return the one in question.
When you get into your insert, updates and deletes, I have always tried to prefix my parameters with something like "#pWhateverColumn" and make sure the SqlCeParameter is by the same name but without the "#". I haven't had any issues and this project has been running for years. Yes it's a .net 3.5 app, but the fundamental basics of connecting and querying SHOULD be the same.
If it IS all within your application, I would try something like creating a single global static "Connection" object. Then, a single static method to handle it. Then, instead of doing a NEW connection during every "using" attempt, change it to something like...
public static class ConnectionHandler
{
static SqlCeConnection myGlobalConnection;
public static SqlCeConnection GetConnection()
{
if( myGlobalConnection == null )
myGlobalConnection = new SqlCeConnection();
return myGlobalConnection;
}
public static bool SqlConnect()
{
GetConnection(); // just to ensure object is created
if( myGlobalConnection.State != System.Data.ConnectionState.Open)
{
try
{
myGlobalConnection.ConnectionString = #"Data Source=\MyFolder\MyDatabase.sdf";
myGlobalConnection.Open();
}
catch( Exception ex)
{
// optionally messagebox, or preserve the connection error to the user
}
}
if( myGlobalConnection.State != System.Data.ConnectionState.Open )
MessageBox.Show( "notify user");
// return if it IS successful at opening the connection (or was already open)
return myGlobalConnection.State == System.Data.ConnectionState.Open;
}
public static void SqlDisconnect()
{
if (myGlobalConnection!= null)
{
if (myGlobalConnection.State == ConnectionState.Open)
myGlobalConnection.Close();
// In case some "other" state, always try to force CLOSE
// such as Connecting, Broken, Fetching, etc...
try
{ myGlobalConnection.Close(); }
catch
{ // notify user if issue}
}
}
}
... in your other class / function...
if( ConnectionHandler.SqlConnect() )
Using( SqlCeConnection conn = ConnectionHandler.GetConnection )
{
// do your stuff
}
... finally, when your app is finished, or any other time you need to...
ConnectionHandler.SqlDisconnect();
This keeps things centralized, and you don't have to worry about open/close, what the connection string is buried all over the place, etc... If you can't connect, you can't run a query, don't try to run the query if it can't even get that far.
I think it may be a permission issue on INFORMATION_SCHEMA system views. Try the following.
GRANT VIEW DEFINITION TO your_user;
See here for more details
This program will copy all records inside the table 1 into table 2 and also write into a text file. After it finishes copied all the records, the records will be delete make the table1 empty before new record is added. i like to enhance my code for example :
like inserting code to verify if records empty or not, if got problem in copying the file, or if it is EOF, what should i do??
This code was in form_load() and running in win form application, what if, if i run the program exe, i dont what the form to be appeared? i want to make this program like it was running on windows behind. Only error or successful messagebox will appeared?
Any help in solution, guidance or reference are very very thankful.
Thank you in advance!
//create connection
SqlConnection sqlConnection1 =
new SqlConnection("Data Source=.\SQLEXPRESS;Database=F:\Test2.mdf;Integrated Security=True;User Instance=True");
//command insert into queries
SqlCommand cmdCopy = new SqlCommand();
cmdCopy.CommandType = System.Data.CommandType.Text;
cmdCopy.CommandText = "INSERT INTO tblSend (ip, msg, date) SELECT ip, msg, date FROM tblOutbox";
cmdCopy.Connection = sqlConnection1;
//insert into text file
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT * FROM tblOutbox";
cmd.Connection = sqlConnection1;
sqlConnection1.Open();
StreamWriter tw = File.AppendText("c:\INMS.txt");
SqlDataReader reader = cmd.ExecuteReader();
tw.WriteLine("id, ip address, message, datetime");
while (reader.Read())
{
tw.Write(reader["id"].ToString());
tw.Write(", " + reader["ip"].ToString());
tw.Write(", " + reader["msg"].ToString());
tw.WriteLine(", " + reader["date"].ToString());
}
tw.WriteLine("Report Generate at : " + DateTime.Now);
tw.WriteLine("---------------------------------");
tw.Close();
reader.Close();
//command delete
String strDel = "DELETE tblOutbox";
SqlCommand cmdDel = new SqlCommand(strDel, sqlConnection1);
//sqlConnection1.Open(); //open con
cmdCopy.ExecuteScalar();
cmd.ExecuteNonQuery(); //execute insert query
cmdDel.ExecuteScalar();//execute delete query
sqlConnection1.Close(); //close con
//*****************************************************
}
catch (System.Exception excep)
{
MessageBox.Show(excep.Message);
}
A few suggestions:
Move it out of the form. Business logic and data access does not belong in the form (View). Move it to a separate class.
Keep the MessageBox code in the form. That's display logic. The entire try..catch can be moved out of the method; just have the method throw exceptions. And don't catch System.Exception - catch the database one(s) you expect.
I echo Ty's comments on IDisposable and using statements.
Read up on Extract Method and the Single Responsibility Principle. This method does a lot, and it's long. Break it up.
Move some of the string hardcodes out. What if your connection string or file paths change? Why not put those in a configuration file (or at least use some constants)?
For starters, anyway. :)
That sure is some code and I sure could recommend a lot of things to improve it if you care.
First thing I would do is read up on IDisposable then I would re-write that DataReader as following.
using(StreamWriter tw = File.AppendText("c:\INMS.txt"))
{
using(SqlDataReader reader = cmd.ExecuteReader())
{
tw.WriteLine("id, ip_add, message, datetime");
while (reader.Read())
{
tw.Write(reader["id"].ToString());
tw.Write(", " + reader["ip_add"].ToString());
tw.Write(", " + reader["message"].ToString());
tw.WriteLine(", " + reader["datetime"].ToString());
}
tw.WriteLine(DateTime.Now);
tw.WriteLine("---------------------------------");
}
}
Then after your catch, put the following and remove the close call.
finally
{
sqlConnection1.Dispose(); //close con
}
In addition to some of the other answers already given, you might also want to consider protecting the data operation with a Transaction.
I assume that you don't want any of the following operation to partially complete:
cmdCopy.ExecuteScalar();
cmd.ExecuteNonQuery(); //execute insert query
cmdDel.ExecuteScalar();//execute delete query
If you are processing MANY rows you might want to batch your updates but that is a whole different issue.
Firstly kudos to you for trying to improve your skill and being open to publish your code like this. I believe that is the first step to being a better programmer, is to have this type of attitude.
Here is an implementation that answers some of your questions.
I have extracted some of the old code into methods and also moved some of the responsibilities to their own classes.
Disclaimer:
Although the code compiles I didn't run it against a database, therefore there might be a couple of small things I missed.
I had to stop short on certain refactorings not knowing the exact requirements and also to still try and keep some of the concepts simple.
.
using System;
using System.Configuration;
using System.Data.SqlClient;
using System.IO;
// Program.cs
static class Program
{
[STAThread]
static void Main()
{
try
{
MailArchiver.Run();
Console.WriteLine("Application completed successfully");
}
catch (Exception ex)
{
Console.WriteLine("Unexpected error occurred:");
Console.WriteLine(ex.ToString());
}
}
}
// Reads new messages from DB, save it to a report file
// and then clears the table
public static class MailArchiver
{
public static void Run()
{
// Might be a good idea to a datetime suffix
ReportWriter.WriteFile(#"c:\INMS.txt");
CopyAndClearMessages();
}
private static void CopyAndClearMessages()
{
SqlConnection cn = DbConnectionFactory.CreateConnection();
cn.Open();
try
{
SqlTransaction tx = cn.BeginTransaction();
try
{
CopyMessages(cn, tx);
DeleteMessages(cn, tx);
tx.Commit();
}
catch
{
tx.Rollback();
throw;
}
}
finally
{
cn.Close();
}
}
private static void DeleteMessages(SqlConnection cn, SqlTransaction tx)
{
var sql = "DELETE FROM tblOutbox";
var cmd = new SqlCommand(sql, cn, tx);
cmd.CommandTimeout = 60 * 2; // timeout 2 minutes
cmd.ExecuteNonQuery();
}
private static void CopyMessages(SqlConnection cn, SqlTransaction tx)
{
var sql = "INSERT INTO tblSend (ip, msg, date) SELECT ip, msg, date FROM tblOutbox";
var cmd = new SqlCommand(sql, cn, tx);
cmd.CommandTimeout = 60 * 2; // timeout 2 minutes
cmd.ExecuteNonQuery();
}
}
// Provides database connections to the rest of the app.
public static class DbConnectionFactory
{
public static SqlConnection CreateConnection()
{
// Retrieve connection string from app.config
string connectionString = ConfigurationManager.ConnectionStrings["MailDatabase"].ConnectionString;
var cn = new SqlConnection(connectionString);
return cn;
}
}
// Writes all the data in tblOutbox to a CSV file
public static class ReportWriter
{
private static SqlDataReader GetData()
{
SqlConnection cn = DbConnectionFactory.CreateConnection();
cn.Open();
try
{
var cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM tblOutbox";
cmd.Connection = cn;
return cmd.ExecuteReader();
}
finally
{
cn.Close();
}
}
public static void WriteFile(string filename)
{
if (File.Exists(filename))
{
// This might be serious, we may overwrite data from the previous run.
// 1. You might want to throw your own custom exception here, should want to handle this
// condition higher up.
// 2. The logic added here is not the best and added for demonstration purposes only.
throw new Exception(String.Format("The file [{0}] already exists, move the file and try again"));
}
var tw = new StreamWriter(filename);
try
{
// Adds header record that describes the file contents
tw.WriteLine("id,ip address,message,datetime");
using (SqlDataReader reader = GetData())
{
while (reader.Read())
{
var id = reader["id"].ToString();
var ip = reader["ip"].ToString();
//msg might contain commas, surround value with double quotes
var msg = reader["msg"].ToString();
var date = reader["data"].ToString();
if (IfValidRecord(id, ip, msg, msg, date))
{
tw.WriteLine(string.Format("{0},{1},{2},{3}", id, ip, msg, date));
}
}
tw.WriteLine("Report generated at : " + DateTime.Now);
tw.WriteLine("--------------------------------------");
}
}
finally
{
tw.Close();
}
}
private static bool IfValidRecord(string id, string ip, string msg, string msg_4, string date)
{
// this answers your question on how to handle validation per record.
// Add required logic here
return true;
}
}
Use a SELECT query to find the non-empty rows (you seem to get along with the EOF issue).
On form_load event make the form invisible by program arguments.
Why not to use INSERT INTO (and then DELETE)?