How to configure MySQL to rollback when using TransactionScope? - c#

I've spent a week trying to get transactions working for my MySQL database. Still no success. Running Windows 7 x64, MySQL Server 5.6.7-rc and MySQL .NET Connector 6.6.4.
MySQL claims that TransactionScope is supported (I've read the entire Internet by now), so I'm guessing I need some kind of special configuration to get it working. Here's what I've tried so far:
autocommit=0
In my.ini I've added autocommit=0 below the [mysqld] section.
In my.ini I've added init_connect='SET autocommit=0' below the [mysqld] section.
In Service Manager, I've added --autocommit=0 as a command line parameter.
I've verified that SELECT ##autocommit returns 0.
sql_mode=TRADITIONAL
In my.ini I've added sql-mode="TRADITIONAL" below the [mysqld] section.
In Service Manager, I've added --sql-mode=TRADITIONAL as a command line parameter.
Distributed Transaction Coordinator
I've tried enabling/disabling the DTC service.
The sample below throws an exception (System.Transactions.TransactionException exceptions must be enabled via Ctr+Alt+E) immediately when calling Open(). The error message is The operation is not valid for the state of the transaction.. Obviously, the server is not happy with the transaction stuff - which is proven by the fact that a rollback does NOT occur.
var factory = System.Data.Common.DbProviderFactories.GetFactory("MySql.Data.MySqlClient");
using (var transaction = new System.Transactions.TransactionScope())
{
var connection = factory.CreateConnection();
connection.ConnectionString = "Server=localhost;Port=3306;Database=test;User ID=user;Password=user";
connection.Open(); // <-- silent TransactionException here!
var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE TestTable (ID INT) ENGINE = InnoDB DEFAULT CHARSET=utf8;";
command.ExecuteNonQuery();
command.CommandText = "INSERT INTO TestTable (ID) VALUES (123);";
command.ExecuteNonQuery();
// ATTENTION! This should imply a rollback!
// transaction.Complete();
}
The sample code works for SQL Server so I know for sure that this problem has something to do with MySQL.
QUESTION: I've tried everything. What do I need to do to get this TransactionScope code working for MySQL?

MySQL will automatically commit the active transaction on executing a DDL statement (see http://dev.mysql.com/doc/refman/5.5/en/cannot-roll-back.html). So anything after the CREATE TABLE will not be part of the transaction.
The same thing would happen if you performed the SQL statements as follows:
START TRANSACTION;
CREATE TABLE TestTable (ID INT) ENGINE = InnoDB DEFAULT CHARSET=utf8;
INSERT INTO TestTable (ID) VALUES (123);
ROLLBACK;
Try changing your code to:
var factory = MySqlClientFactory.Instance;
string connectionString = "Server=localhost;Port=3306;Database=test;User ID=user;Password=user";
// DDL cannot be performed in transaction as will commit
using (var connection = factory.CreateConnection())
{
connection.ConnectionString = connectionString;
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE TestTable (ID INT) ENGINE = InnoDB DEFAULT CHARSET=utf8;";
command.ExecuteNonQuery(); // will always commit here
}
// DML can be performed in transaction
using (var transaction = new System.Transactions.TransactionScope())
using (var connection = factory.CreateConnection())
{
connection.ConnectionString = connectionString;
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "INSERT INTO TestTable (ID) VALUES (123);";
command.ExecuteNonQuery();
// should rollback here
//transaction.Complete();
}

Related

Npgsql AddWithValue doesn't work with transaction

I am attempting to execute a query with a transaction in Npgsql as it make the code significantly cleaner and more consistent with queries in other systems with pure SQL. However I get the error Npgsql.PostgresException: 42703: column "_hash" does not exist on the following code.
var cmd = new NpgsqlCommand(#"
do
$do$
begin
if ((select count(1) from components where hash = #_hash) = 0) then
insert into components (hash, name) values (#_hash, #_name);
end if;
end
$do$", db); // db is NpgsqlConnection connection
cmd.Parameters.AddWithValue("_hash", "00000000-0000-0000-0000-000000000000");
cmd.Parameters.AddWithValue("_name", "t_test");
cmd.ExecuteNonQuery(); // error on this line
The following does work for some reason which make me think that it is an issue with AddWithValue in Transactions
Hard coding the values;
var cmd = new NpgsqlCommand(#"
do
$do$
begin
if ((select count(1) from components where hash = '00000000-0000-0000-0000-000000000000') = 0) then
insert into components (hash, name) values ('00000000-0000-0000-0000-000000000000', 't_test');
end if;
end
$do$", db);
cmd.ExecuteNonQuery();
Getting rid of the transaction
var cmd = new NpgsqlCommand("insert into components (hash, name) values (#_hash, #_name);", db)
cmd.Parameters.AddWithValue("_hash", "00000000-0000-0000-0000-000000000000");
cmd.Parameters.AddWithValue("_name", "t_test");
cmd.ExecuteNonQuery();
What is causing this issue and how can it be fixed?
NOTE: I can run the query which is failing in a database manager like JetBrains DataGrip so the query is not malformed.
You can't pass parameters to an anonymous do block. It is not related to npgsql but to Postgres.
The doc says:
The code block is treated as though it were the body of a function
with no parameters, returning void. It is parsed and executed a single
time.

Prepared MySQL Statement fails insert with no error

I'm using MySQL to try and add a new user to my database. User got an Id, a First Name, a Last Name and a Date of Birth. But when I run the code below (And run conn.close() after I'm done) the database tells me (using HeidiSQL) that in the Table Overview there is now a new row in the table but when I open the Data Tab to look at the rows, there is nothing. It's empty. Running a COUNT(*) also returns 0.
using (MySqlTransaction transaction = conn.BeginTransaction())
{
using (MySqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO USERS(NAME_FIRST,NAME_LAST,DATE_OF_BIRTH) VALUES(#nameFirst,#nameLast,#dateOfBirth)";
cmd.Transaction = transaction;
cmd.Parameters.AddWithValue("#nameFirst", user.NameFirst);
cmd.Parameters.AddWithValue("#nameLast", user.NameLast);
cmd.Parameters.AddWithValue("#dateOfBirth", user.DateOfBirth);
cmd.Prepare();
cmd.ExecuteNonQuery();
lastInsertId = (uint)cmd.LastInsertedId;
}
}
I get no errors. Nothing shows up in any log and everyone sees the same as me.
What am I doing wrong?
It feels like it's the use of begintransaction which starts a transaction. This means autocommit=false for the entirety of the transaction.
After ExecuteNonQuery Do a transaction.Commit(); and see if they show up.
More Info Here

Create SQLite Database and table

Within C# application code, I would like to create and then interact with one or more SQLite databases.
How do I initialize a new SQLite database file and open it for reading and writing?
Following the database's creation, how do I execute a DDL statement to create a table?
The next link will bring you to a great tutorial, that helped me a lot!
How to SQLITE in C#: I nearly used everything in that article to create the SQLite database for my own C# Application.
Preconditions
Download the SQLite.dll
either by addding the SQLite DLL's manually
or by using NuGet
Add it as a reference to your project
Refer to the dll from your code using the following line on top of your class: using System.Data.SQLite;
Code sample
The code below creates a database file and inserts a record into it:
// this creates a zero-byte file
SQLiteConnection.CreateFile("MyDatabase.sqlite");
string connectionString = "Data Source=MyDatabase.sqlite;Version=3;";
SQLiteConnection m_dbConnection = new SQLiteConnection(connectionString);
m_dbConnection.Open();
// varchar will likely be handled internally as TEXT
// the (20) will be ignored
// see https://www.sqlite.org/datatype3.html#affinity_name_examples
string sql = "Create Table highscores (name varchar(20), score int)";
// you could also write sql = "CREATE TABLE IF NOT EXISTS highscores ..."
SQLiteCommand command = new SQLiteCommand(sql, m_dbConnection);
command.ExecuteNonQuery();
sql = "Insert into highscores (name, score) values ('Me', 9001)";
command = new SQLiteCommand(sql, m_dbConnection);
command.ExecuteNonQuery();
m_dbConnection.Close();
After you created a create script in C#, you might want to add rollback transactions. It will ensure that data will be committed at the end in one big piece as an atomic operation to the database and not in little pieces, where it could fail at 5th of 10th query for example.
Example on how to use transactions:
using (TransactionScope transaction = new TransactionScope())
{
//Insert create script here.
// Indicates that creating the SQLiteDatabase went succesfully,
// so the database can be committed.
transaction.Complete();
}
3rd party edit
To read records you can use ExecuteReader()
sql = "SELECT score, name, Length(name) as Name_Length
FROM highscores WHERE score > 799";
command = new SQLiteCommand(sql, m_dbConnection);
SQLiteDataReader reader = command.ExecuteReader();
while(reader.Read())
{
Console.WriteLine(reader[0].ToString() + " "
+ reader[1].ToString() + " "
+ reader[2].ToString());
}
dbConnection.Close();
See also this transactionscope example

Must a T-Sql stored procedure contain script to support a SqlClient transaction?

I have a stored procedure that deletes rows. Sample code:
CREATE PROCEDURE [dbo].[ap_um_delete_tems_grid]
(
#agency char(3)
, #subagency char(1)
, #grid_id int
, #role_id int
)
AS
DELETE FROM [grid_setup]
WHERE agency = #agency
AND subagency = #subagency
AND gs.grid_id = #grid_id
AND role_id = #role_id
If I execute this SProc directly from Sql Mgmt Studio it works fine. However, if I attempt to do it in C# code using a transaction, I get a very interesting SqlException:
Incorrect syntax near 'ap_um_delete_tems_grid'
The exception detail says it occurs on line number 1. The C# code is:
using (SqlConnection connection = new SqlConnection(conn.TemsConnectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
// Start the local transaction
transaction = connection.BeginTransaction("GridReplaceTransaction");
command.Connection = connection;
command.Transaction = transaction;
try
{
command.CommandText = "ap_um_delete_tems_grid";
SqlParameter parm = new SqlParameter();
// Adding the parameters
int cnt = command.ExecuteNonQuery();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
The code above worked before I put it into a transaction. Why am I getting the syntax error? Perhaps because there needs to be something in the SProc to support the transaction? Or am I messsing something else up?
NOTE: Please don't try to tell me I don't need a transaction for a single delete. The above is part of a multi-step process, namely adding new rows after the delete. Thanks.
You are missing:
command.CommandType = CommandType.StoredProcedure;
Otherwise, it uses the default of CommandType.Text and treats "ap_um_delete_tems_grid" as a statement rather than the name of a stored procedure.

C#, MySQL, ADO.NET, delimiter causing syntax error

I have C# code that cycles through .sql files and executes what's inside them to set up a database.
One .sql file is basically as follows:
DROP PROCEDURE IF EXISTS myProc;
DELIMITER $$
CREATE PROCEDURE myProc()
BEGIN
-- procedure stuff goes here
END $$
DELIMITER ;
CALL myProc();
When I input this into the MySQL Query Browser's script window, it runs perfectly... over and over again, just as one would want it to.
However, if I put the string into my IDbCommand and execute it...
connection.Open(); // An IDbConnection
IDbTransaction transaction = connection.BeginTransaction();
using (IDbCommand cmd = connection.CreateCommand())
{
cmd.Connection = connection;
cmd.Transaction = transaction;
cmd.CommandText = line;
cmd.CommandType = CommandType.Text;
try
{
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
transaction.Rollback();
return false;
}
}
transaction.Commit();
connection.Close();
... I get the dreaded exception 1064...
You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use
near 'DELIMITER $$ CREATE PROCEDURE myProc() BEGIN...
So, the question is... why does MySQL let me do this with no problems, but when I try to run it from C#, it fails? And of course the second question is how I'm supposed to fix it.
For those looking for a quick snippet...
var connectionString = #"server=ChangeMe;user=ChangeMe;pwd=ChangeMe;database=ChangeMe;";
var scriptText = File.ReadAllText(#"C:\script.sql");
using (var connection = new MySqlConnection(connectionString))
{
var script = new MySqlScript(connection, scriptText);
connection.Open();
script.Execute();
}
I think what you are looking for is this: "Bug #46429: use DELIMITER command in MySql.Data.MySqlClient.MySqlScript"
Some of the commands in your script are interpreted bt the mysqlk client excutable before the SQL is sent to the server.
See mysql commands
In effect all the delimiters
To interpret the script from C# you will have to write code that knows what the command is, you cannot just pass the commands line by line to the server.
e.g. you have 3 commands in that file and so want to call ExecuteNonQuery only 3 times
and from paracycle's coment you need to use the MySqlScript class

Categories

Resources