Problem Error Handling in Stored Statement? - c#

Here is my stored procedure on updating records :
ALTER
PROCEDURE [dbo].[sp_UpdatetoShipped]
(
#Date datetime,
#SerialNumber
varchar(50),
#User
varchar(50),
#WorkWeek
varchar(50)
)
AS
BEGIN
UPDATE dbo.FG_FILLIN SET Status='SHIPPED',DateModified=#Date,ModifiedBy=#User,WorkWeek=#WorkWeek where (Status='KITTED')and SerialNumber=#SerialNumber
END
Then this is my DAL:
public int UpdatetoShipped(FillinEntity fin)
{
SqlConnection conn = new SqlConnection(connStr);
conn.Open();
SqlCommand cmd = new SqlCommand("sp_UpdatetoShipped", conn);
cmd.CommandType =CommandType.StoredProcedure;
try
{
cmd.Parameters.Add("#SerialNumber", SqlDbType.VarChar,50).Value = fin.SerialNumber;
cmd.Parameters.Add("#WorkWeek", SqlDbType.VarChar, 50).Value = fin.WorkWeek;
cmd.Parameters.Add("#Date", SqlDbType.DateTime).Value = DateTime.Now.ToString();
cmd.Parameters.AddWithValue("#User", fin.ModifiedBy);
return cmd.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
cmd.Dispose();
conn.Close();
conn.Dispose();
}
}
My BLL:
public int UpdatetoShipped(FillinEntity fin)
{
DAL pDAL = new DAL();
try
{
return pDAL.UpdatetoShipped(fin);
}
catch
{
throw;
}
finally
{
pDAL = null;
}
}
And MY UI:
string filepath2 = txtPath2.Text;
Stream stream2 = new FileStream(filepath2, FileMode.Open, FileAccess.Read, FileShare.Read);
ExcelMapper<FillinEntity> exceltoshipped = new ExcelMapper<FillinEntity>();
IExcelParser excelParser2 = new ExcelReaderExcelParser(stream2);
IExcelRowMapper<FillinEntity> mapper2 = new ShippedRowMapper();
IEnumerable<FillinEntity> fillin2 = exceltoshipped.ListAll(excelParser2, mapper2);
int intResult = 0;
BAL pBAL = new BAL();
try
{
foreach (FillinEntity fin in fillin2)
{
fin.ModifiedBy = loggedUser;
intResult = pBAL.UpdatetoShipped(fin);
}
if (intResult > 0)
MessageBox.Show("Record Updated Successfully.");
else
MessageBox.Show("Record couldn't Updated Check Serial");
}
catch (Exception ee)
{
MessageBox.Show(ee.Message.ToString());
}
finally
{
pBAL =null;
}
My problem is it always says updated succussfully. But if i updated it again as duplicate update i want to show serial is already updated.

The key change you need to make is to the following line of SQL from your stored procedure:
UPDATE dbo.FG_FILLIN
SET Status='SHIPPED',
DateModified=#Date,
ModifiedBy=#User,
WorkWeek=#WorkWeek
WHERE (Status='KITTED')
AND SerialNumber=#SerialNumber
You need to return a value that allows you to determine if this UPDATE has already happened or not, for example:
DECLARE #iUpdateAlreadyComplete INT
SET #iUpdateAlreadyComplete = 0;
IF EXISTS
(
SELECT 1
FROM dbo.FG_FILLIN
WHERE Status='SHIPPED'
AND SerialNumber=#SerialNumber
)
BEGIN
SET #iUpdateAlreadyComplete = 1
END
ELSE
BEGIN
UPDATE dbo.FG_FILLIN
SET Status='SHIPPED',
DateModified=#Date,
ModifiedBy=#User,
WorkWeek=#WorkWeek
WHERE (Status='KITTED')
AND SerialNumber=#SerialNumber
END
SELECT #iUpdateAlreadyComplete AS Result
You can then change your DAL from return cmd.ExecuteNonQuery(); to:
var result = Convert.ToInt32(cmd.ExecuteScalar());
return result;
The return value will now be 0 for a record that has been updated, and 1 for one that didn't need updating as it was already processed.
Other Notes
There are a couple of other things that you should consider changing:
sp_UpdatetoShipped is a bad name for a stored procedure. Do not use the sp_ prefix.
Your DAL deliberately catches and re-throws an exception (admittedly in the "best" way), do you really need to?
Rather than explicitly calling Dipose(), use the using() {} syntax instead, as this ensures that Dispose() is called, even in the event of an exception.
using syntax:
using(SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("sp_UpdatetoShipped", conn))
{
}
}

This seems more like a business rule issue than anything to do with an error. What you might want to do is to create a dictionary to hold serial numbers that have already been updated.
e.g.
Dictoinary<string,string> updatedSerialNumbers = new Dictionary<string, string>();
foreach (FillinEntity fin in fillin2)
{
fin.ModifiedBy = loggedUser;
if (updatedSerialNumbers.Contains(fin.SerialNumber) == false)
{
intResult = pBAL.UpdatetoShipped(fin);
updatedSerialNumbers.Add(fin.SerialNumber,fin.SerialNumber);
}
Something like this should sort out your problem.

Related

C# Stored Procedure Return Scalar

I have a stored procedure that will return either a 1 or 0. I cannot seem to properly wrap it in a C# function. Any help is appreciated.
Here is my stored procedure (that I've tested in SQL Server and it works):
CREATE PROCEDURE VerifyAccount
#Email VARCHAR(50),
#Pass VARCHAR(100)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Salt CHAR(25);
DECLARE #PwdWithSalt VARCHAR(125);
DECLARE #PwdHash VARBINARY(20);
SELECT #Salt = Salt, #PwdHash = Pass
FROM users
WHERE EMAIL = #Email;
SET #PwdWithSalt = #Salt + #Pass;
IF (HASHBYTES('SHA1', #PwdWithSalt) = #PwdHash)
RETURN 1;
ELSE
RETURN 0;
END;
If I open up a new SQL query and run this code, it works:
DECLARE #Result INT;
EXEC #Result = VerifyAccount
#Email = 'myemail#email.com', #Pass = 'Str0ngP#ssw0rd!';
SELECT #Result;
When I try to wrap it in C# code, It returns a -1 value, which is not possible with this procedure. It should return a "1". What am I doing wrong?
public static int ValidateUser(User user)
{
int result = 0;
using (SqlConnection conn = new SqlConnection(SQLQuery.connDb))
{
using (var command = new SqlCommand("VerifyAccount", conn)
{
CommandType = CommandType.StoredProcedure,
Parameters =
{
new SqlParameter("#Email", user.Email),
new SqlParameter("#Pass", user.Password)
}
})
{
try
{
conn.Open();
result = command.ExecuteNonQuery();
conn.Close();
}
catch (Exception e)
{
result = -15;
Console.WriteLine(e.Message);
}
finally
{
if (conn.State == ConnectionState.Open)
{
conn.Close();
}
}
}
}
return result;
}
ExecuteNonQuery returns the number of rows affected
You need
result = (int)command.ExecuteScalar();
Here is how you read the return value fro Stored Procedures.
public static int ValidateUser(User user)
{
int result = 0;
using (SqlConnection conn = new SqlConnection(SQLQuery.connDb))
{
using (var command = new SqlCommand("VerifyAccount", conn)
{
CommandType = CommandType.StoredProcedure,
Parameters =
{
new SqlParameter("#Email", user.Email),
new SqlParameter("#Pass", user.Password)
}
})
{
try
{
// STEP 01: **** SETUP UP RETURN VALUE STORED PROCEDURES *****
var returnParameter = command.Parameters.Add("#ReturnVal", SqlDbType.Int);
returnParameter.Direction = ParameterDirection.ReturnValue;
conn.Open();
result = command.ExecuteNonQuery();
// STEP 02: **** READ RETURN VALUE *****
var result = returnParameter.Value;
conn.Close();
}
catch (Exception e)
{
result = -15;
Console.WriteLine(e.Message);
}
finally
{
if (conn.State == ConnectionState.Open)
{
conn.Close();
}
}
}
}
return result;
}

How to convert this method to catch the inserted id?

I have a windows form and I'm inserting values in the button click event like this
Candidate CanObj = new Candidate(txtName.Text);
if (new CandidateOP().saveCandidate(CanObj))
{
MessageBox.Show("NEW candidate details added");
}
this is my business layer method.
public Boolean saveCandidate(Candidate CanObj)
{
string query6 = "EXEC insertToCand01'" + CanObj.NIC + "'";
return (new DataAccessLayer().executeNonQueries(query6));
}
This is my data access layer method
public Boolean executeNonQueries(string query02)
{
Boolean flag = false;
SqlConnection con = null;
SqlCommand com = null;
try
{
con = new SqlConnection(DBConnect.makeConnection());
con.Open();
com = new SqlCommand(query02, con);
com.ExecuteNonQuery();
flag = true;
}
catch (Exception ex)
{
flag = false;
throw ex;
}
finally
{
com.Dispose();
con.Close();
}
return flag;
}
This is the query inside my stored procedure to insert.
In my table the ID is set to auto increment.
INSERT INTO Candidate (User_Name) VALUES (#Uname);
Now I want to display the inserted ID to be displayed when it's inserted.
So I changed the query like this.
INSERT INTO Candidate (User_Name) OUTPUT INSERTED.User_ID VALUES (#Uname);
I want to change my data access layer and business layer to get the value back
How to change my data access layer to achieve this?
Thanks in advance.
Just a quick but important note: you should really use parameterized queries to avoid SQL injection problems, and also using a proper ORM system.
About your concrete question: call your procedure with ExecuteScalar, instead of ExecuteNonQuery, and return the generated id from your stored procedure.
You don't actually need an SP, you can just do a select scope_identity() for example. Or you could use an output parameter in your SP. But just returning a scalar is the simplest way.
Something like this:
Candidate CanObj = new Candidate(txtName.Text);
int id = new CandidateOP().saveCandidate(CanObj);
/* You have **id** here, and you can use it. */
if (id >= 0)
{
MessageBox.Show("NEW candidate details added");
}
Business layer:
public Boolean saveCandidate(Candidate CanObj)
{
string query6 = "EXEC insertToCand01'" + CanObj.NIC + "'";
return new DataAccessLayer().executeNonQueries(query6);
}
and your access layer:
public int executeNonQueries(string query02)
{
long id = -1;
SqlConnection con = null;
SqlCommand com = null;
try
{
con = new SqlConnection(DBConnect.makeConnection());
con.Open();
com = new SqlCommand(query02, con);
SqlParameter returnParameter = com.Parameters.Add("RetVal", SqlDbType.Int);
returnParameter.Direction = ParameterDirection.ReturnValue;
com.ExecuteNonQuery();
id = (int) returnParameter.Value;
}
catch (Exception ex)
{
id = -1;
throw ex;
}
finally
{
com.Dispose();
con.Close();
}
return id;
}

How to insert list of items Using Ado.net C#?

I have a store procedure which is taken 2 parameter 1st projectId and 2nd Userid
But i have to mapped the list to that particular project using single connection but i could not figure out how to insert whole list.
internal bool MapEmployeesToProject(int projectId, List<Users> lstUserToMap)
{
int value = 0;
bool check = true;
SqlConnection conn = new SqlConnection(VSTMConfigurations.ConnectionString);
using (conn)
{
SqlParameter[] param = new SqlParameter[2];
param[0] = new SqlParameter("#ProjectId",projectId );
param[1] = new SqlParameter("#ProjectDetails", );
// if there is a single user than i could pass userid here
// i am phasing a problem how to pass whole listOfUsers
using (SqlCommand cmd1 = CreateCommand(conn, param))
{
cmd1.CommandText = "sp_MapProject";
try
{
conn.Open();
SqlTransaction transaction = conn.BeginTransaction();
cmd1.Transaction = transaction;
try
{
value = Convert.ToInt32(cmd1.ExecuteNonQuery());
if (value > 0)
{
transaction.Commit();
check = true;
}
else
{
transaction.Rollback();
check = false;
}
}
catch (Exception ex1)
{
transaction.Rollback();
check = false;
}
finally
{
transaction.Dispose();
}
}
catch (Exception ex)
{
check = false;
}
}
}
return check;
}
the store procedure is
sp_MapProject
(#ProjectId int,#UserId int)
as
begin
Insert into ProjectUsers values (#ProjectId,#UserId)
end
help please!!
You can define a user define table type in your database with projectId and Userid columns. Then you can use DataTable to pass data to stored procedure.
e.g. http://www.codeproject.com/Articles/412802/Sending-a-DataTable-to-a-Stored-Procedure
Thanks!

C# Scope_Identity Issue

Browsed some answers and doesn't appear to be working for me.
I need the ID field of a table to be returned so I can use it in a different part of the program, I've tried using
Convert.ToInt32(sqlComm.ExecuteScalar());
But no luck, and same for
Convert.ToInt32(sqlComm.Parameters["ID"].Value);
And both return 0, even though the record does get inserted into the table.
I'll dump the code below, can anyone see what I'm doing wrong?
using (SqlConnection sqlConnect = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
using (SqlCommand sqlComm = new SqlCommand("up_Insert_Address", sqlConnect))
{
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.Add("#AddressID", SqlDbType.BigInt).Direction = ParameterDirection.Output;
sqlComm.Parameters.Add("#AddressLineOne", SqlDbType.NVarChar, 40).Value = address.AddressLineOne;
try
{
sqlComm.Connection.Open();
return Convert.ToInt32(sqlComm.ExecuteScalar());
}
catch (SqlException)
{
}
finally
{
sqlComm.Connection.Close();
}
}
}
And Stored Procedure:
#AddressID Bigint OUTPUT,
#AddressLineOne NVarChar(40)
AS
BEGIN
BEGIN TRY
INSERT INTO Address
(
AddressLineOne
)
VALUES
(
#AddressLineOne
)
SET #AddressID = SCOPE_IDENTITY();
END TRY
BEGIN CATCH
DECLARE #Err nvarchar(500)
SET #Err = ERROR_MESSAGE()
RAISERROR(#Err, 16, 1)
END CATCH
END
You should be using
Convert.ToInt64(sqlComm.Parameters["#AddressID"].Value);
after you execute the command using ExceuteNonQuery. For future reference, ExecuteScalar returns the first column of the first row in the result set returned by the query. You're not returning anything, just setting the value of an OUTPUT parameter.
Also you should DEFINITELY not swallow any SqlException. Since your command and connection are already in using blocks you don't need to add another try/catch/finally. Change it to to:
//try
//{
sqlComm.Connection.Open();
sqlComm.ExecuteNonQuery();
return Convert.ToInt64(sqlComm.Parameters["#AddressID"].Value);
// using Int64 since the SQL type is BigInt
//}
//catch (SqlException)
//{
//}
//finally
//{
// sqlComm.Connection.Close();
//}
var connectionstring = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
var addressId = 0L; // long value (64bit)
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand("up_Insert_Address", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#AddressID", addressId);
command.Parameters.AddWithValue("#AddressLineOne", address.AddressLineOne);
command.Parameters["#AddressID"].Direction = ParameterDirection.Output;
try
{
if(connection.State != ConnectionState.Open)
connection.Open();
var rowsAffected = command.ExecuteNonQuery();
addressId = Convert.ToInt64(command.Parameters["#AddressID"].Value);
}
catch (SqlException)
{
// Handle SQL errors here.
}
}

Getting a returned value from a stored procedure in C# method

I'm using visual studios 2010 to create a c# web application with a database. My goal is to have default.aspx call a c# class which runs a stored procedure that selects an entry from the table and returns it. Here's the code:
'The stored procedure. I want it to send back the name it gets from doing
'the query to the c# class.
ALTER PROCEDURE getName (#id int)
AS
BEGIN
SET NOCOUNT ON;
--
SELECT name FROM tableA where id = #id;
END
Return
//Here's the c# class I'm using.
public class student
{
public string name;
public int id;
public student()
{ }
public String doQuery(int id)
{
SqlConnection conn = null;
try
{
conn = new SqlConnection("Server =(local); Database = Database1.mdf;
Integrated Security = SSPI");
conn.Open();
SqlCommand cmd = new SqlCommand("getName", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter param = new SqlParameter("#id", SqlDbType.Int);
param.Direction = ParameterDirection.Input;
param.Value = id;
cmd.Parameters.Add(param);
//This is some code from when I tryed return value
//SqlParameter reVal = cmd.Parameters.Add("#name", SqlDbType.VarChar);
//reVal.Direction = ParameterDirection.ReturnValue;
//before using ExecuteScalar I tried ExcuteNonQuery with the commented
//out code
name = (string)cmd.ExecuteScalar();
//name = (String)cmd.Parameters["#name"].Value;
conn.Close();
}
catch(Exception)
{}
return name;
}
}
Running my program does not return errors it simply doesn't place any value in name. What am I missing to get the name that is selected in the sql procedure into the name variable in my c# class. I hope I'm conveying my problem clearly.
edit1:I didn't put anything in the catch cause hadn't decided what to use to see that it had errored out. I changed it to make name = "error" when it fails the try and that's what I get and that's what I get.
I also tried running "exec getName 5, otherstuff"in sql server management. I'm a little unclear about what to use as the second parameter when running exec getName since the second parameter is suppose to be just output but still seems to be required to run it. It just says the commands are executed successfully but doesn't display the name that goes with id 5
I would recommend using the async/await pattern for SQL statements. Fortunately, it doesn't require much refactoring.
See if this works for you:
public async Task<string> QueryGetNameAsync(int id)
{
using (var dbConn = new SqlConnection("..."))
using (var command = new SqlCommand("getName", dbConn))
{
try
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#id", id);
await dbConn.OpenAsync();
var result = await command.ExecuteScalarAsync();
dbConn.Close();
var name = result as string;
return name;
}
catch (Exception ex)
{
// Handle exception here.
}
}
}
You'd call it with something like:
private async void DoLookup_Clicked(object sender, EventArgs e)
{
var id = int.Parse(idText.Text);
var name = await QueryGetNameAsync(id);
}
Alternatively, can use OUTPUT parameters in SQL but you would have to adjust your stored procedure to something like this:
ALTER PROCEDURE getName
(
#id int,
#name varchar(100) OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
SELECT #name = name FROM tableA where id = #id;
END
Then your C# function would be something like:
public async Task<string> QueryGetNameAsync(int id)
{
using (var dbConn = new SqlConnection("..."))
using (var command = new SqlCommand("getName", dbConn))
{
try
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#id", id);
command.Parameters.Add("#name", SqlDbType.VarChar, 100);
command.Parameters["#name"].Direction = ParameterDirection.Output;
await dbConn.OpenAsync();
await command.ExecuteNonQueryAsync();
dbConn.Close();
var name = command.Parameters["#name"].Value as string;
return name;
}
catch (Exception ex)
{
// Handle exception here.
}
}
}
The problem is in your connection string: unless you have strange naming conventions, you are specifying the database file name and not the name of the database itself.
Try changing this part of the connection string: Database = Database1.mdf; to Database = Database1;.
If you are confused about what is or is not valid in the connection string, you can always use the SqlConnectionStringBuilder which will create the appropriate connection string for you after you have set the correct properties.
You can also use the list of properties specified in the SqlConnection.ConnectionString documentation as a reference that contains examples.
Finally, I strongly recommend the following best practices:
1) Use using blocks with the connection and commands to ensure they are properly closed and disposed.
2) Do not assign name directly to the result of ExecuteScalar in case it is return as DBNull.Value
3) Never ignore exceptions unless you have documented why you are doing so in the code.
Here is a quick rewrite with all of the above recommendations:
try
{
using (var conn = new SqlConnection("Server =(local); Database = Database1; Integrated Security = SSPI"))
{
conn.Open();
using (var cmd = new SqlCommand("getName", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
var param = new SqlParameter("#id", SqlDbType.Int);
param.Direction = ParameterDirection.Input;
param.Value = id;
cmd.Parameters.Add(param);
var oResult = cmd.ExecuteScalar();
if ((oResult != null) && (oResult != DBNull.Value))
{
name = (string)oResult;
}
}
conn.Close();
}
}
catch (Exception)
{
// Do something with the exception here, don't just ignore it
}

Categories

Resources