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
Related
I have problem with my homework code. We must create some database with updating users, but I am getting error with these and program crash..
This is full error code and there is code..
Error: MySql.Data.MySqlClient.MySqlException (0x80004005): error
connecting: Timeout expired. The timeout period elapsed prior to
obtaining a connection from the pool. This may have occurred because
all pooled connections were in use and max pool size was reached.
I try with query.CommandTimeout = 60; but i think its useless, what do u think?
MySqlConnection connect = new
MySqlConnection("Server=localhost;Database=Work;Uid=root;Pwd='1234';");
connect.Open();
MySqlCommand query = new MySqlCommand(#"UPDATE user
SET User_Name=#User_Name,User_BankBalance=#User_BankBalance,
User_Password=#User_Password,LottoTimer=#LottoTimer
WHERE User_Name='" + Escape(u.Username) + "'", connect);
using (query)
{
query.CommandType = System.Data.CommandType.Text;
query.Parameters.AddWithValue("#User_Name", Escape(u.Username));
query.Parameters.AddWithValue("#User_BankBalance", u.BankBalance);
query.Parameters.AddWithValue("#User_Password", u.Password);
query.Parameters.AddWithValue("#LottoTimer", u.LottoTimer);
query.Dispose();
query.Prepare();
query.ExecuteNonQuery();
}
connect.Close();
return;
You method should look something like this:
string query =
#"UPDATE user SET"
+ " User_Name=#User_Name,"
+ " User_BankBalance=#User_BankBalance,"
+ " User_Password=#User_Password,"
+ " LottoTimer=#LottoTimer"
+ " WHERE User_Name='" + Escape(u.Username) + "'";
using( var connect = new MySqlConnection("Server=localhost;Database=Work;Uid=root;Pwd='1234';"))
using( var query = new MySqlCommand(query, connect))
{
connect.Open();
query.CommandType = System.Data.CommandType.Text;
query.Parameters.AddWithValue("#User_Name", Escape(u.Username));
query.Parameters.AddWithValue("#User_BankBalance", u.BankBalance);
query.Parameters.AddWithValue("#User_Password", u.Password);
query.Parameters.AddWithValue("#LottoTimer", u.LottoTimer);
query.Prepare();
query.ExecuteNonQuery();
}
return;
It is important to dispose the MySqlConnection, so it can be available to the pool again. The problem you have is that all connections are used in the pool, and it is waiting for one - until the timeout.
I'm working on some code that creates procedures with C#. When I try to run my code:
StringBuilder sbSP1 = new StringBuilder();
sbSP1.AppendLine("CREATE PROCEDURE " + ProcN + "DELETE #id int=0 AS BEGIN DELETE FROM " + tname + " WHERE " + PK + " = #id END");
SqlConnection connection = new SqlConnection(connectionString);
using (SqlCommand cmd = new SqlCommand(sbSP1.ToString(), connection))
{
connection.Open();
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
connection.Close();
}
This part doesn't work. It throws an exception:
System.Data.SqlClient.SqlException: 'Incorrect syntax near '='.'
But when I try to run this part:
StringBuilder sbSP3 = new StringBuilder();
sbSP3.AppendLine("CREATE PROCEDURE " + ProcN + "GET AS BEGIN SET NOCOUNT ON; SELECT " + columns + " from " + tname + " END");
SqlConnection connection = new SqlConnection(connectionString);
using (SqlCommand cmd = new SqlCommand(sbSP3.ToString(), connection))
{
connection.Open();
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
connection.Close();
}
it runs correctly.
Where is my mistake in the first snippet of code?
Without sbSP1 final text it's difficult to pin down the error; as a working hypothesis I suggest that PK being complex (e.g. "id value")
should be escaped - [PK]. In order to avoid such kind of errors, please, never build the query, but make procedure's text being readable with a help of string interpolation - $ and verbatim strings - #.
//TODO: check the text, is it the procedure you want to create?
string text =
$#"create procedure [{ProcN}Delete]
#id int = 0 as
begin
delete
from [{tname}]
where [{PK}] = #id
end;";
//DONE: do not close connection manually, but wrap it into using
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
using (qlCommand cmd = new SqlCommand(text, connection)) {
cmd.ExecuteNonQuery();
}
}
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
I keep getting the error in GetTotalMaterialCost I have already checked the whole thing. I even remove excess con.Close but the error will still prompt t it.
decimal GetTotalMaterialCost()
{
decimal total = 0;
con.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText =
"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost FROM Resource_Materials rm " +
"JOIN Materials m ON m.MaterialID = rm.MaterialID " +
"JOIN ProjectTasks t ON t.TaskID = rm.TaskID " +
"WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
object data = cmd.ExecuteScalar();
if (data == null)
total = 0;
else
total = (decimal)cmd.ExecuteScalar();
con.Close();
return total;
}
protected void btnSave_Click(object sender, EventArgs e)
{
con.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "UPDATE ProjectTasks SET Name=#Name, Description=#Description " +
"WHERE TaskID=#TaskID; " +
"SELECT TOP 1 TaskID FROM ProjectTasks ORDER BY TaskID DESC;";
cmd.Parameters.AddWithValue("#Name", txtName.Text);
cmd.Parameters.AddWithValue("#Description", txtDescription.Text);
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
cmd.ExecuteNonQuery();
cmd.CommandText = #"UPDATE Resource_Materials SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Equipments SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Vehicles SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Contractors SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Projects SET ActualCost=#ActualCost WHERE ProjectID=#ProjectID";
cmd.Parameters.AddWithValue("#ProjectID", Request.QueryString["ID"].ToString());
cmd.Parameters.AddWithValue("#ActualCost", GetAmount());
con.Close();
Helper.AddLog("1", "Add", "Assigned Resources to Task");
Response.Redirect("~/Projects/Default.aspx");
}
Here is the link for my whole code as reference
The said error is at line 679
This usually happens because you use a global connection object and somewhere in your code the connection was not properly closed. For example, an exception is triggered somewhere in your code and you forget to close the connection in that case.
The resolution is always the same.
Do not keep a global connection object.
Create it when you need it and destroy it with the appropriate syntax.
There is connection pooling infrastructure in ADO.NET that has been devised just to give better performance when you are in a create/open/use/close scenario.
decimal GetTotalMaterialCost()
{
decimal total = 0;
string query = #"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost
FROM Resource_Materials rm
JOIN Materials m ON m.MaterialID = rm.MaterialID
JOIN ProjectTasks t ON t.TaskID = rm.TaskID
WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
using(SqlConnection con = new SqlConnection(....constringhere...)
using(SqlCommand cmd = new SqlCommand(query, con))
{
con.Open();
.....
}
return total;
}
In this way the connection object is local and the using statement ensures that is closed and disposed at the closing brace even if you hit some kind of exception.
Of course this pattern should be applied in every point where you try to reach your database and the global connection object should be removed. The only thing that could be kept global is the connection string and also for this there is a better place to store it (IE. The app.config ConnectionString section)
Said that it is a possibility that you have an error caused by the AddWithValue usage. This method defines the DataType of the parameter looking at the value passed. It seems that your TaskID field is an integer, but you prepare a parameter with AddWithValue and pass a string. So the query will use a parameter with the wrong datatype.
I suggest to use
cmd.Parameters.Add("#TaskID", SqlDbType.Int).Value =
Convert.ToInt32(Request.QueryString["ID"].ToString()));
Finally, just give a cursory glance to your code, I suggest to change the methods called by your Page_Load to receive a connection object opened directly in the Page_Load event
if (!IsPostBack)
{
using(SqlConnection con = new SqlConnection(....constringhere...)
{
GetProjectMaterials(con);
GetProjectEquipments(con);
GetProjectVehicle(con);
GetProjectContractors(con);
GetTasks(con,resourceID);
GetMaterials(con);
GetEquipments(con);
GetVehicles(con);
GetLContractors(con);
}
}
Of course you need to change also the other methods that calls these methods to pass a connection, but if I am not wrong, you already have to build a connection in those callers.
I would suggest you to use local variable for connection together with "using" since .NET SQL provider will do connection pooling for you and "using" will ensure that your connection is properly closed and disposed. So your code will look like:
decimal GetTotalMaterialCost()
{
decimal total = 0;
using (var con = new SqlConnection(/*connection string if not configured via web.config*/))
{
con.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = con;
cmd.CommandText =
"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost FROM Resource_Materials rm " +
"JOIN Materials m ON m.MaterialID = rm.MaterialID " +
"JOIN ProjectTasks t ON t.TaskID = rm.TaskID " +
"WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
object data = cmd.ExecuteScalar();
if (data == null)
total = 0;
else
total = (decimal)cmd.ExecuteScalar();
}
}
return total;
}
You are calling cmd.ExecuteScalar() twice without resetting it. Your line setting the total variable should be total = (decimal) data;. After all, you already have the answer, why not just use it instead of re-executing the code?
I am trying to insert a record and get its newly generated id by executing two queries one by one, but don't know why its giving me the following error.
Object cannot be cast from DBNull to other types
My code is as below: (I don't want to use sql stored procedures)
SqlParameter sqlParam;
int lastInsertedVideoId = 0;
using (SqlConnection Conn = new SqlConnection(ObjUtils._ConnString))
{
Conn.Open();
using (SqlCommand sqlCmd = Conn.CreateCommand())
{
string sqlInsertValues = "#Name,#Slug";
string sqlColumnNames = "[Name],[Slug]";
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + ");";
sqlCmd.CommandText = sqlQuery;
sqlCmd.CommandType = CommandType.Text;
sqlParam = sqlCmd.Parameters.Add("#Name", SqlDbType.VarChar);
sqlParam.Value = txtName.Text.Trim();
sqlParam = sqlCmd.Parameters.Add("#Slug", SqlDbType.VarChar);
sqlParam.Value = txtSlug.Text.Trim();
sqlCmd.ExecuteNonQuery();
//getting last inserted video id
sqlCmd.CommandText = "SELECT SCOPE_IDENTITY() AS [lastInsertedVideoId]";
using (SqlDataReader sqlDr = sqlCmd.ExecuteReader())
{
sqlDr.Read();
lastInsertedVideoId = Convert.ToInt32(sqlDr["lastInsertedVideoId"]);
}
}
}
//tags insertion into tag table
if (txtTags.Text.Trim().Length > 0 && lastInsertedVideoId > 0)
{
string sqlBulkTagInsert = "";
string[] tags = txtTags.Text.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tag in tags)
{
sqlBulkTagInsert += "INSERT INTO tags(VideoId, Tag) VALUES(" + lastInsertedVideoId + ", " + tag.Trim().ToLowerInvariant()+ "); ";
}
using (SqlConnection Conn = new SqlConnection(ObjUtils._ConnString))
{
Conn.Open();
using (SqlCommand sqlCmd = Conn.CreateCommand())
{
string sqlQuery = sqlBulkTagInsert;
sqlCmd.CommandText = sqlQuery;
sqlCmd.CommandType = CommandType.Text;
sqlCmd.ExecuteNonQuery();
}
}
}
And also if possible, please check is the above code coded well or we can optimize it more for improve performance?
Thanks
The call to SCOPE_IDENTITY() is not being treated as being in the same "scope" as the INSERT command that you're executing.
Essentially, what you need to do is change the line:
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + ");";
to:
string sqlQuery = "INSERT INTO videos(" + sqlColumnNames + ") VALUES(" + sqlInsertValues + "); SELECT SCOPE_IDENTITY() AS [lastInsertedVideoId]";
and then call
int lastVideoInsertedId = Convert.ToInt32(sqlCmd.ExecuteScalar());
instead of .ExecuteNonQuery and the code block following the "//getting last inserted video id" comment.
The SCOPE_IDENTITY() should be extracted from the first command (SELECT, RETURN or OUT) and passed into the next command. By that, I mean that the SELECT_IDENTITY() should be at the end of the first command. In SQL 2008 there is additional syntax for bring values back as part of the INSERT, which makes this simpler.
Or more efficiently: combine the commands into one to avoid round-trips.