Two insert queries with linked fields - c#

I have the following two MySQL tables:
questions:
question_id (PK, AI), module_id (FK), author_id (FK), approved, question, correct_answer_id (FK)
answers:
answer_id (PK, AI), question_id (FK), answer
I want to be able to insert a new row in the 'questions' table and multiple rows in the 'answers' tables.
The new rows in the 'answers' table should have the same 'question_id' as the newly generated 'question_id' value in the 'questions' row. Also, the 'correct_answer_id' field in the 'questions' table should equal the 'answer_id' of the first row inserted in the 'answers' table.
Is there a more efficiently way to do this than the following steps?:
insert values (module_id, author_id, approved, question) in
'questions'
get last 'question_id' in 'questions'
insert values (question_id, answer) in 'answers'
update value (correct_answer_id) in 'questions'
code:
string connStr = ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString;
MySqlConnection conn = new MySqlConnection(connStr);
string queryUpdateQuestions = "INSERT INTO questions (module_id, author_id, approved, question) VALUES (#module_id, #author_id, #approved, #question)";
MySqlCommand cmdUpdateQuestions = new MySqlCommand(queryUpdateQuestions, conn);
cmdUpdateQuestions.Parameters.Add("#module_id", MySqlDbType.VarChar);
cmdUpdateQuestions.Parameters["#module_id"].Value = ddlModules.SelectedValue.ToString();
cmdUpdateQuestions.Parameters.Add("#author_id", MySqlDbType.VarChar);
cmdUpdateQuestions.Parameters["#author_id"].Value = Session["UserID"].ToString();
cmdUpdateQuestions.Parameters.Add("#approved", MySqlDbType.VarChar);
cmdUpdateQuestions.Parameters["#approved"].Value = 'N';
cmdUpdateQuestions.Parameters.Add("#question", MySqlDbType.VarChar);
cmdUpdateQuestions.Parameters["#question"].Value = txtQuestion.Text;
try
{
conn.Open();
cmdUpdateQuestions.ExecuteNonQuery();
}
catch
{
lblError.Text="Unable to add question.";
}
finally
{
conn.Close();
}
//????? = get last question_id in 'questions'
int a = Convert.ToInt32(ddlNoOfAnswers.SelectedValue.ToString());
for (int b=1; b <= a; b++)
{
string queryUpdateAnswers = "INSERT INTO answers (question_id, answer) VALUES (#question_id, #answer)";
MySqlCommand cmdUpdateAnswers = new MySqlCommand(queryUpdateAnswers, conn);
cmdUpdateAnswers.Parameters.Add("#answer", MySqlDbType.VarChar);
cmdUpdateAnswers.Parameters["#answer"].Value = ((TextBox)this.FindControl("txtAnswer" + b)).Text;
cmdUpdateAnswers.Parameters.Add("#question_id", MySqlDbType.VarChar);
cmdUpdateAnswers.Parameters["#question_id"].Value = ?????;
try
{
conn.Open();
cmdUpdateAnswers.ExecuteNonQuery();
}
catch
{
lblError.Text="Unable to add answer.";
}
finally
{
conn.Close();
}
}
//update 'correct_answer_id' in 'questions'

Some simplification is possible. First of all you need to enclose all of your commands inside a transaction because this is the classical case where the records inserted are in strictly relationships and it doesn't make sense to have some partially completed set of records.
using(MySqlConnection conn = new MySqlConnection(connStr))
{
conn.Open();
using(MySqlTransaction tr = conn.BeginTransaction())
{
...
// MySqlCommand code goes here
...
tr.Commit();
}
}
Now, you could change your insert question sql to add a second statement that returns the last id inserted
string queryUpdateQuestions = #"INSERT INTO questions (.....);
SELECT LAST_INSERT_ID()";
using(MySqlCommand cmdUpdateQuestions = new MySqlCommand(queryUpdateQuestions, conn, tr))
{
// build the parameters for the question record
......
// Instead of ExecuteNonQuery, run ExecuteScalar to get back the result of the last SELECT
int lastQuestionID = Convert.ToInt32(cmdUpdateQuestions.ExecuteScalar());
..
}
Notice how, at the MySqlCommand constructor, is passed the reference to the current transaction. This is required to work with an connection that has a transaction opened.
Things are a bit more complex for the second part. The same trick to add a second sql statement could be applied also to the loop that insert the answers, but you need to loop backward if the first question is the correct one
string queryUpdateAnswers = #"INSERT INTO answers (question_id, answer)
VALUES (#question_id, #answer);
SELECT LAST_INSERT_ID()";
using(MySqlCommand cmdUpdateAnswers = new MySqlCommand(queryUpdateAnswers, conn, tr))
{
// next move the loop inside the using and prepare the parameter before looping to
// to avoid unnecessary rebuild of the parameters and the command
cmdUpdateAnswers.Parameters.Add("#answer", MySqlDbType.VarChar);
cmdUpdateAnswers.Parameters.Add("#question_id", MySqlDbType.Int32);
int lastAnswerID = 0;
// Loop backward so the last answer inserted is the 'correct' one and we could get its ID
for (int b=a; b >= 1; b--)
{
cmdUpdateAnswers.Parameters["#answer"].Value = ((TextBox)this.FindControl("txtAnswer" + b)).Text;
cmdUpdateAnswers.Parameters["#question_id"].Value = lastQuestionID;
lastAnswerID = Convert.ToInt32(cmdUpdateAnswers.ExecuteScalar());
}
....
}
Now you could run the last command that update the question with the lastAnswerID
(A last note, I suppose that the fields question_id and answer_id are of type numeric, not varchar, this requires that the parameters for these fields will be an Int32 not a varchar)

Yes, the approach you outline is the most efficient. You will need to retrieve the value assigned to the AUTO_INCREMENT column of each row INSERTED. But be careful how you retrieve that value.
insert a row into 'questions' table
retrieve last_insert_id value assigned to AUTO_INCREMENT column
insert row to 'answers' table, using retrieved value for 'question_id' column
retrieve last_insert_id value immediately following insert of "correct answer" row
update row in 'questions' to set 'correct_answer_id' column
MySQL provides the LAST_INSERT_ID() function. That's the mechanism that MySQL provides to retrieve the value assigned to an AUTO_INCREMENT column, following the successful execution of an INSERT statement. (With singleton inserts, it's very straightforward; it just has to be called immediately following the insert.)
Ref: http://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_last-insert-id
A lot of client libraries provide a builtin function to do this, so it's not necessary to prepare and execute a separate SELECT statement. (For example, with PHP, PDO provides lastInsertId, mysqli provides $insertid. It's likely that the C# connector for MySQL has a similar function.)

Related

Datarow id string not matching with object id string even though they are identical strings after checking debug

I am trying to search through a list of ID strings in my database where an ID string is equal to the one of the object i am trying to create. The id being created is in a factory design pattern where a train type "express" is made with an ID of "1E45". After this ID is created, it increments the number section after the letter and then that can be used for the next train added.
When searching through the list with a foreach it returns the id from the database that is similar to the one trying to be created.
But when I try to match these two after using toString to change them both and match in an IF. The match returns false even though when I check the debug it is exactly the same?
It then just continues on to try and add a new object with that ID that already exists and crashes.
What am I doing wrong? it doesn't make sense after checking the values being matched and it saying false.
Here is the code I have set up:
//Create sql command variables to create new commands
SqlCommand insert = new SqlCommand();
SqlCommand checkID = new SqlCommand();
//Set the command type to text
insert.CommandType = CommandType.Text;
checkID.CommandType = CommandType.Text;
//Searches for an ID in the database that matches one that is trying to be created
checkID.CommandText = "SELECT id FROM Train WHERE id = #trainID";
//Parameters for checking ID in database
checkID.Parameters.AddWithValue("#trainID", train.TrainID);
//Set the connection for the command for the checkID sql connection
checkID.Connection = con;
//Start the connection
con.Open();
DataTable dt = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter(checkID);
adapter.Fill(dt);
dt.Load(checkID.ExecuteReader());
//Item last = Module.
foreach (DataRow i in dt.Rows)
{
if (i.ToString() == train.TrainID.ToString())
{
MessageBox.Show("This ID already exists! " + train.TrainID);
return;
}
}
//Close the connection
con.Close();
//Set the text for the command to insert data to the database connected to
insert.CommandText = "INSERT Train (id, departure, destination, type, intermediate, departure_time, departure_date, sleeperBerth, firstClass) " +
"VALUES ( #trainID , #departure, #destination, #type, #intermediate, #dep_time, #dep_date, #sleep, #first)";
//Parameters for adding values from the train object to the database
insert.Parameters.AddWithValue("#trainID", train.TrainID);
insert.Parameters.AddWithValue("#departure", train.Departure);
insert.Parameters.AddWithValue("#destination", train.Destination);
insert.Parameters.AddWithValue("#type", train.Type);
insert.Parameters.AddWithValue("#intermediate", intStops);
insert.Parameters.AddWithValue("#dep_time", train.DepartureTime);
insert.Parameters.AddWithValue("#dep_date", train.DepartureDay);
insert.Parameters.AddWithValue("#sleep", train.SleeperBerth);
insert.Parameters.AddWithValue("#first", train.FirstClass);
//Set the connection for the command for the insert sql connection
insert.Connection = con;
//Start the connection
con.Open();
//Execute the command specified
insert.ExecuteNonQuery();
//Close the connection
con.Close();
Sounds like you need to change your column id in the table train to an IDENTITY column, and let the database handle the ID assignment:
ALTER TABLE dbo.Train ALTER COLUMN id int IDENTITY(1,1); --data type guessed
Then, in your application, you don't need to generate a value for ID, nor do declare it in your INSERT statement (either in the list of columns to INSERT into or in your VALUES clause).

insert data into second table if insert into first table has been done successfully otherwise don't insert data

i have two tables in Sqlite i want if user inserted data into first table then a specific data be inserted into the second table but if insertion on the first table succeded and on second table failed then data inserted into the first table must be removed , how to manage this in c# with sqlite ?
i mean is there any way in sqlite or sql to manage this??
here is the code i use in c# :
queryString = string.Format(#"insert into customerHost values('{0}','{1}','{2}','{3}','{4}','{5}')",
"1", userID, hostPlan, hostServiceDate, hostExpiresOn ,hostPrice);
if (new dataAccess().insertQuery(queryString) == 1)// if data has been inserted into the first table successfully then insert into the second one
{
MessageBox.Show("good", "good");
queryString = String.Format(#"insert into hostUpdate values('{0}','{1}','{2}','{3}')",
Guid.NewGuid().ToString(), userID, hostServiceDate, hostExpiresOn);
if (new dataAccess().insertQuery(queryString) != 1)//if data insertion into the second table failed then throw an errro....in fact data inserted into the first table must be removed
MessageBox.Show("some errors has been occured ", "error");
}
you can use triggers in SQLite.
https://www.sqlite.org/lang_createtrigger.html
you can google about it easily.. i did and below is some part of the code which was found in the first link given by google.
please note that below is just an example, below is the the complete answer and it is a must that you read the attached and may be few more articles on transactions in databases....
string cs = "URI=file:test.db";
using (SqliteConnection con = new SqliteConnection(cs))
{
con.Open();
using(SqliteTransaction tr = con.BeginTransaction())
{
using (SqliteCommand cmd = con.CreateCommand())
{
cmd.Transaction = tr;
cmd.CommandText = "DROP TABLE IF EXISTS Friends";
cmd.ExecuteNonQuery();
...............
}
tr.Commit();
}
con.Close();
}
}

Read one value from the last row of a SQL Server CE database

I have a database with some columns and one of that is called ID (datatype = int).
I need to know the value of the ID of the last row (for last row I mean the last created).
I tried this, but it gives a runtime error:
string query = "SELECT * FROM Customer WHERE ID = (SELECT MAX(ID) FROM Customer)";
SqlCeCommand comSelect = new SqlCeCommand(query, connection);
SqlCeDataReader rdr = comSelect.ExecuteReader();
int ID = rdr.GetInt32(6);
(GetInt32(6) because ID is the 6th column)
Thanks all, and sorry for my english.
P.S.
Customer is my table and ID are set in ascending order. The first row created has ID = 0 the second ID = 1 etc.
I need to know the last id because when I create a new customer I want to set his ID to previous customer ID+1
Errors:
Exception of type 'System.Data.SqlServerCe.SqlCeException' in System.Data.SqlServerCe.dll unhandled in user code
Given the fact that probably there is no problem with concurrency you could simply get the last ID with an ExecuteScalar call
string query ="SELECT MAX(ID) FROM Customer";
SqlCeCommand comSelect = new SqlCeCommand(query, connection);
int ID = (int)comSelect.ExecuteScalar();
ExecuteScalar returns the first column of the first row in the result set.
The query SELECT MAX(ID) returns just one row with only one column. So ExecuteScalar fits perfectly in this scenario. No need to use an ExecuteReader
However, the correct way to handle your autoincrement scenario is to use an IDENTITY column that will automatically set the next value for you when you insert a new record.
In this situation you have marked the ID column with the IDENTITY property to TRUE and then you insert your data in the table Customer without passing any value for the ID column. After the insert you retrieve immediately the value assigned by the database to your ID column
Pseudocode
string query ="INSERT INTO Customers (Name, Address, .....) VALUES (.....)";
SqlCeCommand comInsert = new SqlCeCommand(query, connection);
comInsert.ExecuteNonQuery();
query ="SELECT ##IDENTITY";
SqlCeCommand comSelect = new SqlCeCommand(query, connection);
int ID = (int)comSelect.ExecuteScalar();
If you are setting the Id and you may have other users its better to save the new data and get the I'd, or whole new object, returned from the db by the code saving the row. If you try and keep track of user ids other users could add new items and mess things up.
If you have to get last I'd from the db then you can use #inserted in your sql to get the last row added.

C# , SQL update multiple rows

i have a question regarding an efficient way to update multiple rows via SQL.
Basiclly i have a query i need to run on different RowIDs:
UPDATE TableName SET Column = (some number) WHERE RowID = (some number)
if to be more specific this is a better example:
UPDATE TableName SET Column = 5 WHERE RowID = 1000
UPDATE TableName SET Column = 10 WHERE RowID = 1001
UPDATE TableName SET Column = 30 WHERE RowID = 1002
..
I'd like to know how should i build the update query command on C# (or just give me an example of the resulted query i should get to) so once i use ExecuteQuery it will run all of these commands at one piece and not by executing each command.
edited:
I have another problem, can you also explain what about dynamic situation in which not necessarly the row i want to update exist already, in that case i need to insert instead of update. to explain better, back to my example lets say i want to do
UPDATE TableName SET Column = 5 WHERE RowID = 1000
INSERT INTO TableName [RowID, Column] VALUES (1001, 20)
UPDATE TableName SET Column = 30 WHERE RowID = 1002
..
The meaning of this is that i need to check if the row exist, if so i'd use update otherwise i'll have to insert it.
Thank you!
You could use a DataTable to store your records, insert, delete or change rows and update all changes in one batch by using SqlDataAdapter's UpdateBatchSize(0 means no limit):
public static void BatchUpdate(DataTable dataTable,Int32 batchSize)
{
// Assumes GetConnectionString() returns a valid connection string.
string connectionString = GetConnectionString();
// Connect to the AdventureWorks database.
using (SqlConnection connection = new
SqlConnection(connectionString))
{
// Create a SqlDataAdapter.
SqlDataAdapter adapter = new SqlDataAdapter();
// Set the UPDATE command and parameters.
adapter.UpdateCommand = new SqlCommand(
"UPDATE Production.ProductCategory SET "
+ "Name=#Name WHERE ProductCategoryID=#ProdCatID;",
connection);
adapter.UpdateCommand.Parameters.Add("#Name",
SqlDbType.NVarChar, 50, "Name");
adapter.UpdateCommand.Parameters.Add("#ProdCatID",
SqlDbType.Int, 4, "ProductCategoryID");
adapter.UpdateCommand.UpdatedRowSource = UpdateRowSource.None;
// Set the INSERT command and parameter.
adapter.InsertCommand = new SqlCommand(
"INSERT INTO Production.ProductCategory (Name) VALUES (#Name);",
connection);
adapter.InsertCommand.Parameters.Add("#Name",
SqlDbType.NVarChar, 50, "Name");
adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.None;
// Set the DELETE command and parameter.
adapter.DeleteCommand = new SqlCommand(
"DELETE FROM Production.ProductCategory "
+ "WHERE ProductCategoryID=#ProdCatID;", connection);
adapter.DeleteCommand.Parameters.Add("#ProdCatID",
SqlDbType.Int, 4, "ProductCategoryID");
adapter.DeleteCommand.UpdatedRowSource = UpdateRowSource.None;
// Set the batch size.
adapter.UpdateBatchSize = batchSize;
// Execute the update.
adapter.Update(dataTable);
}
}
http://msdn.microsoft.com/en-us/library/aadf8fk2.aspx
I assume you're misunderstanding how the dbms works internally. This
UPDATE TableName SET Column = 5 WHERE RowID = 1000;
UPDATE TableName SET Column = 5 WHERE RowID = 1002;
is the same as
UPDATE TableName SET Column = 5 WHERE RowID IN(1000,2002);
The dbms will update all affected records one by one anyway even if you would write a statement like UPDATE table SET value=1 which would affect every record in the table. By updating in one batch you ensure that all updates(deletes,inserts)are submitted to the database instead of one roundtrip for every statement.
Use MERGE:
MERGE INTO TableName
USING (
VALUES (1000, 5),
(1001, 10),
(1002, 30)
) AS source (RowID, Column_name)
ON TableName.RowID = source.RowID
WHEN MATCHED THEN
UPDATE
SET Column_name = source.Column_name
WHEN NOT MATCHED THEN
INSERT (RowID, Column_name)
VALUES (RowID, Column_name);
Rather than hard-coding/dynamic SQL, the MERGE statement could be encapsulated into a stored proc that takes a table-valued parameter.

Getting the Last Insert ID with SQLite.NET in C#

I have a simple problem with a not so simple solution... I am currently inserting some data into a database like this:
kompenzacijeDataSet.KompenzacijeRow kompenzacija = kompenzacijeDataSet.Kompenzacije.NewKompenzacijeRow();
kompenzacija.Datum = DateTime.Now;
kompenzacija.PodjetjeID = stranka.id;
kompenzacija.Znesek = Decimal.Parse(tbZnesek.Text);
kompenzacijeDataSet.Kompenzacije.Rows.Add(kompenzacija);
kompenzacijeDataSetTableAdapters.KompenzacijeTableAdapter kompTA = new kompenzacijeDataSetTableAdapters.KompenzacijeTableAdapter();
kompTA.Update(this.kompenzacijeDataSet.Kompenzacije);
this.currentKompenzacijaID = LastInsertID(kompTA.Connection);
The last line is important. Why do I supply a connection? Well there is a SQLite function called last_insert_rowid() that you can call and get the last insert ID. Problem is it is bound to a connection and .NET seems to be reopening and closing connections for every dataset operation. I thought getting the connection from a table adapter would change things. But it doesn't.
Would anyone know how to solve this? Maybe where to get a constant connection from? Or maybe something more elegant?
Thank you.
EDIT:
This is also a problem with transactions, I would need the same connection if I would want to use transactions, so that is also a problem...
Using C# (.net 4.0) with SQLite, the SQLiteConnection class has a property LastInsertRowId that equals the Primary Integer Key of the most recently inserted (or updated) element.
The rowID is returned if the table doesn't have a primary integer key (in this case the rowID is column is automatically created).
See https://www.sqlite.org/c3ref/last_insert_rowid.html for more.
As for wrapping multiple commands in a single transaction, any commands entered after the transaction begins and before it is committed are part of one transaction.
long rowID;
using (SQLiteConnection con = new SQLiteConnection([datasource])
{
SQLiteTransaction transaction = null;
transaction = con.BeginTransaction();
... [execute insert statement]
rowID = con.LastInsertRowId;
transaction.Commit()
}
select last_insert_rowid();
And you will need to execute it as a scalar query.
string sql = #"select last_insert_rowid()";
long lastId = (long)command.ExecuteScalar(sql); // Need to type-cast since `ExecuteScalar` returns an object.
last_insert_rowid() is part of the solution. It returns a row number, not the actual ID.
cmd = CNN.CreateCommand();
cmd.CommandText = "SELECT last_insert_rowid()";
object i = cmd.ExecuteScalar();
cmd.CommandText = "SELECT " + ID_Name + " FROM " + TableName + " WHERE rowid=" + i.ToString();
i = cmd.ExecuteScalar();
I'm using Microsoft.Data.Sqlite package and I do not see a LastInsertRowId property. But you don't have to create a second trip to database to get the last id. Instead, combine both sql statements into a single string.
string sql = #"
insert into MyTable values (null, #name);
select last_insert_rowid();";
using (var cmd = conn.CreateCommand()) {
cmd.CommandText = sql;
cmd.Parameters.Add("#name", SqliteType.Text).Value = "John";
int lastId = Convert.ToInt32(cmd.ExecuteScalar());
}
There seems to be answers to both Microsoft's reference and SQLite's reference and that is the reason some people are getting LastInsertRowId property to work and others aren't.
Personally I don't use an PK as it's just an alias for the rowid column. Using the rowid is around twice as fast as one that you create. If I have a TEXT column for a PK I still use rowid and just make the text column unique. (for SQLite 3 only. You need your own for v1 & v2 as vacuum will alter rowid numbers)
That said, the way to get the information from a record in the last insert is the code below. Since the function does a left join to itself I LIMIT it to 1 just for speed, even if you don't there will only be 1 record from the main SELECT statement.
SELECT my_primary_key_column FROM my_table
WHERE rowid in (SELECT last_insert_rowid() LIMIT 1);
The SQLiteConnection object has a property for that, so there is not need for additional query.
After INSERT you just my use LastInsertRowId property of your SQLiteConnection object that was used for INSERT command.
Type of LastInsertRowId property is Int64.
Off course, as you already now, for auto increment to work the primary key on table must be set to be AUTOINCREMENT field, which is another topic.
database = new SQLiteConnection(databasePath);
public int GetLastInsertId()
{
return (int)SQLite3.LastInsertRowid(database.Handle);
}
# How about just running 2x SQL statements together using Execute Scalar?
# Person is a object that has an Id and Name property
var connString = LoadConnectionString(); // get connection string
using (var conn = new SQLiteConnection(connString)) // connect to sqlite
{
// insert new record and get Id of inserted record
var sql = #"INSERT INTO People (Name) VALUES (#Name);
SELECT Id FROM People
ORDER BY Id DESC";
var lastId = conn.ExecuteScalar(sql, person);
}
In EF Core 5 you can get ID in the object itself without using any "last inserted".
For example:
var r = new SomeData() { Name = "New Row", ...};
dbContext.Add(r);
dbContext.SaveChanges();
Console.WriteLine(r.ID);
you would get new ID without thinking of using correct connection or thread-safety etc.
If you're using the Microsoft.Data.Sqlite package, it doesn't include a LastInsertRowId property in the SqliteConnection class, but you can still call the last_insert_rowid function by using the underlying SQLitePCL library. Here's an extension method:
using Microsoft.Data.Sqlite;
using SQLitePCL;
public static long GetLastInsertRowId(this SqliteConnection connection)
{
var handle = connection.Handle ?? throw new NullReferenceException("The connection is not open.");
return raw.sqlite3_last_insert_rowid(handle);
}

Categories

Resources