I have the following code:
using (TransactionScope tran = new TransactionScope())
{
try
{
OracleConnection _transactionDB = new OracleConnection("ConnectionString");
_transactionDB.Open();
OracleCommand _command = new OracleCommand();
_command.Connection = _transactionDB;
_command.CommandType = CommandType.Text;
_command.CommandText = "INSERT INTO table (id, text) VALUES (3, 'test')";
int rowsAffected = _command.ExecuteNonQuery();
OracleCommand _command2 = new OracleCommand();
_command2.Connection = _transactionDB;
_command2.CommandType = CommandType.Text;
_command2.CommandText = "INSERT INTO log (id, text) VALUES (3, 'Success')";
int rowsAffected2 = _command2.ExecuteNonQuery();
//...some other actions(DB changes)
}
}
Is there a solution to commit the second insert immediately, doesn't matter if the transactionscope fails or not? This insert should always be visible in the database, to easier see what was going on in this transaction.
In oracle there is a 'AUTONOMOUS_TRANSACTION Pragma', which is like the function I need, in C#.
Thanks,
Michael
Maybe this will solve you problem:
Write a Oracle PL/SQL Procedure to write the log. This procedure must have the pragma "AUTONOMOUS_TRANSACTION". Then call this procedure instead of inserting directly.
Related
I tried to do begin transaction on SQL Server, but it returns an error that I can't figure out what the real problem is. So here is some of my code I tried.
This is the error:
Code:
SqlConnection connection = new SqlConnection("Data Source=LOCALHOST\\SQLEXPRESS;Initial Catalog=tempdb;Integrated Security=SSPI;User ID = xxxx; Password=xxx;");
DateTime dt = dateTimePicker1.Value.Date;
dt = dt.AddDays(60);
string selectQuery = "BEGIN TRANSACTION UPDATE tester SET
test_ad=#dateTimePicker1, test_ud=#dt, test_pd=#dt WHERE
test_name=#textBox1;INSERT INTO records(testr_status, testr_name, testr_ad,
testr_ud, testr_pd, apte_name)VALUES(#testr_status, testr_name = #comboBox1,
testr_ad = #dateTimePicker1, testr_ud = #dt, testr_pd = #dt COMMIT";
connection.Open();
SqlCommand command = new SqlCommand(selectQuery, connection);
command.Parameters.AddWithValue("#dateTimePicker1",this.dateTimePicker1.Value.Date);
command.Parameters.AddWithValue("#textBox1", this.textBox1.Text);
command.Parameters.AddWithValue("#comboBox1",this.comboBox1.SelectedItem);
command.Parameters.AddWithValue("#testr_status",SqlDbType.VarChar);
command.Parameters.AddWithValue("#dt", dt);
int iResult = command.ExecuteNonQuery();
if (iResult > 0)
MessageBox.Show("Successfully saved ", "Error",MessageBoxButtons.OK, MessageBoxIcon.Information);
else
MessageBox.Show("Record not saved ", "Error",MessageBoxButtons.OK, MessageBoxIcon.Error);
command.ExecuteNonQuery();
connection.Dispose();
command.Dispose();
Try cleaning up a bit your query or paste it on SSMS and declare your parameters and you will figure out what is wrong.
In your case your INSERT statement has some errors.
This is not valid syntax VALUES (test_name = #combobox1) instead you only pass the parameter VALUES (#combobox1)
There are more columns in the INSERT statement than values specified in the VALUES clause, you are not providing a value for apte_name. In the c# code you will need to add that parameter too.
You are missing the closing parenthesis for the VALUES clause
You should end up with something like this (not tested)
string selectQuery =
#"BEGIN TRANSACTION
UPDATE tester SET
test_ad = #dateTimePicker1,
test_ud = #dt,
test_pd = #dt
WHERE test_name = #textBox1;
INSERT INTO records
(
testr_status,
testr_name,
testr_ad,
testr_ud,
testr_pd,
apte_name
)
VALUES
(
#testr_status,
#comboBox1,
#dateTimePicker1,
#dt,
#dt,
#apte_name
);
COMMIT";
The actual problem is, that is one big, invalid SQL statement. Use the semi-colon to separate statements, like so:
"BEGIN TRANSACTION;
INSERT ...;
UPDATE ...;
ETC ...;
COMMIT;"
That said, don't embed transaction statements in a query string. Do what Oliver suggests in another answer.
You can use SqlTransaction
using (SqlConnection conn = new SqlConnection("Connection String"))
{
conn.Open();
SqlTransaction trans;
trans = conn.BeginTransaction();
string selectQuery = "your sql query";
SqlCommand command = new SqlCommand(selectQuery, connection);
int iResult = command.ExecuteNonQuery();
if (iResult > 0)
{
trans.Commit();
}else{
trans.Rollback();
}
conn.Close();
}
Use a formatted string for your select query by using # and the syntax in the value block in not accurate.
string selectQuery = #"
BEGIN TRANSACTION
UPDATE tester SET test_ad = #dateTimePicker1, test_ud = #dt, test_pd = #dt WHERE test_name = #textBox1;
INSERT INTO records(testr_status, testr_name, testr_ad, testr_ud, testr_pd, apte_name) VALUES(#testr_status, #comboBox1, #dateTimePicker1, #dt, #dt);
COMMIT";
I'm using the ExecuteNonQuery function and stored procedure to insert a new record in an MSSQL database.
During testing the insert of the new record is successful. But my second call to ExecuteScalar and get the newly inserted record's ID fails. The reason for the failure according to the debugger is:
ExecuteScalar requires an open and available Connection. The
connection's current state is closed.
Looking at this error it explains that the connection has been closed after the initial call to ExecuteNonQuery. Meaning that my code to get the ID won't have a valid connection to query with.
Question:
How can you retrieve ##Identity following an ExecuteNonQuery?
This is the piece of code that performs the insert in the DAL:
Database db = GetConnectionString();
string sqlCommand = "CREATE_RECORD";
string idQuery= "Select ##Identity";
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
db.AddInParameter(dbCommand, "#Application", DbType.String, escalation.Application);
db.AddInParameter(dbCommand, "#UpdatedTime", DbType.DateTime, escalation.UpdatedTime);
db.ExecuteNonQuery(dbCommand);
dbCommand.CommandText = idQuery;
recID = (int)dbCommand.ExecuteScalar();
return recID ;
}
DISCLAIMER: This is a bad idea - the correct solution is server-side (server in this case is SQL Server).
You may be able to do this if you use SCOPE_IDENTITY() (which you should anyway - ##IDENTITY is not guaranteed to be your insert's identity) and execute your command as CommandType.Text instead of CommandType.StoredProcedure
WARNING: Serious security implications here, most notably SQL Injection Attack possibility:
Database db = GetConnectionString();
string sqlCommand = $"CREATE_RECORD '{escalation.Application}', '{escalation.UpdatedTime}'";
string idQuery= "Select SCOPE_IDENTITY()"
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandType = commandType.Text;
db.ExecuteNonQuery(dbCommand);
dbCommand.CommandText = idQuery;
recID = (int)dbCommand.ExecuteScalar();
return recID;
}
Of course, if you go this route, you might as well combine both commands into a single query:
Database db = GetConnectionString();
string sqlCommand = $"CREATE_RECORD '{escalation.Application}', '{escalation.UpdatedTime}'; Select SCOPE_IDENTITY()";
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandType = commandType.Text;
//TODO: Open connection using your db object
recID = (int)dbCommand.ExecuteScalar();
//TODO: Close connection using your db object
return recID;
}
Again, I stress that the correct solution is to fix this in SQL, not in C#. Use at your own risk!
You should create and open connection for each query and dispose it after query. Don't worry, there are connection pool in ADO and connection will not be physically established and closed each time. It's only a hint for ADO.NET.
int recID = 0;
string connStr = ProcThatGivesYouConnectionString();
using (SqlConnection con = new SqlConnection(connStr))
{
con.Open();
SqlCommand command = new SqlCommand("CREATE_RECORD", con);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Application", escalation.Application);
command.Parameters.AddWithValue("#UpdatedTime", escalation.UpdatedTime);
command.ExecuteNonQuery();
}
using (SqlConnection con2 = new SqlConnection(connStr))
{
con2.Open();
SqlCommand command = new SqlCommand("Select ##Identity", con2);
recID = (int)command.ExecuteScalar();
}
Also you can execute both queries in one command if you want:
int recID = 0;
string connStr = ProcThatGivesYouConnectionString();
using (SqlConnection con = new SqlConnection(connStr))
{
con.Open();
SqlCommand command = new SqlCommand("
EXEC CREATE_RECORD #Application = #Application, #UpdatedTime = #UpdatedTime
SELECT ##Identity", con);
command.Parameters.AddWithValue("#Application", escalation.Application);
command.Parameters.AddWithValue("#UpdatedTime", escalation.UpdatedTime);
recID = (int)command.ExecuteScalar();
}
object r = command.ExecuteScalar();
Convert.ToInt32(r.ToString());
To prevent the ExecuteScalar gets Specified cast is not valid error , use above
I have an asp app that should be able to let a user update an entry in a sql server database. When I run my stored procedure though in my method, nothing happens to the database.
public void updateFeature(Feature updatedFeature)
{
using (SqlConnection sqlConnection = new SqlConnection(GetConnectionString(updatedFeature.Environment)))
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.CommandText = "usp_UpdateFeature";
sqlCommand.Parameters.AddWithValue("#FeatureName", updatedFeature.FeatureName);
sqlCommand.Parameters.AddWithValue("#FeatureID", updatedFeature.FeatureID);
sqlCommand.Parameters.AddWithValue("#FeatureDescription", updatedFeature.Description);
sqlCommand.Parameters.AddWithValue("#FieldName", updatedFeature.FieldName);
sqlCommand.Parameters.AddWithValue("#OnOffSwitch", updatedFeature.IsOn);
sqlCommand.Parameters.AddWithValue("#ChannelID", updatedFeature.ChannelID);
sqlCommand.Parameters.AddWithValue("#ProductID", updatedFeature.ProductID);
sqlConnection.Open();
int numberOfRowsChanged = sqlCommand.ExecuteNonQuery();
}
}
and my stored procedure looks like this
ALTER PROCEDURE [dbo].[usp_UpdateFeature]
(
#FeatureID int,
#FeatureName varchar(30),
#FeatureDescription varchar(200) = null,
#FieldName varchar(40),
#OnOffSwitch bit,
#ChannelID varchar(3),
#ProductID varchar(2)
)
AS
SET NOCOUNT ON
UPDATE FM_Feature
SET FeatureName = #FeatureName,
FeatureDescription = #FeatureDescription,
FieldName = #FieldName,
OnOffSwitch = #OnOffSwitch,
ProductID = #ProductID,
ChannelID = #ChannelID
WHERE FeatureID = #FeatureID
The numberOfRowsChanged parameter returns -1 but I do not get any exceptions or errors. Is there something that I am missing or not understanding?
I think that you need to open your connection before you create your command, i hope that will be helpful for you .
using (SqlConnection sqlConnection = new SqlConnection(GetConnectionString(updatedFeature.Environment))
sqlConnection.Open();
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.CommandText = "usp_UpdateFeature";
sqlCommand.Parameters.AddWithValue("#FeatureName", updatedFeature.FeatureName);
sqlCommand.Parameters.AddWithValue("#FeatureID", updatedFeature.FeatureID);
sqlCommand.Parameters.AddWithValue("#FeatureDescription", updatedFeature.Description);
sqlCommand.Parameters.AddWithValue("#FieldName", updatedFeature.FieldName);
sqlCommand.Parameters.AddWithValue("#OnOffSwitch", updatedFeature.IsOn);
sqlCommand.Parameters.AddWithValue("#ChannelID", updatedFeature.ChannelID);
sqlCommand.Parameters.AddWithValue("#ProductID", updatedFeature.ProductID);
int numberOfRowsChanged = sqlCommand.ExecuteNonQuery();
}
enjoy it .
I am creating a Web API that accepts two input parameter called ACC. Created a stored procedure to insert or update the Account table in the SQL server. Account table has just two fields AccountID nvarchar(50) (primaryKey) and Cnt int
CREATE PROCEDURE [dbo].[usp_InserUpadte]
#Account_TT AS Account_TT READONLY
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
MERGE dbo.[Account] prj
USING #Account_TT tt
ON prj.AccountID = tt.AccountID
WHEN MATCHED THEN UPDATE SET prj.Cnt = prj.Cnt+1
WHEN NOT MATCHED THEN INSERT (AccountID,Cnt)
VALUES (tt.AccountID, 1);
COMMIT TRANSACTION;
Now I tried to connect to the SQL server not sure how to how would I call the stored procedure into the ASP.NET Web API application and pass the Account ID in it to create or updadte the table
namespace WebService.Controllers
{
public class CreationController : ApiController
{
[HttpGet]
public HttpResponseMessage Get(string ACC)
{
string strcon = System.Configuration.ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
SqlConnection DbConnection = new SqlConnection(strcon);
I know we can call the query directly like
var strQuery = "SELECT * from ACCOUNT where ACC = :ACC"
But dont know how to call the above stored procedure and pass the Account Value. Any help is greatly appreciated.
Here is the complete working example.
Please have a look on it.
string strcon = ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
SqlConnection DbConnection = new SqlConnection(strcon);
DbConnection.Open();
SqlCommand command = new SqlCommand("[dbo].[usp_InserUpadte]", DbConnection);
command.CommandType = CommandType.StoredProcedure;
//create type table
DataTable table = new DataTable();
table.Columns.Add("AccountID", typeof(string));
table.Rows.Add(ACC);
SqlParameter parameter = command.Parameters.AddWithValue("#Account_TT", table);
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "Account_TT";
command.ExecuteNonQuery();
DbConnection.Close();
To call a stored procedure you need to use a SqlCommand something like this:
string strcon = System.Configuration.ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
using (var connection = new SqlConnection(strcon)) {
using (var command = new SqlCommand("usp_InserUpadte", connection)) {
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#Account_TT ", SqlDbType.NVarChar).Value = ACC;
command.Open();
var reader = command.ExecuteReader();
// Handle return data here
}
}
Below is the code I'm using in an SSIS script task. I am trying to make both inserts atomic as they deal with similar customers.
The first .executeNonQuery() works fine, locking the SQL table as it should.
The second .executNonQuery() throws an error:
ExecuteNonQuery requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction.
The Transaction property of the command has not been initialized.
Code:
ConnectionManager cm;
SqlTransaction sqlTrans;
SqlConnection sqlConn;
SqlCommand sqlComm;
cm = Dts.Connections["connectionManager"];
try
{
//Set 'global' variables
SqlParameter agentID = new SqlParameter("#agentID", 1000018); //retrievedMessage.Substring(2, 10));//Primary key
SqlParameter lastChangeOperator = new SqlParameter("#lastChangeOperator", "LVO");
SqlParameter lastChangeDate = new SqlParameter("#lastChangeDate", DateTime.Now);
SqlParameter controlId = new SqlParameter("#controlID", 1); //Hard-coded value for testing - CHANGE LATER
//Set variables for Agent table
SqlParameter entityType = new SqlParameter("#entityType", "P");//retrievedMessage.Substring(162, 1));
SqlParameter fName = new SqlParameter("#fName", "test");//retrievedMessage.Substring(12, 25));
SqlParameter lName = new SqlParameter("#lName", "test");//retrievedMessage.Substring(37, 35));
SqlParameter suffix = new SqlParameter("#suffix", "test");//retrievedMessage.Substring(72, 10));
SqlParameter corporateName = new SqlParameter("#corporateName", "Initech");//retrievedMessage.Substring(82, 80));
//Insert record into Agent table
sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
sqlComm = new SqlCommand
(
"SET IDENTITY_INSERT Agent ON " +
"INSERT INTO Agent (UniqueAgentId, EntityType, FirstName, LastName, NameSuffix, CorporateName, LastChangeOperator, LastChangeDate, ControlId) " +
"VALUES (#agentID, #entityType, #fName, #lName, #suffix, #corporateName, #lastChangeOperator, #lastChangeDate, #controlID)" +
"SET IDENTITY_INSERT Agent OFF",
sqlConn//, sqlTrans
);
sqlTrans = sqlConn.BeginTransaction("SqlAgentTableUpdates");
sqlComm.Parameters.Add(agentID);
sqlComm.Parameters.Add(lastChangeOperator);
sqlComm.Parameters.Add(lastChangeDate);
sqlComm.Parameters.Add(controlId);
sqlComm.Parameters.Add(entityType);
sqlComm.Parameters.Add(fName);
sqlComm.Parameters.Add(lName);
sqlComm.Parameters.Add(suffix);
sqlComm.Parameters.Add(corporateName);
sqlComm.Transaction = sqlTrans;
sqlComm.ExecuteNonQuery();
//Set variables for AgentIdentification table
SqlParameter taxIdType = new SqlParameter("taxIdType", "S");//Hard-coded value for testing - CHANGE LATER
SqlParameter agentTaxId = new SqlParameter("#agentTaxId", "999999999");//Hard-coded value for testing - CHANGE LATER
//Insert record into AgentIdentification table
sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
sqlComm = new SqlCommand
(
"INSERT INTO AgentIdentification (UniqueAgentId, TaxIdType, AgentTaxId, LastChangeOperator, LastChangeDate, ControlId) " +
"VALUES (#agentID, #taxIdType, #agentTaxId, #lastChangeOperator, #lastChangeDate, #controlId)",
sqlConn//, sqlTrans
);
sqlComm.Parameters.Add(taxIdType);
sqlComm.Parameters.Add(agentTaxId);
sqlComm.Transaction = sqlTrans;
sqlComm.ExecuteNonQuery();
}
catch (Exception)
{
sqlTrans.Rollback();
cm.ReleaseConnection(sqlConn);
}
finally
{
sqlTrans.Commit();
cm.ReleaseConnection(sqlConn);
}
EDIT
I was able to make this transaction work by eliminating the second connection. However, both queries use a couple of the same variables (SqlParameters). I was forced to duplicate these in order for this to run without errors. Is there a way for them to share the variables so I do not have to waste space re-creating them?
I think the problem might be with the connection, or when you set the command to a new command for the second insert, you can use two different commands with the same connection or reuse one command just changing the CommandText property.
Hope this helps... Using SqlTransaction
A transaction cannot span multiple connections... does cm.AcquireConnection return a new connection each time? If so, try using the same connection for both commands.
use transactionscope
using(TransactionScope ts = new TransactionScope())
{
using(SqlConnection conn = new SqlConnection(myconnstring)
{
conn.Open();
//call first executenonquery
//call second executenonquery
ts.Complete();
conn.Close();
}
}