C# SQLite database is locked - c#

I have a class named ImageData who contains a list of Tags
I get the database locked error only if an image has more than 1 tag and I can't find out why
0 tag and 1 tag is always fine, with 1 image or a 100.
As soon as 1 image has 2 tags, I get the error
I make sure of disposing of everything with the using statement
here is the method
public static int addImages(List<ImageData> images)
{
int rows = 0;
using (SQLiteConnection con = new SQLiteConnection(ConnectionString()))
{
con.Open();
foreach (ImageData img in images)
{
using (SQLiteCommand cmd = new SQLiteCommand($"INSERT INTO images(Hash, Extension, Name) VALUES(#IHash,#IExtension, #IName)", con))
{
cmd.Parameters.AddWithValue("#IHash", img.Hash);
cmd.Parameters.AddWithValue("#IExtension", img.Extension);
cmd.Parameters.AddWithValue("#IName", img.Name);
rows += cmd.ExecuteNonQuery();
}
foreach (Tag tag in img.Tags)
{
using (SQLiteCommand cmd = new SQLiteCommand($"INSERT INTO ImagesTags(ImageHash, TagName) VALUES(#IHash,#IName)", con))
{
cmd.Parameters.AddWithValue("#IHash", img.Hash);
cmd.Parameters.AddWithValue("#IName", tag.Name);
cmd.ExecuteNonQuery();
}
}
}
con.Close();
}
return rows;
}
here are my tables Creation
string[] createTable =
{
"CREATE TABLE images(Hash TEXT PRIMARY KEY, Extension TEXT, Name TEXT)",
"CREATE TABLE tags(NAME TEXT PRIMARY KEY NOT NULL,DESCRIPTION TEXT,COLLECTIONNAME TEXT)",
"CREATE TABLE ImagesTags(ImageHash TEXT,TagName TEXT,Primary KEY (ImageHash, TagName),FOREIGN KEY (ImageHash) REFERENCES images(Hash),FOREIGN KEY (TagName) REFERENCES tags(Name))"
};
There are multiple cases where I insert data in a foreach loop and this is the only place where I get this error.

After googling quite a lot I learned about cleaner ways of working with sqlite.
The probleme was fixed by using using statement for every connections, commands, transactions and readers.
something somewhere must have been incorrectly disposed
here is the same function as before but with better coding
I also added transactions to make only 1 call to the database and allow a rollback if something went wrong
public static int addImages(List<ImageData> images)
{
int rows = 0;
using (var con = new SQLiteConnection(ConnectionString()))
{
con.Open();
using (var tra = con.BeginTransaction())
{
try
{
foreach (ImageData img in images)
{
SQLiteParameter p1 = new SQLiteParameter("#IHash", System.Data.DbType.String);
SQLiteParameter p2 = new SQLiteParameter("#IExtension", System.Data.DbType.String);
SQLiteParameter p3 = new SQLiteParameter("#IName", System.Data.DbType.String);
using (SQLiteCommand cmd = new SQLiteCommand($"INSERT INTO images(Hash, Extension, Name) VALUES(#IHash,#IExtension, #IName)", tra.Connection))
{
cmd.Parameters.Add(p1);
cmd.Parameters.Add(p2);
cmd.Parameters.Add(p3);
p1.Value = img.Hash;
p2.Value = img.Extension;
p3.Value = img.Name;
cmd.ExecuteNonQuery();
}
foreach (Tag tag in img.Tags)
{
using (SQLiteCommand cmd = new SQLiteCommand($"INSERT INTO ImagesTags(ImageHash, TagName) VALUES(#IHash,#IName)", tra.Connection))
{
cmd.Parameters.AddWithValue("#IHash", img.Hash);
cmd.Parameters.AddWithValue("#IName", tag.Name);
cmd.ExecuteNonQuery();
}
}
}
tra.Commit();
}
catch(Exception ex)
{
tra.Rollback();
throw;
}
}
}
return rows;
}

Related

Refering to view values one by one

I have the following code which establishing an SQL connection inside of a project I am working on. What I want to do is to create a for loop which contains a method and every time the loop repeats the method runs with a different value until all views of the returned query are used.
I can't figure out how to refer to every value of the view without saving the view to a list or an array first. Any ideas?
SqlConnection Con = new SqlConnection(#"Data Source=localhost\**;Initial Catalog=ML;User Id=sa;Password='**'");
string sql = #"select product_id,Name from E_PRODUCT_PROPERTY";
var mylist = new List<WineRating>();
using (var command = new SqlCommand(sql, Con))
{
Con.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
new WineRating { product_id = reader.GetInt32(0), Name = reader.GetString(1) };
///Here goes the code I suppose
method_name(reader.GetInt32(0), reader.GetString(1));
}
}
public static int method_name(int product_id, string Name)
{
int num = x *2;
Console.WriteLine(num + Name);
}
Perhaps like this:
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
MyMethodToPrintToScreen(reader.GetInt32(0), reader.GetString(1));
}
}
With the method to print to screen
private static void MyMethodToPrintToScreen(int id, string product)
{
//Do whatever you wish with the data: example
Console.WriteLine($"My id: {id} | Product: {product}");
}
Edit
Let me make it even more obvious(using your exact code):
SqlConnection Con = new SqlConnection(#"Data Source=localhost\**;Initial Catalog=ML;User Id=sa;Password='**'");
string sql = #"select product_id,Name from E_PRODUCT_PROPERTY";
var mylist = new List<WineRating>();
using (var command = new SqlCommand(sql, Con))
{
Con.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
method_name(reader.GetInt32(0), reader.GetString(1));
}
}
}

C# SQLite Bulk parameterized DELETE slow even when using a transaction?

The following code is taking ~2 minutes to delete 30k records which I am sure is too long. Most of the similar questions I have seen on here have been solved by using a SQLiteTransaction object, but I am already doing that.
private void removeProxiesButton_Click(object sender, EventArgs e)
{
using (var conn = new SQLiteConnection(Properties.Settings.Default.dbConnectionString))
{
conn.Open();
using (var trans = conn.BeginTransaction())
{
using (var cmd = new SQLiteCommand("DELETE FROM Proxy WHERE IP=#ip AND Port=#port", conn, trans))
{
foreach (DataGridViewRow row in proxiesDataGridView.SelectedRows)
{
var proxy = proxies[row.Index];
cmd.Parameters.AddWithValue("#ip", proxy.IP);
cmd.Parameters.AddWithValue("#port", proxy.Port);
cmd.ExecuteNonQuery();
proxies.Remove(proxy);
}
}
trans.Commit();
}
}
}
And here is the CREATE statement for the Proxy table.
CREATE TABLE "Proxy"
(
`ProxyID` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
`Status` TEXT,
`IP` TEXT,
`Port` INTEGER,
`Country` TEXT,
`Speed` INTEGER,
`DateAdded` TEXT
)
Building one long SQLite statement with a StringBuilder and only executing the SQLiteCommand object once sped things up significantly.
private void removeProxiesButton_Click(object sender, EventArgs e)
{
using (var conn = new SQLiteConnection(Properties.Settings.Default.dbConnectionString))
{
conn.Open();
var sb = new StringBuilder();
sb.Append("DELETE FROM Proxy WHERE ProxyID IN (");
foreach (DataGridViewRow row in proxiesDataGridView.SelectedRows)
{
var proxy = proxies[row.Index];
sb.Append(proxy.ProxyID + ",");
}
sb[sb.Length - 1] = ')';
using (var cmd = new SQLiteCommand(sb.ToString(), conn))
{
cmd.ExecuteNonQuery();
}
}
}

There is already an open DataReader associated with this Command which must be closed first. C#

When I start debugging that error shows, and it associted with the line:
textBox1.Text = cmd.ExecuteReader().ToString();
private void Form1_Load(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection(#"server= M_SHAWAF\ORCHESTRATE; integrated security=true; database=MyData");
try
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd = new SqlCommand(#"select MAX(Nodelevel) from Org", conn);
int s = Int32.Parse(cmd.ExecuteScalar().ToString());
for (int i = 0; i <= s; i++)
{
cmd = new SqlCommand(#"select Name from Org where NodeLevel=" + i.ToString(),conn);
textBox1.Text = cmd.ExecuteReader().ToString();
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
conn.Close();
}
}
How can I fix that??
You don't need to continually execute readers in order to obtain the next row of data. If all you need to do is to iterate through all row values of Name from table Org, you can execute a single Sql query to return all rows into the reader, and then to traverse the reader, e.g.:
try
{
using (var conn = new SqlConnection(#"..."))
{
conn.Open();
using (var cmd = new SqlCommand(#"select Name from Org", conn))
{
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
textBox1.Text = reader["Name"].ToString();
}
}
}
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
Edit, Re Hierarchical table structures
If you do need to retain separate iterators while navigating through multiple levels of a hierarchy, you will need multiple readers. As per #Philips answer, in order to have more than one active result set per SqlConnection, you'll need to enable MARS (or open multiple connections).
try
{
using (var conn = new SqlConnection(#"...;MultipleActiveResultSets=True"))
using (var cmdOuter = new SqlCommand(#"select distinct NodeLevel from Org", conn))
{
conn.Open();
using (var outerReader = cmd.ExecuteReader())
{
while (outerReader.Read())
{
var nodeLevel = reader.GetInt32(0);
Console.WriteLine("Node Level {0}", nodeLevel);
using (var cmdInner = new SqlCommand(#"select Name from Org WHERE NodeLevel = #NodeLevel", conn))
{
cmdInner.Parameters.AddWithValue("#NodeLevel", nodeLevel);
using (var innerReader = cmdInner.ExecuteReader())
{
while (innerReader.Read())
{
Console.WriteLine("Name: {0}", innerReader.GetString(0));
}
}
}
}
}
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
modify your connection string to allow multiple results :
connectionString="Data source=localhost; initial catalog=Interstone; integrated security=True; multipleactiveresultsets=True;"
scroll to the right for the right information ;-)
But there are lots of alternatives to avoid needing multiple queries at the same time. Each query you issue that is still pending is a resource that is being used at the server which should be minimized.
So first consider algorithms that don't require multiple cursors and if there is no alternative then setup mars.
Reader is the wrong tool
And you do have an open reader
textBox1.Text = cmd.ExecuteScalar();

ADO.NET ExecuteReader Returns No Results

I'm updating some old legacy code and I ran into a problem with the
SqlCommand.ExecuteReader() method. The problem is that it's not returning any
results. However, using SqlDataAdapter.Fill(), I get results back from the
database. What am I doing wrong? How can I get results back using the data
reader?
var connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ToString();
using (var sqlConnection = new SqlConnection(connectionString))
{
using (var sqlCommand = new SqlCommand())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.CommandText = "SELECT * FROM MyTable WHERE ID = 1";
sqlConnection.Open();
// This code works.
//var dataTable = new DataTable();
//using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
//{
// sqlDataAdapter.Fill(dataTable);
//}
// This code is not working.
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// This fails because the data reader has no results.
var id = sqlDataReader.GetInt32(0);
}
}
}
}
Could it be that there is no Int32 in your results ?
var id = sqlDataReader.GetInt32(0); // <-- this might not be an Int32
Either try:
var id = sqlDataReader.GetValue(0);
Or cast to the correct type (BIGINT for example is Int64), not sure without seeing your data.
Try this..
var id = 0;
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
id = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("ColName"));
}
}
I have moved the variable outside of the reader code or the variable will only be accessible inside that scope. I would avoid specifying the ordinal in the code, in case someone altered the columns in the DB.
Also, specify the columns in the SQL statement... SELECT ColName FROM ... and use params in the query
If you got to that line then it has results
Does not mean the value is not null
And you should not use a SELECT *
If may have a problem with an implicit cast
Try Int32
try
{
if(sqlDataReader.IsDBNull(0))
{
// deal with null
}
else
{
Int32 id = sqlDataReader.GetInt32(0);
}
}
catch (SQLexception Ex)
{
Debug.WriteLine(Ex.message);
}
var connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ToString();
using (var sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
string sql = "SELECT * FROM MyTable WHERE ID = 1";
using (var sqlCommand = new SqlCommand(sql, sqlConnection))
{
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// This fails because the data reader has no results.
var id = sqlDataReader.GetInt32(0);
}
}
}
}

T-SQL Delete * from table then insert into table

Hello I'm trying to do following
Delete all from table X
insert desired values into table X
I thought then T-SQL would be way to achieve that because when something messes up in the INSERT command then everything will be deleted.
But this code does nothing it doesn't insert or delete the data. May someone help me to fix this issue?
spojeni.Open();
SqlTransaction sqlTrans = spojeni.BeginTransaction();
try
{
string delCmdTxt = "TRUNCATE TABLE PLODINY";
SqlCommand cmdDel = spojeni.CreateCommand();
cmdDel.CommandText = delCmdTxt;
cmdDel.Transaction = sqlTrans;
cmdDel.ExecuteNonQuery();
string insert_sql =
"INSERT INTO PLODINY(PLODINA,CENAZAQ,MJ)VALUES(#PLODINA,#CENAZAQ,#MJ)";
SqlCommand sqlcom = spojeni.CreateCommand();
sqlcom.CommandText = insert_sql;
sqlcom.Transaction = sqlTrans;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
sqlcom.Parameters.AddWithValue("#PLODINA", row.Cells["PLODINA"].Value);
sqlcom.Parameters.AddWithValue("#CENAZAQ", row.Cells["CENAZAQ"].Value);
sqlcom.Parameters.AddWithValue("#MJ", row.Cells["MJ"].Value);
sqlcom.ExecuteNonQuery();
sqlcom.Dispose();
}
sqlTrans.Commit();
}
catch (System.Data.SqlClient.SqlException)
{
sqlTrans.Rollback();
}
finally
{
spojeni.Close();
spojeni.Dispose();
}
this.Close();
Your problem is in your foreach loop. You need to define your parameters before hand, and do not dispose the command object until you're all done with it. You can also use the Where extension method to filter out any invalid rows from your data source since its a UI element.
string insert_sql = "INSERT INTO PLODINY(PLODINA,CENAZAQ,MJ)VALUES(#PLODINA,#CENAZAQ,#MJ)";
SqlCommand sqlcom = spojeni.CreateCommand();
sqlcom.CommandText = insert_sql;
sqlcom.Transaction = sqlTrans;
sqlcom.Parameters.Add("#PLODINA");
sqlcom.Parameters.Add("#CENAZAQ");
sqlcom.Parameters.Add("#MJ");
// some validation - add what you need.
var validRows = dataGridView1.Rows.Cast<DataGridViewRow>()
.Where(row => row.Cells["PLODINA"].Value != null);
foreach (DataGridViewRow row in validRows)
{
sqlcom.Parameters[0].Value = row.Cells["PLODINA"].Value;
sqlcom.Parameters[1].Value = row.Cells["CENAZAQ"].Value;
sqlcom.Parameters[2].Value = row.Cells["MJ"].Value;
sqlcom.ExecuteNonQuery();
}
sqlTrans.Commit();
sqlcom.Dispose();
You are doing your parameters totally wrong, because the only thing in your catch is the sqlTrans.Rollback(); you never see the errors you are getting, the first thing I would change is make that catch
catch (System.Data.SqlClient.SqlException)
{
sqlTrans.Rollback();
throw;
}
so you can now see the errors happen.
The next issue is if the table has any foreign key constraints your TRUNCATE TABLE will fail, if it is failing you can simply replace it with
string delCmdTxt = "delete from PLODINY";
SqlCommand cmdDel = spojeni.CreateCommand();
cmdDel.CommandText = delCmdTxt;
cmdDel.Transaction = sqlTrans;
cmdDel.ExecuteNonQuery();
As to why your inserts are not working, you are disposing the command every instance of the for loop, you are also trying to re-add the parameters every time, reformat that loop to the following
string insert_sql = "INSERT INTO PLODINY(PLODINA,CENAZAQ,MJ)VALUES(#PLODINA,#CENAZAQ,#MJ)";
using(SqlCommand sqlcom = spojeni.CreateCommand())
{
sqlcom.CommandText = insert_sql;
sqlcom.Transaction = sqlTrans;
sqlcom.Parameters.Add("#PLODINA", SqlDbType.NVarChar); //Replace with whatever the correct datatypes are
sqlcom.Parameters.Add("#CENAZAQ", SqlDbType.NVarChar);
sqlcom.Parameters.Add("#MJ", SqlDbType.NVarChar);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
sqlcom.Parameters["#PLODINA"] = row.Cells["PLODINA"].Value;
sqlcom.Parameters["#CENAZAQ"] = row.Cells["CENAZAQ"].Value;
sqlcom.Parameters["#MJ"] = row.Cells["MJ"].Value;
sqlcom.ExecuteNonQuery();
}
}
sqlTrans.Commit();
However your code can be made even better, if your DataGridView was backed by a DataTable via binding you could use a SqlTableAdapter instead, Lets say you load the table from the database, display it on the grid, and then you want to push back the updated information. With a DataTable it would be as simple as
private string _getDataQuery = "select PLODINA, CENAZAQ, MJ from PLODINY";
public void GetData(DataTable data)
{
//You do not need to call open here as SqlDataAdapter does it for you internally.
using(var spojeni = new SqlConnection(GetConnectionString())
using(var adapter = new SqlDataAdapter(_getDataQuery, spojeni)
{
data.Clear();
adapter.Fill(data);
}
}
public void UpdateData(DataTable data)
{
using(var spojeni = new SqlConnection(GetConnectionString())
using(var adapter = new SqlDataAdapter(_getDataQuery, spojeni)
using(var commandBuilder = new SqlCommandBuilder(adapter)
{
//This may or may not be nessesary for spojeni.BeginTransaction()
spojeni.Open();
using(var sqlTrans = spojeni.BeginTransaction())
{
adapter.SelectCommand.Transaction = sqlTrans;
adapter.UpdateCommand = commandBuilder.GetUpdateCommand();
adapter.UpdateCommand.Transaction = sqlTrans;
adapter.DeleteCommand = commandBuilder.GetDeleteCommand();
adapter.DeleteCommand.Transaction = sqlTrans;
adapter.InsertCommand = commandBuilder.GetInsertCommand()
adapter.InsertCommand.Transaction = sqlTrans;
try
{
adapter.Update(data);
sqlTrans.Commit();
}
catch
{
sqlTrans.Rollback();
throw;
}
}
}
}
Truncate Table only works if the table has not foreign key constraints... it's probably failing there and then rolling back the transaction in the catch statement...
Instead of Truncate try Delete From table and see if that fixes it...

Categories