SQLite exception: Database is locked - sqlite

I have looked into all the questions on the "database is locked" exception but none solve my problem. I have a static function in DBActions class that inserts a record in DB as follows:
public static class DBActions
{
// save col, value pairs in DB table
public static int SaveInDB(string table, string[] cols, object[] vals)
{
int resultID = 0;
string query = $"insert into {table} (";
for (int i = 0; i < cols.Length - 1; i++)
{ // leave the last column coz comma does not follow it
query += cols[i] + ", ";
}
query += cols[cols.Length - 1] + ") values (";
for (int i = 0; i < cols.Length - 1; i++)
{
query += $"'{vals[i]}', ";
}
query += $"'{vals[vals.Length - 1]}')";
//MessageBox.Show(query);
using (SQLiteConnection con = new SQLiteConnection(Global.ConnectionString))
{
using (SQLiteCommand cmd = new SQLiteCommand(con))
{
try
{
con.Open();
using (SQLiteTransaction trans = con.BeginTransaction())
{
cmd.CommandText = query;
cmd.ExecuteNonQuery();
resultID = (int)con.LastInsertRowId;
trans.Commit(); // raises the exception
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
con.Close();
cmd.Dispose();
}
}
}
return resultID;
}
}
and I am calling this static function whenever I need to save some record in any table like this:
Global.StartTime = GetCurrentTimeStamp();
string[] cols = { "SampleID", "OperatorID", "StartTimeStamp"};
object[] vals = { SampleID, CurrentUser, Global.StartTime};
Global.ExpID = DBActions.SaveInDB("ExperimentSettings", cols, vals);
When I call it the very first time, it throws the "database is locked" exception. For all others, it executes fine. What could be the possible cause of this? I think all my DB objects are properly being disposed off due to the using statements.

Related

Stored procedure executing even with the error message

I'm working with two stored procedures in an ASP.NET button function. While I get an error message based on the results that the invoice number is already dispatched from the other stored procedure, it still moves to the other stored procedure and executes it.
If the user gets this error message:
This invoice num was already dispatched!
then it shouldn't move on to this aspect of the function
protected void Button2_Click(object sender, EventArgs e)
{
try
{
for (int i = GridView2.Rows.Count - 1; i >= 0; i--)
{
var row = GridView2.Rows[i];
CheckBox chk = row.FindControl("chkInvoice") as CheckBox;
//CheckBox chk = (CheckBox)GridView2.Rows[i].Cells[0].FindControl("CheckBox3");
if (chk != null && chk.Checked)
{
string strSQLconstring = System.Configuration.ConfigurationManager.ConnectionStrings["TWCL_OPERATIONSConnectionString"].ToString();
using (SqlConnection objConnection = new SqlConnection(strSQLconstring))
{
objConnection.Open();
using (SqlTransaction transaction = objConnection.BeginTransaction())
{
string SID = GridView2.Rows[i].Cells[3].Text.Trim();
SqlDataReader myReader = null;
using (SqlCommand command = new SqlCommand("PP_SelectStatus", objConnection, transaction))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#invoiceNum", SID);
command.Parameters.AddWithValue("#custPONum", GridView2.Rows[i].Cells[4].Text.Trim());
myReader = command.ExecuteReader();
if (myReader.Read())
{
string invoice1 = (myReader["status"].ToString());
if (invoice1 == "0")
{
ClientScript.RegisterClientScriptBlock(this.GetType(), "alert", "alert('This invoice num was already dispatched!')", true);
}
myReader.Close();
}
}
else if (invoice1=="1")
{
using (SqlCommand cmd = new SqlCommand("PP_RemoveInvoice", objConnection, transaction))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#loadSheetNum", txtDispatchNum.Text);
cmd.Parameters.AddWithValue("#invoiceNum", SID);
cmd.Parameters.AddWithValue("#removeUser", lblUsername.Text.Replace("Welcome", ""));
**int a = cmd.ExecuteNonQuery();**
cmd.Dispose();
if (a > 0)
{
dt.Rows.RemoveAt(i);
////Read invoice qty from grid view 2
string invoice = GridView2.Rows[i].Cells[5].Text.ToString();
decimal invoiceTotal = Convert.ToDecimal(txtInvoiceTotal.Text) - Convert.ToDecimal(invoice);
txtInvoiceTotal.Text = invoiceTotal.ToString();
////Read invoice weight from grid view 2
string weight = GridView2.Rows[i].Cells[6].Text.ToString();
decimal invoiceWeight = Convert.ToDecimal(txtQtyWeight.Text) - Convert.ToDecimal(weight);
txtQtyWeight.Text = invoiceWeight.ToString();
lblError.ForeColor = Color.Green;
lblError.Text = "Selected record(s) successfully updated";
}
else
{
lblError.ForeColor = Color.Red;
lblError.Text = " Record has not yet been recorded";
}
}
//objConnection.Close();
transaction.Commit();
}
}
}
//Button2.Visible = false;
//showData();
GridView2.DataSource = dt;
GridView2.DataBind();
txtInvoiceCount.Text = dt.Rows.Count.ToString();
}
}
}
catch (Exception ex)
{
if (ex.Message.StartsWith("Violation of PRIMARY KEY constraint"))
{
lblError.ForeColor = Color.Red;
lblError.Text = " This invoice number was remove from dispatch sheet before!!";
}
else
{
// re-throw the error if you haven't handled it
lblError.Text = ex.Message;
throw;
}
}
}
You have a very, very simple logic error, but it is incredibly hard to see because your code is such a mess. Therefore, my answer is:
REFACTOR REFACTOR REFACTOR
It is important to get into the habit of writing short functions and controlling their inputs and outputs. If you don't do this, even a fairly trivial operation like this one gets very confusing and error-prone.
Here is an example of how to organize things. We remove most of the code from the click handler:
protected void DeleteButton_Click(object sender, EventArgs e)
{
for (int i = GridView2.Rows.Count - 1; i >= 0; i--)
{
var row = GridView2.Rows[i];
if (IsChecked(row))
{
var result = ProcessRow(row, i);
DisplayResult(i, result);
}
}
}
Firstly, notice it has a meaningful name. These become very important as your application grows. Also, look how short it is! Where did all the code go? Well, it went into two separate methods, which are now short enough for us to view on one page-- a common requirement that IT organizations impose on their programmers, to avoid spaghetti code.
protected TransactionResult ProcessRow(GridViewRow row, int index)
{
var SID = GridView2.Rows[index].Cells[3].Text.Trim();
var custPONum = GridView2.Rows[index].Cells[4].Text.Trim();
var loadSheetNum = txtDispatchNum.Text;
var removeUser = lblUsername.Text.Replace("Welcome", "");
return ExecuteInvoiceTransaction(SID, custPONum, loadSheetNum, removeUser);
}
And
public void DisplayResult(int rowIndex, TransactionResult result)
{
switch result
{
case TransactionResult.Success:
dt.Rows.RemoveAt(rowIndex);
DisplayTotals(rowIndex);
DisplaySuccess("Selected record(s) successfully updated");
break;
case TransactionResult.AlreadyDispatched;
ClientScript.RegisterClientScriptBlock(this.GetType(), "alert", "alert('This invoice num was already dispatched!')", true);
break;
case TransactionResult.RecordNotRecorded;
DisplayError("Record has not yet been recorded");
break;
case TransactionResult.AlreadyRemoved:
DisplayError("This invoice number was remove from dispatch sheet before!!");
break;
}
}
These methods in turn call a variety of helper methods, each of which does one thing and one thing only. This could be referred to as separation of concerns, which is really important for structured code.
Here's the rest of the methods:
enum TransactionResult
{
Success,
AlreadyDispatched,
RecordNotRecorded,
AlreadyRemoved
}
private bool ExecuteSelectStatus(SqlConnection connection, SqlTransaction transaction, string invoiceNum, string custPONum)
{
using (SqlCommand command = new SqlCommand("PP_SelectStatus", objConnection, transaction))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#invoiceNum", invoiceNum);
command.Parameters.AddWithValue("#custPONum", custPONum);
using (var myReader = command.ExecuteReader())
{
if (myReader.Read())
{
string invoice1 = (myReader["status"].ToString());
if (invoice1 == "0")
{
return false;
}
}
}
return true;
}
}
private int ExecuteRemoveInvoice(SqlConnection objConnection, SqlTransaction transaction, string loadSheetNum, string invoiceNum, string removeUser)
{
try
{
using (SqlCommand cmd = new SqlCommand("PP_RemoveInvoice", objConnection, transaction))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#loadSheetNum", loadSheetNum);
cmd.Parameters.AddWithValue("#invoiceNum", invoiceNum);
cmd.Parameters.AddWithValue("#removeUser", removeUser);
return cmd.ExecuteNonQuery();
}
}
catch (SqlException ex)
{
if (ex.Number == 2627) //Primary key violation
{
return -1;
}
}
}
protected TransactionResult ExecuteInvoiceTransaction(string invoiceNum, string custPONum, string loadSheetNum, string removeUser)
{
var strSQLconstring = System.Configuration.ConfigurationManager.ConnectionStrings["TWCL_OPERATIONSConnectionString"].ToString();
using (SqlConnection objConnection = new SqlConnection(strSQLconstring))
{
objConnection.Open();
using (SqlTransaction transaction = objConnection.BeginTransaction())
{
var ok = ExecuteSelectStatus(objConnection, transaction, invoiceNum, custPONum);
if (!ok) return TransactionResult.AlreadyDispatched;
var a = ExecuteRemoveInvoice(objConnection, transaction, loadSheetNum, invoiceNum, removeUser);
switch a
{
case -1:
return TransactionResult.AlreadyRemoved;
case 0:
return TransactionResult.RecordNotRecorded;
default:
transaction.Commit();
return TransactionResult.Success;
}
}
}
}
public void DisplayTotals(int i)
{
////Read invoice qty from grid view 2
string invoice = GridView2.Rows[i].Cells[5].Text;
decimal invoiceTotal = Convert.ToDecimal(txtInvoiceTotal.Text) - Convert.ToDecimal(invoice);
txtInvoiceTotal.Text = invoiceTotal.ToString();
////Read invoice weight from grid view 2
string weight = GridView2.Rows[i].Cells[6].Text();
decimal invoiceWeight = Convert.ToDecimal(txtQtyWeight.Text) - Convert.ToDecimal(weight);
txtQtyWeight.Text = invoiceWeight.ToString();
}
public void DisplaySuccess(string message)
{
lblError.ForeColor = Color.Green;
lblError.Text = message;
}
public void DisplayError(string message)
{
lblError.ForeColor = Color.Red;
lblError.Text = message;
}
A few things to note:
You don't need to call Dispose() if you are using using.
You should always catch the most specific exception possible, per Microsoft's guidance. My example does this.
The exception handling for the primary key error is isolated into the method that calls the stored procedure. The overall business logic shouldn't have to know details about the SQL implementation. I've shown how you can identify the specific error based on this post.
Because there are four possible outcomes, I added an enumeration called TransactionResult so we could return the status to the caller easily.
Some of these methods are short-- just two lines-- and that is OK. The main reason to separate them out is to give them a meaningful name and make the code shorter and easier to read.
This code is much more structured but it could still be improved! In many implementations, the code that accesses the database is actually moved to a completely different layer or project.
See if this works. Moved your if/else together:
protected void Button2_Click(object sender, EventArgs e)
{
try
{
for (int i = GridView2.Rows.Count - 1; i >= 0; i--)
{
var row = GridView2.Rows[i];
CheckBox chk = row.FindControl("chkInvoice") as CheckBox;
if (chk != null && chk.Checked)
{
string strSQLconstring = System.Configuration.ConfigurationManager.ConnectionStrings["TWCL_OPERATIONSConnectionString"].ToString();
using (SqlConnection objConnection = new SqlConnection(strSQLconstring))
{
objConnection.Open();
using (SqlTransaction transaction = objConnection.BeginTransaction())
{
string SID = GridView2.Rows[i].Cells[3].Text.Trim();
SqlDataReader myReader = null;
using (SqlCommand command = new SqlCommand("PP_SelectStatus", objConnection, transaction))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#invoiceNum", SID);
command.Parameters.AddWithValue("#custPONum", GridView2.Rows[i].Cells[4].Text.Trim());
myReader = command.ExecuteReader();
if (myReader.Read())
{
string invoice1 = (myReader["status"].ToString());
if (invoice1 == "0")
{
ClientScript.RegisterClientScriptBlock(this.GetType(), "alert", "alert('This invoice num was already dispatched!')", true);
}
else if (invoice1 == "1")
{
using (SqlCommand cmd = new SqlCommand("PP_RemoveInvoice", objConnection, transaction))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#loadSheetNum", txtDispatchNum.Text);
cmd.Parameters.AddWithValue("#invoiceNum", SID);
cmd.Parameters.AddWithValue("#removeUser", lblUsername.Text.Replace("Welcome", ""));
int a = cmd.ExecuteNonQuery();
cmd.Dispose();
if (a > 0)
{
dt.Rows.RemoveAt(i);
////Read invoice qty from grid view 2
string invoice = GridView2.Rows[i].Cells[5].Text.ToString();
decimal invoiceTotal = Convert.ToDecimal(txtInvoiceTotal.Text) - Convert.ToDecimal(invoice);
txtInvoiceTotal.Text = invoiceTotal.ToString();
////Read invoice weight from grid view 2
string weight = GridView2.Rows[i].Cells[6].Text.ToString();
decimal invoiceWeight = Convert.ToDecimal(txtQtyWeight.Text) - Convert.ToDecimal(weight);
txtQtyWeight.Text = invoiceWeight.ToString();
lblError.ForeColor = Color.Green;
lblError.Text = "Selected record(s) successfully updated";
}
else
{
lblError.ForeColor = Color.Red;
lblError.Text = " Record has not yet been recorded";
}
}
//objConnection.Close();
transaction.Commit();
}
}
}
GridView2.DataSource = dt;
GridView2.DataBind();
txtInvoiceCount.Text = dt.Rows.Count.ToString();
}
}
}
}
}
catch (Exception ex)
{
if (ex.Message.StartsWith("Violation of PRIMARY KEY constraint"))
{
lblError.ForeColor = Color.Red;
lblError.Text = " This invoice number was remove from dispatch sheet before!!";
}
else
{
// re-throw the error if you haven't handled it
lblError.Text = ex.Message;
throw;
}
}
}
}

Inserting dynamics 365 records into SQL Server database using asp.net

I am currently working on a project to insert a list of records from a dynamics 365 website into to a SQL Server database. However when I call the class file no insert is currently made into the database.
Can someone assist me? I have placed an ellipsis at the where the code which pulls the data from crm would be as that code works fine and so what you're reading isn't as long. Let me know if it is needed.
public class ProgramPVT
{
static void Main (string[] args)
{
try
{
...
int count = 0;
int n = count;
foreach (var item in performancevt)
{
performancevt.Add(item);
}
var totalnumber = performancevt.Count;
var t = totalnumber;
var accountmanager = new string[t];
var monthlytarget = new string[t];
var forecast_ = new string[t];
var actual_ = new string[t];
var managedservices = new string[t];
var pvtpercentage_ = new string[t];
var mspercentage_ = new string[t];
SqlConnection crmdbconnection = new SqlConnection("Data Source =*****;Initial Catalog=****;User Id = ******;Password = ******;");
crmdbconnection.Open();
foreach (var performanceitem in performancevt)
{
accountmanager[n] = performanceitem.accountmanager.ToString();
monthlytarget[n] = performanceitem.monthlytarget.ToString();
forecast_[n] = performanceitem.accountmanager.ToString();
actual_[n] = performanceitem.accountmanager.ToString();
managedservices[n] = performanceitem.monthlytarget.ToString();
pvtpercentage_[n] = performanceitem.accountmanager.ToString();
mspercentage_[n] = performanceitem.accountmanager.ToString();
var i = 0;
do
{
try
{
string cmdtext = "INSERT INTO PerformanceVTarget (Account_Manager, Month_Target, Forecast, Achieved, Total_Percentage, MS_Percentage) VALUES (#Account_Manager, #Month_Target, #Forecast, #Achieved, #Total_Percentage, #MS_Percentage)";
using (var cmd = new SqlCommand(cmdtext, crmdbconnection))
{
{
cmd.Parameters.AddWithValue("#Account_Manager", accountmanager[n]);
cmd.Parameters.AddWithValue("#Month_Target", accountmanager[n]);
cmd.Parameters.AddWithValue("#Forecast", accountmanager[n]);
cmd.Parameters.AddWithValue("#Achieved", accountmanager[n]);
cmd.Parameters.AddWithValue("#Total_Percentage", accountmanager[n]);
cmd.Parameters.AddWithValue("#Account_Manager", accountmanager[n]);
}
cmd.ExecuteNonQuery();
}
}
catch (Exception fx)
{
Console.Write(fx);
Console.WriteLine("Line with ID:", n, " not inserted");
Console.WriteLine("Error - Press enter to Continue");
Console.ReadLine();
}
i++;
} while (i < t);
}
n = n + 1;
}
catch (Exception ex)
{
Console.Write(ex);
Console.WriteLine("Press Enter to Continue");
Console.ReadLine();
}
}
}
}
Table PerformanceVTarget
[ID] pk, int ,not null
[Report_ID] int, null
[Account_Manager] varchar(50) not null
[Month_Target] varchar(50) not null
[Forecast] varchar(50) not null
[Achieved] varchar(50) not null
[Total_Percentage] varchar(50) not null
[MS_Percentage] varchar(50) not null
[Team] varchar(50) null
Your code execution will never come out of this loop.
foreach (var item in performancevt)
{
performancevt.Add(item);
}
This is a deadlock or you should get an error.
Also you are not passing any value to [MS_Percentage] & Id (primary key field), both are non-null columns.

how to refer to a non-declared variables in setResultConverter

I'd like to re-use a dialog class for data manipulation. The data will be retrieved from database. It depends on which table the class retrieve the data from, the size of the table columns is not fixed, so I can't declare column variables. After users update data, I would like to convert the input data using setResultConverter but do not know how to refer to the variable, since the program generates TextFields dynamically. Please help. Here is the the code.
public class AddDialog {
private Dialog<DBtable> dialog = new Dialog<DBtable>();
private ButtonType saveBtn;
//database variables
private Connection connect; // = null;
private String dbTblName;
//gridpane content variables
private GridPane contentPane = new GridPane();
private HashMap<String, TextField> fieldMap =
new HashMap<String, TextField>();
private ArrayList<String> dataList = new ArrayList<String>();
public AddDialog (String title, String header, String dbTable) {
this.dbTblName = dbTable;
dialog.setTitle(title);
dialog.setHeaderText(header);
saveBtn = new ButtonType("Save", ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(saveBtn,
ButtonType.CANCEL);
dialog.getDialogPane().setContent(getLayout(dbTable));
Optional<DBtable> result = dialog.showAndWait();
result.ifPresent(data -> {
System.out.println(" data="+data+" 0="+data.getID()+
" 1="+data.getField1());
});
} // constructor ends
public GridPane getLayout(String dbTable) {
String sql = "select column_name, description ";
sql += "from syscolumn_description ";
sql += "where table_name = \'" + dbTable + "\'";
String fieldLabel, fieldCol;
ResultSet ds = null;
// retrieve meta data from database
connect = DBConnect.getConnect(connect);
try {
Statement labelStmnt = connect.createStatement();
ds = labelStmnt.executeQuery(sql);
int row = 0;
while (ds.next()) {
row += 2;
//label....column=0 row=row+2;
fieldLabel = ds.getString("DESCRIPTION");
contentPane.add(new Text(fieldLabel), 0, row);
//textField...column=1 row=row+2;
contentPane.add(new TextField(), 1, row);
fieldCol = ds.getString("COLUMN_NAME");
fieldMap.put(fieldCol, new TextField());
} // while result set loop ends
} catch (Exception e) {
e.printStackTrace();
} finally {
try {if(ds != null) ds.close();} catch (Exception e) {};
}
// convert result
dialog.setResultConverter(dialogButton -> {
if (dialogButton == saveBtn) {
int i=0;
for (Map.Entry<String, TextField> e : fieldMap.entrySet()) {
dataList.add(e.getValue().getText());
i++;
System.out.println("col="+e.getKey()+
" data="+e.getValue().getText());
} // map loop end
return new DBtable(dataList, i);
}
return null;
});
return contentPane;
} //getLayout ends
} // AddDialog ends

SQLite keep expanding

I'm new to the blackberry development and to this site. right now, i'm working on an app that retrieve data from a json service. In my app I should parse the data into a database and save it in four tables. I already parsed the data and I was successful able to create the database and add the first and the second tables.
The problem that I'm facing right now is, the second table in my data base keep expanding. I checked the database in the sql browser and I discovered that everytime I click on the app icon it adds the 700 rows to the table again.(ex. 700 becomes 1400) .
(only to the second table, the first table works so fine).
Thank you in advance
This is my code:
public void parseJSONResponceInBB(String jsonInStrFormat)
{
try {
JSONObject json = newJSONObject(jsonInStrFormat);
JSONArray jArray = json.getJSONArray("tables");
for (inti = 0; i < jArray.length(); i++) {
//Iterate through json array
JSONObject j = jArray.getJSONObject(i);
if (j.has("Managers")) {
add(new LabelField("Managers has been added to the database"));
JSONArray j2 = j.getJSONArray("Managers");
for (intk = 0; k < j2.length(); ++k) {
JSONObject MangersDetails = j2.getJSONObject(k);
if (MangersDetails.has("fName")) {
try {
URI myURI =
URI.create
("file:///SDCard/Databases/SQLite_Guide/"
+ "MyTestDatabase.db");
d = DatabaseFactory.openOrCreate(myURI);
Statement st =
d.createStatement
("CREATE TABLE Managers ( "
+ "fName TEXT, " +
"lName TEXT, " + "ID TEXT," + "Type TEXT )");
st.prepare();
st.execute();
st.close();
d.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
try {
URI myURI =
URI.create
("file:///SDCard/Databases/SQLite_Guide/"
+ "MyTestDatabase.db");
d = DatabaseFactory.open(myURI);
Statement st =
d.createStatement
("INSERT INTO Managers(fName, lName, ID, Type) "
+ "VALUES (?,?,?,?)");
st.prepare();
for (intx = 0; x < j2.length(); x++) {
JSONObject F = j2.getJSONObject(x);
//add(new LabelField ("f"));
st.bind(1, F.getString("fName"));
st.bind(2, F.getString("lName"));
st.bind(3, F.getString("ID"));
st.bind(4, F.getString("Type"));
st.execute();
st.reset();
}
d.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}
}
}
catch(JSONException e) {
e.printStackTrace();
}
}
//Owners method
public voidparseJSONResponceInBB1(String jsonInStrFormat)
{
try {
JSONObject json = newJSONObject(jsonInStrFormat);
JSONArray jArray = json.getJSONArray("tables");
for (inti = 0; i < jArray.length(); i++) {
//Iterate through json array
JSONObject j = jArray.getJSONObject(i);
if (j.has("Owners")) {
add(new LabelField("Owners has been added to the database"));
JSONArray j2 = j.getJSONArray("Owners");
for (intk = 0; k < j2.length(); ++k) {
JSONObject OwnersDetails = j2.getJSONObject(k);
if (OwnersDetails.has("fName")) {
try {
Statement st =
d.createStatement
("CREATE TABLE Owners ( "
+ "fName TEXT, " +
"lName TEXT, " + "ID TEXT," + "Type TEXT )");
st.prepare();
st.execute();
st.close();
d.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
try {
Statement st =
d.createStatement
("INSERT INTO Owners(fName, lName, ID, Type) "
+ "VALUES (?,?,?,?)");
st.prepare();
for (intx = 0; x < j2.length(); x++) {
JSONObject F = j2.getJSONObject(x);
//add(new LabelField ("f"));
st.bind(1, F.getString("fName"));
st.bind(2, F.getString("lName"));
st.bind(3, F.getString("ID"));
st.bind(4, F.getString("Type"));
st.execute();
st.reset();
}
d.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}
}
}
catch(JSONException e) {
e.printStackTrace();
}
}
It depends on what your goals are here. If you want to replace the data in the database each time the json query runs, you should add a sqlite command to remove all the existing rows with the newly fetched ones coming in via JSON.
If you just want to keep certain types of records unique, you should add an index to the sqlite table. The 'ID' column is a likely candidate for this. You'll have to do some experiments to make sure a conflict is handled correctly - it may abort the entire transaction. "INSERT OR REPLACE" is useful in that situation.

How do I store and retrieve a blob from sqlite?

I have used sqlite in c++, python and now (perhaps) in C#. In all of these I have no idea how to insert a blob into a table. How do I store and retrieve a blob in sqlite?
Here's how you can do it in C#:
class Program
{
static void Main(string[] args)
{
if (File.Exists("test.db3"))
{
File.Delete("test.db3");
}
using (var connection = new SQLiteConnection("Data Source=test.db3;Version=3"))
using (var command = new SQLiteCommand("CREATE TABLE PHOTOS(ID INTEGER PRIMARY KEY AUTOINCREMENT, PHOTO BLOB)", connection))
{
connection.Open();
command.ExecuteNonQuery();
byte[] photo = new byte[] { 1, 2, 3, 4, 5 };
command.CommandText = "INSERT INTO PHOTOS (PHOTO) VALUES (#photo)";
command.Parameters.Add("#photo", DbType.Binary, 20).Value = photo;
command.ExecuteNonQuery();
command.CommandText = "SELECT PHOTO FROM PHOTOS WHERE ID = 1";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
byte[] buffer = GetBytes(reader);
}
}
}
}
static byte[] GetBytes(SQLiteDataReader reader)
{
const int CHUNK_SIZE = 2 * 1024;
byte[] buffer = new byte[CHUNK_SIZE];
long bytesRead;
long fieldOffset = 0;
using (MemoryStream stream = new MemoryStream())
{
while ((bytesRead = reader.GetBytes(0, fieldOffset, buffer, 0, buffer.Length)) > 0)
{
stream.Write(buffer, 0, (int)bytesRead);
fieldOffset += bytesRead;
}
return stream.ToArray();
}
}
}
This worked fine for me (C#):
byte[] iconBytes = null;
using (var dbConnection = new SQLiteConnection(DataSource))
{
dbConnection.Open();
using (var transaction = dbConnection.BeginTransaction())
{
using (var command = new SQLiteCommand(dbConnection))
{
command.CommandText = "SELECT icon FROM my_table";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
if (reader["icon"] != null && !Convert.IsDBNull(reader["icon"]))
{
iconBytes = (byte[]) reader["icon"];
}
}
}
}
transaction.Commit();
}
}
No need for chunking. Just cast to a byte array.
I ended up with this method for inserting a blob:
protected Boolean updateByteArrayInTable(String table, String value, byte[] byteArray, String expr)
{
try
{
SQLiteCommand mycommand = new SQLiteCommand(connection);
mycommand.CommandText = "update " + table + " set " + value + "=#image" + " where " + expr;
SQLiteParameter parameter = new SQLiteParameter("#image", System.Data.DbType.Binary);
parameter.Value = byteArray;
mycommand.Parameters.Add(parameter);
int rowsUpdated = mycommand.ExecuteNonQuery();
return (rowsUpdated>0);
}
catch (Exception)
{
return false;
}
}
For reading it back the code is:
protected DataTable executeQuery(String command)
{
DataTable dt = new DataTable();
try
{
SQLiteCommand mycommand = new SQLiteCommand(connection);
mycommand.CommandText = command;
SQLiteDataReader reader = mycommand.ExecuteReader();
dt.Load(reader);
reader.Close();
return dt;
}
catch (Exception)
{
return null;
}
}
protected DataTable getAllWhere(String table, String sort, String expr)
{
String cmd = "select * from " + table;
if (sort != null)
cmd += " order by " + sort;
if (expr != null)
cmd += " where " + expr;
DataTable dt = executeQuery(cmd);
return dt;
}
public DataRow getImage(long rowId) {
String where = KEY_ROWID_IMAGE + " = " + Convert.ToString(rowId);
DataTable dt = getAllWhere(DATABASE_TABLE_IMAGES, null, where);
DataRow dr = null;
if (dt.Rows.Count > 0) // should be just 1 row
dr = dt.Rows[0];
return dr;
}
public byte[] getImage(DataRow dr) {
try
{
object image = dr[KEY_IMAGE];
if (!Convert.IsDBNull(image))
return (byte[])image;
else
return null;
} catch(Exception) {
return null;
}
}
DataRow dri = getImage(rowId);
byte[] image = getImage(dri);
Since there is no complete example for C++ yet, this is how you can insert and retrieve an array/vector of float data without error checking:
#include <sqlite3.h>
#include <iostream>
#include <vector>
int main()
{
// open sqlite3 database connection
sqlite3* db;
sqlite3_open("path/to/database.db", &db);
// insert blob
{
sqlite3_stmt* stmtInsert = nullptr;
sqlite3_prepare_v2(db, "INSERT INTO table_name (vector_blob) VALUES (?)", -1, &stmtInsert, nullptr);
std::vector<float> blobData(128); // your data
sqlite3_bind_blob(stmtInsertFace, 1, blobData.data(), static_cast<int>(blobData.size() * sizeof(float)), SQLITE_STATIC);
if (sqlite3_step(stmtInsert) == SQLITE_DONE)
std::cout << "Insert successful" << std::endl;
else
std::cout << "Insert failed" << std::endl;
sqlite3_finalize(stmtInsert);
}
// retrieve blob
{
sqlite3_stmt* stmtRetrieve = nullptr;
sqlite3_prepare_v2(db, "SELECT vector_blob FROM table_name WHERE id = ?", -1, &stmtRetrieve, nullptr);
int id = 1; // your id
sqlite3_bind_int(stmtRetrieve, 1, id);
std::vector<float> blobData;
if (sqlite3_step(stmtRetrieve) == SQLITE_ROW)
{
// retrieve blob data
const float* pdata = reinterpret_cast<const float*>(sqlite3_column_blob(stmtRetrieve, 0));
// query blob data size
blobData.resize(sqlite3_column_bytes(stmtRetrieve, 0) / static_cast<int>(sizeof(float)));
// copy to data vector
std::copy(pdata, pdata + static_cast<int>(blobData.size()), blobData.data());
}
sqlite3_finalize(stmtRetrieve);
}
sqlite3_close(db);
return 0;
}
You need to use sqlite's prepared statements interface. Basically, the idea is that you prepare a statement with a placeholder for your blob, then use one of the bind calls to "bind" your data...
SQLite Prepared Statements
In C++ (without error checking):
std::string blob = ...; // assume blob is in the string
std::string query = "INSERT INTO foo (blob_column) VALUES (?);";
sqlite3_stmt *stmt;
sqlite3_prepare_v2(db, query, query.size(), &stmt, nullptr);
sqlite3_bind_blob(stmt, 1, blob.data(), blob.size(),
SQLITE_TRANSIENT);
That can be SQLITE_STATIC if the query will be executed before blob gets destructed.

Resources