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");
Related
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;
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.
I am given connection to a database which is owned by another company. The user that they gave to me has restricted privilidges, meaining that I can only make select queries on certain views.
I got a little problem here since the other company is not being so cooperative. They change my users password without telling me, or they change the names of the views. Since there are more than 40 views I want to make an automatic system that checks if everything is alright.
My question is what kind of checks I can make on the views and database? is just trying the connection to open and making select * queries for each view enough?
BTW the database is SQLServer 2008 R2 and I use C#.
Here is a function for checking that all required views exists:
bool IsAllviewsExists()
{
string DatabaseName= "Your_DB_NAME";
string[] viewsInDB = GetAllViewsNamesInDB();
for (int i = 0; i < viewsInDB.Length; ++i)
{
using (SqlCommand cmd = CreateSqlCommand(String.Format("SELECT id FROM sysobjects WHERE ID = OBJECT_ID('{0}.dbo.{1}') AND (type = 'V')", DatabaseName,viewsInDB [i])))
{
using (DataTable objects = ExecuteDataTableQuery(cmd))
{
if (objects.Rows.Count == 0)
{
return false;
}
}
}
}
return true;
}
The functions that are called from IsAllviewsExists :
(Pleas note that they assume you have a data member of a connection called _conn)
SqlCommand CreateSqlCommand(string sql, SqlParameterCollection parameters)
{
SqlCommand cmd = _conn.CreateCommand();
cmd.Connection = _conn;
cmd.CommandText = sql;
if (parameters != null)
foreach (SqlParameter param in parameters)
cmd.Parameters.Add(param);
return cmd;
}
DataTable ExecuteDataTableQuery(SqlCommand cmd)
{
DataTable table = null;
using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
{
table = new DataTable();
try
{
adapter.Fill(table);
}
catch (SqlException sqlEx)
{
rethrow;
}
}
return table;
}
I am starting to encapsulate my queries in a single parameterized query class
Now can you please evaluate my classes for performance security and every other aspect possible ?
Do you have any suggestions ?
Here my select class
public static DataSet cmd_SelectQuery(string srCommandText, List<string> lstParameterNames, List<string> lstParameters)
{
DataSet dsCmdPara = new DataSet();
try
{
using (SqlConnection connection = new SqlConnection(DbConnection.srConnectionString))
{
using (SqlCommand cmd = new SqlCommand(srCommandText, connection))
{
cmd.CommandType = CommandType.Text;
for (int i = 0; i < lstParameterNames.Count; i++)
{
cmd.Parameters.AddWithValue(lstParameterNames[i], lstParameters[i]);
}
connection.Open();
using (SqlDataAdapter sqlDa = new SqlDataAdapter(cmd))
{
sqlDa.Fill(dsCmdPara);
return dsCmdPara;
}
}
}
}
catch (Exception E)
{
csPublicFunctions.insertIntoTblSqlErrors(srCommandText + " " + E.Message.ToString());
}
return dsCmdPara;
}
And here my update,delete class
public static void cmd_UpdateDeleteQuery(string srCommandText, List<string> lstParameterNames, List<string> lstParameters)
{
try
{
using (SqlConnection connection = new SqlConnection(DbConnection.srConnectionString))
{
using (SqlCommand cmd = new SqlCommand(srCommandText, connection))
{
cmd.CommandType = CommandType.Text;
for (int i = 0; i < lstParameterNames.Count; i++)
{
cmd.Parameters.AddWithValue(lstParameterNames[i], lstParameters[i]);
}
connection.Open();
cmd.ExecuteNonQuery();
}
}
}
catch (Exception E)
{
csPublicFunctions.insertIntoTblSqlErrors(srCommandText + " " + E.Message.ToString());
}
}
Thanks a lot for answers
Two quick suggestions since I'm writing this on a phone.
Take in objects, not string
Taking in params object[] as the parameter list might make your methods a little more natural to use.
The code is valid and correct as it stands. You asked for feedback, so I have the following points.
(1) In the code where you are building up the parameters, it's a good idea to specify the data type.
for (int i = 0; i < lstParameterNames.Count; i++)
{
var parameter = cmd.Parameters.AddWithValue(lstParameterNames[i], lstParameters[i]);
parameter.SqlDbType = SqlDbType.NVarChar; // or whatever type you need
}
(2) You don't need to explicitly open/close the connection, as the Fill method does that for you.
connection.Open();
using (SqlDataAdapter sqlDa = new SqlDataAdapter(cmd))
{
sqlDa.Fill(dsCmdPara);
connection.Close();
return dsCmdPara;
}
(3) If you are only going to have one table, use a DataTable instead of a DataSet. It will be faster and less complex.
(4) This is more of a style point, but I find it is very helpful to explicitly close your connections. If there is an exception in the closing, having the line number (via the pdb) is very helpful. Also, it helps make sure you don't leak connections if you forget a using.
Edit
I want to clarify point 4. Assume that the when a connection in closed, the cleanup caused an exception. If you don't explicitly close the connection, the exception would be raised in the Dispose method, which might be doing a bunch of other work. This makes it harder to see what I did to cause the problem.
Take this simple example. There is an underlying resource that needs to be cleaned up (the entire reason for Dispose), and it throws an exception.
public class Resource: IDisposable
{
public void Close()
{
CleanupMemory();
}
private void CleanupMemory()
{
throw new Exception();
}
public void Dispose()
{
CleanupMemory();
}
}
The (cleaned up) stack trace for the two different approaches are:
using (var r = new Resource())
{
r.Close();
}
at ConsoleApplication1.Resource.CleanupMemory() in Program.cs:line 31
at ConsoleApplication1.Resource.Close() in Program.cs:line 26
at ConsoleApplication1.Program.Main(String[] args) Program.cs:line 16
versus
using (var r = new Resource())
{
}
at ConsoleApplication1.Resource.CleanupMemory() in Program.cs:line 35
at ConsoleApplication1.Resource.Dispose() in Program.cs:line 40
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 18
In the first example, I know that my calling Close caused the exception, I have a place to start debugging. If I only know that the exception was thrown, I need to completely rely on the information in the exception. I have no other context. And most exception messages are not especially clear. :)
Both are totally correct, just different.
Erick
public DataTable Get_DTable(String Query, Dictionary<String, String> Parameters)
{
try
{
using (con = new SqlConnection(cls_Connection.URL()))
{
if (con.State == 0)
con.Open();
using (cmd = new SqlCommand(Query, con))
{
foreach (KeyValuePair<string, string> item in Parameters)
{
cmd.Parameters.AddWithValue(item.Key, item.Value);
}
using (da = new SqlDataAdapter(cmd))
{
using (dt = new DataTable())
{
da.Fill(dt);
if (dt.Rows.Count > 0)
{
return dt;
}
}
}
}
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message,
"Information",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
return null;
}
My function causes alot of exception, therefore performace is not that great.
Could you advise on how to fix the function and validate the data before I return the string back? I have tried to see if amount of tables are more than 0, but this is not always the case.
public String getString(String sql)
{
DataSet ds = new DataSet();
string connstring = String.Format("Server={0};Port={1}; User Id={2};Password={3};Database={4};", tbHost, tbPort, tbUser, tbPass, tbDataBaseName);
NpgsqlConnection conn = new NpgsqlConnection(connstring);
try
{
conn.Open();
NpgsqlDataAdapter da = new NpgsqlDataAdapter(sql, conn);
ds.Reset();
da.Fill(ds);
conn.Close();
return ds.Tables.Count == 0 ? "0" : ds.Tables[0].Rows[0][0].ToString();
}
catch (Exception msg)
{
if (conn.State.ToString() == "Open")
{
conn.Close();
}
return "0";
}
}
You should be using using to automatically dispose of objects you create. You might explicitly open/close connections, but it is not needed here because the behaviour will be exactly the same. DataAdapter will open connection if not opened and remember to close it afterwards.
About validate the data - see comments in code.
public String getString(String sql)
{
using (DataSet ds = new DataSet())
{
string connstring = String.Format("Server={0};Port={1}; User Id={2};Password={3};Database={4};", tbHost, tbPort, tbUser, tbPass, tbDataBaseName);
using (NpgsqlConnection conn = new NpgsqlConnection(connstring))
{
using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(sql, conn))
{
da.Fill(ds);
// You did check count of tables
if (ds.Tables.Count > 0)
{
DataTable dt = ds.Tables[0];
// But forgot to check count of Rows
if (dt.Rows.Count > 0)
{
object o = dt.Rows[0][0];
// And returned value for nulls
// Check for null is here because I don't know
// This Postgresql classes
if (o != DBNull.Value && o != null)
{
return o.ToString();
}
}
}
}
}
}
// Return default value
return "0";
}
One final thing - when testing enumeration, don't convert it to string. To see if connection is opened, simply say:
if (conn.State == ConnectionState.Open)
try using a "finally" block as well.
Also I would recommend to divide the Connect functionality to a other method to have better control of where an exception would occur.
Also use stored procedures with SQL to minimize errors with the SQL string.
public String getString(String sql)
{
DataSet ds = new DataSet();
string connstring = String.Format("Server={0};Port={1}; User Id={2};Password={3};Database={4};", tbHost, tbPort, tbUser, tbPass, tbDataBaseName);
NpgsqlConnection conn = new NpgsqlConnection(connstring);
conn.Open();
NpgsqlDataAdapter da = new NpgsqlDataAdapter(sql, conn);
ds.Reset();
try
{
da.Fill(ds);
}
catch (Exception msg)
{
// do something here or log the error.
}
finally
{
if (conn.State.ToString() == "Open")
{
conn.Close();
}
}
return ds.Tables.Count == 0 ? "0" : ds.Tables[0].Rows[0][0].ToString();
}