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)?
Related
I'm trying to fill a combo box on from load from a database, I'm getting the error "Invalid object name 'POOL'"
Form load event to populate dropdown on form load
private void FRMAddTeam_Load(object sender, EventArgs e)
{
if (CMBBXPool.Items.Count > 0)
CMBBXPool.Items.Clear();
Database.CLSDB DatabaseClass = new Database.CLSDB();
DatabaseClass.FillDropDownList();
}
This is the code in my database connection class
public void FillDropDownList()
{
string PoolName = "";
Team.FRMAddTeam TeamAdd = new Team.FRMAddTeam();
SqlConnection conn = GetConnection();
string selStmt = "SELECT [Name] FROM dbo.TBL_pool";
SqlCommand selCmd = new SqlCommand(selStmt, conn);
try
{
conn.Open();
SqlDataReader reader = selCmd.ExecuteReader();
while (reader.Read())
{
PoolName = reader["Name"].ToString();
TeamAdd.addPoolItem(PoolName);
}
}
catch (SqlException ex) { throw ex; }
finally { conn.Close(); }
return;
}
Code to add the pool name
public void addPoolItem(string PoolName)
{
CMBBXPool.Items.Add(PoolName);
}
Any help is much appreciated
Your code needs to be:
public void FillDropDownList(Team.FRMAddTeam TeamAdd)
{
string PoolName = "";
SqlConnection conn = GetConnection();
string selStmt = "SELECT [Name] FROM dbo.TBL_pool";
SqlCommand selCmd = new SqlCommand(selStmt, conn);
try
{
conn.Open();
SqlDataReader reader = selCmd.ExecuteReader();
while (reader.Read())
{
PoolName = reader["Name"].ToString();
TeamAdd.addPoolItem(PoolName);
}
}
catch (SqlException ex) { throw ex; }
finally { conn.Close(); }
return;
}
You call it from your form like this:
DatabaseClass.FillDropDownList(this);
This will work, however it is strongly advised to change the implementation of your database class and remove tight coupling with GUI.
It is WRONG to fill your GUI from database class - instead of that, you should return data from your database class and bind your data to GUI in the GUI class.
http://en.wikipedia.org/wiki/Loose_coupling
http://en.wikipedia.org/wiki/Object_orgy
http://en.wikipedia.org/wiki/Separation_of_concerns
Sounds like you dont have table Pool.Are you sure its there?
Log into your SQL management studio and use query analyzer to run the same command.
There could be many reasons for that If your schema is different or do you have permission to access that table ? or are you checking the right database ?
UPDATE:
You should try using SELECT [Name] FROM dbo.TBL_Pool
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.
I have a Windows Form app with a handful of fields, including a 'Company' Field and a 'Contact' Field. When you type an item in the company field and hit a button, it makes a query to a SQL database to fill in the contact information for that company in the 'Contact' field. I included really basic autocomplete in the 'Company' field, mostly for convenience.
Problem is that when I load up the form, as soon as I type anything into the 'Company' field, the program crashes. There are no other calls being made on a keystroke and I narrowed it down to autocomplete causing the problems.
The code that manages it all is as follows:
public void GetRowCount()
{
try
{
_DbRows = db.CountRows();
tContact.Text = _DbRows.ToString();
}
catch (Exception tEx)
{
MessageBox.Show("Exception in GetRowCount. Exception: " + tEx.Message);
}
}
private void GetCustomerList()
{
String customerQuery = "SELECT DISTINCT Name FROM Customers";
try
{
_CustomerList = db.ReturnCustomers(customerQuery, _DbRows);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void PopulateAutofillList()
{
try
{
tCompany.AutoCompleteSource = AutoCompleteSource.CustomSource;
tCompany.AutoCompleteCustomSource.AddRange(_CustomerList);
MessageBox.Show(_CustomerList.Length.ToString());
tCompany.AutoCompleteMode = AutoCompleteMode.Append;
}
catch (Exception tEx)
{
MessageBox.Show("Exception On Autocomplete. Exception: " + tEx.Message);
}
}
These are all called separately in a OnLoad Method, like so:
private void Form1_Load(object sender, EventArgs e)
{
try
{
GetRowCount();
GetCustomerList();
PopulateAutofillList();
}
catch (Exception ex)
{
MessageBox.Show("Initial Connection to the Database Failed.");
}
}
And the DB queries themselves:
public String[] ReturnCustomers(string sqlQuery, int size)
{
createConnectionString();
StreamWriter file = new StreamWriter("dbCustomerList");
int i = 0;
String[] results = new String[size];
SqlConnection myConnection = new SqlConnection(_ConnectionString);
{
myConnection.Open();
SqlCommand cmd = new SqlCommand(sqlQuery, myConnection);
{
SqlDataReader reader;
reader = cmd.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
results[i] = reader.GetString(0);
//file.WriteLine(i ": " + results[i]);
i++;
}
return results;
}
}
}
public int CountRows()
{
createConnectionString();
int rows;
SqlConnection myConnection = new SqlConnection(_ConnectionString);
{
myConnection.Open();
SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM Customers;", myConnection);
rows = Convert.ToInt32(cmd.ExecuteScalar());
Console.Write("Row Count: " + rows);
}
return rows;
}
I'm not totally sure what's broken. All my little checks that show up along the way indicate that things are right. For testing, I had all this running on SQLite and it was fine. It broke when I moved it to SQL.
--
Edit:
The full exception that Windows Small Business Server 2011 gives:
Problem signature:
Problem Event Name: APPCRASH
Application Name: SSLP.exe
Application Version: 1.0.0.0
Application Timestamp: 5213d1b8
Fault Module Name: shell32.dll
Fault Module Version: 6.1.7600.17038
Fault Module Timestamp: 4fd2d370
Exception Code: c0000005
Exception Offset: 000ac2c5
OS Version: 6.1.7600.2.0.0.305.9
Locale ID: 1033
Additional Information 1: a7aa
Additional Information 2: a7aa91f17ea749d42a4de3b390fa5b3d
Additional Information 3: a7aa
Additional Information 4: a7aa91f17ea749d42a4de3b390fa5b3d
I ran into a similar issue with this - grabbing a list of numbers from a database to use as an autocomplete source. In particular, I was using Linq to SQL and returning a distinct list.
One of the values in that distinct list was null, and that was causing my program to crash when I would enter a value into the text box with the autocomplete source. Couldn't find a way to catch any exception, though, so debugging it was a bit of a hassle.
Simply adding a "where number != null" to my Linq to SQL query fixed the issue for me.
This code is more than a little funky. The biggest issue is not using a dynamic list. Then you won't need two DB calls. You won't need the count, etc. etc. You should also use using for these objects. Like this:
public String[] ReturnCustomers(string sqlQuery, int size)
{
createConnectionString();
StreamWriter file = new StreamWriter("dbCustomerList");
List<string> results = new List<string>();
using (SqlConnection myConnection = new SqlConnection(_ConnectionString))
{
myConnection.Open();
using(SqlCommand cmd = new SqlCommand(sqlQuery, myConnection))
{
using(SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
results.Add(reader.GetString(0));
}
}
}
myConnection.Close();
}
return results.ToArray();
}
Alright, so this is the silliest of things:
In desperation, I went through the entire database looking for something that would throw it off. There was a duplicate entry in the DB and I'm guessing it broke when Total Rows (as returned by CountRows) didn't match up with the Distinct number of rows.
Really dumb, but after removing the duplicate entries, it does work.
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
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();
}