I am using gridview to select multiple records and then play loop over it to individually send each record ID to database and update it but I don't find it very good way to implement because it opens and close connection each time so what is the good way ?
foreach (GridViewRow r in grdViewLastHearingDates.Rows)
{
int CaseHearingID = Convert.ToInt32(r.Cells[0].Text);
CheckBox chkBox = r.FindControl("chkBoxIsConveyed") as CheckBox;
TextBox txtboxConvenienceRemarks = r.FindControl("txtBoxConvenienceRemarks") as TextBox;
string ConvenienceRemarks = txtboxConvenienceRemarks.Text;
MngCaseHearings.UpdateCasesIsConveyed(CaseHearingID, ConvenienceRemarks, chkBox.Checked);
}
MngCaseHearings.UpdateCasesIsConveyed sends and executes this code every time for updating each ID. Please review and give suggestions
public Boolean UpdateCasesIsConveyed(int CaseHearingID, string ConvenienceRemarks, bool IsConveyed)
{
try
{
SqlCommand SqlCom = new SqlCommand("UpdateCasesIsConveyed", DatabaseConnection.OpenConnection());
SqlCom.CommandType = CommandType.StoredProcedure;
SqlCom.Parameters.AddWithValue("#pk_CaseHearings_ID ", CaseHearingID);
SqlCom.Parameters.AddWithValue("#IsConveyed", IsConveyed);
SqlCom.Parameters.AddWithValue("#ConvenienceRemarks", ConvenienceRemarks);
SqlParameter SqlParamReturnStatus = new SqlParameter("#ReturnStatus", SqlDbType.Bit);
SqlCom.Parameters.Add(SqlParamReturnStatus);
SqlParamReturnStatus.Direction = ParameterDirection.Output;
SqlParameter SqlParamReturnStatusMessage = new SqlParameter("#ReturnStatusMessage", SqlDbType.VarChar, -1);
SqlCom.Parameters.Add(SqlParamReturnStatusMessage);
SqlParamReturnStatusMessage.Direction = ParameterDirection.Output;
SqlCom.ExecuteNonQuery();
string ReturnStatusMessage = Convert.ToString(SqlParamReturnStatusMessage);
Boolean ReturnStatus = Convert.ToBoolean(SqlParamReturnStatus.Value);
return ReturnStatus;
}
catch (Exception)
{
throw;
}
finally
{
DatabaseConnection.CloseConnection();
}
this would waste the resources so if any good work around ?
Since the SqlConnection and SqlCommand objects both implement IDisposable you should probably dispose of them when you're done. The simplest way would be via the using statement:
using (var conn = DatabaseConnection.OpenConnection())
using (var SqlCom = new SqlCommand("UpdateCasesIsConveyed", conn))
{
// setup and execute the SP, can return from in here
}
This will ensure that the resources used by the objects are properly closed as soon as you are done. While this isn't strictly necessary - the Dispose method will be called when the garbage collector destroys the objects - it will ensure that you aren't holding open the database objects any longer than necessary. Depending on how often this is called you can end up with resource shortages on the SQL server, excessive handle usage, etc.
In general anything that implements IDisposable should be disposed as soon as practical.
(Sorry, missed the loop part of the question)
This type of create/destroy cycle is fine for single operations, but becomes wasteful when used to update a lot of records. I would put the loop in the middle of the code rather than repeatedly calling this code from outside.
I would create a record class or struct that holds the SP parameters and pass an IEnumerable of that record class to your update method. This way you can do the setup once, process all of the updates, then tear down the database objects after all of the changes have been made. Throw a transaction in too so you can undo it all if one of the records fails.
Something like:
public struct UpdateCaseConveyanceRec
{
public int CaseHearingID;
public string ConvenienceRemarks;
public bool IsConveyed;
}
public bool UpdateCasesIsConveyed(IEnumerable<UpdateCaseConveyanceRec> uopdates)
{
using (SqlConnection conn = DatabaseConnection.OpenConnection())
using (SqlCommand cmd = new SqlCommand("UpdateCasesIsConveyed", conn))
using (SqlTransaction trans = conn.BeginTransaction("UpdateCasesIsConveyed"))
{
cmd.CommandType = CommandType.StoredProcedure;
var pID = cmd.Parameters.Add("#pk_CaseHearings_ID", SqlDbType.Int);
var pConveyed = cmd.Parameters.Add("#IsConveyed", SqlDbType.Bit);
var pRemarks = cmd.Parameters.Add("#ConvenienceRemarks", SqlDbType.VarChar, -1);
var retStatus = cmd.Parameters.Add("#ReturnStatus", SqlDbType.Bit);
retStatus.Direction = ParameterDirection.Output;
var retStatusMsg = cmd.Parameters.Add("#ReturnStatusMessage", SqlDbType.VarChar, -1);
retStatusMsg.Direction = ParameterDirection.Output;
try
{
foreach (var row in updates)
{
pID.Value = row.CaseHearingID;
pConveyed.Value = row.IsConveyed;
pRemarks.Value = row.ConvenienceRemarks;
cmd.ExecuteNonQuery();
if (!Convert.ToBoolean(retStatus))
{
trans.Rollback();
return false;
}
}
trans.Commit();
}
catch ()
{
trans.Rollback();
throw;
}
return true;
}
}
You can then feed that with a LINQ to Objects query:
var source =
from r in grdViewLastHearingDates.Rows.OfType<GridViewRow>()
select new UpdateCaseConveyanceRec
{
CaseHearingID = Convert.ToInt32(r.Cells[0].Text),
ConvenienceRemarks = (r.FindControl("txtBoxConvenienceRemarks") as TextBox).Text;
IsConveyed = (r.FindControl("chkBoxIsConveyed") as CheckBox).Checked
};
bool updated = UpdateCasesIsConveyed(source);
Related
I'm writing an API controller that inserts and updates records in a table. I'm able to insert new items into the database pretty easily, but I'm having a hard time understanding how to update existing records. My current solution is to query the number of records that have the same UserName and DeviceId as the request. If the count is > 0, then we execute the update query. Else, we execute the insert query. But I'm not sure how to return the count of records from the countQuery. Also, I would rather not use the patch or put methods for this. I want all the logic in the post method. Thanks for your help!
public BaseResponse Post([FromBody]PendingAttachmentRequest pending)
{
var datasource = "";
var appVersion = "";
var sessionId = "";
var updateQuery = "UPDATE PendingAttachments SET PendingCount = #PendingCount,LastUpdated = #LastUpdated,DataSource = #DataSource WHERE DeviceId = #deviceId AND WHERE UserName = #userName";
var countQuery = "SELECT count(*) PendingAttachments WHERE DeviceId = #DeviceId AND WHERE UserName = #UserName";
MobileCompleteServer.Helpers.Connection.GetHeaderInfo(out sessionId, out datasource, out appVersion);
using (var onbaseConnection = MobileCompleteServer.Helpers.Connection.Connect(sessionId, datasource))
{
var connectionString = System.Configuration.ConfigurationManager.AppSettings["ConnectionString"];
try
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (SqlCommand comm = new SqlCommand(countQuery, sqlConnection))
{
if (/*how to check if the result of countQuery is > 0*/)
{
using (SqlCommand sqlComm = new SqlCommand(updateQuery, sqlConnection))
{
sqlComm.CommandType = System.Data.CommandType.Text;
//replace that row with request body
sqlComm.Parameters.Add(new SqlParameter("#DataSource", pending.DataSource));
sqlComm.Parameters.Add(new SqlParameter("#LastUpdated", pending.LastUpdated));
sqlComm.Parameters.Add(new SqlParameter("#PendingCount", pending.PendingCount));
sqlComm.Parameters.Add(new SqlParameter("#DeviceId", pending.DeviceId));
sqlComm.Parameters.Add(new SqlParameter("#UserName", pending.UserName));
}
}
using (SqlCommand sqlCommand = new SqlCommand("sp_InsertPendingAttachments", sqlConnection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Parameters.Add(new SqlParameter("#DataSource", pending.DataSource));
sqlCommand.Parameters.Add(new SqlParameter("#UserName", pending.UserName));
sqlCommand.Parameters.Add(new SqlParameter("#DeviceId", pending.DeviceId));
sqlCommand.Parameters.Add(new SqlParameter("#PendingCount", pending.PendingCount));
sqlCommand.Parameters.Add(new SqlParameter("#LastUpdated", pending.LastUpdated));
sqlCommand.ExecuteNonQuery();
}
}
}
return new BaseResponse();
}
catch (Exception e)
{
if (e.Message == Constants.SessionNotFound)
{
return new BaseResponse
{
Exception = Constants.SessionNotFound,
ExceptionStackTrace = e.ToString()
};
}
else
{
return new BaseResponse
{
Exception = Constants.PendingAttachmentError,
ExceptionStackTrace = e.ToString()
};
}
}
}
}
If you don't care about how many records are there and you just want to check whether at least a record exists or not in table, then use "exists". It will definitely improve query performance. So you need to only check for true or false condition: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/exists-transact-sql?view=sql-server-ver15
You also need to use "ExecuteReader" / "ExecuteScalar" method: Check if a record exists in the database
Try to use Repository design pattern which will separate your data access logic and domain logic and it will also help in making your code testable. Also, Your method is doing lots of things at a time so its violating Single responsibility principle.
Do you not already have a method serving a GET endpoint taking deviceId and userName as parameters? If not create it and call that method to check for existence and, depending on the result, you either call an update or insert handler. That way your API will be more RESTful and modular thus less coupled to the business logic and more testable.
background
I have some code which opens a sql connection, begins a transaction and performs some operations on the DB. This code creates an object from the DB (dequeue), gets some values and saves it back. The whole operation needs to take place in a transaction. All the code works perfectly without the transaction.
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var transaction = connection.BeginTransaction();
try
{
var myObject = foo.Dequeue(connection, transaction);
var url = myObj.GetFilePathUri(connection, transaction);
//some other code that sets object values
myObj.SaveMessage(connection, transaction);
transaction.Commit(); //error here
}
catch(Exception ex)
{
transaction.Rollback();
//logging
}
finally
{
//cleanup code
}
}
dequeue method code
public foo Dequeue(SqlConnection connection, SqlTransaction transaction)
{
using (var command = new SqlCommand(DEQUEUE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
{
var reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
ID = (Guid) reader["ID"];
Name = reader["Name"].ToString();
return this;
}
return null;
}
}
Get Path Code
public string GetFilePathUri(SqlConnection connection, SqlTransaction transaction)
{
using (var command = new SqlCommand(FILEPATH_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
{
var reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
return reader["Path"].ToString();
}
return "";
}
}
Save Code
public void SaveMessage(SqlConnection connection, SqlTransaction transaction)
{
using (var command = new SqlCommand(SAVE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
{
command.Parameters.Add("#ID", SqlDbType.UniqueIdentifier).Value = ID;
command.Parameters.Add("#Name", SqlDbType.VarChar).Value = Name;
//other object params here
command.ExecuteNonQuery();
}
}
The problem
When transaction.Commit() is called, I get the following error:
The transaction operation cannot be performed because there are pending requests working on this transaction.
What am I doing wrong?
EDIT: Quick edit to say I have read the other questions about this problem on SO, but couldn't find any related to ADO.net
I have had this issue before and the problem was the reader needed to be closed.
Try this:
public foo Dequeue(SqlConnection connection, SqlTransaction transaction)
{
using (var command = new SqlCommand(DEQUEUE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
{
var reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
ID = (Guid) reader["ID"];
Name = reader["Name"].ToString();
reader.Close();//Closing the reader
return this;
}
return null;
}
}
public string GetFilePathUri(SqlConnection connection, SqlTransaction transaction)
{
string filePathUri = "";
using (var command = new SqlCommand(FILEPATH_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
{
var reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
filePathUri = reader["Path"].ToString();
}
reader.Close();//Closing the reader
}
return filePathUri;
}
I had this problem when I forgot to use await on an async method that was doing the DB call - the connection was being disposed while the transaction was running because the program was not waiting for the query to complete before trying to dispose everything.
We ran into this problem and while the accepted answer probably would've worked, it wouldn't have been the most correct thing to do -- it did, however, give us a clue as to what we were doing wrong.
In our case, there was more data to read. Here's a simplified version of the problem (using Dapper):
public Foo GetFooById(int fooId, IDbTransaction transaction = null) {
var sql = #"
SELECT * FROM dbo.Foo WHERE FooID = #FooID;
SELECT * FROM dbo.FooBar WHERE FooID = #FooID;
SELECT * FROM dbo.FooBaz WHERE FooID = #FooID;
";
using var data = await _connection.QueryMultipleAsync(sql, new { fooId }, transaction);
var foo = data.ReadSingle<Foo>();
foo.Bars = data.Read<Bar>();
// Oops, didn't read FooBaz.
return foo;
}
Even though data gets disposed, it leaves a "pending request" on the transaction, which then blows up later when transaction.Commit() is called.
The solution is to either read FooBaz records out of data or get rid of the unread SELECT.
For who will come here in the future, I had this issue with a combination of IQueryable and AutoMapper.
I passed an IQueryable for mapping to Mapper.Map<>(myData) by thinking that since it maps data from a class to another, it also materializes it.
Apparently, I was wrong and the issue mentioned in the question was thrown when trying to transaction.Commit().
Solution
As easy as forcing the materialization with a .ToList() in Mapper.Map<>(myData.ToList()).
Just wasted an hour finding that, I hope this will save some time for someone else.
How to loop through a list of objects in order to pass said object to a method inserting rows in SQL db via stored procedure?
With help in this question I got to this point:
namespace NA.Controllers
{
public class NC : ApiController
{
[Route("AddNote")]
[HttpPost]
public HttpResponseMessage PostNote(List<Note> items)
{
//NoteJson deserializednote = JsonConvert.DeserializeObject<NoteJson>(item);
//Note notesdata = new Note(item);
NotesAccept.Models.INoteRepository Repository = new NotesAccept.Models.NoteDataRepository();
foreach (Note item in items)
{
item = Repository.Add(item);
}
var response = Request.CreateResponse<List<Note>>(HttpStatusCode.OK, items);
return response;
}
}
}
but now I'm stuck as item= is now an iteration variable, but I need to pass it to an method:
namespace NA.Models
{
class NoteDataRepository : INoteRepository
{
public void Add(Note item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
else
{
String strConnString = ConfigurationManager.ConnectionStrings["conString"].ConnectionString;
SqlConnection con = new SqlConnection(strConnString);
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "BL_IntegrationInsertNote";
cmd.Parameters.Add("#Client", SqlDbType.VarChar).Value = item.Client.Trim();
cmd.Parameters.Add("#Case", SqlDbType.VarChar).Value = item.Case;
cmd.Parameters.Add("#Text", SqlDbType.VarChar).Value = item.Text.Trim();
cmd.Parameters.Add("#When", SqlDbType.DateTime).Value = item.Date;
cmd.Parameters.Add("#Ext", SqlDbType.Bit).Value = item.Type;
cmd.Parameters.Add("#return", SqlDbType.Int).Direction = ParameterDirection.Output;
cmd.Connection = con;
try
{
con.Open();
cmd.ExecuteNonQuery();
string id = cmd.Parameters["#return"].Value.ToString();
string lblMessage = null;
lblMessage = "Record inserted successfully. ID = " + id;
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
con.Dispose();
}
}
//return item;
}
IEnumerable<Note> INoteRepository.GetAll()
{
throw new NotImplementedException("getitems");
}
}
}
I'm still green-as-a-grass-newbie to C# so I've no idea how to accomplish that, especially since whole solution is still "copy&Paste" from all over the web, and whole web curiously concentrates on looping through simple types. How to do that with complex type?
As noted in other question, this is a matter of professional life and death (I'm a db dev, not a VS guru, especially not after two days and two nights).
You are still forgetting to assign that ID from DB to the item.
You also still have
return item;
in a method that does not return anything (public void Add(Note item)).
So just delete that return line.
And replace
item = Repository.Add(item);
with just
Repository.Add(item);
You can pass it as xml and iterate in sql stored procedure and do bulk insert, or you can use table datatype if sql and .net version you are using supports it.
Try this in the foreach loop:
var tempItem = item;
tempItem = Repository.Add(tempItem);
I'm getting "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." error, but can't find where is the problem. Little help, please. :)
public static void Update(string p, int c)
{
using (SqlConnection conn = new SqlConnection("ConnectionString"))
{
SqlCommand cmd = new SqlCommand();
SqlDataReader myRdr;
cmd.Connection = conn;
cmd.CommandText = "SOME SQL QUERY #parameter";
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("#parameter", SqlDbType.NVarChar, 10).Value = p;
conn.Open();
myRdr = cmd.ExecuteReader();
if (myRdr.HasRows)
{
while (myRdr.Read())
{
//do something using myRdr data
}
myRdr.Close();
cmd.Dispose();
foreach (DataRow r in dt.Rows)
{
SqlCommand cmd1 = new SqlCommand();
cmd1.Connection = conn;
cmd1.CommandText = "SOME SQL QUERY #parameter";
cmd1.CommandType = CommandType.Text;
cmd1.Parameters.Add("#parameter", SqlDbType.NVarChar, 10).Value = r["SomeData"];
myRdr = cmd1.ExecuteReader();
myRdr.Read();
//do something with myRdr data
myRdr.Close();
cmd1.Dispose();
int a = Convert.ToInt32(r["SomeData"]) - Convert.ToInt32(r["SomeData1"]);
if (a >= 0)
{
//do something
}
else
{
//do something else and runn the Update() again with some other parameters
Update(x, y); //I think here is some problem...
//because when this condition is not met and program
//does not need to run Update() again, it goes OK and I get no error
}
}
}
else
{
myRdr.Close();
cmd.Dispose();
}
}
}
You should cosider moving your call to Update(x,y) somewhere outside the outer using() block.
Calling it from the inside using means you're establishing another connection to the same database without first freeing the outer one. If you get number of recursive calls then you'll run out of free connections VERY quickly.
In general, using recursion calls in parts of code dealing with databases is considered a very bad practice.
If you REALLY need this recursion here then still it should be done outside the outer using. I'd suggest caching your x,y in some kind of collection and issuing the calls to Update(x,y) by traversing this collection outside the using block, like this :
public static void Update(string p, int c)
{
// I'd suggest Dictionary, but don't know whether your strings are unique
var recursionParameters = new List<KeyValuePair<string, int>>();
using (SqlConnection conn = new SqlConnection("ConnectionString"))
{
...
//do something else and runn the Update() again with some other parameters
//Update(x, y); Don't do it here! Instead:
recursionParameters.Add(new KeyValuePair<string, int>(x,y));
...
}
foreach (var kvp in recursionParameters
{
Update(kvp.Key, kvp.Value)
}
}
Your Reader may not be closed when exception occurs. Surround it with try finally block, so when you meet exception your reader will be closed on finally statement.
try
{
myRdr = cmd.ExecuteReader();
// do some other stuff
}
catch(SqlException)
{
// log the exception or deal with ex.
}
finally
{
myRdr.Close(); // you can be sure it will be closed even on exception
}
I'm trying to implement a method which will take a given connection string and return an ArrayList containing the contents of a SQL view.
I've verified the validity of the connection string and the view itself. However I don't see what the problem is in the code below. In debug, when it runs the ExecuteReader method and then try to enter the while loop to iterate through the records in the view, it immediately bails because for some reason sqlReader.Read() doesn't.
public ArrayList GetEligibles(string sConnectionString)
{
string sSQLCommand = "SELECT field1, field2 FROM ViewEligible";
ArrayList alEligible = new ArrayList();
using (SqlConnection sConn = new SqlConnection(sConnectionString))
{
// Open connection.
sConn.Open();
// Define the command.
SqlCommand sCmd = new SqlCommand(sSQLCommand, sConn);
// Execute the reader.
SqlDataReader sqlReader = sCmd.ExecuteReader(CommandBehavior.CloseConnection);
// Loop through data reader to add items to the array.
while (sqlReader.Read())
{
EligibleClass Person = new EligibleClass();
Person.field1 = sqlReader["field1"].ToString();
Person.field2 = sqlReader["field2"].ToString();
alEligible.Add(Person);
}
// Call Close when done reading.
sqlReader.Close();
}
return alEligible;
}
Note, EligibleClass is just a class object representing one row of the view's results.
A couple of things I would check:
Is the connection string ok
Does the user in your connection string have access to the database/view
Can you access that database from the pc your at
Does the ViewEligable view exist
Does the view contain a field1 and field2 column.
Here one way you could possibly clean up that code somewhat (assuming you have .net 2.0)
public List<EligibleClass> GetEligibles(string sConnectionString)
{
List<EligibleClass> alEligible = null;
try
{
using (SqlConnection sConn = new SqlConnection(sConnectionString))
{
sConn.Open();
using (SqlCommand sCmd = new SqlCommand())
{
sCmd.Connection = sConn;
sCmd.CommandText = "SELECT field1, field2 FROM ViewEligible";
using (SqlDataReader sqlReader = sCmd.ExecuteReader())
{
while (sqlReader.Read())
{
EligibleClass Person = new EligibleClass();
Person.field1 = sqlReader.GetString(0);
Person.field2 = sqlReader.GetString(1);
if (alEligible == null) alEligible = new List<EligibleClass>();
alEligible.Add(Person);
}
}
}
}
}
catch (Exception ex)
{
// do something.
}
return alEligible;
}