I'm creating an error log for a webpage.
I need a way to get the parameters from a SQL Command to output to string so that the parameters passed through keep the query as dynamic as possible for use throughout the site.
Heres the SQL Command Type I'm talking about:
SqlCommand cmdErrReport = new SqlCommand("ERROR_LOG", conn.sbConn);
cmdErrReport.CommandType = CommandType.StoredProcedure;
cmdMarineResort.Parameters.Add("#Statement", SqlDbType.VarChar).Value = "FindSeason";
I was trying something like this:
cmdMarineResort.Parameters.ToString()
but I can't find within the .Parameters query how to find parameter names or values.
How would I be able to get the values to string? Would i have to list the parameters and the loop through that list writing the parameter and its value to string? If so, how?
cmdMarineResort.Parameters is a SqlParameterCollection.
You can iterate through it using a foreach statement:
foreach(SqlParameter Para in cmdMarineResort.Parameters)
{
Console.WriteLine((string)Para.Value); //value of the parameter as an object
}
This worked for me Using MSSQL Server:
private static string getQueryFromCommand(SqlCommand cmd)
{
string CommandTxt = cmd.CommandText;
foreach (SqlParameter parms in cmd.Parameters)
{
string val = String.Empty;
CommandTxt+=parms.ParameterName + "=";
if ((parms.Value == null)) // string.IsNullOrEmpty(parms.Value))
{
CommandTxt+="NULL, ";
}
else
{
CommandTxt+=(string)parms.Value.ToString() + ", ";
}
}
return (CommandTxt);
}
Related
I'm very new to backend development, and I'm feeling a bit stuck with accomplishing my goal here. I'm trying to pull testimonials and blurbs out of an SQL database and have them populate on the page that I've built. Currently, the code that I've written does nothing but pull an error (8178) and tell me that it expects a parameter that hasn't been supplied. Any guidance would be appreciated. I feel like it's something rather small that I'm missing, but I haven't had any luck in figuring it out.
This currently will provide the Response.Write that I've included, and that's working, but I can't seem to figure out what is stopping me from pulling the info off of the database. I've checked that my connection String is correct and active.
//This will load blurbs that will initially appear on page.
protected void Page_Load(object sender, EventArgs e)
{
//BlurbID = Session["Blurb"];
Page.Title = "Testimonials";
try
{
sqlConnectionStr.Open();
SqlCommand getBlurb = new SqlCommand(#"SELECT b.BlurbID,
b.BlurbText
FROM TestimonialBlurb b LEFT JOIN Testimonial t ON
t.BlurbID = b.BlurbID WHERE t.BlurbID=#BlurbID", sqlConnectionStr);
getBlurb.Parameters.AddWithValue("#BlurbID", SqlDbType.Int);
getBlurb.Parameters.AddWithValue("#BlurbText", SqlDbType.VarChar);
DataTable userBlurb = new DataTable();
using (SqlDataAdapter blurbDA = new SqlDataAdapter(getBlurb))
{
blurbDA.Fill(userBlurb);
DataView blurbDV = new DataView(userBlurb);
if (blurbDV.Count < 1)
{
Response.Write("There are currently no testimonials available.");
}
else
{
for (int i = 0; i < blurbDV.Count; i++)
{
blurbPH.Controls.Add(new Literal
{
Text = blurbDV[i].Row["BlurbText"].ToString() + "<strong> " + blurbDV[i].Row["Blurb"].ToString() + "</strong>"
});
}
}
}
}
catch (Exception ex)
{
blurbPH.Controls.Add(new Literal
{
Text = ex.ToString()
});
}
finally
{
sqlConnectionStr.Close();
}
You have this query:
sqlConnectionStr.Open();
SqlCommand getBlurb = new SqlCommand(#"SELECT b.BlurbID,
b.BlurbText
FROM TestimonialBlurb b LEFT JOIN Testimonial t ON
t.BlurbID = b.BlurbID WHERE t.BlurbID=#BlurbID", sqlConnectionStr);
You can see here that you have no need for #BlurbText as only #BlurbID is used in the query. We can remove that parameter.
Now, let's look at your parameter that you're adding:
getBlurb.Parameters.AddWithValue("#BlurbID", SqlDbType.Int);
When you use AddWithValue, the second argument is the value you're assigning to #BlurbId. Clearly this is not what we want. I think you've got this mixed up with Add.
We can thus write it correctly as:
getBlurb.Parameters.Add("#BlurbID", SqlDbType.Int).Value = 5;
The value 5 would then be used where #BlurbID appears in your query.
By the way, you don't need to read into a DataTable to access your data, only to create a new object. You can just use SqlDataReader:
getBlurb.Parameters.Add("#BlurbID", SqlDbType.Int).Value = 5;
using (SqlDataReader reader = getBlurb.ExecuteReader())
{
while (reader.Read())
{
blurbPH.Controls.Add(new Literal
{
Text = reader["BlurbText"].ToString() + "<strong> " + reader["Blurb"].ToString() + "</strong>"
});
}
if (blurbPH.Controls.Count == 0)
{
Response.Write("There are currently no testimonials available.");
}
}
Side note for using .Add with string values. You should use this overload. If your column type in the database is a varchar(255) then adding a string should look like this:
myCommand.Parameters.Add("#MyParameter", SqlDbType.VarChar, 255).Value = "Hello";
Note how we specify the length of the field here.
Following lines, the code needs to be updated
getBlurb.Parameters.AddWithValue("#BlurbID", SqlDbType.Int);
getBlurb.Parameters.AddWithValue("#BlurbText", SqlDbType.VarChar);
as
getBlurb.Parameters.AddWithValue("#BlurbID", 1001);//Considering 1001 as as the ID BlurbID
and #BlurbText Parameter does not exist in the SQL query any need to pass it.
If you are using AddWithValue then no need to set the SqlDbType.
I find it unnecessarily cumbersome to create prepared statements in C# generally what you do is something like this:
public T GetData<T>(string userInput)
{
string selectSomething = "SELECT * FROM someTable where someCol = #userInput";
using (IDbCommand command = new SqlCommand(selectSomething))
{
IDbDataParameter parameter = new SqlParameter("#userInput", SqlDbType.NVarChar);
parameter.Value = userInput;
command.Parameters.Add(parameter);
IDataReader reader = command.ExecuteReader();
reader.Read();
}
...
}
But now as there are interpolated strings it could be as easy like this:
public T GetData<T>(string userInput)
{
string selectSomething = $"SELECT * FROM someTable where someCol = {userInput}";
using (IDbCommand command = new SqlCommand(selectSomething))
{
IDataReader reader = command.ExecuteReader();
reader.Read();
}
}
There's still some other boilerplate code but still an improvement. Is there maybe a way to get the comfort of interpolated strings but still keep the safety of prepared statements with something like this:
string selectSomething = $"SELECT * FROM someTable where someCol = {userInput.PreventSQLInjections()}";
If you don't want to use EF which has FromSqlInterpolated method or any other ORM which will help you to handle your data access you can leverage fact that compiler uses FormattableString type to handle string interpolation to write helper method looking something like this (not fully working, but you should get the idea) to remove the boilerplate code:
public static class SqlCommandEx
{
// maps CLR type to SqlDbType
private static Dictionary<Type, SqlDbType> typeMap;
static SqlCommandEx()
{
typeMap = new Dictionary<Type, SqlDbType>();
typeMap[typeof(string)] = SqlDbType.NVarChar;
//... all other type maps
}
public static SqlCommand FromInterpolatedString(FormattableString sql)
{
var cmdText = sql.Format;
int count = 0;
var #params = new IDbDataParameter[sql.ArgumentCount];
foreach (var argument in sql.GetArguments())
{
var paramName = $"#param_{count}";
cmdText = cmdText.Replace($"{{{count}}}", paramName);
IDbDataParameter parameter = new SqlParameter(paramName, typeMap[argument.GetType()]);
parameter.Value = argument;
#params[count] = parameter;
count++;
}
var sqlCommand = new SqlCommand(cmdText);
sqlCommand.Parameters.AddRange(#params);
return sqlCommand;
}
}
And usage:
using (IDbCommand command = SqlCommandEx.FromInterpolatedString($"Select * from table where id = {val}"))
{
...
}
But this comes close to writing your own ORM which you usually should not do.
Prepared statements/parameterized queries go further than just sanitizing or escaping the inputs. When you use parameterized queries, the parameter data are sent as separate values from the SQL statement. The parameter data is never substituted directly into the SQL, and therefore injection is perfectly protected in a way that escaping/sanitizing the input never will.
In other words, DON'T COUNT ON STRING INTERPOLATION FOR "FIXING" SQL PARAMETERS!
Moreover, it's really not that much extra work. What the question shows is the hard way for adding parameters. You can simplify that code like this:
public T GetData<T>(string userInput)
{
string selectSomething = "SELECT * FROM someTable where someCol = #userInput";
using (IDbCommand command = new SqlCommand(selectSomething))
{
command.Parameters.Add("#userInput", SqlDbType.NVarChar).Value = userInput;
IDataReader reader = command.ExecuteReader();
reader.Read();
}
...
}
This gets the extra work for parameters down to one line of code per parameter.
If you have high-confidence in the mapping between C# types and SQL types, you can simplify even further like this:
command.Parameters.AddWithValue("#userInput", userInput);
Just be careful with that shortcut: if ADO.Net guesses the SQL data type wrong, it can break indexing and force per-row type conversions, which can really kill performance.
I am trying to run data validation, execute some code and pass data from one SQL query to another.
My current code looks like the below:
public string SelectUniqueKeyNumber()
{
string newList = string.Join(Environment.NewLine, listOfSkus).ToString();
string key_id;
string sqlConnectionString = #"someConnectionString";
using (SqlConnection connection = new SqlConnection(sqlConnectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("select top 1 KEY_NUMBER from MyTable where QTY_ON_HAND > 0 " + newList + " order by NEWID()", connection);
SqlDataReader readerKey = command.ExecuteReader();
readerKey.Read();
key_id = String.Format(readerKey[0].ToString());
}
SelectSkuNumber(key_id);
return key_id;
}
What I am trying to do is to check if my readerKey.Read() is not returning null value. If it does then stop the process, otherwise continue. I've tried it in the way as shown below:
public string SelectUniqueKeyNumber()
{
string newList = string.Join(Environment.NewLine, listOfSkus).ToString();
string key_id;
string sqlConnectionString = #"someConnectionString";
using (SqlConnection connection = new SqlConnection(sqlConnectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("select top 1 KEY_NUMBER from MyTable where QTY_ON_HAND > 0 " + newList + " order by NEWID()", connection);
SqlDataReader readerKey = command.ExecuteReader();
readerKey.Read();
if(readerkey.Read().ToString() == null)
{
//--- stop processing
}
else
{
key_id = String.Format(readerKey[0].ToString());
}
}
SelectSkuNumber(key_id); //---> Then ...(key_id) is not declared value
return key_id;
}
By doing so, I cannot access and pass data of SelectSkuNumber(key_id) due to: Use of unassigned local variable 'key_id'
Any ideas?
All you need do is assign something to key_id when you declare it, like:
string key_id = null; // not string key_id;
and later, after the using:
if (key_id != null)
{
SelectSkuNumber(key_id); //---> Then ...(key_id) is not declared value
}
return key_id;
The caller of the function should, of course, know what to do if a null is returned.
To avoid that particular problem, you can assign some value or nnull to key_id, eg. key_id = "";.
But you have some more problem there:
You are prone to SQL injection, you should use Parameters collection of SqlCommand class.
Are you sure you are concatenating your query correctly? Let's suppose
newList = {"some", "thing"};
Then your query would be:
select top 1 KEY_NUMBER
from MyTable where QTY_ON_HAND > 0
some
thing
order by NEWID()
Which is very, very incorrect to say the least.
if(readerkey.Read().ToString() == null) condition... Read returns bool, which is either true or false, it isn't reference type, so ToString() will never be null, thus the condition will always fail. If you want to check if there was NULL in database you should check:
if (readerKey.Read() && readerKey["KEY_NUMBER"] == DBNull.Value)
which first read row, then receives value of column in that row. It uses short-circuiting for the case, where no records are returned.
readerKey.Read(); is unnecessary before the if statement.
I would like to build a SQL function that has large amounts of reuse with ExecuteNonQuery but the biggest issue I have are the parameters.
I am not sure what others would do to make this simple and resilient so that I can simply pass the SQL script.
For example SELECT * FROM table WHERE userid = #userid AND isactive = #isactive, and then perhaps the peramiters can be an array.
public void ExecuteSQLCeNonQuery(string _Sql, ?parameter array of some type?)
{
SqlCeConnection sCon = null;
SqlCeCommand sCmd = null;
int countOf = 0;
try
{
sCon = new SqlCeConnection( sqlConnectionStringLocal);
sCmd = new SqlCeCommand(_Sql, sCon);
sCmd.Parameters.AddWithValue("#recBatchDateTarget", sDateOnly); <----- I know this will have to parse the collection some how.
sCon.Open();
countOf = (int)sCmd.ExecuteNonQuery();
sCon.Close();
}
catch (SqlCeException ex)
{
Console.WriteLine("** DEBUG: ExecuteSQLCeNonQuery: {0} - {1}", ex.Message, sSql);
}
finally
{
if (sCmd != null)
sCmd.Dispose();
if (sCon != null)
sCon.Dispose();
}
}
Any suggestions on how to handle the array or collection of parameters?
Just declare it as an IEnumerable<SqlParameter> so the caller can provide any collection he wants (array, List<SqlParameter>, ...)
public void ExecuteSQLCeNonQuery(string _Sql, IEnumerable<SqlParameter> parameters)
{
...
if (parameters != null) // Null check so caller can pass null if there are no parameters
{
foreach(SqlParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
...
}
}
Following the rabbit hole let me to this post. The .AddRange method for the parameters should work.
How to use SqlCeParameterCollection?
You could try to make a separate class for accessing data,
and place your methods there(like delete method,update,insert,etc..),
your connectionstring,if always accessing same database,could be outside the methods.
In your methods parameters you can pass a string with your formatted
sql statements,setting your sqlcommand object to that parameter.
You can make that class static or public(you would then need to
instatiate it in your clientform).
ex:
inside clientform....
string query = "SELECT * from tablename";
sqlaccessclass sqlclass = new sqlaccessclass();
sqlclass.GetAll(query);
Your return type for fetching data methods would be DataSet while in ExecuteNonQuery
methods would be void.
I have a problem with the folowwing piece of code. I am passing a parameter (List<SqlParameter>) to a method executing the following code.
When it executes SQL Server throws an error saying that the proc expects a parameter that was not provided. I know this error and understand it, and when stepping through the code I can see that the cmdExecuteReader object has a collection of parameters with the correct name and value. What could be the problem?
public SqlDataReader ExecuteReader(string storedProcedure, List<SqlParameter> parameters = null)
{
SqlCommand cmdExecuteReader = new SqlCommand()
{
CommandType = System.Data.CommandType.Text,
Connection = conn,
CommandText = storedProcedure
};
if (parameters != null)
{
foreach (SqlParameter param in parameters)
{
cmdExecuteReader.Parameters.AddWithValue(param.ParameterName, param.Value);
}
}
if (conn.State == System.Data.ConnectionState.Closed)
conn.Open();
return cmdExecuteReader.ExecuteReader();
}
Is the .Value set to null for any of the parameters? If so, they aren't sent. Try:
cmdExecuteReader.Parameters.AddWithValue(param.ParameterName,
param.Value ?? DBNull.Value);
(note the null-coalescing with DBNull.Value)
Also, note that AddWithValue may impact your query-plan re-use, as (for strings etc) it uses the length of the value. If you need maximum performance it is better to setup the parameter manually with the defined sizes.
Also note that potentially some of the parameters in the incoming list could be input-output, output or result. I would be very tempted to substitute for something more like:
SqlParameter newParam = cmdExecuteReader.Parameters.Add(
param.ParameterName, param.SqlDbType, param.Size);
newParam.Value = param.Value ?? DBNull.Value;
newParam.Direction = param.Direction;
I did the stuff that you are trying to do, here some examples:
public int ChangeState(int id, int stateId)
{
return DbUtil.ExecuteNonQuerySp("changeDossierState", Cs, new { id, stateId });
}
public IEnumerable<Dossier> GetBy(int measuresetId, int measureId, DateTime month)
{
return DbUtil.ExecuteReaderSp<Dossier>("getDossiers", Cs, new { measuresetId, measureId, month });
}
I recommend you to look here
and to download the samples solution (where there is a DAL Sample project included)
http://valueinjecter.codeplex.com/