I've got a nested transaction, where after an exception data ends up being in the database without it being committed by the outer transaction.
There are three insert statements, every time followed by an select statement. The second select statement throws the exception. And for some reason I currently can not explain the third statement ends up in the database.
If however the exception does not occur (either by removing the select statement or by removing the 0 in the input array) nothing is committed in the database which is the expected behavior.
First of all the small working example:
using (var transactionScope = new TransactionScope())
{
var input = new[] {1, 0, 2 };
foreach (var i in input)
{
using (SqlConnection cnn = new SqlConnection(connetionString))
{
cnn.Open();
using (var tran = cnn.BeginTransaction())
{
var sql = "INSERT INTO [Test].[dbo].[Test] ([Timestamp] ,[Message]) VALUES ('" + DateTime.Now + "', 1 / " + i + ");";
using (SqlCommand cmd = new SqlCommand(sql, cnn, tran))
{
try
{
cmd.ExecuteNonQuery();
tran.Commit();
}
catch (Exception e)
{
tran.Rollback();
}
}
}
}
}
}
What am I missing?
I'm using SQL Server 2016 & C# 7
Related
I am trying to Delete a Token from my SQL Database after it has been used.
MySqlCommand cmdSel = new MySqlCommand("SELECT * FROM tokens WHERE token = " + int.Parse(passbox.Text), dbCon);
MySqlDataReader dbRead = cmdSel.ExecuteReader();
if (dbRead.Read())
{
int sqlkey = int.Parse(dbRead["token"].ToString());
if (keyint == sqlkey)
{
using (MySqlCommand delTok = new MySqlCommand("DELETE FROM tokens WHERE token = " + keyint, dbCon))
{
delTok.ExecuteNonQuery(); //MAIN PROBLEM HERE.
/*
MySql.Data.MySqlClient.MySqlException: 'There is already an open DataReader associated with this Connection which must be closed first.'
*/
//ERROR ^^^^^^
}
try
{
dbCon.Close();
loading loading = new loading();
loading.Show();
this.Hide();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
}
do i have to close the DataReader or is there some way else, and how do i close the reader? i want to Delete the token once the if keyint is sqlkey statement is true/done. the Error only shows once i try to execute the script for the if statement.
The "token" is an int(10)
Connection allows only one open reader.
You can get rid of problem with two datareaders, by executing only one "delete" query.
If token found, query will remove it, if not, query will do nothing.
using (var connection = new MySqlConnection("connection-string"))
using (var command = connection.CreateCommand())
{
command.CommandText = "DELETE FROM tokens WHERE token = #token";
var token = new MySqlParameter
{
ParameterName = "#token",
MySqlDbType = MySqlDbType.Int32,
Value = int.Parse(passbox.Text)
};
command.Parameters.Add(token);
connection.Open();
command.ExecuteNonQuery();
}
Don't try to "keep" connection, simply dispose previous and create new every time you need. ADO.NET in background effectively reuse already opened actual connections.
Use sql parameters for passing values to the query. Sql parameters defend from sql injections and improve sql queries performance, by reusing precompiled query plans.
You have to close the reader first in order to execute another command.
Also, you can't execute a command while Db reader is reading data. So you can use a function or you can execute the command after the reader is closed.
int sqlkey=0;
using(MySqlCommand cmdSel = new MySqlCommand("SELECT * FROM tokens WHERE token = " + int.Parse(passbox.Text), dbCon))
{
MySqlDataReader dbRead = cmdSel.ExecuteReader();
if (dbRead.Read())
{
sqlkey = int.Parse(dbRead["token"].ToString());
}
reader.close();
}
if (keyint == sqlkey)
{
using (MySqlCommand delTok = new MySqlCommand("DELETE FROM tokens WHERE token = " + keyint, dbCon))
{
delTok.ExecuteNonQuery();
try
{
dbCon.Close();
loading loading = new loading();
loading.Show();
this.Hide();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
}
Made a new private void and called it with the token after the if statement.
private void delQuery(int token)
{
//SETUP CONNECTION
MySqlConnection dbConn = new MySqlConnection("some connection");
//OPEN CONNECTION
dbConn.Open();
//DELETE TOKEN
MySqlCommand delcmd = new MySqlCommand("DELETE FROM tokens WHERE token = " + token, dbConn);
MySqlDataReader dbReader = delcmd.ExecuteReader();
dbReader.Read();
//CLOSE CONNECTION
dbConn.Close();
}
called it using:
if (dbRead.Read())
{
int sqlkey = int.Parse(dbRead["token"].ToString());
if (keyint == sqlkey)
{
dbCon.Close();
delQuery(keyint);
}
}
I get error trying to work with transactions. Without it everything runs fine, but with it I get a strange error: the transaction is completed and cannot be utilized anymore (my translation of the error)
Here is my code till the error:
using (var conn = new SqlConnection(gl.constr))
{
using (SqlCommand cm = new SqlCommand())
{
conn.Open();
using (SqlTransaction tr = conn.BeginTransaction())
{
try
{
cm.Connection = conn;
cm.Transaction = tr;
cm.CommandText = "INSERT INTO Bookings (Time, Price, BookingRef, BookingInternalRef)" +
" VALUES(" +
"getdate(), "+ sPrice + ", " +
db.AddAphens(reference) + ", " +
db.AddAphens(internalBookref) +
")";
cm.ExecuteNonQuery(); //this works
tr.Commit();
On the commit the error popups.
You don't need a explicit transaction declaration SqlTransaction tr = conn.BeginTransaction() for a single DML operation since every DML operation will be implicit transaction bound. Thus you can remove your transaction declaration all-together and it should work fine
can you help me with inserting rows into Oracle database in C#..
I have foreach and in it I just make sql query.. Then when I try to insert into database (with support of debbuging), first row insert in couple of miliseconds but the second insert/update is about 5 minutes.. All the magic is at line where is cmd.ExecuteNonQuery();
With the second row inserted/updated the debbuger return focus to the application and then after cca 5 minutes return back to debbuger.. But it's simple update so there is no so much time needed..
using (var connection = new OracleConnection(_connectionTNS))
{
connection.Open();
var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
int rowsInserted = 0;
foreach (var item in _tableNameAndColumnsList)
{
if (item.Replace)
{
using (OracleCommand cmd = new OracleCommand())
{
cmd.Connection = connection;
cmd.Transaction = transaction;
cmd.CommandText = "UPDATE TABLE TEST WHERE id_test = "+id_test+" ";
rowsInserted += cmd.ExecuteNonQuery();
}
}
}
transaction.Commit();
MessageBox.Show("Changed " + rowsInserted + " database rows...");
Do you have same problem someone?
Thanks Lukas
You are creating a new connection in every loop which is not required and you should avoid it for network consumption
using (OracleCommand cmd = new OracleCommand())
{
cmd.Connection = connection;
cmd.Transaction = transaction;
foreach (var item in _tableNameAndColumnsList)
{
if (item.Replace)
{
cmd.CommandText = "UPDATE TABLE TEST WHERE id_test = "+id_test+" "; /*Assuming this is only test command*/
rowsInserted += cmd.ExecuteNonQuery();
}
}
}
P.S. Not to forgot, Please use the parameterized query to avoid SQL Injection.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have a problem. I want to make a Books database with C# with serialNumber, Author, Name and Year of publishing columns. I did the code and I don't have any errors but when i start it the console is left black and it doesn't do anything. Can someone tell me why it´s not working?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Data;
namespace T4DB1
{
class Program
{
static void Main(string[] args)
{
SqlConnection conn = new SqlConnection("Data Source=localhost; Initial Catalog=master; Integrated Security = True");
{
string create = "if not exists (select * from sys.databases where name='Buecher') create database [Buecher] else begin drop database [Buecher] create database [Buecher] end ";
SqlCommand createDB = new SqlCommand(create, conn);
try
{
conn.Open();
createDB.ExecuteNonQuery();
Console.WriteLine("Database created");
}
catch (Exception ex)
{
Console.WriteLine("Error");
}
finally
{
if (conn.State == System.Data.ConnectionState.Open)
{
conn.Close();
}
}
conn = new SqlConnection("Data Source = localhost; Initial Catalog=Buecher; Integrated Security = True");
create = " create table Buecher1 ( ISBNNummer varchar(10) not null Primary Key, Autor varchar(40), Titel varchar(50), Erscheinungsjahr smallint)";
createDB = new SqlCommand(create, conn);
try
{
conn.Open();
createDB.ExecuteNonQuery();
Console.WriteLine("Table created");
string insertrow = "insert into Buecher1 (ISBNNummer, Autor, Titel, Erscheinungsjahr) values('2658A42', 'Douglas Adams', 'Galaxy', 2007)";
createDB = new SqlCommand(insertrow, conn);
createDB.ExecuteNonQuery();
string insertrow2 = "insert into Buecher1 (ISBNNummer, Autor, Titel, Erscheinungsjahr) values('58624FG85', 'Charles Dickens', 'White Fang',1992)";
createDB = new SqlCommand(insertrow2, conn);
createDB.ExecuteNonQuery();
string insertrow3 = "insert into Buecher1 (ISBNNummer, Autor, Titel, Erscheinungsjahr) values('65224AS4', 'Erik Corr', 'Somewhere', 2014)";
createDB = new SqlCommand(insertrow3, conn);
createDB.ExecuteNonQuery();
string select = "Select * From Buecher1";
SqlDataAdapter da = new SqlDataAdapter(select, conn);
DataTable dt = new DataTable(); da.Fill(dt);
foreach (DataRow row in dt.Rows)
{
Console.WriteLine("ISBNNummer: " + row["ISBNNummer"] + " " + "Autor: " + row["Autor"] + " " + "Titel: " + row["Titel"] + " " + "Erscheinungsjahr:" + row["Erscheinungsjahr"]);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (conn.State == System.Data.ConnectionState.Open)
{
conn.Close();
}
}
}
Console.ReadKey();
}
}
}
I'm assuming you are using SQL Server. Just consider this statement:
if not exists (select * from sys.databases where name='Buecher')
create database [Buecher]
It does not behave as you expect. The command is first compiled. During this phase, if Buecher exists, you'll get an error on create database [Buecher]' because you cannot create a database that already exists. You might protest, "But that is why I have the if!" Too bad. the compiler is not listening. You see the if isn't executed until the execution phase -- after the compile phase.
And, the same is true of the drop.
A typical way to handle this is to use dynamic SQL. That postpones the compilation until the execution. So, this snipper should work:
if not exists (select * from sys.databases where name='Buecher')
exec('create database [Buecher] ');
I have a huge list of INSERT INTO ... strings. Currently I run them with:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
foreach (var commandString in sqlCommandList)
{
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
}
}
I see that each ExecuteNonQuery() also executes commit.
Is there a way to insert all rows in a single transaction (commit in the end)?
The reason I want a single transaction is to make my "inserts" process faster. Will a single transaction also make it quicker?
Its recommended to use SQL transaction in case you are executing Multiple queries in one thread , you can have it like this :
SqlTransaction trans;
try
{
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
trans = connection.BeginTransaction();
foreach (var commandString in sqlCommandList)
{
SqlCommand command = new SqlCommand(commandString, connection,trans);
command.ExecuteNonQuery();
}
trans.Commit();
}
catch (Exception ex) //error occurred
{
trans.Rollback();
//Handel error
}
You might probably gain some performance by using just one single transaction and command, as follows:
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
using (SqlTransaction trans = connection.BeginTransaction())
{
using (SqlCommand command = new SqlCommand("", connection,trans))
{
command.CommandType = System.Data.CommandType.Text;
foreach (var commandString in sqlCommandList)
{
command.CommandText = commandString;
command.ExecuteNonQuery();
}
}
trans.Commit();
}
}
catch (Exception ex) //error occurred
{
//Handel error
}
}
A little late, but if you are inserting all of the values into the same table, code the SQL insert as "insert into tablex (f1, f2, f3,...) values (#F1,#F2,#F3...)". Create the command and add the parameters #F1..., and then set the Prepare flag on the command. Now as you loop through your list of values to insert, you can set them into the appropriate parameters and then do the ExecuteNonQuery. SQL will pre-parse the command string once, and then use the new parameters each time. This is a bit faster.
Finally, you can execute multiple SQL statements in a single command by appending ';' to each statement, if you must execute the entire string. You can bunch a number of these commands together and make one request to SQL server to execute them.
You can just concatenate the sql and let the server handle it:
using (SqlConnection connection = new SqlConnection(connectionString))
{
string lsSql = string.Empty;
foreach (var commandString in sqlCommandList)
{
lsSql = lsSql + commandString + " ; " + Environment.NewLine;
}
connection.Open();
SqlCommand command = new SqlCommand(lsSql, connection);
command.ExecuteNonQuery();
}
Here is what I use on my daily work, before it a use a foreach for any non-query that I need to run on database. You can see that I'm using the OracleCommand, but if you need you can change to SQL statement
public static void ExecuteDatabaseNonQuery(string command)
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
OracleTransaction transaction;
transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
cmd.Transaction = transaction;
try
{
cmd.CommandText = command;
var update = cmd.ExecuteNonQuery();
transaction.Commit();
Console.WriteLine("{0} rows updated", update);
}
catch (Exception e)
{
transaction.Rollback();
throw new Exception("Error: " + e);
}
}
Note: If theres any uncommited changes on database this method will wait indefinitely
You can use Parallel for each
using (SqlConnection connection = new SqlConnection(connectionString))
{
List<string> sqlCommandList = new List<string>();
connection.Open();
Parallel.ForEach(sqlCommandList, commandString =>
{
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
});
}