My query returns results, but for some reason my DataTable always shows 0. The only thing I altered was the fact that I added parameters to the C# syntax (altho if I manually run the stored procedure it returns results). This is my syntax, does anyone see something that is incorrect syntactically in it?
protected void btnPress1464()
{
RunSQLStoredProc();
DataTable tableA = ebdb.Tables[0];
if (this.dtgAttendanceTracker.Items.Count == 0)
{
this.gvwTest.DataSource = tableA
this.gvwTest.DataBind();
}
}
public DataSet RunSQLStoredProc()
{
ebdb = new DataSet();
SqlQueryBuilder = new StringBuilder();
SqlQueryBuilder.Append("exec alphadawg ");
ebdb = DoThis(SqlQueryBuilder.ToString());
return ebdb;
}
public DataSet DoThis(string sqlQuery, int employeeid, DateTime hiredate, DateTime terminationdate)
{
try
{
System.Configuration.ConnectionStringSettings connstring = System.Configuration.ConfigurationManager.ConnectionStrings["SQLServer1"];
using (SqlConnection conn = new SqlConnection(connstring.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandText = sqlQuery;
cmd.Connection = conn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#employeeid", employeeid.ToString());
cmd.Parameters.AddWithValue("#hiredate", hiredate.ToShortDateString());
cmd.Parameters.AddWithValue("#terminationdate", terminationdate.ToShortDateString());
conn.Open();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(ebdb);
conn.Close();
}
}
return ebdb;
}
catch (Exception exception) { throw exception; }
}
The CommandText should only contain the stored-procedure name and not also exec if the command's CommandType is StoredProcedure. The StringBuilder is also redundant.
I also think that the way how you use AddWithValue with the wrong types could cause this issue(look at the last paragraph of my answer):
So not
SqlQueryBuilder = new StringBuilder();
SqlQueryBuilder.Append("exec alphadawg ");
ebdb = DoThis(SqlQueryBuilder.ToString());
but
ebdb = DoThis("alphadawg", otherParamaters...);
It's also bad practice to pass a sql-string to a method that executes it, that often introduces sql injection issues. You should not have a method DoThis but GetAlphaDawg which encapsulates the sql-query and only pass the parameter-values.
Apart from that, why do you return the DataSet from a method if it's actually a field in your class that you return? Instead initialize and fill it in the method, that's much clearer and also prevents issues when you load an already filled dataset(data will be appended by default).
This would be a possible implementation. Note that you shouldn't use AddWithValue and don't use String for DateTime but always use the correct type, all the more if you use AddWithValue which needs to infer the type from the value:
public DataSet GetAlphaDawg(int employeeid, DateTime hiredate, DateTime terminationdate)
{
DataSet dsAlpha = new DataSet();
try
{
System.Configuration.ConnectionStringSettings connstring = System.Configuration.ConfigurationManager.ConnectionStrings["SQLServer1"];
using (var conn = new SqlConnection(connstring.ConnectionString))
{
using (var da = new SqlDataAdapter("alphadawg", conn))
{
da.SelectCommand.CommandType = CommandType.StoredProcedure;
var parameter = da.SelectCommand.Parameters;
parameter.Add("#employeeid", SqlDbType.Int).Value = employeeid;
parameter.Add("#hiredate", SqlDbType.Date).Value = hiredate;
parameter.Add("#terminationdate", SqlDbType.Date).Value = terminationdate;
da.Fill(dsAlpha); // Open/Close not needed with Fill
return dsAlpha;
}
}
} catch (Exception) { throw; }
}
Since you use ToShortDateString, if you actually want to remove the time portion of your DateTime use DateTime.Date, for example:
parameter.Add("#hiredate", SqlDbType.Date).Value = hiredate.Date;
Related
I've a SQL query which tries to fetch all records that were created within the last one hour
string query = "select * from Monarchchangelog mcl WHERE LOWER(mcl.mcl_usercomment) LIKE 'bolt%' AND mcl.mcl_createtime > DATEADD(MINUTE, -#minutesBack, GETDATE()) ORDER BY mcl.mcl_createtime DESC";
string tablename = "NEW_UPDATES";
string minutesBack = "60"; //set by another function but for eg sake I've hardcoded the value
SqlCommand command = new SqlCommand(query);
command.Parameters.AddWithValue("minutesBack", minutesBack);
DataSet ds = RunQuery(command, tablename);
which I'm executing with the below code
private DataSet RunQuery(SqlCommand command, String tablename)
{
DataSet ds = null;
SqlDataAdapter adapter = null;
using (SqlConnection oc = new SqlConnection(CONNECTIONSTRING))
{
try
{
oc.Open();
command.CommandType = CommandType.Text;
command.Connection = oc;
adapter = new SqlDataAdapter(command);
ds = new DataSet();
adapter.Fill(ds, tablename);
}
catch
{
if (ds != null)
{
ds.Dispose();
}
}
finally
{
if (oc != null)
{
oc.Close();
oc.Dispose();
}
}
}
return ds;
}
when I try to execute this code I'm getting the below error but mcl_createtime is of datetime datatype
operand data type nvarchar is invalid for minus operator
Can someone let me know where am I going wrong
Thank you
With the help of #DaleK's comment I resolved the issue, replaced the code
command.Parameters.AddWithValue("minutesBack", minutesBack);
with this piece
command.Parameters.Add("minutesBack", SqlDbType.Int).Value = minutesBack;
A good way of passing your parameters is by explicitly mentioning the sql datatype to avoid datatype issues like this. For more details please follow the link AddWithValue vs Add.
So I passed my parameter as a number and not a string, also got a good understanding of try-catch, using statements.
Good learning, thank you everyone for your contribution.
I have a method to retrieve data from a database using a stored procedure as a DataTable like:
public DataTable GetTableBySQL(string sql)
{
SqlCommand cmd = new SqlCommand(sql.ToString(), this.dbconn)
{
CommandTimeout = 0,
CommandType = CommandType.Text
};
DataTable tbl = new DataTable("Table1")
{
Locale = System.Globalization.CultureInfo.InvariantCulture
};
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.SelectCommand.CommandTimeout = 0;
da.Fill(tbl);
}
catch (SqlException e)
{
this.HandleSQLError(e, "GetTableBySQL", sql.ToString());
}
finally
{
cmd.Dispose();
da.Dispose();
}
return tbl;
}
Now I call the stored procedure like this:
var empList = db.GetTableBySQL("$exec getMySP");
But when I execute, it just don't return any columns.
What am I doing wrong? Regards
There are three main problems here (other smaller ones, but three that are important):
The $exec part of the SQL doesn't mean anything. Maybe you just want exec.
When the bad SQL fails, the error is hidden from the program, so you don't really know what happened.
The method signature doesn't support query parameters, and therefore will force you to write horribly insecure code that will result in someone hacking your application. Probably sooner rather than later. This is really bad, and you should not ignore it.
Try something more like this:
public DataTable GetTableBySQL(string sql, params SqlParameter[] parameters)
{
var result = new DataTable();
//ADO.Net really does work better when you create a **NEW** connection
// object for most queries. Just share the connection string.
//Also: "using" blocks are a better way to make sure the connection is closed.
using (var dbconn = new SqlConnection(this.dbConnectionString))
using (var cmd = new SqlCommand(sql, dbconn))
using (var da = new SqlDataAdapter(cmd))
{
cmd.CommandTimeout = 0;
// A number of the properties set on the cmd and tbl variables just set the same value that was already there, didn't accomplish anything
//It's hard to understate how important it is to use parameterized queries.
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
try
{
da.Fill(result);
}
catch (SqlException e)
{
this.HandleSQLError(e, "GetTableBySQL", sql.ToString());
//you may want to re-throw here,
// or even just remove the try/catch and let the error bubble up to calling code
}
}
return result;
}
Here it is again without all the extra explanatory comments, so you can see that doing it right is less code, rather than more:
public DataTable GetTableBySQL(string sql, params SqlParameter[] parameters)
{
var result = new DataTable();
using (var dbconn = new SqlConnection(this.dbConnectionString))
using (var cmd = new SqlCommand(sql, dbconn))
using (var da = new SqlDataAdapter(cmd))
{
cmd.CommandTimeout = 0;
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
da.Fill(result);
}
return result;
}
Then call it like this:
var empList = db.GetTableBySQL("exec getMySP");
I was trying to add a combo box which could get all the product name but unfortunately I follow some tutorials and end up like this.
void fillCombo()
{
try
{
con.Open();
OleDbCommand command = new OleDbCommand("Select * from IblInventory");
command.Connection = con;
OleDbDataReader reader = command.ExecuteReader();
while (reader.Read())
{
String product = reader.GetString("ProductName"); // << invalid argument
cmbProduct.Items.Add(product);
}
con.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What could possibly the reason?
From the documentation of OleDbDataReader.GetString you will notice that the argument required by the method is an integer representing the position of the column in the returned record not its name.
If you (rightly) prefer to use the column name then you need to take a detour and use the GetOrdinal method to retrieve the position of the column given the name.
while (reader.Read())
{
int pos = reader.GetOrdinal("ProductName");
String product = reader.GetString(pos);
cmbProduct.Items.Add(product);
}
Another example, practically identical to your situation, can be found in the documentation page on MSDN about OleDbDataReader.GetOrdinal
It is also a common practice to write an extension method that allows you to write code as yours hiding the details of the mapping between name and position. You just need a static class with
public static class ReaderExtensions
{
public string GetString(this OleDbDataReader reader, string colName)
{
string result = "";
if(!string.IsNullOrEmpty(colName))
{
int pos = reader.GetOrdinal(colName);
result = reader.GetString(pos);
}
return result;
}
... other extensions for Int, Decimals, DateTime etc...
}
Now with this class in place and accessible you can call
string product = reader.GetString("ProductName");
it is working in my project
First fill your data in to datatable see the below code
DataTable results = new DataTable();
using(OleDbConnection conn = new OleDbConnection(connString))
{
OleDbCommand cmd = new OleDbCommand("Select * from IblInventory", conn);
conn.Open();
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
adapter.Fill(results);
}
Now
cmbProduct.DataSource = results ;
cmbProduct.DisplayMember = "ProductName";
cmbProduct.ValueMember = "Id feild of IblInventory table";
I've created a RadGrid Programmatically and binding it using NeedDataSource -> GetDataTable.
Within the GetDataTable, I'm calling my connstring and fill the grid with an adapter (see code below). Problem is that, in my SQL Server, the query takes 0 sec to run, but in the ASP.NET debug mode, it's taking about 3~5s, in my case of having a lot of RadGrids on the page, this is causing my page to load slowly.
Is this processing speed of adapter.Fill a general issue or have I done something wrong with the setting? (ie, orders of conn.open/close or any others)?
public DataTable GetDataTable(int Year, int month, string datatype)
{
String ConnString = ConfigurationManager.ConnectionStrings["IHG_MSTConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(ConnString);
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = new SqlCommand("[Yield_Planner_With_Strategy]", conn);
adapter.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;
adapter.SelectCommand.Parameters.AddWithValue("#Holidex_Code", RadComboBox_Hotels.SelectedValue);
adapter.SelectCommand.Parameters.AddWithValue("#Event_Year", Year);
adapter.SelectCommand.Parameters.AddWithValue("#Event_Month", month);
adapter.SelectCommand.Parameters.AddWithValue("#DataType", datatype);
adapter.SelectCommand.Parameters.AddWithValue("#MktSeg", Fruitful.Get_Checked_Values_As_CSV(RadComboBox_MktSeg));
string exportdate = DateTime.Now.ToString("yyyy/MM/dd");
if (RadComboBox_ExportTimeStamp.Text != "" && RadComboBox_ExportTimeStamp.Text != "Create New Strategy")
{ exportdate = Convert.ToDateTime(RadComboBox_ExportTimeStamp.Text).ToString("yyyy/MM/dd"); }
adapter.SelectCommand.Parameters.AddWithValue("#ExportTimeStamp", exportdate);
DataTable myDataTable = new DataTable();
conn.Open();
try
{
adapter.Fill(myDataTable);
}
finally
{
conn.Close();
}
return myDataTable;
}
Why do you use a string for the ExportTimeStamp parameter? Use
DateTime if it's a date or datetime column.
I'd also replace all of your calls to AddWithValue with Add. When you call AddWithValue it has to guess what the type of your parameter is. If it guesses wrong the optimizer cannot select the correct index and falls back to a table scan, and that speaks to the core of database performance.
AddWithVaue may result in multiple query plans. Since .NET doesn't know what the size of the database column is, it will use the size of the variable. so if you have a parameterized query and pass two strings in, one of length 10, the other of length 20, you will get two plans: #text nvarchar(10) and #text nvarchar(20). It will also assume that your field is nvarchar when it may be varchar and you will get an implicit conversion.
So always either pass the correct type to AddWithValue or (better) use SqlParameterCollection.Add with the correct type and size. It'll also validate the parameter before it gets sent to the database.
Related:
SqlCommand Parameters Add vs. AddWithValue
Bad habits to kick : mis-handling date
The Seven Sins against TSQL Performance
Also, use the using-statement to ensure that the connection gets closed as soon as you're finished with it - even in case of an error.
Here is an example:
public DataTable GetDataTable(int Year, int month, string datatype)
{
DataTable myDataTable = new DataTable();
String ConnString = ConfigurationManager.ConnectionStrings["IHG_MSTConnectionString"].ConnectionString;
using(SqlConnection conn = new SqlConnection(ConnString))
using (SqlDataAdapter adapter = new SqlDataAdapter())
{
var cmd = new SqlCommand("[Yield_Planner_With_Strategy]", conn);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("#Holidex_Code", SqlDbType.Int).Value = int.Parse(RadComboBox_Hotels.SelectedValue);
cmd.Parameters.Add("#Event_Year", SqlDbType.Int).Value = Year;
cmd.Parameters.Add("#Event_Month", SqlDbType.Int).Value = month;
cmd.Parameters.Add("#DataType", SqlDbType.VarChar).Value = datatype;
cmd.Parameters.Add("#MktSeg", SqlDbType.NVarChar).Value = Fruitful.Get_Checked_Values_As_CSV(RadComboBox_MktSeg);
DateTime exportdate = DateTime.Now;
if (RadComboBox_ExportTimeStamp.Text != "" && RadComboBox_ExportTimeStamp.Text != "Create New Strategy")
{
exportdate = DateTime.Parse(RadComboBox_ExportTimeStamp.Text);
}
cmd.Parameters.Add("#ExportTimeStamp", SqlDbType.DateTime).Value = exportdate;
adapter.SelectCommand = cmd;
// you don't need to open it with Fill
adapter.Fill(myDataTable);
}
return myDataTable;
}
Environment:
C#
Visual Studio 2012
.NET Framework 3.5
Hi
Could I parameterize where clause in SQL Server?
In my scenario, once a WHERE clause String is input, application will concatenate it to other part of query and execute in SQL Server then return the result.
For example,
User inputs "[CookingTime] < 30 and [Cost] < 20"
Application creates query "select [RecipeID] from [Recipes] where [CookingTime] < 30 and [Cost] < 20" and executes in SQL Server.
Application returns result to user.
For security reason, I would like to make whole WHERE CLAUSE as parameter.
But I have no idea how to achieve.
Thanks in advance.
This is how it can be done
string commandText = "UPDATE Sales.Store SET Demographics = #demographics "
+ "WHERE CustomerID = #ID;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("#ID", SqlDbType.Int);
command.Parameters["#ID"].Value = customerID;
// Use AddWithValue to assign Demographics.
// SQL Server will implicitly convert strings into XML.
command.Parameters.AddWithValue("#demographics", demoXml);
try
{
connection.Open();
Int32 rowsAffected = command.ExecuteNonQuery();
Console.WriteLine("RowsAffected: {0}", rowsAffected);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The whole WHERE clause as parameter will be a victim of sql injection in any way. To prevent this you'd better to:
Setup proper permissions. So even in case of sql injected user can't access anything not granted. In this case sample of #Dhaval is better, because dymanic sql generation incapsulated in stored procedure requires less permissions to execute.
Check the statement for sql injection. The simplest way is to check for semicolons in order to avoid another statements in the batch. More complex and more precise way is to use t-sql DOM parser. For example:
using Microsoft.SqlServer.TransactSql.ScriptDom;
TSql110Parser parser = new TSql110Parser(true);
IList<ParseError> errors = null;
var condition = "a > 100; delete from [Recipes]";
var script = parser.Parse(new StringReader("select [RecipeID] from [Recipes] where " + condition), out errors) as TSqlScript;
if (errors.Count > 0)
{
throw new Exception(errors[0].Message);
}
foreach (var batch in script.Batches)
{
if (batch.Statements.Count == 1)
{
var select = batch.Statements[0] as SelectStatement;
if (select != null)
{
QuerySpecification query = select.QueryExpression as QuerySpecification;
if (query.WhereClause is BooleanBinaryExpression)
{
...
}
}
else
{
throw new Exception("Select statement only allowed");
}
}
else
{
throw new Exception("More than one statement detected");
}
}
You can create a dynamic query in sql server and pass the parameter from C#
Something like this
Create Procedure usp_Test
#WhereCond Varchar(max)
AS
Bgein
Set NoCount ON
Declare #SQLQuery AS Varchar(max)
Set #SQLQuery = 'Select * From tblEmployees where ' + #WhereCond
Execute sp_Executesql #SQLQuery
End
C# Code to execute the procedure
DataSet ds = new DataSet();
using(SqlConnection conn = new SqlConnection("ConnectionString"))
{
SqlCommand sqlComm = new SqlCommand("usp_Test", conn);
sqlComm.Parameters.AddWithValue("#WhereCond", WhereCond);
sqlComm.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
da.Fill(ds);
}
I guess the original question wanted to find out how to make it dynamically from user's input and then use proper sql parameter to do the query.
For the usage of sql parameter, normally what I do is to use a generic helper method, a quick example (not tested):
public static class SqlHelpers
{
public static IEnumerable<T> ExecuteAdhocQuery<T>(SqlConnection con, string sql, CommandType cmdType, Func<SqlDataReader, T> converter, params SqlParameter[] args)
{
try
{
using (SqlCommand cmd = new SqlCommand(sql, con) { CommandType = cmdType })
{
cmd.Parameters.AddRange(args);
if (con.State != ConnectionState.Open) { con.Open(); }
var ret = new List<T>();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
ret.Add(converter.Invoke(rdr));
}
}
return ret;
}
}
catch (Exception e)
{
// log error?
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
throw e; // handle exception...
}
}
public void Test()
{
using (SqlConnection con = new SqlConnection("connection string here"))
{
var data = ExecuteAdhocQuery(con,
"SELECT ID, Name FROM tblMyTable WHERE ID = #Id and Status = #Status;",
CommandType.Text, (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
new SqlParameter("#Id", SqlDbType.Int) { Value = 1 },
new SqlParameter("#Status", SqlDbType.Bit) { Value = true });
Console.WriteLine(data.Count());
}
}
}
of course, this is only Reading, for Insert/Update, similar methods could be created too.
But the complicated part is how to make it dynamic with unknown number of conditions and the relationship between them. So a quick suggestion is use a delegated method or class to do the work. sample (not tested):
public static Dictionary<string, SqlParameter> GetParamsFromInputString(string inputString)
{
var output = new Dictionary<string, SqlParameter>();
// use Regex to translate the input string (something like "[CookingTime] < 30 and [Cost] < 20" ) into a key value pair
// and then build sql parameter and return out
// The key will be the database field while the corresponding value is the sql param with value
return output;
}
public void TestWithInput(string condition)
{
var parameters = GetParamsFromInputString(condition);
// first build up the sql query:
var sql = "SELECT Id, Name from tblMyTable WHERE " + parameters.Select(m => string.Format("{0}={1}", m.Key, m.Value.ParameterName)).Aggregate((m,n) => m + " AND " + n);
using (SqlConnection con = new SqlConnection("connection string here"))
{
var data = ExecuteAdhocQuery(con,
sql,
CommandType.Text,
(x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
parameters.Select(m => m.Value).ToArray());
}
}
for the static function GetParamsFromInputString, it's just a sample. actually it could be very complicated depending on your needs.
for example, you might want to include the operator (whether it's >, < or <>,...).
and you might also want to include the conjunctions between the conditions, whether it's AND or OR.
Build delegated classes to do the job if it's very complicated.