SQL Parameter as DBNull.Value is not working - c#

I'm loading a select box list with a distinct list of values from our database table on CompanyName.
If the value in the column is NULL, I am adding '-- NULL --'
If the value in the column is empty or white space, I'm adding '-- EMPTY / WHITE SPACE --'
Otherwise, it will be the value in the column.
When I am trying to update the parameter to the property value DBNull.value is not working.
Here is my SQL statement:
SELECT Column1
,Column2
,[CreatedBy]
,RestOfColumns...
FROM [dbo].[CompanyTable]
where CompanyName = #DataVisParam
Here is my code:
public DataTable GetDataVisQueryParameterizedResults(string query, string parameterizedSelectedValue, string connectionString)
{
DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(ConnectionUtilities.GetWebConfigConnectionString(connectionString)))
{
conn.Open();
using (SqlDataAdapter sda = new SqlDataAdapter(query, conn))
{
sda.SelectCommand.Parameters.Clear();
sda.SelectCommand.CommandType = CommandType.Text;
SqlParameter param = sda.SelectCommand.Parameters.Add(SharedUtilities.DataVisParameterPlaceholder(), SqlDbType.NVarChar);
/*if (parameterizedSelectedValue == SharedUtilities.DataVisParamSelectBoxValueIsNull())
{
param.Value = DBNull.Value;
}
else*/
if (parameterizedSelectedValue == SharedUtilities.DataVisParamValueSelectBoxIsEmptyOrWhiteSpace())
{
param.Value = string.Empty;
}
else
{
param.Value = parameterizedSelectedValue;
}
sda.Fill(dt);
}
}
return dt;
}
/// <summary>
/// Data Vis parameterized query placeholder.
/// </summary>
/// <returns>Data vis parameterized placeholder text that will be used in the data vis query</returns>
public static string DataVisParameterPlaceholder()
{
return "#DataVisParam";
}
/// <summary>
/// Data vis parameter value is null.
/// </summary>
/// <returns>Parameter value for null on the select box</returns>
public static string DataVisParamSelectBoxValueIsNull()
{
return "-- NULL --";
}
/// <summary>
/// Data vis parameter value is empty or white space.
/// </summary>
/// <returns>Parameter value for empty or white space on the select box</returns>
public static string DataVisParamValueSelectBoxIsEmptyOrWhiteSpace()
{
return "-- EMPTY / WHITE SPACE --";
}
I've tried DBNull.Value and not even setting it, but always 0 records are return for NULL. If I do the empty or white space, it works and 2 records are returned. Everything else is working fine.
Thoughts?
Main Question: Is there a way to NOT have to change the query from = #DataVisParam to IS NULL ?. I really do not want to have to change the query because then I have to make sure they put the text in perfect. I was hoping I could use the sda.SelectCommand.Parameters.Add() to check for NULL, but that appears it is not possible.
I've tried DBNull.Value in the commented out part of my code where this line is: param.Value = DBNull.Value; is set. It should enter this If statement.
Update: Yes, I know in SQL that you usually check IS Null for thanks in SSMS. Do I need to change my actual query from = #dataVisParam to Is Null? If I need to do that, doesn't that mess up the issue with SQL injection?
Update 2:
Appears I just need to replace the text = #DataVisParam with IS NULL when -- NULL -- is selected in the select box

After discussing with a co-worker, since we are replacing the query to IS NULL from = #DataVisParam and we are NOT using user selection / input, then we can accept the fact that there is not a possibility of a security risk.
The one downside to this, is the user that inputs the query must make sure they put the parameter correctly so that we can match it and replace it with IS NULL
public DataTable GetDataVisQueryParameterizedResults(string query, string parameterizedSelectedValue, string connectionString)
{
DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection(ConnectionUtilities.GetWebConfigConnectionString(connectionString)))
{
conn.Open();
// update the query with IS NULL to check on null values
if (parameterizedSelectedValue == SharedUtilities.DataVisParamSelectBoxValueIsNull())
{
// replace the param with IS NULL -> replace for one space or two spaces (if there is a typo mistake with updating the query etc.)
#pragma warning disable CA1307 // Specify StringComparison
query = query.Replace("= #DataVisParam", "IS NULL").Replace("= #DataVisParam", "IS NULL");
#pragma warning restore CA1307 // Specify StringComparison
}
using (SqlDataAdapter sda = new SqlDataAdapter(query, conn))
{
// the parameterizedSelectedValue is not Null, query by the parameter and query will not be changed.
if (parameterizedSelectedValue != SharedUtilities.DataVisParamSelectBoxValueIsNull())
{
sda.SelectCommand.Parameters.Clear();
sda.SelectCommand.CommandType = CommandType.Text;
SqlParameter param = sda.SelectCommand.Parameters.Add(SharedUtilities.DataVisParameterPlaceholder(), SqlDbType.NVarChar);
if (parameterizedSelectedValue == SharedUtilities.DataVisParamValueSelectBoxIsEmptyOrWhiteSpace())
{
param.Value = string.Empty;
}
else
{
param.Value = parameterizedSelectedValue;
}
}
sda.Fill(dt);
}
}
return dt;
}

Related

IndexOutOfRange Exception in sqldatareader using c#

I create an application using c# , In my authentification interface , i have a test control , i want to know profile user .
My database contains table named user which contains 4 columns
(id_user,name ,mail, profile)
Here is my code
public string profil_user(string login)
{
SqlConnection conn = new database().connect_user();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select profile from user where name = '" + login + "';";
SqlDataReader s = cmd.ExecuteReader();
if (s.Read())
{
return ( s.GetString(3));
}
else{return ("false"); }
}
but i have an exception in s.GetString(3)
system.IndexOutOfRange : index was outside the bounds of the array
You're only selecting a single field (profile) but then you're trying to select the 4th field (index 3) here:
return ( s.GetString(3));
In addition to just returning s.GetString(0) I would strongly advise you to:
Use parameterized SQL - always do this, to prevent SQL injection attacks, make your code more readable, and prevent unexpected text conversion problems
Either throw an exception or return null if the profile isn't found, instead of returning the string "false"
Use using statements for disposable things like SqlCommand, SqlConnection and SqlDataReader to ensure that you clean up resources appropriately
Start following .NET naming conventions to make your code more idiomatic
So something like:
public string GetUserProfile(string login)
{
string sql = select profile from user where name = #login";
// I assume Connect() returns an *open* connection?
using (var conn = new Database().Connect())
{
using (var command = new SqlCommand(sql, conn))
{
command.Parameters.Add("#login", SqlDbType.NVarChar).Value = login;
using (var reader = command.ExecuteReader())
{
// If it's an error (code failure) for there to be no matching profile,
// you may want to throw an exception instead.
return s.Read() ? s.GetString(0) : null;
}
}
}
}
So you want the fourth row, not the fourth column which you try to access with s.GetString(3):
int rowNum = 0;
while(s.Read())
{
if(++rowNum == 4)
{
return s.GetString(0);
}
}
return "false";
However, it is a bit strange to access the fourth row when you don't use an Order By. You should also only return the row that you want with the correct sql query.
You are also open for sql injection if you use string concatenation here:
cmd.CommandText = "select profile from user where name = '" + login + "';";
Use sql parameters:
cmd.CommandText = "select profile from user where name = #login";
cmd.Parameters.Add("#login", SqlDbType.VarChar).Value = login;
have 4 columns not rows
Ok, so you instead want the fourth column. Why don't you use the name instead?
Since you only select the profile-column(the fourth), you could simply use GetString(0). But you could also select all columns and then determine the correct index with GetOrdinal:
int profileColumnIndex = s.GetOrdinal("profile");
return s.GetString(profileColumnIndex);
This is useful if you don't control the query or it might be changed in future.
You are selecting only 1 field, thus index 3 is out of bounds. It also very important to Use parameters. Try:
cmd.CommandText = "select profile from user where name = #login;";
cmd.Parameters.Add("#login, SqlDbType.NVarChar).Value = login;
SqlDataReader s = cmd.ExecuteReader();
while (s.Read())
{
return s[0].ToString();
}
The parameter for SqlDataReader.GetString should be the column index. You're only selecting one column so you get an exception.
Because you do not have all the fields in your select list
Change the SQL to:
select id_user,name ,mail, profile from user where name = '" + login + "';

Can't find stored procedure parameters in code-behind

The Overview: I've got a dropdown with a list of reports the user can run. In the table that holds this list, I have ReportID, ReportName, SProc and SQLView fields. The idea here is, the user selects a report name, and based on that a specific Stored Procedure will run, and then a specific view will be bound to a datagrid to display the report. For some reports you need to enter a date, for others you don't.
The Code: Here is what I have written;
protected void btnSubmit_OnClick(object sender, EventArgs e)
{
List<ReportData> myReportData = new List<ReportData>();
using (SqlConnection connection1 = new SqlConnection(str2))
{
//Query the Reports table to find the record associated with the selected report
using (SqlCommand cmd = new SqlCommand("SELECT * from tblManagerReports WHERE ReportID = " + cboFilterOption.SelectedValue + "", connection1))
{
connection1.Open();
using (SqlDataReader DT1 = cmd.ExecuteReader())
{
while (DT1.Read())
{
//Read the record into an "array", so you can find the SProc and View names
int MyRptID = Convert.ToInt32(DT1[0]);
string MyRptName = DT1[1].ToString();
string MyRptSproc = DT1[2].ToString();
string MySQLView = DT1[3].ToString();
//Run the Stored Procedure first
SqlConnection connection2 = new SqlConnection(str2);
SqlCommand cmd2 = new SqlCommand("" + MyRptSproc + "", connection2);
//Set up the parameters, if they exist
if (string.IsNullOrEmpty(this.txtStartDate.Text))
{
}
else
{
cmd2.Parameters.Add("#StDate", SqlDbType.Date).Value = txtStartDate.Text;
}
if (string.IsNullOrEmpty(this.txtEndDate.Text))
{
}
else
{
cmd2.Parameters.Add("#EnDate", SqlDbType.Date).Value = txtEndDate.Text;
}
if (MyRptSproc != "")
{
connection2.Open();
cmd2.ExecuteNonQuery();
}
try
{
//Now open the View and bind it to the GridView
string SelectView = "SELECT * FROM " + MySQLView + "";
SqlConnection con = new SqlConnection(str2);
SqlCommand SelectCmd = new SqlCommand(SelectView, con);
SqlDataAdapter SelectAdapter = new SqlDataAdapter(SelectCmd);
//Fill the dataset
DataSet RunReport = new DataSet();
SelectAdapter.Fill(RunReport);
GridView_Reports.DataSource = RunReport;
GridView_Reports.DataBind();
}
catch
{
ScriptManager.RegisterStartupScript(btnSubmit, typeof(Button), "Report Menu", "alert('There is no View associated with this report.\\nPlease contact the developers and let them know of this issue.')", true);
return;
}
}
}
}
}
The Problem: When the code hits the line
cmd2.ExecuteNonQuery();
and there is a start and end date entered, it's telling me "Procedure or function expects parameter '#StDate', which is not supplied." I've stepped through the code and see that cmd2 has 2 parameters, so why isn't the function seeing them?
Additionally, here's the specific stored procedure which is causing the snafu (I've got 2 others that run fine, but neither of them are trying to pass parameters to a stored procedure:
ALTER procedure [dbo].[usp_DailyProc]
#StDate smalldatetime,
#EnDate smalldatetime
AS
BEGIN
IF OBJECT_ID('Temp_DailyProduction') IS NOT NULL
drop table Temp_DailyProduction;
IF OBJECT_ID('Temp_AuditorDailyProduction') IS NOT NULL
drop table Temp_AuditorDailyProduction;
SELECT
[Audit Date],
Auditor,
Count([Doc #]) AS [Claim Count],
Count([Primary Error Code]) AS [Final Error],
SUM(case when [Status]='removed' then 1 else 0 end) as Removed,
SOCNUM
INTO Temp_DailyProc
FROM PreClosed
WHERE (((Get_Next_Status)='Closed' Or (Get_Next_Status)='Panel' Or (Get_Next_Status)='HPanel'))
GROUP BY [Audit Date], Auditor, SOCNUM
HAVING ((([Audit Date]) Between #StDate And #EnDate));
SELECT
TDP.[Audit Date],
TDP.Auditor,
EID.EMPLOYEE AS [Auditor Name],
TDP.[Claim Count],
TDP.[Final Error],
TDP.Removed,
TDP.[Removed]/TDP.[Final Error] AS [Error Removal Ratio],
TDP.SOCNUM
INTO Temp_AuditorDailyProc
FROM Temp_DailyProc TDP
LEFT JOIN PreLookup EID
ON TDP.Auditor = EID.ID_Trim;
drop table Temp_DailyProduction;
END
I think you need to use the AddWithValue method instead of the Add method.
AddWithValue replaces the SqlParameterCollection.Add method that takes
a String and an Object. The overload of Add that takes a string and an
object was deprecated because of possible ambiguity with the
SqlParameterCollection.Add overload that takes a String and a
SqlDbType enumeration value where passing an integer with the string
could be interpreted as being either the parameter value or the
corresponding SqlDbType value. Use AddWithValue whenever you want to
add a parameter by specifying its name and value.
Had another thought, you are passing a string (Text) value as Date parameter. I think you should convert this to a date type. e.g.
cmd2.Parameters.Add("#StDate", SqlDbType.Date).Value = DateTime.Parse(txtStartDate.Text);
A more robust way of doing this would be to use DateTime.TryParseExact.

Multiple conditional parameters in SQL and C#

Consider the following function which has 2 optional variables
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
SqlCommand selectCommand = null;
if (settingsgroup == null)
{
selectCommand = new SqlCommand(
"select * from ApplicationSettings ", con);
}
else
{
selectCommand = new SqlCommand(
string.Format(
"select * from ApplicationSettings where settingsgroup='{0}' ",
settingsgroup),
con);
}
if (state != null)
{
selectCommand.CommandText +=
!selectCommand
.CommandText
.ToLower()
.Contains("where")
? string.Format("where state={0}", state)
: string.Format("and state={0}", state);
}
//etc..
}
I have 4 possibilities:
settingsgroup==null && state==null
settingsgroup==null && state!=null
settingsgroup!=null && state==null
settingsgroup!=null && state!=null
From every case above a different SQL command has to be produced. What are the built in functionalities in C# that could help me achieve such things without a lot of conditional statements, and if you were to write the above how would you write it other than having to overload the function 4 times?
This is a common problem in SQL that can be effectively handled in the query itself, thus allowing queries to be created in advance, use parameters, and be accessed through stored procedures.
Use of parameters is an important recommendation and should not be considered optional. SQL Parameters will help prevent SQL injection attacks. For example, imagine if someone were to call your method using the following parameter values:
SelectSqlItems<T>("' OR settingsgroup <> '", null);
Your query would now become:
select * from ApplicationSettings where settingsgroup='' OR settingsgroup<>''
This would of course return all rows from the table, and potentially expose private information. Even worse possibilities exist, however, such as inserting a DELETE clause which could delete your whole table, or even drop your entire database (though hopefully your user permissions are configured to at least prevent these worst-case scenarios).
To prevent this, your SelectSqlItems method can be restated to the following:
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
var cmdText = "..."; // See Query Below
var selectCommand = new SqlCommand(cmdText, con);
// Set the values of the parameters
selectCommand.Parameters.AddWithValue("#settingsgroup", settingsgroup);
selectCommand.Parameters.AddWithValue("#state", state);
// etc...
}
Your query can now be stated as follows:
SELECT
*
FROM
ApplicationSettings
WHERE
((#settingsgroup IS NULL) OR (settingsgroup=#settingsgroup))
AND
((#state IS NULL) OR (state=#state))
If a parameter value is null, the left side of the conditional statement joined by OR will always have the value TRUE, and therefore all rows will be matched. If, however, the parameter value is not NULL, the left side of the conditional will have the value FALSE and the right side will be inspected. The right side will only have the value TRUE if the row's value matches the parameter value, and therefore only the rows matching the parameter value will be returned. This concept can be repeated with as many parameters as required.
Why not switch to an SQL stored procedure with both parameters being optional and pass the parameters passed to SelectSqlItems directly to it ?
If you switched to a ORM solution like Entity Framework you could dynamically build your query with functions easily.
public List<T> SelectSqlItems<T>(string settingsgroup=null,int? state=null)
{
using(var context = new MyContext())
{
IQueyable<ApplicationSettings> query = context.ApplicationSettings;
if(settingsgroup != null)
query = query.Where(row => row.settingsgroup = settingsgroup);
if(state != null)
query = query.Where(row => row.state = state.Value)
Expression<Func<ApplicationSettings, T>> selectExpression = GetSelectExpression<T>();
return query.Select(selectExpression).ToList();
}
}
This would probably work. It also won't enforce null if a parameter is not passed in. You should look into using Parameters if you are concerned about injection attacks. This is not a safe way to add parameters to a query.
string stateCompare = state.HasValue ? "state = " + state.Value : "";
string settingsgroupCompare = String.IsNullOrEmpty(settingsgroup) ? "IS NULL" : "= " + settingsgroup;
string whereCondition = !String.IsNullOrEmpty(stateCompare) || !String.IsNullOrEmpty(settingsgroupCompare)?"WHERE":"";
SqlCommand selectCommand = new SqlCommand(String.Format("select * from ApplicationSettings {0} {1} {2}",whereCondition, settingsgroupCompare, stateCompare);
Mandatory SQL injection warning: Do not use string constants originating in the user's input directly. Parameterize your queries.
If you insist on building a SQL statement dynamically (as opposed to having it built by one of the built-in or open-source ORM solutions available in .NET), you could either simplify your code by using a fake WHERE 1=1 condition, a common trick of dynamic SQL builders, or by "encoding" the states as numbers, and processing them in a switch.
The "trick" solution starts like this:
if (settingsgroup == null) {
selectCommand = new SqlCommand("select * from ApplicationSettings WHERE 1=1 ", con);
} else {
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}' ", settingsgroup), con);
}
This looks similar to what you have, except that you no longer need to check the existing string for presence or absence of the WHERE clause: it's always there. You can continue with your simplified code:
if (state != null) {
selectCommand.CommandText += string.Format("and state={0}",state);
} ... // and so on
An alternative would be "encoding" the state explicitly in a state number, and using it in a switch, like this:
int conditionForm = 0;
if (settingsgroup != 0) conditionForm |= 1;
if (state != 0) conditionForm |= 2; // Use powers of two
Now the conditionForm variable can have one of four values from the range 0..3, inclusive. You can write a switch statement that deals with each condition separately:
switch (conditionForm) {
case 0:
selectCommand = new SqlCommand("select * from ApplicationSettings", con);
break;
case 1:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}'", settingsgroup), con);
break;
case 2:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where state='{0}'", state), con);
break;
case 3:
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}' and state='{1}'", settingsgroup, state), con);
break;
}

Unexpected behavior between String.IsNullOrEmpty and DBNull.Value

I have the following query:
public static string GetCustomerName(string customerNo)
{
string query = "query to get customer";
var myConn= new MYConnection();
using (SqlConnection con = new SqlConnection(myConn.MYConnectionString))
{
con.Open();
SqlCommand cmd = new SqlCommand(query, con);
cmd.Parameters.Add("#customerNo", SqlDbType.NVarChar).Value = customerNo;
object result = cmd.ExecuteScalar();
return result == DBNull.Value ? String.Empty : (string)result;
}
}
I'm calling the method above like this:
string customerName = GetCustomerName(CustomerID);
if (customerName.Contains(Constants.Company.CompanyName))
{
Additional Logic...
}
However, I'm getting a Object Reference Null error if my method doesn't return a customer name. I would think that the GetCustomer method would return an empty string.
If I change the call to get the CustomerName to below, it works perfectly.
string customerName = GetCustomerName(emailAndSTCodeInfo.CustomerID);
if (String.IsNullOrEmpty(customerName))
{
customerName = "";
}
if (customerName.Contains(Constants.Chase.ACCOUNT_NAME))
{
Additional Logic
}
So, my question is, what would be the proper way of handling this if my GetCustomer method doesn't find a record and returns null. I'm currently using the working code above but it seems like a hack or something.
Any help would be greatly appreciated.
ExecuteScalar returns null if no record is returned.
To guarantee that GetCustomerName never returns null, you could change the last line to
return Convert.ToString(result);
Convert.ToString(object) returns an empty string if the argument is either null or DBNull.Value.
If a query returns no rows, then executing it with ExecuteScalar will return null, not DBNull.Value.
So your GetCustomerName method needs to check for a null return value as well as DBNull.Value.

Must declare the scalar variable in C#

I am developing an app in C# in which when I am interacting with my database SQL Server
it is giving me the exception of 'Must declare the Scalar Variable'. The code is following
public DataTable Search(string clas)
{
try
{
DataTable table = new DataTable();
string query = "";
using (SqlConnection connection = new SqlConnection(connectionString))
{
if (clas != "")
{
query = "Select * from StudentManagement Where classEnrolled=#cls";
//dataAdapter
dataAdapter = new SqlDataAdapter(query, connectionString);
dataAdapter.SelectCommand.Parameters.Add(new SqlParameter("cls", clas));
}
dataAdapter = new SqlDataAdapter(query, connectionString);
// Create a command builder to generate SQL update, insert, and
// delete commands based on selectCommand. These are used to
// update the database.
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(dataAdapter);
// Populate a new data table and bind it to the BindingSource.
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
dataAdapter.Fill(table);
}
return table;
}
catch (Exception e)
{
return null;
}
}
Please help me
I have a strong suspicion that clas is a null reference. Note that this will still trigger your != "" branch, since a null-reference is not the same as an empty string.
Maybe use:
if(!string.IsNullOrEmpty(clas)) {...}
Instead?
A peculiarity of db-parameters is that they are not included if the .Value is null. Check the value you are sending in.
It doesn't apply in your case (since in normal SQL nothing ever equals NULL) but: if you intend to send NULL as a parameter, you must set the value to DBNull.Value instead.

Categories