How can I conditionally prevent a column from being updated in SQLite? - c#

I have a table with CREATED and MODIFIED columns. I only want to insert the CREATED value once and have it be thereafter immutable. I know how to do this in a tedious way (write a "DoesRecordExist()" method and then alter the query and number of query parameters based on that), but surely there is a slicker way to accomplish this. After all, this has to be a common requirement (a "database pattern" if you will).
My code is this:
public void InsertUserSiteRecord(UserSite us)
{
using (SQLiteConnection conn = new SQLiteConnection(HHSUtils.GetDBConnection()))
{
conn.Open();
using (SQLiteCommand cmd = new SQLiteCommand(conn))
{
cmd.CommandText =
String.Format(
#"INSERT INTO UserSite (SiteNum, SerialNum, UserName, Created, Modified)
VALUES (#SiteNum, #SerialNum, #UserName, #Created, #Modified)");
cmd.Parameters.Add(new SQLiteParameter("SiteNum", us.SiteNum));
cmd.Parameters.Add(new SQLiteParameter("SerialNum", us.SerialNum));
cmd.Parameters.Add(new SQLiteParameter("UserName", us.UserName));
cmd.Parameters.Add(new SQLiteParameter("Created", us.Created));
cmd.Parameters.Add(new SQLiteParameter("Modified", us.Modified));
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
...and I want to avoid having to do something like this:
public void InsertUserSiteRecord(UserSite us)
{
using (SQLiteConnection conn = new SQLiteConnection(HHSUtils.GetDBConnection()))
{
conn.Open();
using (SQLiteCommand cmd = new SQLiteCommand(conn))
{
if (!RecordExists(us.SiteNum, us.SerialNum, us.UserName))
{
cmd.CommandText =
String.Format(
#"INSERT INTO UserSite (SiteNum, SerialNum, UserName, Created, Modified)
VALUES (#SiteNum, #SerialNum, #UserName, #Created, #Modified)");
else
{
cmd.CommandText =
String.Format(
#"INSERT INTO UserSite (SiteNum, SerialNum, UserName, Modified)
VALUES (#SiteNum, #SerialNum, #UserName, #Modified)");
}
cmd.Parameters.Add(new SQLiteParameter("SiteNum", us.SiteNum));
cmd.Parameters.Add(new SQLiteParameter("SerialNum", us.SerialNum));
cmd.Parameters.Add(new SQLiteParameter("UserName", us.UserName));
if (!RecordExists(us.SiteNum, us.SerialNum, us.UserName))
{
cmd.Parameters.Add(new SQLiteParameter("Created", us.Created));
}
cmd.Parameters.Add(new SQLiteParameter("Modified", us.Modified));
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
private bool RecordExists(String SiteNum, String SerialNum, String UserId)
{
// query the table to see if those three values exist in any record
}
Is there a SQL[ite] construct that is something like:
cmd.Parameters.AddOnlyIfColumnIsEmpty(new SQLiteParameter("Created", us.Created));
? Or how can this be best tackled?

You need to check for existance of the record; then do an UPDATE if it exists (not changing your Created value), and an INSERT if it doesn't.
Your original code will give a duplicate key error, assuming you have a PK on the table.

You may be able to accomplish this using a trigger. See the SQLite documentation on triggers here: https://www.sqlite.org/lang_createtrigger.html
Basically, you would create an INSTEAD OF trigger, then set up your query accordingly. From the documentation:
For an example of an INSTEAD OF trigger, consider the following schema:
CREATE TABLE customer(
cust_id INTEGER PRIMARY KEY,
cust_name TEXT,
cust_addr TEXT
);
CREATE VIEW customer_address AS
SELECT cust_id, cust_addr FROM customer;
CREATE TRIGGER cust_addr_chng
INSTEAD OF UPDATE OF cust_addr ON customer_address
BEGIN
UPDATE customer SET cust_addr=NEW.cust_addr
WHERE cust_id=NEW.cust_id;
END;
With the schema above, a statement of the form:
UPDATE customer_address SET cust_addr=$new_address WHERE cust_id=$cust_id;
Causes the customer.cust_addr field to be updated for a specific customer entry that has customer.cust_id equal to the $cust_id parameter. Note how the values assigned to the view are made available as field in the special "NEW" table within the trigger body.
What you would want to do is just set up the trigger to not update that column even if the original query passes in that column to be updated.

Related

MS Access - C# - Retrieve the latest inserted guid

Is there a way to retrieve the latest inserted guid in access with C#?
I tried this: Created a table Cars with a field Id of type autonumber, replicationID and a field Name varchar(250).
var command = myConnection.CreateCommand();
command.Connection.Open();
command.CommandText = "INSERT INTO Cars(Name) VALUES ('Pagani')";
command.ExecuteNonQuery();
command = context.Database.Connection.CreateCommand();
command.CommandText = "SELECT ##Identity";
Console.WriteLine(command.ExecuteScalar());
command.Connection.Close();
The issue which I am getting is:
Console.WriteLine(command.ExecuteScalar());
always shows 0
EDIT
To create the table you can use this statement over the C# OleDb connection (I think that from MS Access query does not work)
CREATE TABLE [Cars] (
[Id] guid not null DEFAULT GenGUID(),
[Name] text null
);
ALTER TABLE [Cars] ADD CONSTRAINT [PK_Cars_6515ede4] PRIMARY KEY ([Id])
I know this is not exactly what you are asking for, but let me suggest an alternative solution which might solve your underlying problem.
Create the GUID in C# and pass it to your insert:
var newGuid = Guid.NewGuid();
var command = myConnection.CreateCommand();
command.Connection.Open();
command.CommandText = "INSERT INTO Cars(Id, Name) VALUES (?, 'Pagani')";
command.Parameters.AddWithValue("#Id", newGuid); // Note: OleDb ignores the parameter name.
command.ExecuteNonQuery();
Console.WriteLine(newGuid);
GUIDs are unique. It really doesn't matter whether it is generated by your application or by the Access database driver.
This option is in all respects superior to reading the GUID afterwards:
You only need one database access.
It's less code.
It's easier.
And you can still omit the GUID in your INSERT in cases where you don't need to know the GUID - no need to change existing code.
If SELECT ##IDENTITY does not work for "ReplicationID" AutoNumber fields then the most likely way to retrieve such a value for a new record is to use an Access DAO Recordset insert, like this:
// required COM reference:
// Microsoft Office 14.0 Access Database Engine Object Library
var dbe = new Microsoft.Office.Interop.Access.Dao.DBEngine();
Microsoft.Office.Interop.Access.Dao.Database db = dbe.OpenDatabase(
#"C:\Users\Public\Database1.accdb");
Microsoft.Office.Interop.Access.Dao.Recordset rst = db.OpenRecordset(
"SELECT [Id], [Name] FROM [Cars] WHERE FALSE",
Microsoft.Office.Interop.Access.Dao.RecordsetTypeEnum.dbOpenDynaset);
rst.AddNew();
// new records are immediately assigned an AutoNumber value ...
string newReplId = rst.Fields["Id"].Value; // ... so retrieve it
// the returned string is of the form
// {guid {1D741E80-6847-4CB2-9D96-35F460AEFB19}}
// so remove the leading and trailing decorators
newReplId = newReplId.Substring(7, newReplId.Length - 9);
// add other field values as needed
rst.Fields["Name"].Value = "Pagani";
// commit the new record
rst.Update();
db.Close();
Console.WriteLine("New record added with [Id] = {0}", newReplId);
which produces
New record added with [Id] = 1D741E80-6847-4CB2-9D96-35F460AEFB19
You can try like this using the OUTPUT :
INSERT INTO myTable(myGUID)
OUTPUT INSERTED.myGUID
VALUES(GenGUID())
You can try like this:
string str1 = "INSERT INTO Cars(Name) VALUES ('Pagani')";
string str2 = "Select ##Identity";
int ID;
using (OleDbConnection conn = new OleDbConnection(connect))
{
using (OleDbCommand cmd = new OleDbCommand(str1, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
cmd.CommandText = str2;
ID = (int)cmd.ExecuteScalar();
}
}

Convert ID to Int and save to datatable

I have been trying to add the Customer_ID from the Customer table to Customer_ID in Customer_Ship table. I keep running into the Customer_ID not converting to Int properly. It's possible that I am not actually getting the new row added to Customer_Ship table first. Your help is greatly appreciated and many thanks in advance.
if (customer_ID == "")
{
string SQL = "INSERT INTO Customer (Customer_Name) VALUES (#customer_Name); SELECT Customer_ID FROM Customer WHERE Customer_ID = SCOPE_IDENTITY();";
SqlCommand sqlCommand = new SqlCommand(SQL, sqlConnection);
sqlCommand.Parameters.Add("#customer_Name", SqlDbType.VarChar, 100).Value = customer_Name;
sqlConnection.Open();
int customer_Id = (int)sqlCommand.ExecuteScalar();
SQL = "INSERT INTO Customer_Ship (Customer_ID) VALUES (#customer_Id)";
sqlCommand = new SqlCommand(SQL, sqlConnection);
sqlCommand.Parameters.AddwithValue("#customer_Id", customer_Id);
sqlCommand.ExecuteNonQuery();
sqlConnection.Close();
}
Two mistakes I see:
you should be just returning SCOPE_IDENTITY - you can simplify your first INSERT statement to read:
INSERT INTO Customer (Customer_Name) VALUES (#customer_Name); SELECT SCOPE_IDENTITY();";
This will return the newly inserted Customer_ID identity value from the Customer table - no need to do this complicated SELECT that you had in your question
You need to call .ExecuteScalar() right from the beginning - don't call .ExecuteNonQuery() first and then ExecuteScalar() - that'll execute the statement twice - just use:
using(SqlCommand sqlCommand = new SqlCommand(SQL, sqlConnection))
{
sqlCommand.Parameters.Add("#customer_Name", SqlDbType.VarChar, 100).Value = customer_Name;
sqlConnection.Open();
int customer_Id = (int)sqlCommand.ExecuteScalar();
sqlConnection.Close();
}
That'll insert the values into Customer and return the newly created Customer_ID as the return value into customer_id (which already is an Int) from .ExecuteScalar(). You can then use this int value to insert into the Customer_Ship table - no conversion necessary - this already is an int
The possible reason for not converting the value is you are trying to convert an empty string(customer_ID : Refer Line :#1 of your code) and not "customer_Id " what you are fetching from the database .

Insert auto increment

I have a method Insert(). Everything is working as expected except for the auto increment. Here's the code:
public void Insert(string m1,int y1,int new_count)
{
string query = "INSERT INTO page_counter (id,month,year,page_count) VALUES('','"+m1+"',"+y1+","+new_count+")";
//create command and assign the query and connection from the constructor
MySqlCommand cmd = new MySqlCommand(query, connection);
//Execute command
cmd.ExecuteNonQuery();
//close connection
this.CloseConnection();
}
My Id column is an auto-increment. So my question is how can the value be inserted in the database an continue the auto increment in the table for id?
Simply don't specify value for id :
string query = "INSERT INTO page_counter (month,year,page_count) VALUES('"+m1+"',"+y1+","+new_count+")";
And look into better approach, parameterized query, instead of concatenating query string.
All you have to do is exclude the auto-incremented IDENTITY column from your insert.
Change your query to:
//NOTE: We leave the "id" column out of the insert, SQL Server will handle this automatically
string query = "INSERT INTO page_counter (month, year, page_count) VALUES (#Month, #Year, #PageCount)";
SQL will take care of the ID field.
You might notice I used Scalar variables in my query. You can (and should) assign these in the command so that you exclude the possibility of SQL injection:
EDIT DUE TO THE FACT THAT THIS IS COMING FROM MySql.Data.MySqlClient PRE 4.0
MySqlCommand cmd = new MySqlCommand(query, connection);
cmd.Parameters.Add(new MySqlParameter("#Month", m1));
cmd.Parameters.Add(new MySqlParameter("#Year", y1));
cmd.Parameters.Add(new MySqlParameter("#PageCount", new_count));
//Execute the INSERT
cmd.ExecuteNonQuery();
For a little background on SQL Injection I would recommend reading:
SQL Injection on W3Schools
Why use Parameterized SQL on SO
Using AUTO_INCREMENT
No value was specified for the AUTO_INCREMENT column, so MySQL assigned sequence numbers automatically.
string query = "INSERT INTO page_counter (month,year,page_count)
VALUES('"+m1+"',"+y1+","+new_count+")";
You can always use Parameterized query to avoid SQL Injection
string query = "INSERT INTO page_counter (month,year,page_count)
VALUES(#month,#year,#page_count)";
cmd.Parameters.AddWithValue("#month",Value1);
cmd.Parameters.AddWithValue("#year", Value2);
cmd.Parameters.AddWithValue("#page_count", Value3);
string query = "INSERT INTO page_counter (month,year,page_count) VALUES('"+m1+"',"+y1+","+new_count+")";
http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html
Do not specify ID from here:
If you are using SQL Server, have your ID field in your DB created like so:
ID int IDENTITY(1,1) PRIMARY KEY
If MySQL then:
ID int NOT NULL AUTO_INCREMENT
Look at the following link:
http://www.w3schools.com/sql/sql_autoincrement.asp
You can change identity of id in database
"INSERT INTO page_counter (month,year,page_count) VALUES('"+m1+"',"+y1+","+new_count+")"

Error Using SqlCeCommandBuilder while inserting row with column identity turned on

I am trying to use the SqlCeCommandBuilder, and I am having an issue with it. My table, I am using has three columns. The first is set to primary key, and identity is on and set to increment by one. When I am creating my SqlCeCommand, I cannot get it to execute. I thought if I leave that column out, it will automatically add the value, but it returns an error stating the number of columns in the command have to match the number of columns in the table. So if I add the "BillerID" column to the command builder, it says I need to add a value for it. Then when I add a value, it says that the column "BillerID" cannot be modified. What am I doing wrong?
using (SqlCeConnection con = new SqlCeConnection(Properties.Settings.Default.BillsConnectionStringDefault))
{
con.Open();
try
{
using (SqlCeCommand command = new SqlCeCommand(
"INSERT INTO Billers VALUES(#BillerID, #Name, #Type)", con))
{
command.Parameters.Add(new SqlCeParameter("BillerID", 999999));
command.Parameters.Add(new SqlCeParameter("Name", billerName));
command.Parameters.Add(new SqlCeParameter("Type", "0"));
command.ExecuteNonQuery();
}
}
catch(Exception ex)
{
MessageBox.Show(string.Format(ex.Message.ToString()));
}
}
You can specify the columns you're inserting into, try changing the query to this:
"INSERT INTO Billers (Name, Type) VALUES(#Name, #Type)"
and leaving out the ID parameter entirely.

use sql command more than once with different commands

I've been trying to use the same SqlConnection and SqlCommand objects to execute to different commands.
the first one checks for duplicate and the second one inserts the data if the data the user entered is not a duplicate.
Here's a sample of my code:
using (SqlConnection conn = new SqlConnection(ConnStr))
{
string Command = "SELECT CountryName FROM [Countries] WHERE CountryName = #Name";
using (SqlCommand comm = new SqlCommand(Command, conn))
{
comm.Parameters.Add("#Name", System.Data.SqlDbType.NVarChar, 20);
comm.Parameters["#Name"].Value = Name;
comm.Parameters.Add("#IsVisible", System.Data.SqlDbType.Bit);
comm.Parameters["#IsVisible"].Value = IsVisible;
conn.Open();
if (comm.ExecuteScalar() == null)
{
Command = "INSERT INTO [Countries] (CountryName, IsVisible) VALUES (#Name, #IsVisible);";
comm.ExecuteNonQuery();
}
}
I was trying to save a trip to the database by using one connection.
The Problem is:
The first command runs okay but the
second command which inserts into the
database won't work (it doesn't add
any records to the db) and when I
tried to display the rows affected it
gave me -1 !!
The Question is:
Is this is the ideal way to check for
a duplicate records to constraint a
unique country ? and why the second
command is not executing ?
You are changing the value of string Command, but you are never actually changing the command string in SqlCommand comm.
When you rewrite the Command variable with the insert statement, you are simply modifying the string named Command that you've defined earlier. You are not modifying the command text stored inside of the SqlCommand object.
Try:
comm.CommandText = "INSERT INTO [Countries] (CountryName, IsVisible) VALUES (#Name, #IsVisible);";
To answer your first question: no, this is not the way to ensure uniqueness for country name. In your database, you should define your Countries table so that CountryName is the primary key (alternatively, you can declare some other column as the PK and define a unique constraint on CountryName).
The attempt to insert a duplicate value, then, will throw an exception, which you can handle appropriately (discard the existing record, overwrite it, prompt the user for a different value etc.).
Checking for uniqueness via your method is considered bad because A) it places logic that belongs in the database itself into your application's code; and B) it introduces a potential race condition, wherein some other application or thread inserts a value in between your read of the database and your write to it.
I thing i suggest to seperate the insert with your select statement..
someting like:
private void Insert()
{
using (SqlConnection conn = new SqlConnection(ConnStr))
{
string Command = "INSERT INTO [Countries] (CountryName, IsVisible) VALUES (#Name, #IsVisible)";
using (SqlCommand comm = new SqlCommand(Command, conn))
{
comm.Parameters.Add("#Name", System.Data.SqlDbType.NVarChar, 20);
comm.Parameters["#Name"].Value = Name;
comm.Parameters.Add("#IsVisible", System.Data.SqlDbType.Bit); comm.Parameters["#IsVisible"].Value = IsVisible;
conn.Open();
comm.ExecuteNonQuery();
conn.Close();
}
}
private void SelectInsert()
{
using (SqlConnection conn = new SqlConnection(ConnStr))
{
string Command = "SELECT CountryName FROM [Countries] WHERE CountryName = #Name";
using (SqlCommand comm = new SqlCommand(Command, conn))
{
comm.Parameters.Add("#Name", System.Data.SqlDbType.NVarChar, 20);
comm.Parameters["#Name"].Value = Name;
conn.Open();
if (comm.ExecuteScalar() == null)
{
Insert(); //your save method
}
}
}
Regards

Categories

Resources