Access DB Transaction on insert or updating - c#

I am going to implement the database access layer of the Window application using C#. The database (.accdb) is located to the project files. When it comes to two notebooks (clients) connecting to one access database through switches, it throws DBConcurrency Exception Error. My target is to check the timestamp of the sql executed first and then run the sql .
Would you please provide me some guidelines to achieve this ?
The below is my code
protected void btnTransaction_Click(object sender, EventArgs e)
{
string custID = txtID.Text;
string CompName = txtCompany.Text;
string contact = txtContact.Text;
string city = txtCity.Text;
string connString = ConfigurationManager.ConnectionStrings["CustomersDatabase"].ConnectionString;
OleDbConnection connection = new OleDbConnection(connString);
connection.Open();
OleDbCommand command = new OleDbCommand();
command.Connection = connection;
OleDbTransaction transaction = connection.BeginTransaction();
command.Transaction = transaction;
try
{
command.CommandText = "INSERT INTO Customers(CustomerID, CompanyName, ContactName, City, Country) VALUES(#CustomerID, #CompanyName, #ContactName, #City, #Country)";
command.CommandType = CommandType.Text;
command.Parameters.AddWithValue("#CustomerID", custID);
command.Parameters.AddWithValue("#CompanyName", CompName);
command.Parameters.AddWithValue("#ContactName", contact);
command.Parameters.AddWithValue("#City", city);
command.ExecuteNonQuery();
command.CommandText = "UPDATE Customers SET ContactName = #ContactName2 WHERE CustomerID = #CustomerID2";
command.CommandType = CommandType.Text;
command.Parameters.AddWithValue("#CustomerID2", custIDUpdate);
command.Parameters.AddWithValue("#ContactName2", contactUpdate);
command.ExecuteNonQuery();
adapter.Fill(table);
GridView1.DataSource = table;
GridView1.DataBind();
transaction.Commit();
lblMessage.Text = "Transaction successfully completed";
}
catch (Exception ex)
{
transaction.Rollback();
lblMessage.Text = "Transaction is not completed";
}
finally
{
connection.Close();
}
}

I notice a couple of things:
Your INSERT CommandText has a #Country parameter specified, but you don't add a corresponding parameter to the .Parameters collection.
You try to re-use the command object for your UPDATE by changing the CommandText and adding more parameters, but you don't get rid of the previous parameters from your INSERT so presumably they're still in place, which could definitely confuse the UPDATE command.
You specify the UPDATE parameters in the wrong order. ACE.OLEDB ignores parameter names, so the parameters must be added to the .Parameters collection in the exact order that they appear in the CommandText.
I'm also confused as to:
why you don't commit the transaction before trying to bind the GridView, and
why you need a transaction at all, since the INSERT and the UPDATE appear to be unrelated.

Related

Inserting data into a MS Access database

try
{
OleDbConnection myConnection = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Users\\HP8200\\Desktop\\ELISA2014Data.mdb ;Persist Security Info=False;");
myConnection.Open();
// Create Oledb command to execute particular query
OleDbCommand myCommand = new OleDbCommand();
myCommand.Connection = myConnection;
// Query to create table with specified data columne
myCommand.CommandText = "CREATE TABLE UXZona([IDZona] int, [Morada] text)";
//myCommand.ExecuteNonQuery();
MessageBox.Show("Tabela criada");
}
catch
{
OleDbConnection myConnection = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Users\\HP8200\\Desktop\\ELISA2014Data.mdb ;Persist Security Info=False;");
myConnection.Open();
OleDbCommand cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO UXZona (IDZona, Morada) VALUES ('" +
transaction.UnloadPlaceAddress.AddressID + "','" +
transaction.UnloadPlaceAddress.AddressLine2 + "')";
cmd.ExecuteNonQuery();
MessageBox.Show("Dados inseridos");
}
I need to insert data into the database but it isn't working. I launch the program and there are no errors, I do everything but when I check the database the table is empty.
UPDATE
Now when i launch the program I have this error:
"System.InvalidOperationException: 'ExecuteNonQuery: Connection property has not been initialized." on cmd.ExecuteNonQuery();
There are a number of things wrong! I give below corrected code:
try
{
bool success = false;
using (var myConnection = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Users\\HP8200\\Desktop\\ELISA2014Data.mdb ;Persist Security Info=False;"))
{
// Create Oledb command to execute particular query
using (var myCommand = new OleDbCommand())
{
myCommand.Connection = myConnection;
// Query to create table with specified data columne
//myCommand.CommandText = "CREATE TABLE UXZona([IDZona] int, [Morada] text)";
//myCommand.ExecuteNonQuery();
//MessageBox.Show("Tabela criada");
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO UXZona (IDZona, Morada) VALUES (#id, #morada)";
var param = cmd.CreateParameter();
param.ParameterName = "#id";
param.OleDbType = OleDbType.Integer;
param.Value = transaction.UnloadPlaceAddress.AddressID;
cmd.Parameters.Add(param);
param = cmd.CreateParameter();
param.ParameterName = "#morada";
param.OleDbType = OleDbType.VarChar;
param.Value = transaction.UnloadPlaceAddress.AddressLine2;
cmd.Parameters.Add(param);
myConnection.Open();
if (cmd.ExecuteNonQuery() == 1)
{
success = true;
}
}
}
if (success)
{
MessageBox.Show("Dados inseridos");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
By way of explanation. I have commented out (but not deleted) all references to creating the table. Table creation and table insertion should be in two different routines. Normally you only create a table once, but insert is probably called many times.
I have placed the OleDbConnection and OleDbCommand within using loops. This is good practice, as they both implement IDisposable. Writing your code like this means that the Garbage Collector (GC) knows immediately that it can safely dispose of the objects after use.
I have changed the insert statement such that it takes parameters. This is highly recommended practice to safeguard against SQL Injection (if you do not know what this is please Google it). In fact Access is relatively immune from the worst forms of SQL Injection, because it rejects any command that contains multiple statements, but please get into good habits. With time you will progress to other databases which do not have this restriction.
I deliberately wait before opening the connection until just before it is needed. Connections consume resources, so it is good practice to use them as sparingly as possible. Also for this reason, I have moved your success message outside of the using loops. This means that the cleanup of resources is not waiting for the user to click OK in the message box.
Finally try catch is all well and good, but normally you want to know why the error occurred. Hence you add (Exception ex) to catch so that you can find the reason.
PS What I forgot to mention. In your original INSERT, you were surrounding both VALUES with single quotes. Only use single quotes for strings/text. Integers and other numbers require no quotes. If you quote them, the database will treat it as a string and you will get a data type error.

Execute DELETE only when the SELECT returns

I have a routine where I update the local database with other database data.
I only execute a DELETE and then an INSERT INTO tblX (SELECT * FROM tblY (tblY is a linked table)), as below.
The problem is that, in some cases the SELECT takes a long time after the DELETE and I´d like to diminish the possibility of the user to make a request to this table while it´s processing.
I´d like to know if there is some mechanism to execute the DELETE only after the return of the SELECT.
conn = new OleDbConnection(Conexao.getConexaoPainelGerencialLocal());
conn.Open();
OleDbCommand cmd = new OleDbCommand(" DELETE * FROM tblClienteContato; ", conn);
cmd.ExecuteNonQuery();
cmd = new OleDbCommand(" INSERT INTO tblClienteContato " +
" SELECT * FROM tblClienteContatoVinculada;", conn);
cmd.ExecuteNonQuery();
It sounds like what you need to do is wrap both of those commands in a transaction.
The cool thing about a transaction is that it either ALL WORKS or ALL FAILS, meaning that if something happens to stop the select statement, the database will not finalise the delete statement.
This looks like a really good example to work with:
https://msdn.microsoft.com/en-us/library/93ehy0z8(v=vs.110).aspx
Note that they have one command object, and replace the CommandText, rather than create a new object each time. This is probably important.
Try something like this:
conn = new OleDbConnection(Conexao.getConexaoPainelGerencialLocal());
OleDbCommand cmd = new OleDbCommand();
OleDbTransaction transaction = null;
try {
conn.Open();
transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
cmd.Connection = conn;
cmd.Transaction = transaction;
cmd.CommandText = " DELETE * FROM tblClienteContato; ";
cmd.ExecuteNonQuery();
cmd.CommandText = " INSERT INTO tblClienteContato " +
" SELECT * FROM tblClienteContatoVinculada;";
cmd.ExecuteNonQuery();
// The data isn't _finally_ completed until this happens
transaction.Commit();
}
catch (Exception ex)
{
// Something has gone wrong.
// do whatever error messaging you do
Console.WriteLine(ex.Message);
try
{
// Attempt to roll back the transaction.
// this means your records won't be deleted
transaction.Rollback();
}
catch
{
// Do nothing here; transaction is not active.
}
}
You should look into BeginTransaction, Commit and rollback, here's an example:
_con.Open();
_con_trans = _con.BeginTransaction();
using(SqlCommand cmd = _con.CreateCommand())
{
cmd.CommandText = "delete from XXXXX";
cmd.CommandType = CommandType.Text;
cmd.Transaction = _con_trans;
cmd.ExecuteNonquery();
}
using(SqlCommand cmd = _con.CreateCommand())
{
cmd.CommandText = "insert into XXXX";
cmd.CommandType = CommandType.Text;
cmd.Transaction = _con_trans;
cmd.ExecuteNonquery();
}
_con_trans.Commit();
_con_trans = null;
_con.Close();
This way, everything is wrapped under a single transaction, so when the delete begins, the table will be locked for reading and writing.
Without knowing the schema of the table, it is hard to identify why the delete process is taking an extended amount of time.
An alternative to wrapping the commands within a transaction would be to simply delete the table itself rather than the data within it by using the DROP TABLE command. And then you can recreate the table utilizing the SELECT...INTO...FROM statement to recreate. A potential advantage to this is that the schemas will match identically, and any inherent conversions (eg decimal to int) will not need to be done.
using (conn = new OleDbConnection(Conexao.getConexaoPainelGerencialLocal())) {
conn.Open();
using (OleDbCommand cmd = new OleDbCommand()) {
cmd.CommandText = "DROP TABLE tblClienteContato; ";
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * INTO tblClienteContato FROM tblClienteContatoVinculada;";
cmd.ExecuteNonQuery();
}
}
The following does not apply here (MS Access), but may to other SQL variants
Another option is to utilize the TRUNCATE command, which will delete everything in the table in one fell swoop. There is no logging of the individual rows and the indexes (if present) don't need to be recalculated on each and every line being deleted. The catch to this method is that this will not work within the transaction. If there is an Identity column the value will be reset as well. There are other potential cons to this but without knowing the design of the table I have no way of identifying them.
using (conn = new OleDbConnection(Conexao.getConexaoPainelGerencialLocal())) {
conn.Open();
using (OleDbCommand cmd = new OleDbCommand()) {
cmd.CommandText = "TRUNCATE TABLE tblClienteContato; ";
cmd.ExecuteNonQuery();
cmd.CommandText = " INSERT INTO tblClienteContato " +
" SELECT * FROM tblClienteContatoVinculada;";
cmd.ExecuteNonQuery();
}
}
As Greg commented, I created temporary tables to receive data from the external database and then I tranfer the data to the definitive tables, so that the probability of the users being impacted is very low.

Return on my function doesn't work if using sql

I am populating a text box with the returned value of a function, but it doesn't work if I run the sql code inside the function. I can remove the sql related code and it works. so i'm stumped.
And by "doesn't work" i mean that the text box never gets populated with anything. it remains blank.
thanks
public string CreateResident()
{
string result = "hmm";
SqlConnection sqlConnection = new SqlConnection("Server=DELLXPS\\SQLEXPRESS; Initial Catalog=Warren_SEINDATASYSTEMS; Integrated Security=true;");
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "INSERT INTO [dbo].[NewUsers]([ResidentAccountNumber],[ResidentName],[ResidentAddress],[NumberOfVisitors],[TempPass],[Role])VALUES(#ResidentAccountNumber,#ResidentName,#ResidentAddress,#NumberOfVisitors,(select cast((Abs(Checksum(NewId()))%10) as varchar(1)) + char(ascii('a')+(Abs(Checksum(NewId()))%25)) + char(ascii('A')+(Abs(Checksum(NewId()))%25)) + left(newid(),5)),'resident')";
cmd.CommandType = CommandType.Text;
cmd.Connection = sqlConnection;
SqlParameter ResidentAccountNumber = new SqlParameter();
ResidentAccountNumber.ParameterName = "#ResidentAccountNumber";
ResidentAccountNumber.Value = txtboxResidenetAccountNumber.Text.Trim();
cmd.Parameters.Add(ResidentAccountNumber);
SqlParameter ResidentName = new SqlParameter();
ResidentName.ParameterName = "#ResidentName";
ResidentName.Value = txtboxResidentName.Text.Trim();
cmd.Parameters.Add(ResidentName);
SqlParameter ResidentAddress = new SqlParameter();
ResidentAddress.ParameterName = "#ResidentAddress";
ResidentAddress.Value = txtboxResidentAddress.Text.Trim();
cmd.Parameters.Add(ResidentAddress);
SqlParameter NumberOfVisitors = new SqlParameter();
NumberOfVisitors.ParameterName = "#NumberofVisitors";
NumberOfVisitors.Value = txtboxNumberOfVisitors.Text.Trim();
cmd.Parameters.Add(NumberOfVisitors);
try
{
sqlConnection.Open();
result = (string)cmd.ExecuteScalar();
sqlConnection.Close();
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
protected void btnCreateResident_Click(object sender, EventArgs e)
{
txtboxTempPassword.Text = CreateResident();
}
Your SQL is wrong and you have a lot of problems but I want to show you a way to make your code more readable. Format it like this:
cmd.CommandText = #"INSERT INTO [dbo].[NewUsers] ([ResidentAccountNumber],[ResidentName],[ResidentAddress], NumberOfVisitors],[TempPass], Role])
VALUES(
#ResidentAccountNumber,
#ResidentName,
#ResidentAddress,
#NumberOfVisitors,
(select cast((Abs(Checksum(NewId()))%10) as varchar(1)) + char(ascii('a')+(Abs(Checksum(NewId()))%25)) + char(ascii('A')+(Abs(Checksum(NewId()))%25)) + left(newid(),5)),
'resident')";
cmd.CommandType = CommandType.Text;
cmd.Connection = sqlConnection;
We know that a select in a VALUES constructor is not legal so that is one problem.
Also having a SELECT without a from seems strange -- did you copy your code correctly?
You are using ExecuteScalar -- do you know what that does? It shouldn't include a query that includes INSERT query.
I'm guessing you probably want a stored procedure.
I would suggest do not write query in C# code, you must use Stored Procedure for the same purpose.
If you want your query to return some id, primary key or some value then you must write query for that after your insert query.
you can use the following keywords in your select query,if you want to return id from table.
SCOPE_IDENTITY returns the last IDENTITY value inserted into an IDENTITY column in the same scope.
IDENT_CURRENT returns the last identity value generated for a specific table in any session and any scope.
##IDENTITY returns the last identity value generated for any table in the current session, across all scopes.
If you want to return only one record then use ExecuteScalar else you can use ExecuteReader.
If your only purpose is to insert data into the table then you should use ExecuteNonQuery.
With the help of comments I went with ExecuteReader instead of the ExecuteScaler. And changed the statement to return a value
INSERT INTO [table] ([fields]) OUTPUT Inserted.MyColumn VALUES(values)
C# Code:
reader = cmd.ExecuteReader();
try
{
while (reader.Read())
{
result = reader[0].ToString();
}
}
catch (Exception ex)
{
result = ex.Message;
}
return result;

C# No value given for 1 or more required parameters, but I can't see why

I have posted the code I have below
I am trying to get the data from an Access 2002-2003 database
If I take out everything after the WHERE clause and just use "SELECT * FROM [{0}] then it takes all the data from the table with no problems. I have double checked the field names, they are definitely correct. I have more than 1 table with the same field names, so I thought maybe I would need to include the table name before the field name, but with or without the table I still get the same exception. I have tried moving the position of the square brackets, again with no success...
Even if I include only one of the WHERE clauses, the code no longer works, and I can't for the life of me work out why.. I have spent hours looking at numerous posts here and on other sites related to this error, but none of the suggestions have helped me..
The Destination field is a 'memo' field in Access.
The Next Collection fields are date fields, GVars.currentDate is set earlier in the code to be today's date (with the time portion set to 00:00:00).
GVars.thisFY is also set programatically as a string prior to this.
Any tips would be appreciated.
string sql;
OleDbDataAdapter adapter;
sql = string.Format(
"SELECT * FROM [{0}] WHERE {0}.[Destination] = #Destination AND {0}.[Next Collection] BETWEEN #NextCollectionA AND #NextCollectionB"
, GVars.thisFY);
// Create the command object
OleDbCommand cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
// Add values to the fields
cmd.Parameters.AddWithValue("#Destination", "Henwood");
cmd.Parameters.AddWithValue("#NextCollectionA", GVars.currentDate);
cmd.Parameters.AddWithValue("#NextCollectionB", GVars.currentDate.AddDays(1));
adapter = new OleDbDataAdapter(cmd.CommandText, conn);
System.Diagnostics.Debug.Print(cmd.CommandText);
try
{
adapter.Fill(ds);
GVars.bLblLastUpdate = DateTime.Now.ToString("HH:mm:ss");
}
catch (Exception ex)
{
}
EDIT:
Thanks Vladislav for the answer, corrected code posted below:
string sql;
OleDbDataAdapter adapter;
sql = string.Format(
"SELECT * FROM [{0}] WHERE [{0}].[Destination] = #Destination AND [{0}].[Next Collection] BETWEEN #NextCollectionA AND #NextCollectionB"
, GVars.thisFY);
// Create the command object
OleDbCommand cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd.Connection = conn;
// Add values to the fields
cmd.Parameters.Add("#Destination", OleDbType.Char).Value = "Henwood";
cmd.Parameters.Add("#NextCollectionA", OleDbType.DBDate).Value = GVars.currentDate;
cmd.Parameters.Add("#NextCollectionB", OleDbType.DBDate).Value = GVars.currentDate.AddDays(1);
adapter = new OleDbDataAdapter(cmd);
try
{
adapter.Fill(ds);
GVars.bLblLastUpdate = DateTime.Now.ToString("HH:mm:ss");
}
Try to specify types for the parameters you add.
Another thing I notice is that to your adapter you are passing only the CommandText.
You should pass the whole command object.

Question in error in asp.net c#

i have a question if you please help me i have an error
Must declare the scalar variable
"#Deitails".
and i can not find out whats the problem since i am not aware what Scalar is about
var sqlCon = new
SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
// GET CONFERENCE ROLE ID
SqlCommand cmd = new SqlCommand();
cmd.Connection = sqlCon;
cmd.CommandText = "select Conference_Role_ID from AuthorPaper
where Paper_ID = #PaperId";
cmd.Parameters.AddWithValue("#PaperId",
paperId);
cmd.Connection.Open();
string ConferenceRoleId = cmd.ExecuteScalar().ToString();
cmd.Connection.Close();
cmd.Dispose();
string query2 = #"insert into
ReviewPaper(Overall_Rating,Paper_id,Conference_role_id,Deitails)
values(0,#paperId,#ConferenceRoleId,#Deitails);select
SCOPE_IDENTITY() as RPID";
cmd = new SqlCommand(query2, sqlCon);
cmd.Parameters.AddWithValue("#paperId",
paperId);
cmd.Parameters.AddWithValue("#ConferenceRoleId",
ConferenceRoleId);
string ReviewPaperId;
try
{
cmd.Connection.Open();
ReviewPaperId = cmd.ExecuteScalar().ToString();
cmd.Connection.Close();
}
catch (Exception ee) { throw ee; }
finally { cmd.Dispose(); }
thanks
You have a SQL query with a parameter named Details, but you forgot to add the parameter.
You have a line of code which says
string query2 = #"insert into ReviewPaper(Overall_Rating, Paper_id,
Conference_role_id, Deitails) values (0,#paperId,#ConferenceRoleId,#Deitails);
select SCOPE_IDENTITY() as RPID";
You provide the parameters #paperId, #ConferenceRoleId and #Deitails for the values for the insert statement. Later you specify the value for the first two parameters, but not #Deitails:
cmd.Parameters.AddWithValue("#paperId", paperId);
cmd.Parameters.AddWithValue("#ConferenceRoleId", ConferenceRoleId);
You need to add a similar line to add the value for #Deitails so that SQL server knows what to do with it. The error you are getting is coming from SQL server because by not adding a value for #Deitails in your C# code, it is not being declared for you in the SQL code which is sent to the server.
To answer your other question, 'Scalar' in this case means that the variable #Deitails represents a single value.

Categories

Resources