How to stay connected to database until screen close? - c#

I developed a C# program for Windows-CE platform. The program open and close the connection to the database for every single interaction. See the code below.
Button click:
private void btnStkIn_Click(object sender, EventArgs e)
{
formStockIn = new frmStkIn();
formStockIn.Show();
}
Select Data:
try
{
using (SqlConnection sqlConn = new SqlConnection(<connection-string>))
{
sqlConn.Open();
//Execute command
SqlCommand sqlCmd = new SqlCommand(<Select Query>, sqlConn);
SqlDataReader sqlReader = sqlCmd.ExecuteReader();
while (sqlReader.Read())
{
<Statement here>
}
}
}
catch
{
//SQL command error
itemDT.ErrorMessage = "Select process is failed.Please contact system admin.";
itemDT.Result = 12;
}
Update Data:
try
{
using (SqlConnection sqlConn = new SqlConnection(<connection-string>))
{
sqlConn.Open();
//Execute command
SqlCommand sqlCmd = new SqlCommand(<Update Query>, sqlConn);
if (sqlCmd.ExecuteNonQuery() <= 0)
{
//No row affect
return -99;
}
else
{
//Completed
return 0;
}
}
}
catch
{
//Sql command error
return 99;
}
I would like to connect to database once (when the form in shown) and then do select, Insert, Update the data using the same connection and close the connection when I close the screen. At run-time, some screen can select-update more than once.
What should I do?

What you are doing is fine. It is good practice to keep the connection open for the shortest time possible and then disposing it. This is what you are doing and that's good.
If you keep it open and the user goes off on lunch or vacation and clicks nothing else, you are keeping a connection for no good reason.
If you need to do multiple things at the same time, then open one connection and execute the queries and then close the connection right away.

Related

C# ExecuteReader requires an open and available Connection. The connection's current state is closed

private void comT_SelectedIndexChanged(object sender, EventArgs e)
{
if (comT.SelectedIndex != -1)
{
SqlCommand cmd = new SqlCommand(#"SELECT ISNULL(substring(MAX(tCode),3,2),'00')
FROM Teacher
WHERE dCode = '" + comT.SelectedValue.ToString() + "'", MUControlClass.con);
SqlDataReader dr = cmd.ExecuteReader();
dr.Read(); <--HERE IS ERROR **"ExecuteReader requires an open and available Connection. The connection's current state is closed."**
if (dr[0].ToString() != "00")
{
int dugaar = Convert.ToInt32(dr[0]) + 1;
if (dugaar > 9)
{
msk.Text = comT.SelectedValue.ToString() + dugaar.ToString();
}
else
msk.Text = comT.SelectedValue.ToString() + "0" + dugaar.ToString();
}
else
{
msk.Text = comT.SelectedValue.ToString() + "01";
}
dr.Close();
}
}
ExecuteReader requires an open and available Connection. The connection's current state is closed. Error
The immediate error is that the connection is not open, as it told you; so ... open it; however, there are a lot of other serious problems here - most notably "SQL injection", but also non-disposed objects. You can fix SQL injection by using parameters, and the non-disposed problem with lots of using, but: I strongly suggest you make use of tools that will help you get this right by default. For example:
private void comT_SelectedIndexChanged(object sender, EventArgs e)
{
if (comT.SelectedIndex != -1)
{
using (var conn = new SqlConnection(yourConnectionString))
{
string max = conn.QuerySingle<string>(#"
SELECT ISNULL(substring(MAX(tCode),3,2),'00')
FROM Teacher
WHERE dCode = #dCode", new { dCode = comT.SelectedValue.ToString() });
if (max != null)
{
// your parse/etc logic
}
}
}
}
This is:
moving our connection lifetime to be local to this method, which will stop a lot of connection usage problems
using "Dapper" to provide the QuerySingle<T> code
which means you don't need to mess with commands or readers
adding parameter usage via #dCode and the new { dCode = ... } usage
Note it might look like we've still not opened the connection here, but Dapper is happy to do that for us; if it is given a closed connection, it opens the connection for the duration of the query only.
Wherever the connection is created in MUControlClass you need to call con.Open().
Your connection is not opened.
Simplified flow of connection and interaction with database is like this:
using(SqlConnection con = new SqlConnection(yourConnectionString))
{
con.Open() // You do not open this one so it returns that error
using(SqlCommand cmd = new SqlCommand(yourSqlCommand, con))
{
using(SqlDataReader dr = cmd.ExecuteReader())
{
if(dr.Read())
{
// Do something
}
}
}
}
We do not need to do close/destroy anything as long as it is wrapped in using since they all implements IDisposable

SQLite Database Locked exception

I am getting Database is locked exception from SQLite for some queries only.
Below is my code:
When I execute any select statement it works fine.
When I am executing any write statement on Jobs Table it also works fine.
This works fine:
ExecuteNonQuery("DELETE FROM Jobs WHERE id=1");
But the same way if I am executing queries for Employees table it is throwing an exception that database is locked.
This throws Exception:
ExecuteNonQuery("DELETE FROM Employees WHERE id=1");
Below are my functions:
public bool OpenConnection()
{
if (Con == null)
{
Con = new SQLiteConnection(ConnectionString);
}
if (Con.State == ConnectionState.Closed)
{
Con.Open();
//Cmd = new SQLiteCommand("PRAGMA FOREIGN_KEYS=ON", Con);
//Cmd.ExecuteNonQuery();
//Cmd.Dispose();
//Cmd=null;
return true;
}
if (IsConnectionBusy())
{
Msg.Log(new Exception("Connection busy"));
}
return false;
}
public Boolean CloseConnection()
{
if (Con != null && Con.State == ConnectionState.Open)
{
if (Cmd != null) Cmd.Dispose();
Cmd = null;
Con.Close();
return true;
}
return false;
}
public Boolean ExecuteNonQuery(string sql)
{
if (sql == null) return false;
try
{
if (!OpenConnection())
return false;
else
{
//Tx = Con.BeginTransaction(IsolationLevel.ReadCommitted);
Cmd = new SQLiteCommand(sql, Con);
Cmd.ExecuteNonQuery();
//Tx.Commit();
return true;
}
}
catch (Exception exception)
{
//Tx.Rollback();
Msg.Log(exception);
return false;
}
finally
{
CloseConnection();
}
}
This is the Exception:
At line 103 : Cmd.ExecuteNonQuery();
Exception Found:
Type: System.Data.SQLite.SQLiteException
Message: database is locked
database is locked
Source: System.Data.SQLite
Stacktrace: at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
at TimeSheet6.DbOp.ExecuteNonQuery(String sql) in d:\Projects\C# Applications\Completed Projects\TimeSheet6\TimeSheet6\DbOp.cs:line 103
Somewhere along the way a connection is getting left open. Get rid of OpenConnection and CloseConnection and change ExecuteNonQuery to this:
using (SQLiteConnection c = new SQLiteConnection(ConnectionString))
{
c.Open();
using (SQLiteCommand cmd = new SQLiteCommand(sql, c))
{
cmd.ExecuteNonQuery();
}
}
Further, change the way you read data to this:
using (SQLiteConnection c = new SQLiteConnection(ConnectionString))
{
c.Open();
using (SQLiteCommand cmd = new SQLiteCommand(sql, c))
{
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
...
}
}
}
Do not attempt, to manage connection pooling on your own like you are here. First, it's much more complex than what you have coded, but second, it's handled already inside the SQLiteConnection object. Finally, if you're not leveraging using, you're not disposing these objects properly and you end up with issues like what you're seeing now.
You can use 'using' statement as below, that will make sure connection & command disposed correctly even in exception
private static void ExecuteNonQuery(string queryString)
{
using (var connection = new SQLiteConnection(
ConnectionString))
{
using (var command = new SQLiteCommand(queryString, connection))
{
command.Connection.Open();
command.ExecuteNonQuery();
}
}
}
You should close your DataReader before attempting to write any data to the database. Use:
dr.Close();
after you finish using the DataReader.
In my case it was very stupid of me, I was making changes in SQLite browser and did not click on write changes, which locked the DB to be modified by the services. After I clicked the Write changes button, all the post request worked as expected.
A lot of helpful posts here for folks that may have forgotten to clean up a dangling connection, but there is another way this can happen: SQLite does not support concurrent INSERTs; if you issue two INSERTs at the same time the will be processed in serial. When the INSERTs are quick this is fine, but if an INSERT takes longer than the timeout the second INSERT can fail with this message.
I had this happen when I used a long running transaction to accumulate a bunch of INSERTs into one big commit. Basically I locked the database from any other activity during the transaction. Switching to journal_mode=WAL will allow concurrent writes and reads, but not concurrent writes.
I got rid of the long running transaction and let each INSERT autocommit, and that solved my problem.
Mine was caused by not closing a SqliteDataReader when calling HasRows().
I had this:
using (SQLiteConnection connection = new SQLiteConnection(DbPath))
{
connection.Open();
string sql = $"SELECT * FROM ...";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
return command.ExecuteReader().HasRows;
}
connection.Close();
}
But needed to put a using around the ExecuteReader like so:
using (SQLiteDataReader reader = command.ExecuteReader())
{
return command.ExecuteReader().HasRows;
}
Even though the DbConnection was being disposed and re-created each time the db was still being kept locked by the reader.
I was also getting the same error here:
if (new basics.HindiMessageBox(HMsg, HTitle).ShowDialog()==true)
{
SQLiteConnection m_dbConnection = new SQLiteConnection(MainWindow.con);
m_dbConnection.Open();
sql = "DELETE FROM `users` WHERE `id`=" + SelectedUser.Id;
command = new SQLiteCommand(sql, m_dbConnection);
command.ExecuteNonQuery();
m_dbConnection.Close();
LoadUserDG();
}
but when I just changed SQLiteConnection declaration location
public partial class User : Window
{
SQLiteCommand command;
string sql;
AddUser AddUserObj;
List<basics.users> usersList;
basics.users SelectedUser;
SQLiteConnection m_dbConnection;
// ...
private void DeleteBtn_Click(object sender, RoutedEventArgs e)
{
// ...
if (new basics.HindiMessageBox(HMsg, HTitle).ShowDialog()==true)
{
m_dbConnection = new SQLiteConnection(MainWindow.con);
m_dbConnection.Open();
sql = "DELETE FROM `users` WHERE `id`=" + SelectedUser.Id;
command = new SQLiteCommand(sql, m_dbConnection);
command.ExecuteNonQuery();
m_dbConnection.Close();
LoadUserDG();
}
}
Everything is fine now.
I hope this may work for you, too.
If someone can say how this happened, I would like to know the details to improve my knowledge, please.
I had the same issue when loading a lot of data to different tables from multiple threads.
When trying to do the inserts I was getting database locked because the program was doing too many insert too fast and SQLite didn't have time to complete each transaction before another one came.
The insert are done through threading because I didn't want the interface to be locked and wait for the insert to be done.
My solution is to use BlockingCollection with ThreadPool.QueueUserWorkItem.
This allows me to free the interface while doing the inserts.
All the insert are queued and executed in FIFO (First In First Out) order.
Now the database is never locked while doing any SQL transaction from any thread.
public class DatabaseQueueBus
{
private BlockingCollection<TransportBean> _dbQueueBus = new BlockingCollection<TransportBean>(new ConcurrentQueue<TransportBean>());
private CancellationTokenSource __dbQueueBusCancelToken;
public CancellationTokenSource _dbQueueBusCancelToken { get => __dbQueueBusCancelToken; set => __dbQueueBusCancelToken = value; }
public DatabaseQueueBus()
{
_dbQueueBusCancelToken = new CancellationTokenSource();
DatabaseQueue();
}
public void AddJob(TransportBean dto)
{
_dbQueueBus.Add(dto);
}
private void DatabaseQueue()
{
ThreadPool.QueueUserWorkItem((param) =>
{
try
{
do
{
string job = "";
TransportBean dto = _dbQueueBus.Take(_dbQueueBusCancelToken.Token);
try
{
job = (string)dto.DictionaryTransBean["job"];
switch (job)
{
case "SaveClasse":
//Save to table here
break;
case "SaveRegistrant":
//Save Registrant here
break;
}
}
catch (Exception ex)
{//TODO: Handle this exception or not
}
} while (_dbQueueBusCancelToken.Token.IsCancellationRequested != true);
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
}
});
}
}
The inserts are done this way, but without the queuing I was still getting the lock issue.
using (SQLiteConnection c = new SQLiteConnection(BaseDal.SQLiteCon))
{
c.Open();
using (SQLiteCommand cmd = new SQLiteCommand(sql, c))
{
cmd.ExecuteNonQuery();
}
c.Close();
}

Connection issue in C# WinForms application with multiple forms

When I run the application, are all forms in the app loaded/initialized even though I have not opened them yet? (I.E. Form.Show)
this is how I closed the connection in the login form:
if (usertype == "UT1") //admin rights
{
//GET LOGGED USER
Home_Admin homeAdmin = new Home_Admin();
homeAdmin.SetUsername(username);
cString.Close();
this.Close();
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(OpenHomeAdmin));
t.Start();
}
And how I got to the back up form from Home_Admin's menu strip
private void backUpToolStripMenuItem_Click(object sender, EventArgs e)
{
BackUp BackUpForm = new BackUp();
BackUpForm.Show();
}
I am trying to create a back up of my database and it works perfectly if I run only the back up form. If I start the app from the very beginning, it says back up failed the database is in use. I already closed the connection from login to the form where I will launch the back up form and even set a
if(conn.State = connectionState.Open)
{
conn.close();
}
prior to the back up procedure. Is there any way I can just kill all connections to the SQL database > back up > and then restore the connections?
BACK UP CODE
public void BackupDatabase(String destinationPath)
{
SqlConnection cString = new SqlConnection();
cString.ConnectionString = "Data Source=.\\SQLEXPRESS;AttachDbFilename=D:\\MY_THESIS\\WORKING FILES\\NNIT-RMS.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True";
if (cString.State == ConnectionState.Open)
{
cString.Close();
}
try
{
//MY SERVER
String userName = "NNIT-Admin";
String password = "password";
String serverName = #"RITZEL-PC\SQLEXPRESS";
ServerConnection connection = new ServerConnection(serverName, userName, password);
Server sqlServer = new Server(connection);
Backup BackupMgr = new Backup();
BackupMgr.Devices.AddDevice(destinationPath, DeviceType.File);
BackupMgr.Database = "NNIT DB";
BackupMgr.Action = BackupActionType.Database;
BackupMgr.SqlBackup(sqlServer);
MessageBox.Show("Back up saved!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + " " + ex.InnerException);
}
}
FORM LOAD
private void BackUp_Load(object sender, EventArgs e)
{
string date = DateTime.Now.Day.ToString();
string year = DateTime.Now.Year.ToString();
string month = DateTime.Now.Month.ToString();
Filename_txt.Text = "NNIT-RMSDB_" + month + date + year;
}
Error message
http://img824.imageshack.us/img824/8541/error1lj.jpg
In this piece of code:
SqlConnection cString = new SqlConnection();
cString.ConnectionString = "Data Source=.\\SQLEXPRESS;AttachDbFilename=D:\\MY_THESIS\\WORKING FILES\\NNIT-RMS.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True";
if (cString.State == ConnectionState.Open)
{
cString.Close();
}
You are instantiating a new connection and never opening it. The code in the if clause will never be hit.
Your mistake is assuming that the new connection is the only one - there may be multiple connections (opened in other forms and never properly closed) - some not even from your application (for example, using SQL Server Management Studio - having an open query window to your database will mean there is an open connection).
The best solution for to kill all connections to db is to take it offline, other solutions almost fail
using (SqlConnection sqlcnn = new SqlConnection("Data Source=.\\SQLEXPRESS;Integrated Security=True"))
{
SqlCommand sqlcmd = new SqlCommand("ALTER DATABASE DB_NAME SET OFFLINE WITH ROLLBACK IMMEDIATE", sqlcnn);
sqlcnn.Open();
sqlcmd.ExecuteNonQuery();
sqlcnn.Close();
}
Add the above code before your backup or restore code and then your backup or restore will work even if you have open connections to your db.

There is already an open DataReader associated with this Connection which must be closed first

I am using Visual Studio 2010 (C#) with mysqlConnector and everything seems to be fine.
However, when I try to request something from the server I get this error:
"There is already an open DataReader associated with this Connection which must be closed first."
This is my code:
gc.connect();
List<Conseiller> conseillers = gc.getAllConseillers();
--
public void connect()
{
string connStr = "SERVER=localhost;UID=root;DATABASE=Projet;Password=root";
oConn = new MySqlConnection(connStr);
try
{
oConn.Open();
Console.WriteLine("Successfully connected to the data base");
}
catch (OdbcException caugth)
{
/* Traitement de l'erreur */
Console.WriteLine(caugth.Message);
}
}
--
public List<Conseiller> getAllConseillers()
{
MySqlCommand oComm = oConn.CreateCommand();
oComm = oConn.CreateCommand();
Console.WriteLine("SELECT * FROM conseillers");
oComm.CommandText = "SELECT * FROM conseillers";
MySqlDataReader oReader = oComm.ExecuteReader(); // Error here
}
Where I am wrong ?
don't try to separate the connect with the get data. The call to Open may in-fact not go to the database at all and you will not detect issues at that point. Note the using statement to close the connection. add SEH as required
List<Conseiller> conseillers = gc.getAllConseillers();
public void getAll() {
string connStr = "SERVER=localhost;UID=root;DATABASE=Projet;Password=root";
using (oConn = new MySqlConnection(connStr))
using (MySqlCommand oComm = oConn.CreateCommand())
{
oConn.Open();
oComm.CommandText = "SELECT * FROM conseillers";
MySqlDataReader oReader = oComm.ExecuteReader(); // no Error here
// user reader here
} catch (OdbcException caugth) {
/* Traitement de l'erreur */
Console.WriteLine(caugth.Message);
}
}
You're not disposing of your objects, which basically means that your previous call to getAllConseillers, or a similar method, opened a data reader that is still open.
The following objects in your question are disposable (ie. implements IDisposable), you should dispose of them all:
MySqlConnection
MySqlCommand
MySqlDataReader
Basically, I would change the code as shown to this:
using (var gc = new Whatever())
{
gc.connect();
List<Conseiller> conseillers = gc.getAllConseillers();
}
--
public void connect()
{
string connStr = "SERVER=localhost;UID=root;DATABASE=Projet;Password=root";
oConn = new MySqlConnection(connStr);
try
{
oConn.Open();
Console.WriteLine("Successfully connected to the data base");
}
catch (OdbcException ex)
{
/* Traitement de l'erreur */
Console.WriteLine(ex.Message);
oConn.Dispose();
oConn = null;
// optional: throw;
}
}
--
public List<Conseiller> getAllConseillers()
{
using (MySqlCommand oComm = oConn.CreateCommand())
{
Console.WriteLine("SELECT * FROM conseillers");
oComm.CommandText = "SELECT * FROM conseillers";
using (MySqlDataReader oReader = oComm.ExecuteReader()) // No error here
{
// process the result in oReader here
return ...;
}
...
}
...
}
A few suggestions that may help:
First, in your code above you have called CreateCommand twice and don't need to.
Secon, you can instantiate your Command a little different to make this easier to read:
MySqlCommand oComm = new MySqlCommand("Select * from conseillers", oConn);
then call the ExecuteReader.
Third, your code above doesn't show when the connection is open. Are you sure it's open? Are you sure you haven't already called a data reader with the open connection and didn't close it?
Fourth, you should always open the connection as late as possible and close it as early as possible. The code you have seems like you are going to open the connection and leave it open but I'm not really sure of your intent.
I would suggest using the Using syntax:
Using connection As New SqlConnection(connectionString)
Dim command As New SqlCommand(queryString, connection)
connection.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
Try
While reader.Read()
Console.WriteLine(String.Format("{0}, {1}", _
reader(0), reader(1)))
End While
Finally
' Always call Close when done reading.
reader.Close()
End Try
End Using
Modify the above code for your situation....

Guide to enhance my code

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)?

Categories

Resources