I am trying to understand how Transactions work in c# using the TransactionScope object. I have an understanding about them in SQL server but I don't know how to verify this in c#. For example I want to check if I set the IsolationLevel to ReadUnCommitted, how can I verify this behavior in my code?
Currently this is my simple code, What I want to test is while my first Update command is executing and not committed/rolled back how can I verify that other transactions would be able to read the data it affects? Can anybody help me with a code example?
Here is my code:
public void TransferAmount(Account a)
{
bool debit = false;
bool credit= true;
var option = new TransactionOptions();
option.IsolationLevel = System.Transactions.IsolationLevel.ReadUnCommitted;
try
{
using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Required, option))
{
using (IDbCommand cmd = Idbconnection.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "usp_Update_Debit";
IDataParameter param = cmd.CreateParameter();
param.ParameterName = "#mDebitAmount";
param.Value = a.Amount;
cmd.Parameters.Add(param);
Idbconnection.Open();
debit = cmd.ExecuteNonQuery() == 1;
}
using (IDbCommand cmd = Idbconnection.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "usp_Update_Credit";
IDataParameter param = cmd.CreateParameter();
param.ParameterName = "#mCreditAmount";
param.Value = a.Amount;
cmd.Parameters.Add(param);
credit = cmd.ExecuteNonQuery() == 1;
}
if (debit == credit)
transactionScope.Complete();
}
}
catch (Exception ex)
{
System.ServiceModel.Web.WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
throw new FaultException(new FaultReason(new FaultReasonText(ex.Message)));
}
}
Related
Im new to ado.net in c#. I need to add a couple of Id's(primary keys) that will be placed in one table.These primary keys have already a value from other tables so i nedd to grab them and fill them in. The the next columns will be filled in by user => firstname,lastname,dateOfBirth,securitynumber. And i need to add the id keywords also. i have no clue how to start with it. I just know how to do an insert. So i have to get the addressId and #vehicleid,#fuelcardid from somwhere but i dont get it how?
string query = "INSERT INTO [Driver] (firstName,lastName,dateOfBirth,addressId,securityNumber,vehicleid,fuelcardid) Values (#firstName,#lastName,#dateOfBirth,#addressId,#securityNumber,#vehicleid,#fuelcardid);SELECT CAST(scope_identity() AS int)";
int? newdriverID= null;
SqlConnection connection = getConnection();
using (SqlCommand command = new SqlCommand(query, connection)) {
SqlTransaction transaction = connection.BeginTransaction();
command.Transaction = transaction;
try {
SqlParameter parDriverFirstName = new SqlParameter();
parDriverFirstName.ParameterName = "#firstName";
parDriverFirstName.SqlDbType = System.Data.SqlDbType.NVarChar;
command.Parameters.Add(parDriverFirstName);
SqlParameter parDriverLastName = new SqlParameter();
parDriverLastName.ParameterName = "#lastName";
parDriverLastName.SqlDbType = System.Data.SqlDbType.NVarChar;
command.Parameters.Add(parDriverLastName);
SqlParameter parDriverDateOfBirth = new SqlParameter();
parDriverDateOfBirth.ParameterName = "#dateOfBirth";
parDriverDateOfBirth.SqlDbType = System.Data.SqlDbType.DateTime;
command.Parameters.Add(parDriverDateOfBirth);
SqlParameter parAddressId = new SqlParameter();
parAddressId.ParameterName = "#addressId";
parAddressId.SqlDbType = System.Data.SqlDbType.Int;
command.Parameters.Add(parAddressId);
SqlParameter parDriverSecurityNr = new SqlParameter();
parDriverSecurityNr.ParameterName = "#securityNumber";
parDriverSecurityNr.SqlDbType = System.Data.SqlDbType.NVarChar;
command.Parameters.Add(parDriverSecurityNr);
SqlParameter parvehicleId = new SqlParameter();
parvehicleId.ParameterName = "#vehicleid";
parvehicleId.SqlDbType = System.Data.SqlDbType.Int;
command.Parameters.Add(parvehicleId);
SqlParameter parFuelCardID = new SqlParameter();
parFuelCardID.ParameterName = "#fuelcardid";
parFuelCardID.SqlDbType = System.Data.SqlDbType.Int;
command.Parameters.Add(parFuelCardID);
command.Parameters["#firstName"].Value = driver.FirstName;
command.Parameters["#lastName"].Value = driver.LastName;
command.Parameters["#dateOfBirth"].Value = driver.DateOfBirth;
command.Parameters["#securityNumber"].Value = driver.SecurityNumber;
command.Parameters["#addressId"].Value = addressId;
command.Parameters["#vehicleid"].Value = vehicleid; //TODO test if this works
command.Parameters["#fuelcardid"].Value = fuelcardid;
connection.Open();
newdriverID = (int)command.ExecuteScalar();
transaction.Commit();
} catch (Exception ex) {
transaction.Rollback();
throw new Exception(ex.Message);
} finally {
connection.Close();
}
}
if(newdriverID != null) {
foreach (string licensetype in driver.DriversLicenceType) {
//if a driver has no license type this will simply not loop once
//inserts a driverid and typeid into the DriversLicenceType table that can be used elsewhere
if (Alldriverlicensetypes.ContainsValue(licensetype)) {
string insertLicenseQuery = "INSERT INTO [DriverLicenseType] (driverId,licenseTypeId) Values (#driverid,#licenseID)";
SqlConnection licenseconnection = getConnection();
using (SqlCommand command = new SqlCommand(insertLicenseQuery, licenseconnection)) {
SqlTransaction transaction = connection.BeginTransaction();
command.Transaction = transaction;
try {
SqlParameter pardriverId = new SqlParameter();
pardriverId.ParameterName = "#driverid";
pardriverId.SqlDbType = System.Data.SqlDbType.Int;
command.Parameters.Add(pardriverId);
command.Parameters["#driverid"].Value = newdriverID;
SqlParameter parlicenseID = new SqlParameter();
parlicenseID.ParameterName = "#licenseID";
parlicenseID.SqlDbType = System.Data.SqlDbType.Int;
command.Parameters.Add(parlicenseID);
int key = Alldriverlicensetypes.FirstOrDefault(x => x.Value == licensetype).Key;
command.Parameters["#driverid"].Value = key;
connection.Open();
command.ExecuteNonQuery();
transaction.Commit();
} catch (Exception ex) {
transaction.Rollback();
throw new Exception(ex.Message);
} finally {
connection.Close();
}
}
} else {
//trying to add a type that is not in the database => ASK what to do here?
}
}
}//if the previous insert failed, the id will be null so dont add anything
}
I'm beginner in C# and SQL Server, and I wrote this query for creating a stored procedure in SQL Server:
create procedure newBehzad
#id bigint
as
DECLARE #ResultValue int
select *
from TABLEA
where id > #id
SET #ResultValue = -5
go
Everything is working, and I wrote this C# code to call that stored procedure and it return a single value:
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand("newBehzad", conn)
{
CommandType = CommandType.StoredProcedure
})
{
conn.Open();
command.Parameters.Add("#id", SqlDbType.BigInt).Value = 2;
command.Parameters.Add("#ResultValue", SqlDbType.Int);
SqlParameter retval = command.Parameters.Add("#ResultValue", SqlDbType.Int);
retval.Direction = ParameterDirection.ReturnValue;
retunvalue = (string)command.Parameters["#ResultValue"].Value;
//SqlParameter retval = sqlcomm.Parameters.Add("#b", SqlDbType.VarChar);
command.ExecuteNonQuery();
conn.Close();
}
MessageBox.Show(returnValue);
But when I run the C# windows application, I get this error:
Procedure or function newBehzad has too many arguments specified.
How can I solve that? Thanks.
Change you procedure to:
create procedure newBehzad #id bigint, #ResultValue int OUT
as
SET #ResultValue = 0
BEGIN
select *from TABLEA
where id>#id
SET #ResultValue = -5
END
go
Please try somethink like this:
object returnValue = null;
using (var conn = new System.Data.SqlClient.SqlConnection(AbaseDB.DBFactory.GetInstance().GetConnectionString()))
{
using (System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand("newBehzad", conn) { CommandType = CommandType.StoredProcedure })
{
conn.Open();
command.Parameters.Add("#id", SqlDbType.BigInt).Value = 2;
command.Parameters.Add("#ResultValue", SqlDbType.Int).Direction = ParameterDirection.Output;
command.ExecuteNonQuery();
returnValue = command.Parameters["#ResultValue"].Value;
conn.Close();
}
if (returnValue != null)
MessageBox.Show(returnValue.ToString());
}
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand("newBehzad", conn)
{
CommandType = CommandType.StoredProcedure
})
{
conn.Open();
command.Parameters.Add("#id", SqlDbType.BigInt).Value = 2;
// command.Parameters.Add("#ResultValue", SqlDbType.Int); Comment this line
SqlParameter retval = command.Parameters.Add("#ResultValue", SqlDbType.Int);
retval.Direction = ParameterDirection.ReturnValue;
retunvalue = (string)command.Parameters["#ResultValue"].Value;
//SqlParameter retval = sqlcomm.Parameters.Add("#b", SqlDbType.VarChar);
command.ExecuteNonQuery();
conn.Close();
}
MessageBox.Show(returnValue);
First of all you need to change the stored proc to return the value:
create procedure newBehzad #id bigint
as
DECLARE #ResultValue int
select *from TABLEA
where id>#id
SET #ResultValue = -5
Return #ResultValue
go
Then grab it with:
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
using (var cmd = new SqlCommand("newBehzad", conn)
{
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter retval = new SqlParameter();
retval.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add("#id", SqlDbType.BigInt).Value = 2;
cmd.Parameters.Add(retval);
cmd.ExecuteNonQuery();
returnValue = (int)retval.Value;
}
}
But I really can not get why are you selecting data in the stored proc...
I have a web service that executes a stored procedure. My web service function returns string[].
Sometimes I need to call the Web service many times.
For optimization reasons, I thought about adding a function to my web service which executes the stored procedure many times, in a for loop. That way the web service is called only once instead of several times.
1-Is my thinking correct ?
2-Below is my code, only the part specific to the problem described above.
3-Is it just not possible to do this using a for loop ?
My Problem: If I only use this code to call the stored procedure once, it works, but as soon as it is more (for loop iterates second time), the catch block is accessed.
If you can explain to me why is this happening and/or suggest a solution/workaround I would really appreciate.
try
{
for (int i = 0; i < number; i++)
{
connection.Open();
cmd = new SqlCommand();
//SqlTransaction transaction;
transaction = connection.BeginTransaction();
cmd.Transaction = transaction;
cmd.Connection = connection;
cmd.Parameters.Clear();
cmd.CommandText = "InsertMsg";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#ID", SqlDbType.VarChar).Value = IDs[i];
cmd.Parameters.Add("#name", SqlDbType.VarChar).Value = names[i];
cmd.Parameters.Add("#age", SqlDbType.DateTime).Value = age;
cmd.ExecuteNonQuery();
data[i] = IDs[i];
transaction.Commit();
}
connection.Close();
return data;
}
catch (SqlException ex)
{
transaction.Rollback();
data[0] = "Error";
return data;
}
}
The issues appears to be with the open and close statements. Close is outside the for loop, change it like
try
{
connection.Open();
transaction = connection.BeginTransaction();
for (int i = 0; i < number; i++)
{
cmd = new SqlCommand();
//SqlTransaction transaction;
cmd.Transaction = transaction;
cmd.Connection = connection;
cmd.Parameters.Clear();
cmd.CommandText = "InsertMsg";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#ID", SqlDbType.VarChar).Value = IDs[i];
cmd.Parameters.Add("#name", SqlDbType.VarChar).Value = names[i];
cmd.Parameters.Add("#age", SqlDbType.DateTime).Value = age;
cmd.ExecuteNonQuery();
data[i] = IDs[i];
transaction.Commit();
}
connection.Close();
return data;
}
catch (SqlException ex)
{
transaction.Rollback();
data[0]="Error";
return data;
}
Closing the connection should be inside a finally block, better use a using statement instead. Also if possible do the looping and transaction inside the stored procedure which will be faster.
Put your connection.Open() outside the loop
I wanted to chime in and mention there's probably an advantage to using a DataAdapter in this circumstance:
https://msdn.microsoft.com/en-us/library/aadf8fk2(v=vs.110).aspx
FTA - "Batch support in ADO.NET allows a DataAdapter to group INSERT, UPDATE, and DELETE operations from a DataSet or DataTable to the server, instead of sending one operation at a time. The reduction in the number of round trips to the server typically results in significant performance gains. "
I just winged this off real quick so ignore any syntax errors. Basically you want to make sure you are taking advantage of "using" statements. When you use a "using" it automatically calls Dispose() after the scope of the code has been reached, that way you dont have to worry about opening or closing a connection that is in use.
for (int i = 0; i < number; i++)
{
//Initialize this however you need to
using (SqlConnection connection = new SqlConnection())
{
connection.Open();
using (SqlCommand command =
new SqlCommand("InsertMsg", connection, connection.BeginTransaction())
{
CommandType = CommandType.StoredProcedure
})
{
try
{
command.Parameters.Clear();
command.Parameters.Add("#ID", SqlDbType.VarChar).Value = IDs[i];
command.Parameters.Add("#name", SqlDbType.VarChar).Value = names[i];
command.Parameters.Add("#age", SqlDbType.DateTime).Value = age;
command.ExecuteNonQuery();
data[i] = IDs[i];
command.Transaction.Commit();
}
catch (SqlException ex)
{
command.Transaction.Rollback();
data[0] = "Error";
}
}
}
}
return data;
try
{
connection.Open();
cmd = new SqlCommand();
transaction = connection.BeginTransaction();
cmd.Transaction = transaction;
cmd.Connection = connection;
cmd.CommandText = "InsertMsg";
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter ID = cmd.Parameters.Add("#ID", SqlDbType.VarChar);
SqlParameter name = cmd.Parameters.Add("#name", SqlDbType.VarChar);
SqlParameter age = cmd.Parameters.Add("#age", SqlDbType.DateTime);
for (int i = 0; i < number; i++)
{
ID.Value = IDs[i];
name.Value = names[i];
age.Value = age;
cmd.ExecuteNonQuery();
data[i] = IDs[i];
}
transaction.Commit();
}
catch (SqlException ex)
{
transaction.Rollback();
data[0] = "Error";
}
finally
{
connection.Close();
}
return data;
Ok, I am at wits end. I have a login page for teachers. The Username which is the teacher ID(integer) and the password(string) have to match what's already in the Teachers table that I've got in SQL. Please be aware that my knowledge and understanding is very basic. This is part of an assignment for C# module in SD Diploma. Obviously my code so far is not working. What an I doing wrong? My code is below....
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Configuration;
using System.Data.SqlClient;
namespace School_System_Project
{
public partial class Login2 : Form
{
SqlConnection conn;
SqlDataAdapter adapter = new SqlDataAdapter();
DataTable table = new DataTable();
SqlCommand command = new SqlCommand();
public Login2()
{
InitializeComponent();
}
private void btnTLogin_Click(object sender, EventArgs e)
{
string msg = "Teacher ID or Password cannot be left blank!";
if (txtTID.Text == "")
{
lblMessage1.Text = msg;
return;
}
if (txtTPW.Text == "")
{
lblMessage1.Text = msg;
return;
}
conn = new SqlConnection
(ConfigurationManager.ConnectionStrings["Name of connection string"].ConnectionString);
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandType = CommandType.Text;
command.CommandText = "SELECT TID, Password FROM Teacher WHERE TID = #ID
AND Password = #Password";
command.Connection.Open();
SqlParameter param = new SqlParameter();
param.ParameterName = "#ID";
param.SqlDbType = SqlDbType.Int;
param.Direction = ParameterDirection.Input;
param.Value = txtTID.Text;
command.Parameters.Add(param).Value = Int32.Parse(txtTID.Text);
SqlParameter param2 = new SqlParameter();
param2.ParameterName = "#Password";
param.SqlDbType = SqlDbType.Int;
param.Direction = ParameterDirection.Input;
param.Value = txtTPW.Text;
command.Parameters.Add(param2);
adapter.SelectCommand = command;
adapter.SelectCommand.ExecuteReader();
SqlDataReader reader = command.ExecuteReader();
if (txtTID.Text == param.ParameterName && txtTPW.Text == param2.ParameterName)
{
MainTeachers mainteachers = new MainTeachers();
mainteachers.ShowDialog();
}
else
{
lblMessage1.Text = "Incorrect login details, please try again";
}
reader.Dispose();
command.Dispose();
conn.Dispose();
}
OK - lots of stuff to clear up...
SqlParameter param = new SqlParameter();
param.ParameterName = "#ID";
param.SqlDbType = SqlDbType.Int;
param.Direction = ParameterDirection.Input;
param.Value = txtTID.Text;
command.Parameters.Add(param).Value = Int32.Parse(txtTID.Text);
Why are you setting the value twice? First you set it to what looks like a string (but the parameter is defined as SqlDbType.Int, and then you add the parameter and set its value again - this time to an int. Which one of those assignments do you want to keep??
SqlParameter param2 = new SqlParameter();
param2.ParameterName = "#Password";
param.SqlDbType = SqlDbType.Int;
From here on out, you're using param., while the parameter you're defining is really param2 - copy&paste error??? Needs to be fixed!
Also: is the password parameter really of type SqlDbType.Int??? .....
Please tell me you're not really storing all those passwords in clear text in your database! That would be a MAJOR no-no and gaping security issue in your system!
Also: you execute your reader - but you're never really reading any data from it.....
I would rewrite your code to something like this to take advantage of all the usual best practices working with ADO.NET and raw SQL:
// define the query you want to execute
string query = "SELECT TID, Password FROM Teacher WHERE TID = #ID AND Password = #Password";
// establish connection and command objects, both wrapped into using(){} blocks to
// ensure proper disposal, even in case of an exception
using (conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Name of connection string"].ConnectionString))
using (SqlCommand command = new SqlCommand(query, conn))
{
// add paramters and set values
command.Parameters.Add("#ID", SqlDbType.Int).Value = Convert.ToInt32(txtTID.Text);
command.Parameters.Add("#Password", SqlDbType.VarChar, 50).Value = txtTPW.Text;
// open connection
conn.Open();
// execute your reader
using (SqlDataReader reader = command.ExecuteReader())
{
// you need to actually *read* from the reader here! What are you trying to do?
// just check if that row with ID and password exist? Fetch some data?
bool idExists = reader.HasRows();
reader.Close();
}
conn.Close();
}
Except for the error pointed out by Rahul Singh.
The data that you want is inside your SqlDataReader reader variable. param.ParameterName will always be "#ID"
Try logging in with the user = #ID and password= #Password and you will be redirected to the form.
procedure select_card_transaction(trans_id nvarchar2,
usr_id number,
Quantity out number) is
begin
select count(*)
into Quantity
from user_cards u
where u.transaction_id = trans_id
and u.user_id = usr_id;
end;
and Consuming it:
using(var conn = new OracleConnection(Settings.Default.OraWUConnString))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "for_temporary_testing.select_card_transaction";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("trans_id", TransactionID);
cmd.Parameters.AddWithValue("usr_id", UserID);
var q = new OracleParameter("Quantity", OracleType.Number);
q.Direction = ParameterDirection.Output;
cmd.Parameters.Add(q);
//cmd.Parameters[0].OracleType = OracleType.NVarChar;
//cmd.Parameters[1].OracleType = OracleType.Number;
conn.Open();
var obj = cmd.ExecuteNonQuery();
conn.Close();
return (int)q.Value == 1;
}
It returns the following error.
ORA-06550 wrong number or types of arguments when calling Oracle stored procedure...
ANY IDEA?
I have had the same problem before. Are you using the ODP.Net drivers? I was able to solve the problem by adding the output parameter first. This needs to be done before the input parameters. In your case it would look like
using(var conn = new OracleConnection(Settings.Default.OraWUConnString))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "for_temporary_testing.select_card_transaction";
cmd.CommandType = CommandType.StoredProcedure;
// Return value parameter has to be added first !
var Quantity = new OracleParameter();
Quantity.Direction = ParameterDirection.ReturnValue;
Quantity.OracleDbType = OracleDbType.Int32;
cmd.Parameters.Add(Quantity);
//now add input parameters
var TransID = cmd.Parameters.Add("trans_id", TransactionID);
TransID.Direction = ParameterDirection.Input;
TransID.OracleDbType = OracleDbType.NVarchar2;
var UsrID = cmd.Parameters.Add("usr_id", UserID);
UsrID.Direction = ParameterDirection.Input;
UsrID.OracleDbType = OracleDbType.Int32;
cmd.ExecuteNonQuery();
conn.Close();
return Convert.ToInt32(Quantity.Value);
}
The problem was in the parameter. It was null and oracle returned error. I got that if argument is null, it should be sent as DBNULL