I'm working on a web form that validates data that the user uploads in the form of an Excel file. The code iterates through each row in the spreadsheet and checks against various rules, one of which is that the reference must be a unique value. I have a stored procedure that takes the userID and referenceNum as parameters:
BEGIN
SET NOCOUNT ON;
DECLARE #Status AS BIT
IF EXISTS (SELECT [TransactionMstID]
FROM [dbo].[tbl_TransactionMst]
WHERE [TransactionRef] = #DocumentNumber
AND [SupplierID] = #SupplierID)
BEGIN
SET #Status = 0
END
ELSE
BEGIN
SET #Status = 1
END
SELECT #Status AS [Status]
END
When I try different scenarios in SQL Server Management Studio (SSMS), I get the desired outputs e.g. a "0" when the reference exists and a "1" when it doesn't.
The problem arises in my C# code, it executes the stored procedure, but in my testing I get the same result irrespective of whether the data exists or not.
Here's the core of the C#:
bool returnValue = true;
if (Docnumber != null)
{
SqlConnection con = new SqlConnection(GlobalSettings.connection);
con.Open();
SqlCommand Cmd = new SqlCommand("p_ValRefNumber", con);
Cmd.CommandType = System.Data.CommandType.StoredProcedure;
Cmd.Parameters.AddWithValue("#DocumentNumber", Docnumber);
Cmd.Parameters.AddWithValue("#SupplierID", SupplierID);
Cmd.ExecuteNonQuery();
SqlDataReader dr = Cmd.ExecuteReader();
while (dr.Read())
{
bool Status = convertor.ConvertToBool(dr["Status"]);
string test = dr["Status"].ToString();
int testint = convertor.ConvertToInt(dr["Status"].ToString());
if (Status == false)
{
//throw new System.Exception(CEObj.GetErrorDesc(101));
returnValue = false;
}
}
dr.Close();
con.Close();
}
return returnValue;
}
No matter what the value of docnumber is in testing, it always shows as True. I've added a breakpoint so that I can check each time and then test in SSMS and I get conflicting results.
Is my logic wrong? Does Visual Studio treat the values differently? How is the result not consistent when converting it to a string - at the very least? It always seems to read a value of "1" in VS but varies in SSMS
Edit: here's my converter method's code:
public static bool ConvertToBool(object value)
{
bool result = false;
if (value != null)
{
bool.TryParse(value.ToString(), out result);
}
return result;
}
bool.TryParse isn't doing what the convertor (sic) code thinks it does.
bool.TryParse returns true if the value parameter equals bool.TrueString, which is the literal string "True". It returns false for any other value, which means it returns false for both 0 and 1.
Also, T-SQL bit values are numbers. The converter code isn't really necessary - just convert the return value to an Int32 and do a comparison.
using (var con = new SqlConnection(GlobalSettings.connection))
{
con.Open();
using (var cmd = new SqlCommand() { Connection = con, CommandType = CommandType.StoredProcedure, CommandText = "p_ValRefNumber" })
{
/* Assuming both parameters are integers.
Change SqlDbType if necessary. */
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#DocumentNumber", SqlDbType = SqlDbType.Int, Value = Docnumber });
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#SupplierID", SqlDbType = SqlDbType.Int, Value = SupplierID });
using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
return dr.Read() && (Convert.ToInt32(dr["Status"]) == 1)
}
}
}
Following the post
Using Dapper.TVP TableValueParameter with other parameters
I have been able to execute table valued parameter but my question is regarding part of its implementation. Procedure seems to be working but how can i make this implementation more generic? Adding parameters in DynamicParameters with 'new {}' is very specialized implementation according requirement. I want to be able to specify my parameter name as well.. for example
public IEnumerable<TCustomEntity> SqlQuery<TCustomEntity>(string query, IDictionary<string, object> parameters,
CommandType commandType) where TCustomEntity : class
{
DataTable dt;
DynamicParameters dp;
var sqlConnection = _context.Database.Connection;
try
{
if (sqlConnection.State == ConnectionState.Closed)
{
sqlConnection.Open();
}
var dynamicParameters = new DynamicParameters();
if (parameters != null)
foreach (var parameter in parameters)
{
if (parameter.Value is int)
dynamicParameters.Add(parameter.Key, parameter.Value, DbType.Int32);
else if (parameter.Value is string)
dynamicParameters.Add(parameter.Key, parameter.Value.ToString(), DbType.String);
else if (parameter.Value is DateTime)
dynamicParameters.Add(parameter.Key, parameter.Value, DbType.DateTime);
else if (parameter.Value is Decimal)
dynamicParameters.Add(parameter.Key, parameter.Value, DbType.Decimal);
else if (parameter.Value is DataTable)
{
dt = (DataTable)parameter.Value;
dt.SetTypeName(dt.TableName);
var parameterName = parameter.Key;
dp = new DynamicParameters(new { TVPName = dt });
// Here i want TVPName replaced with parameterName/parameter.Key but it doesn't seem possible
dynamicParameters.AddDynamicParams(dp);
}
else
dynamicParameters.Add(parameter.Key, parameter.Value);
}
var test = sqlConnection.Query<TCustomEntity>(query, dynamicParameters, commandType: commandType);
return test;
}
finally
{
sqlConnection.Close();
}
}
Any advice how may i proceed on the issue by making parameter name more generic? If not, it will be specialized implementation each time i use Table value parameter
It isn't obvious to me that any of that is necessary, since:
DynamicParameters is perfectly happy with TVPs as direct elements,
all of the data types shown would be handled automatically
dapper works happily with dictionaries
dapper already opens and closed the connection appropriately if it isn't open when invoked
It seems to me that the only interesting step here is the SetTypeName, which could be done:
foreach(object val in parameters.Values)
{
if(val is DataTable) {
var dt = (DataTable)val;
dt.SetTypeName(dt.TableName);
}
}
And then pass your original parameters object in:
return sqlConnection.Query<TCustomEntity>(query, parameters, commandType: commandType);
That leaves just:
public IEnumerable<TCustomEntity> SqlQuery<TCustomEntity>(string query,
IDictionary<string, object> parameters, // suggestion: default to null
CommandType commandType // suggestion: default to CommandType.Text
) where TCustomEntity : class
{
var sqlConnection = _context.Database.Connection;
if (parameters != null) {
foreach (object val in parameters.Values) {
if (val is DataTable) {
var dt = (DataTable)val;
// suggestion: might want to only do if dt.GetTypeName() is null/""
dt.SetTypeName(dt.TableName);
}
}
}
return sqlConnection.Query<TCustomEntity>(query, parameters,
commandType: commandType);
}
I have a method that executes a SqlCommand and returns the result to a winforms application.
My method is this:
public bool ApplyRoles(string roleApp, string roleAppPassword)
{
Command = new SqlCommand("EXEC sp_setapprole #roleApp, #rolePassword", Connection);
AssignParam("roleApp", roleApp, SqlDbType.VarChar);
AssignParam("rolePassword", roleAppPassword, SqlDbType.VarChar);
bool ret = Command.ExecuteNonQuery() == -1;
return ret;
}
and the AssignParam method is this:
public void AssignParam(string name, object value, SqlDbType type)
{
var parameter = new SqlParameter(name, type)
{
Value = value ?? DBNull.Value
};
Command.Parameters.Add(parameter);
}
Now, this ApplyRoles method throws the exception: Application roles can only be activated at the ad hoc level. but if i change the ApplyRoles to this:
public bool ApplyRoles(string roleApp, string roleAppPassword)
{
Command = new SqlCommand(string.Format("EXEC sp_setapprole '{0}', '{1}'", roleApp, roleAppPassword), Connection);
bool ret = Command.ExecuteNonQuery() == -1;
return ret;
}
The method works fine.. so i'm guessing that the problem is in the AssignParam method.
What is the problem? I don't want to use the "working" method because i could have sql injection on it.
sp_setapprole uses the parameter names #rolename and #password. You have to use those parameter names if you are passing the parameter names into the a SqlCommand` instance. It was working for you in your second example because you were passing only the parameter values in the correct order that the stored proc was expecting them.
Since you're executing a stored procedure, use CommandType.StoredProcedure
Also put the '#' character in front of your parameter names.
Command = new SqlCommand("sp_setapprole", Connection);
Command.CommandType = CommandType.StoredProcedure;
Command.Parameters.AddWithValue( #rolename, roleApp);
Command.Parameters.AddWithValue( #password, rolePassword);
bool ret = Command.ExecuteNonQuery() == -1;
return ret;
I have a custom written DB provider. When I run my tests, they're breaking on the ExecuteScalar command with a NullReferenceException. What might I be missing here? I've read that some people have a MultiThreading issue, but I don't "think" that's what I'm running into.
Here's my GetOpenConnection method
public SqliteConnection GetOpenConnection()
{
var connection = new SqliteConnection(_connectionString);
if (connection == null) throw new Exception("Could not create a database connection.");
connection.Open();
return connection;
}
And the ExecuteScalar method
public TKey ExecuteScalar<TKey> ( string commandText, IDictionary<string, object> parameters )
{
using ( var connection = _connectionProvider.GetOpenConnection() )
{
using ( var command = connection.CreateCommand() )
{
command.CommandType = CommandType.Text;
command.CommandText = commandText;
foreach ( var parameter in parameters )
{
command.Parameters.Add( new SqliteParameter( parameter.Key, parameter.Value ?? DBNull.Value ) );
}
// BREAKING HERE
return ( TKey )command.ExecuteScalar();
}
}
}
And this is the method that's calling the ExecuteScalar
private const string CheckTableExists = "SELECT name FROM sqlite_master WHERE type='table' AND name='{0}'";
public bool CheckIfTableExists ( string tableName )
{
var exists = ExecuteScalar<int>( string.Format( CheckTableExists, tableName ) ) == 1;
return exists;
}
I put a break point on it, and try to step into it.. .and the code just breaks and throws the exception... I can't track it down
ExecuteScalar returns null if no records were returned by the query. This seems to resolve the NullReferenceException.
public TKey ExecuteScalar<TKey> ( string commandText, IDictionary<string, object> parameters )
{
using ( var connection = _connectionProvider.GetOpenConnection() )
{
using ( var command = connection.CreateCommand() )
{
command.CommandType = CommandType.Text;
command.CommandText = commandText;
foreach ( var parameter in parameters )
{
command.Parameters.Add( new SqliteParameter( parameter.Key, parameter.Value ?? DBNull.Value ) );
}
if (typeof (TKey) != typeof (int))
{
return (TKey) command.ExecuteScalar();
}
var executeScalar = command.ExecuteScalar();
var item = executeScalar == null ? 0 : 1;
return (TKey)(object)item;
}
}
}
I am using the following SQL query and the ExecuteScalar() method to fetch data from an Oracle database:
sql = "select username from usermst where userid=2"
string getusername = command.ExecuteScalar();
It is showing me this error message:
System.NullReferenceException: Object reference not set to an instance of an object
This error occurs when there is no row in the database table for userid=2.
How should I handle this situation?
According to MSDN documentation for DbCommand.ExecuteScalar:
If the first column of the first row in the result set is not found, a
null reference (Nothing in Visual Basic) is returned. If the value in
the database is null, the query returns DBNull.Value.
Consider the following snippet:
using (var conn = new OracleConnection(...)) {
conn.Open();
var command = conn.CreateCommand();
command.CommandText = "select username from usermst where userid=2";
string getusername = (string)command.ExecuteScalar();
}
At run-time (tested under ODP.NET but should be the same under any ADO.NET provider), it behaves like this:
If the row does not exist, the result of command.ExecuteScalar() is null, which is then casted to a null string and assigned to getusername.
If the row exists, but has NULL in username (is this even possible in your DB?), the result of command.ExecuteScalar() is DBNull.Value, resulting in an InvalidCastException.
In any case, the NullReferenceException should not be possible, so your problem probably lies elsewhere.
First you should ensure that your command object is not null. Then you should set the CommandText property of the command to your sql query. Finally you should store the return value in an object variable and check if it is null before using it:
command = new OracleCommand(connection)
command.CommandText = sql
object userNameObj = command.ExecuteScalar()
if (userNameObj != null)
string getUserName = userNameObj.ToString()
...
I'm not sure about the VB syntax but you get the idea.
I just used this:
int? ReadTerminalID()
{
int? terminalID = null;
using (FbConnection conn = connManager.CreateFbConnection())
{
conn.Open();
FbCommand fbCommand = conn.CreateCommand();
fbCommand.CommandText = "SPSYNCGETIDTERMINAL";
fbCommand.CommandType = CommandType.StoredProcedure;
object result = fbCommand.ExecuteScalar(); // ExecuteScalar fails on null
if (result.GetType() != typeof(DBNull))
{
terminalID = (int?)result;
}
}
return terminalID;
}
The following line:
string getusername = command.ExecuteScalar();
... will try to implicitly convert the result to string, like below:
string getusername = (string)command.ExecuteScalar();
The regular casting operator will fail if the object is null.
Try using the as-operator, like this:
string getusername = command.ExecuteScalar() as string;
sql = "select username from usermst where userid=2"
var _getusername = command.ExecuteScalar();
if(_getusername != DBNull.Value)
{
getusername = _getusername.ToString();
}
Check out the example below:
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteScalar
{
public static void Main()
{
SqlConnection mySqlConnection =new SqlConnection("server=(local)\\SQLEXPRESS;database=MyDatabase;Integrated Security=SSPI;");
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText ="SELECT COUNT(*) FROM Employee";
mySqlConnection.Open();
int returnValue = (int) mySqlCommand.ExecuteScalar();
Console.WriteLine("mySqlCommand.ExecuteScalar() = " + returnValue);
mySqlConnection.Close();
}
}
from this here
SQL NULL value
equivalent in C# is DBNull.Value
if a NULLABLE column has no value, this is what is returned
comparison in SQL: IF ( value IS NULL )
comparison in C#: if (obj == DBNull.Value)
visually represented in C# Quick-Watch as {}
Best practice when reading from a data reader:
var reader = cmd.ExecuteReader();
...
var result = (reader[i] == DBNull.Value ? "" : reader[i].ToString());
In my experience, there are some cases the returned value can be missing and thus execution fails by returning null. An example would be
select MAX(ID) from <table name> where <impossible condition>
The above script cannot find anything to find a MAX in. So it fails. In these such cases we must compare the old fashion way (compare with C# null)
var obj = cmd.ExecuteScalar();
var result = (obj == null ? -1 : Convert.ToInt32(obj));
If you either want the string or an empty string in case something is null, without anything can break:
using (var cmd = new OdbcCommand(cmdText, connection))
{
var result = string.Empty;
var scalar = cmd.ExecuteScalar();
if (scalar != DBNull.Value) // Case where the DB value is null
{
result = Convert.ToString(scalar); // Case where the query doesn't return any rows.
// Note: Convert.ToString() returns an empty string if the object is null.
// It doesn't break, like scalar.ToString() would have.
}
return result;
}
Always have a check before reading row.
if (SqlCommand.ExecuteScalar() == null)
{
}
This is the easiest way to do this...
sql = "select username from usermst where userid=2"
object getusername = command.ExecuteScalar();
if (getusername!=null)
{
//do whatever with the value here
//use getusername.toString() to get the value from the query
}
In your case either the record doesn't exist with the userid=2 or it may contain a null value in first column, because if no value is found for the query result used in SQL command, ExecuteScalar() returns null.
Alternatively, you can use DataTable to check if there's any row:
SqlCommand cmd = new SqlCommand("select username from usermst where userid=2", conn);
SqlDataAdapter adp = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
adp.Fill(dt);
string getusername = "";
// assuming userid is unique
if (dt.Rows.Count > 0)
getusername = dt.Rows[0]["username"].ToString();
private static string GetUserNameById(string sId, string connStr)
{
System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(connStr);
System.Data.SqlClient.SqlCommand command;
try
{
// To be Assigned with Return value from DB
object getusername;
command = new System.Data.SqlClient.SqlCommand();
command.CommandText = "Select userName from [User] where userid = #userid";
command.Parameters.AddWithValue("#userid", sId);
command.CommandType = CommandType.Text;
conn.Open();
command.Connection = conn;
//Execute
getusername = command.ExecuteScalar();
//check for null due to non existent value in db and return default empty string
string UserName = getusername == null ? string.Empty : getusername.ToString();
return UserName;
}
catch (Exception ex)
{
throw new Exception("Could not get username", ex);
}
finally
{
conn.Close();
}
}
Slight conjecture: if you check the stack for the exception, it is being thrown then the ADO.NET provider for Oracle is reading the underlying rowset to get the first value.
If there is no row, then there is no value to find.
To handle this case execute for a reader and handle Next() returning false for the case of no match.
I Use it Like This with Microsoft Application Block DLL (Its a help library for DAL operations)
public string getCopay(string PatientID)
{
string sqlStr = "select ISNULL(Copay,'') Copay from Test where patient_id=" + PatientID ;
string strCopay = (string)SqlHelper.ExecuteScalar(CommonCS.ConnectionString, CommandType.Text, sqlStr);
if (String.IsNullOrEmpty(strCopay))
return "";
else
return strCopay ;
}
I have seen in VS2010
string getusername = command.ExecuteScalar();
gives compilation error,
Cannot implicitly convert type object to string.
So you need to write
string getusername = command.ExecuteScalar().ToString();
when there is no record found in database it gives error
Object reference not set to an instance of an object
and when I comment '.ToString()', it is not give any error. So I can say ExecuteScalar not throw an exception. I think anserwer given by #Rune Grimstad is right.
I had this issue when the user connecting to the database had CONNECT permissions, but no permissions to read from the database. In my case, I could not even do something like this:
object userNameObj = command.ExecuteScalar()
Putting this in a try/catch (which you should probably be doing anyway) was the only way I could see to handle the insufficient permission issue.
object objUserName;
objUserName = command.ExecuteScalar();
if (objUserName == null) //if record not found ExecuteScalar returns null
{
return "";
}
else
{
if (objUserName == DBNull.Value) //if record found but value in record field is null
{
return "";
}
else
{
string getusername = objUserName.ToString();
return getusername;
}
}
/* Select some int which does not exist */
int x = ((int)(SQL_Cmd.ExecuteScalar() ?? 0));
I used this in my vb code for the return value of a function:
If obj <> Nothing Then
Return obj.ToString()
Else
Return ""
End If
Try this code, it appears to solve your problem.
Dim MaxID As Integer = Convert.ToInt32(IIf(IsDBNull(cmd.ExecuteScalar()), 1, cmd.ExecuteScalar()))
I'm using Oracle.
If your sql returns numeric value, which is int, you need to use Convert.ToInt32(object). Here is the example below:
public int GetUsersCount(int userId)
{
using (var conn = new OracleConnection(...)){
conn.Open();
using(var command = conn.CreateCommand()){
command.CommandText = "select count(*) from users where userid = :userId";
command.AddParameter(":userId", userId);
var rowCount = command.ExecuteScalar();
return rowCount == null ? 0 : Convert.ToInt32(rowCount);
}
}
}
Try this
sql = "select username from usermst where userid=2"
string getusername = Convert.ToString(command.ExecuteScalar());