I'm writing out formatted text to a CLOB in Oracle table. Eventually as the process runs the table will get locked. When our DBA checks out the connections it appears that I've created multiple locks on the table and there are no other connections from other users. Any ideas on why the code below would eventually create locks on the table? It normally takes a few days of this code running a couple 100 times a day before the lock is created. There appear to be no hanging transactions.
public void Update_Html_Out(string key, string shortTitle, string htmlText)
{
byte[] newvalue = Encoding.Unicode.GetBytes(htmlText);
string sql = "UPDATE html_out SET short_title = :short_title, actual_text = :clob WHERE key = :key";
using (var conn = new OracleConnection(_connectionString))
using (var cmd = new OracleCommand(sql, conn))
{
conn.Open();
using (var transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted))
{
cmd.Transaction = transaction;
using (var clob = new OracleClob(conn))
{
clob.Write(newvalue, 0, newvalue.Length);
cmd.Parameters.Add("short_title", shortTitle);
cmd.Parameters.Add("clob", clob);
cmd.Parameters.Add("key", key);
cmd.ExecuteNonQuery();
transaction.Commit();
}
}
}
}
Try this code. Note, you need to save CLOB, not BLOB (you said it)
public void Update_Html_Out(string key, string shortTitle, string htmlText)
{
string sql = #"UPDATE html_out SET
short_title = :short_title,
actual_text = :clob
WHERE key = :key";
using (var conn = new OracleConnection(_connectionString))
{
conn.Open();
using (var transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted))
{
using (var cmd = new OracleCommand(sql, conn))
{
cmd.Transaction = transaction;
cmd.Parameters.Add("short_title", OracleDbType.Varchar2, shortTitle, ParameterDirection.Input);
cmd.Parameters.Add("clob", OracleDbType.Clob, htmlText, ParameterDirection.Input);
cmd.Parameters.Add("key", OracleDbType.Varchar2, key, ParameterDirection.Input);
cmd.ExecuteNonQuery();
}
transaction.Commit();
}
}
}
Related
I know people are going to scream that this topic is all over the internet. I know, I've read them. And I still don't understand it. I simply want to populate my object from the results of a stored procedure. Now this stored procedure takes at least 2 parameters - an action and what to find.
#Action
#customername
The #Action simply determine what the stored procedure needs to do and returns the results. So for example if I want to update a customer, I'd call the sp through my object:
public class Customer()
{
//properties
public int UpdateCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
SQLCommand = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
SqlParameterCollection parameterCollection = command.Parameters;
parameterCollection.Add("#Action", SqlDbType.NVarChar, -1).Value = "Update"
//complete the rest of it....
}
}
}
This works well. So the problem arises when I simply want to populate the object with the results of the sp. In this case I would pass "Retrieve" as the #Action parameter and this.customer_name as the #customername parameter to the sp.
But how do I put the results from the stored procedure into the object?
I have this...
public void GetCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
var retrieval = new DynamicParameters();
retrieval.Add("#Action", "Retrieve");
retrieval.Add("#name", this.customer_Name);
connection.Open():
connection.Execute(Helper.Procedure("Customer"), retrieval, commandType: CommandType.StoredProcedure);
}
}
But I don't think it's working.
Back a long time ago I used to run a "fetch" for PHP when I needed to populate an object. Should I go back to this?
You need to execute a SqlReader on the command, Something like this:
using (var connection = new SqlConnection("Connection"))
using (var command = new SqlCommand("Retrieve", connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_Name);
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var item = new YourClass();
// You can use GetX() methods to get the value of the fields
item.Name = reader.GetString("name");
// You can also access the fields by using bracket notation
item.Age = (int)reader["age"];
// Be careful of nullable fields though!
}
}
}
Using #Encrypt0r advice and guidance I got it working:
public void GetCustomer() {
using (SqlConnection connection = new SqlConnection(Helper.CnnVal("DataConnection"))) {
SqlCommand command = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_name);
connection.Open();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
this.tbl_id = (int)reader["tbl_id"];
this.customer_name = (string)reader["customer_name"];
this.customer_id = reader.GetInt32(customer_id);
this.customer_address = (string)reader["customer_address"];
this.customer_city = (string)reader["customer_city"];
this.customer_state = (string)reader["customer_state"];
this.customer_zip = reader.GetInt32(customer_zip);
}
}
When I execute the following code in C#, I can insert one record without issues. When I have two objects in my collection, I get the following error:
The variable name '#scoreboardId' has already been declared. Variable names must be unique within a query batch or stored procedure
Is there a way to work around this batch exception?
public void insertActiveMonitorsForScoreboard(SqlConnection dbConn, SqlTransaction dbTrans, int scoreboardId,
ObservableCollection<AvailableMonitorBo> availableMonitorsForAddOC)
{
using (SqlCommand dbCommand = new SqlCommand(CreateAndDisplaySQLStrings.INSERT_SCOREBOARD_MONITORS, dbConn))
{
dbCommand.Transaction = dbTrans;
foreach (AvailableMonitorBo bo in availableMonitorsForAddOC)
{
if (bo.IsActive)
{
dbCommand.Parameters.Add("scoreboardId", SqlDbType.Int).Value = scoreboardId;
dbCommand.Parameters.Add("availableMonitorId", SqlDbType.Int).Value = bo.AvailableMonitorId;
dbCommand.ExecuteNonQuery();
}
}
}
}
Try to add the parameters only once and subsequently only change their values.
public void insertActiveMonitorsForScoreboard(SqlConnection dbConn, SqlTransaction dbTrans, int scoreboardId,
ObservableCollection<AvailableMonitorBo> availableMonitorsForAddOC) {
using (SqlCommand dbCommand = new SqlCommand(CreateAndDisplaySQLStrings.INSERT_SCOREBOARD_MONITORS, dbConn)) {
dbCommand.Transaction = dbTrans;
dbCommand.Parameters.Add("scoreboardId", SqlDbType.Int);
dbCommand.Parameters.Add("availableMonitorId", SqlDbType.Int);
foreach (AvailableMonitorBo bo in availableMonitorsForAddOC) {
if (bo.IsActive) {
dbCommand.Parameters["scoreboardId"].Value = scoreboardId;
dbCommand.Parameters["availableMonitorId"].Value = bo.AvailableMonitorId;
dbCommand.ExecuteNonQuery();
}
}
}
}
Another approach is to put the SqlCommand inside your loop. This has the advantage that the SqlCommand is completely new for each loop, so nothing is carried over between iterations. This does not matter in this example, but in other cases it might.
public void insertActiveMonitorsForScoreboard(SqlConnection dbConn, SqlTransaction dbTrans, int scoreboardId,
ObservableCollection<AvailableMonitorBo> availableMonitorsForAddOC) {
foreach (AvailableMonitorBo bo in availableMonitorsForAddOC) {
if (bo.IsActive) {
using (SqlCommand dbCommand = new SqlCommand(CreateAndDisplaySQLStrings.INSERT_SCOREBOARD_MONITORS, dbConn)) {
dbCommand.Transaction = dbTrans;
dbCommand.Parameters.Add("scoreboardId", SqlDbType.Int).Value = scoreboardId;
dbCommand.Parameters.Add("availableMonitorId", SqlDbType.Int).Value = bo.AvailableMonitorId;
dbCommand.ExecuteNonQuery();
}
}
}
}
I am trying to doing this:
Read a row from an SQLite db (in GetRuleByID() method)
Update the same row that I just read during (1) (See UpdateStatusForRuleID() method)
However my problem is that SQLite locks the database after the SELECT in GetRuleByID() so that update in UpdateStatusForRuleID() is only successful when called the first time.
I have tried enabling Write-Ahead-Logging in SQLite as well as PRAGMA read_uncommitted=1 in order to avoid SQLite locking the database for the SELECT, but this does not appear to work.
This should be simple but I have so far spent a complete night trying to solve this... Please help !
private static MicroRuleEngine.Rule GetRuleByID(int ruleID, SQLiteConnection connection, out Dictionary<string, string> dict)
{
dict = new Dictionary<string, string>();
string sql = String.Format("select * from rules WHERE ID = {0} ", ruleID.ToString());
SQLiteCommand command = new SQLiteCommand(sql, connection);
SQLiteDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
// Convert row into a dictionary
for (int lp = 0; lp < reader.FieldCount; lp++)
{
dict.Add(reader.GetName(lp), reader.GetValue(lp) as string);
}
string json = dict["fulljson"];
MicroRuleEngine.Rule r = Newtonsoft.Json.JsonConvert.DeserializeObject<MicroRuleEngine.Rule>(json);
//command.Dispose();
return r;
}
}
internal static void UpdateStatusForRuleID(SQLConnectionManager DBMANAGER, int ruleID, bool status)
{
Dictionary<string, string> dict = null;
string dbVal = (status) ? "1" : "0";
MicroRuleEngine.Rule r = null;
string newJSON = null;
using (SQLiteConnection connection = DBMANAGER.CreateConnection())
{
r = GetRuleByID(ruleID, connection, out dict);
r.Active = (status);
newJSON = Newtonsoft.Json.JsonConvert.SerializeObject(r);
Thread.Sleep(1000);
string sql = "UPDATE rules SET active = #a, fulljson=#j WHERE ID = #i";
using (var command = new SQLiteCommand(sql, connection))
{
command.Parameters.Add(new SQLiteParameter("#a", dbVal));
command.Parameters.Add(new SQLiteParameter("#i", ruleID));
command.Parameters.Add(new SQLiteParameter("#j", newJSON));
command.ExecuteNonQuery(); // Database is locked here ???
}
connection.Close();
}
}
"Database is locked" means that some other connection (in the same or some other process) still has an active transaction.
You don't need multiple connections (unless you are using multiple threads); just use a single connection object for all database accesses.
Ensure that all command, reader, and transaction objects (and connections, if you decide to use temporary ones) are properly cleaned up, by using using:
using (var command = new SQLiteCommand(sql, connection))
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
...
}
Apparently, the code below works. I basically dropped the GetRuleByID() method (but then I had to re-write 4 other methods)
Thanks to all who provided input.
internal static void UpdateStatusForRuleID(SQLConnectionManager DBMANAGER, int ruleID, bool status)
{
string dbVal = (status) ? "1" : "0";
MicroRuleEngine.Rule r = null;
string newJSON = null;
using (SQLiteConnection conn = DBMANAGER.CreateConnection())
{
string sql = String.Format("select * from rules WHERE ID = {0} ", ruleID.ToString());
using (var command = new SQLiteCommand(sql, conn))
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
reader.Read();
string json = reader["fulljson"].ToString();
r = Newtonsoft.Json.JsonConvert.DeserializeObject<MicroRuleEngine.Rule>(json);
r.Active = (status);
newJSON = Newtonsoft.Json.JsonConvert.SerializeObject(r);
string sql2 = "UPDATE rules SET active = #a, fulljson=#j WHERE ID = #i";
using (var command2 = new SQLiteCommand(sql2, conn))
{
command2.Parameters.Add(new SQLiteParameter("#a", dbVal));
command2.Parameters.Add(new SQLiteParameter("#i", ruleID));
command2.Parameters.Add(new SQLiteParameter("#j", newJSON));
command2.ExecuteNonQuery();
}
}
}
}
}
I'm updating some old legacy code and I ran into a problem with the
SqlCommand.ExecuteReader() method. The problem is that it's not returning any
results. However, using SqlDataAdapter.Fill(), I get results back from the
database. What am I doing wrong? How can I get results back using the data
reader?
var connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ToString();
using (var sqlConnection = new SqlConnection(connectionString))
{
using (var sqlCommand = new SqlCommand())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.CommandText = "SELECT * FROM MyTable WHERE ID = 1";
sqlConnection.Open();
// This code works.
//var dataTable = new DataTable();
//using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
//{
// sqlDataAdapter.Fill(dataTable);
//}
// This code is not working.
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// This fails because the data reader has no results.
var id = sqlDataReader.GetInt32(0);
}
}
}
}
Could it be that there is no Int32 in your results ?
var id = sqlDataReader.GetInt32(0); // <-- this might not be an Int32
Either try:
var id = sqlDataReader.GetValue(0);
Or cast to the correct type (BIGINT for example is Int64), not sure without seeing your data.
Try this..
var id = 0;
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
id = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("ColName"));
}
}
I have moved the variable outside of the reader code or the variable will only be accessible inside that scope. I would avoid specifying the ordinal in the code, in case someone altered the columns in the DB.
Also, specify the columns in the SQL statement... SELECT ColName FROM ... and use params in the query
If you got to that line then it has results
Does not mean the value is not null
And you should not use a SELECT *
If may have a problem with an implicit cast
Try Int32
try
{
if(sqlDataReader.IsDBNull(0))
{
// deal with null
}
else
{
Int32 id = sqlDataReader.GetInt32(0);
}
}
catch (SQLexception Ex)
{
Debug.WriteLine(Ex.message);
}
var connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ToString();
using (var sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
string sql = "SELECT * FROM MyTable WHERE ID = 1";
using (var sqlCommand = new SqlCommand(sql, sqlConnection))
{
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// This fails because the data reader has no results.
var id = sqlDataReader.GetInt32(0);
}
}
}
}
I created a sale table which Insert function does not work properly. It shows the error message like this "ExecuteNonQuery requires an open and available Connection. The connection's current state is closed." If I removed Sql Close Statement on Line 14, it shows this error msg "There is already an open DataReader associated with this Command which must be closed first." My code below work like this. I checked available stocks from my Product table. If quantity order is greater than quantity from Product Table, show error message. Otherwise, proceed to inserting order information into Sale Table. Any help is appreciated.
private void btnOrder_Click(object sender, EventArgs e)
{
int iQuantityDB;
int iCustomerID = Convert.ToInt32(txtCustomerID.Text);
int iProductID = Convert.ToInt32(txtProductID.Text);
decimal dPrice = Convert.ToDecimal(txtPrice.Text);
int iQuantity = Convert.ToInt32(txtQuantity.Text);
decimal dSubtotal = Convert.ToDecimal(txtSubTotal.Text);
decimal dGST = Convert.ToDecimal(txtGST.Text);
decimal dTotal = Convert.ToDecimal(txtTotal.Text);
string strConnectionString = #"Data Source = KK\SQLEXPRESS; Integrated Security = SSPI; Initial Catalog = JeanDB; MultipleActiveResultSets=True;";
using (var sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = #"Select Quantity from dbo.JeanProduct WHERE ProductID = #iProductID";
using (var cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
using (var sdRead = cmdOrder.ExecuteReader())
{
sdRead.Read();
iQuantityDB = Convert.ToInt32(sdRead["Quantity"]);
}
}
if (iQuantityDB > iQuantity)
{
string InsertQuery = #"INSERT INTO Sale(CustomerID, ProductID, Price, Quantity, Subtotal, GST, Total)VALUES(#iCustomerID, #iProductID, #dPrice, #iQuantity, #dSubtotal, #dGST, #Total)";
using (var InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
InsertCMD.Connection = sqlconn;
InsertCMD.Parameters.AddWithValue("#iCustomerID", iCustomerID);
InsertCMD.Parameters.AddWithValue("#iProdcutID", iProductID);
InsertCMD.Parameters.AddWithValue("#dPrice", dPrice);
InsertCMD.Parameters.AddWithValue("#iQuantity", iQuantity);
InsertCMD.Parameters.AddWithValue("#dSubtotal", dSubtotal);
InsertCMD.Parameters.AddWithValue("#dGST", dGST);
InsertCMD.Parameters.AddWithValue("#dTotal", dTotal);
InsertCMD.ExecuteNonQuery();
LoadDataonTable();
}
}
else
{
MessageBox.Show("no more stock");
}
sqlconn.Close();
}
}
You should change your connection string to
string strConnectionString = #"Data Source = KK\SQLEXPRESS;
Integrated Security = SSPI;
Initial Catalog = JeanDB;
MultipleActiveResultSets=True";
And do not close the connection between the Reader.Read and the ExecuteNonQuery.
You need at least Sql Server 2005 for this to work.
The connection used by a SqlDataReader cannot be used for other operations unless you set the connection string with the MultipleActiveResultSets key. Of course you could open two connection objects (with the same connection string) and use one for the SqlDataReader and one to Execute your command.
Not really linked to your problem, but I suggest to use a parameterized query also for the SELECT part of your code.
Moreover, you should use the Using Statement around the disposable object to ensure the proper closing and disposing also in case of exceptions. Finally, the syntax used in the INSERT INTO is not correct. I think that this code could explain some of the points explained above.
string strConnectionString = #"......;MultipleActiveResultSets=True;";
using(SqlConnection sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = #"Select Quantity from dbo.JeanProduct
WHERE ProductID = #id";
using(SqlCommand cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
cmdOrder.AddWithValue("#id", Convert.ToInt32(txtProductID.Text));
using(SqlDataReader sdRead = cmdOrder.ExecuteReader())
{
if(sdRead.Read())
{
.....
string InsertQuery = #"INSERT INTO Sale(SaleID, CustomerID, ProductID,
Price, Quantity, Subtotal, GST, Total)VALUES(#iCustomerID,
#iProductID, #dPrice, #iQuantity,
#dSubtotal, #dGST, #Total)";
using(SqlCommand InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
InsertCMD.Parameters.AddWithValue("#iCustomerID", iCustomerID);
....
InsertCMD.ExecuteNonQuery();
LoadDataonTable();
}
}
else
{
MessageBox.Show("no more stock");
}
}
}
}
You've closed your SqlConnection after the reader execution / read cycle (and in the other error, you've kept the reader open while trying to execute another command).
Either close the reader and leave the connection open for the insert, or open a new connection for the insert.
Better still, use using to handle the disposal of the resources for you, and scope the DB resources to be released as soon as you are done with them, e.g.:
using (var sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = "Select Quantity ...";
using var (cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
int iQuantityDB;
using (var sdRead = cmdOrder.ExecuteReader())
{
sdRead.Read();
iQuantityDB = Convert.ToInt32(sdRead["Quantity"]);
} // Dispose reader
// sqlconn.Close(); <-- Don't close
} // cmdOrder disposed here
if (iQuantityDB > iQuantity)
{
string InsertQuery = "INSERT INTO ...";
using var (InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
// ...
} // InsertCmd disposed here
}
} // Sql Connection disposed here
This will overcome many bugs, such as the one you've got where you are conditionally closing the command + connection in an if branch.