While using Visual Studio 2010 for my ASP.NET website, we have code for a stored procedure call:
SqlDataAccess sqlDataAccess = new SqlDataAccess();
SqlParameter[] parameters =
{
new SqlParameter("#R", rptType.Replace("'", "''")),
new SqlParameter("#M", hybrDct["mod"].ToString().Replace("'", "''")),
new SqlParameter("#C", hybrDct["CG"].ToString().Replace("'", "''")),
new SqlParameter("#Ts$", hybrDct["TFields"].ToString().Replace("'", "''")),
};
sqlDataAccess.ProcName = "MyStoredProc";
sqlDataAccess.Parameters = parameters;
Is it possible to get the execute printed out for debugging purposes instead of finding out each individual SqlParameter and typing it in individually?
Thanks.
I use something like the following - everything goes through this and gets logged - you get the idea.
/// <summary>
/// Executes a stored procedure with no return.
/// </summary>
/// <returns>The number of records affected by stored proc.</returns>
public static int ExecuteStoredProc(string storedProcName, params SqlParameter[] parameters)
{
StringBuilder callDefinition = new StringBuilder();
callDefinition.Append(string.Format("ExecuteStoredProc: {0} (", storedProcName));
for (int i = 0; i < parameters.Count(); i++)
{
callDefinition.Append(string.Format("{0}={1}", parameters[i].ParameterName, parameters[i].Value));
if (i < parameters.Count - 1)
{
callDefinition.Append(",");
}
}
callDefinition.Append(")";
log.Debug(callDefinition.ToString());
using (var ctx = ConnectionManager<SqlConnection>.GetManager(ConnectionProfile.ConnectionName))
{
using (SqlCommand command = new SqlCommand(storedProcName, ctx.Connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandTimeout = 1000;
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
//log your param here
}
return command.ExecuteNonQuery();
}
}
}
/// <summary>
/// Executes a query and returns a dataset
/// </summary>
public static DataSet ExecuteQueryReturnDataSet(string query, params SqlParameter[] parameters)
{
try
{
//implement the parameter logging here as in the above code sample as well
log.Debug("Executing ExecuteQueryReturnDataSet() calling query " + query);
using (var ctx = ConnectionManager<SqlConnection>.GetManager(ConnectionProfile.ConnectionName))
{
using (SqlCommand command = new SqlCommand(query, ctx.Connection))
{
command.CommandType = System.Data.CommandType.Text;
command.CommandTimeout = 1000;
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
return dataSet;
}
}
}
}
catch (Exception ex)
{
log.Error(ex);
throw;
}
}
I usually run the SQL Server Profiler (assuming that this is your database) and can copy the complete query from the log.
Unfortunately SqlClient (which I assume you are using to implement SqlDataAccess, which I also assume is your own data access layer) does not directly expose the commands sent to the SQL Server. What I typically do inside my data access layer is log all commands in a format that can be executed in a query window.
Related
I have the following two functions:
//-----------------------------------FUNCTION 4-----------------------------------
/// <summary>
/// Call the specified procedure that will import file1 in SQL Server
/// </summary>
/// <param name="connectionString"> The connection string to SQL server instance</param>
/// <param name="importedfilepath"> The path of the browsed file</param>
public static void LoadLayout(string connectionString, string importedfilepath)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "createselectedtableinDB";
command.Parameters.AddWithValue("#TableName", importedfilepath);
command.ExecuteNonQuery();
if (command.ExecuteNonQuery() > 0)
MessageBox.Show("Selected file was loaded successfully");
}
}
}
//-----------------------------------FUNCTION 5-----------------------------------
/// <summary>
/// Call the specified procedure that will import file2 in SQL Server
/// </summary>
/// <param name="connectionString"> The connection string to SQL server instance</param>
/// <param name="importedfilepath"> The path of the browsed file</param>
public static void LoadBusinessChecks(string connectionString, string importedfilepath)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "createselectedtableinDB";
command.Parameters.AddWithValue("#TableName", importedfilepath);
command.ExecuteNonQuery();
if (command.ExecuteNonQuery() > 0)
MessageBox.Show("Selected file was loaded successfully");
}
}
}
As you can see the two functions take two parameters (which vary):
Parameter 1: connection string to SQL server
Parameter 2: FilePath
With those 2 parameters I am ok so far. What I want is to further optimize those two functions and basically have 1 function instead of those two. To do so, I know that I need to somehow specify:
Proceduce name:
command.CommandText = "createselectedtableinDB"; //can take different values
Procedure parameters and values (2 equal length list)
command.Parameters.AddWithValue("#TableName", importedfilepath); //more than 1 parameter so import a list here
As a result, my expected new mutated function would be like follows:
Note that the code below it's not correct but I am trying to replicate my intuition so you can understand what I am trying to do. The code below will help you to understand what I am trying to achieve on high level.
public static void LoadFiles(string connectionString, string importedfilepath, string ProcedureName, list ParameterName, list ParameterValue)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = ProcedureName;
foreach (string parametername in ParameterName)
{
foreach (string parametervalue in ParameterValue)
{
command.Parameters.AddWithValue(parametername, parametervalue ); //Generate as many as the length of the two lists
}
}
command.ExecuteNonQuery();
if (command.ExecuteNonQuery() > 0)
MessageBox.Show("Selected file was loaded successfully");
}
}
}
When calling the method from a LoadButton1_Click
private void LoadButton1_Click(object sender, RoutedEventArgs e)
{
ParameterNamelist = list ["#TableName", "#Username", "#Email"]
ParameterValuelist = list [importedfilepath, "Nikos", "nikos#stackoverflow.com"]
var connectionString = SQLServerConnectionDetails();
LoadFiles(connectionString, FileNameTextBox.Text, "SelectUsernameProcedure", ParameterNamelist, ParameterValuelist);
}
When calling the exact method from a LoadButton2_Click
private void LoadButton2_Click(object sender, RoutedEventArgs e)
{
ParameterNamelist = list ["#TableName", "#ProductName"]
ParameterValuelist = list [importedfilepath, "Choco"]
var connectionString = SQLServerConnectionDetails();
LoadFiles(connectionString, FileNameTextBox.Text, "SelectProductProcedure", ParameterNamelist, ParameterValuelist);
}
You can use a Dictionary instead:
public static bool LoadFiles(string connectionString, string ProcedureName,
IReadOnlyDictionary<string, object> parameters)
{
using var sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
using var command = sqlConnection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = ProcedureName;
foreach (KeyValuePair<string, object> parameter in parameters)
{
command.Parameters.AddWithValue(parameter.Key, parameter.Value);
}
return (command.ExecuteNonQuery() > 0);
}
and then
private void LoadButton1_Click(object sender, RoutedEventArgs e)
{
var parameters = new Dictionary<string, object>()
{
["#TableName"] = importedfilepath,
["#Username"] = "Nikos",
["#Email"] = "nikos#stackoverflow.com"
};
var connectionString = SQLServerConnectionDetails();
bool commandExecuted = LoadFiles(connectionString,
"SelectUsernameProcedure", parameters);
if (commandExecuted)
{
MessageBox.Show("Selected file was loaded successfully");
}
}
You could pass a Dictionary<string, object> into the method instead of two separate lists:
Example Dictionary:
Dictionary<string, object> myparams = new Dictionary<string, object>()
{
{"#TableName", importedfilepath },
{"#ProductName", "Choco" }
};
New Method:
public static void LoadFiles(string connectionString, string ProcedureName,
Dictionary<string, object> #params)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = ProcedureName;
foreach (string key in #params.Keys)
{
command.Parameters.AddWithValue(key, #params[key]);
}
command.ExecuteNonQuery();
if (command.ExecuteNonQuery() > 0)
MessageBox.Show("Selected file was loaded successfully");
}
}
}
Since you state you want to use your pseudo code from above, with the two lists, then it would work as so, I would suggest using the Dictionary approach provided above, but to satisfy your question:
//This is not a very safe way
//1. Both lists would need to have the same Count
//2. Parameters names would need to be ordered the same as Parameter values
public static void LoadFiles(string connectionString, string ProcedureName,
List<string> ParameterName, List<object> ParameterValue)
{
if(ParameterName.Count != ParameterValue.Count)
throw new Exception("Lists are of different Count");
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = ProcedureName;
for(int i = 0; i < ParameterName.Count; i++)
{
command.Parameters.AddWithValue(ParameterName[i], ParameterValue[i]); //Generate as many as the length of the two lists
}
command.ExecuteNonQuery();
if (command.ExecuteNonQuery() > 0)
MessageBox.Show("Selected file was loaded successfully");
}
}
}
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 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 creating a small helper function to return a DataTable. I would like to work across all providers that ADO.Net supports, so I thought about making everything use IDbCommand or DbCommand where possible.
I have reached a stumbling block with the following code:
private static DataTable QueryImpl(ref IDbConnection conn, String SqlToExecute, CommandType CommandType, Array Parameters)
{
SetupConnection(ref conn);
// set the capacity to 20 so the first 20 allocations are quicker...
DataTable dt = new DataTable();
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = SqlToExecute;
cmd.CommandType = CommandType;
if (Parameters != null && Parameters.Length > 0)
{
for (Int32 i = 0; i < Parameters.Length; i++)
{
cmd.Parameters.Add(Parameters.GetValue(i));
}
}
dt.Load(cmd.ExecuteReader(), LoadOption.OverwriteChanges);
}
return dt;
}
When this code is executed, I receive an InvalidCastException which states the following:
The SqlParameterCollection only accepts non-null SqlParameter type objects, not String objects.
The code falls over on the line:
cmd.Parameters.Add(Parameters.GetValue(i));
Any ideas?
Any improvements to the above code is appreciated.
Actual solution:
private static readonly Regex regParameters = new Regex(#"#\w+", RegexOptions.Compiled);
private static DataTable QueryImpl(ref DbConnection conn, String SqlToExecute, CommandType CommandType, Object[] Parameters)
{
SetupConnection(ref conn);
DataTable dt = new DataTable();
using (DbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = SqlToExecute;
cmd.CommandType = CommandType;
if (Parameters != null && Parameters.Length > 0)
{
MatchCollection cmdParams = regParameters.Matches(cmd.CommandText);
List<String> param = new List<String>();
foreach (var el in cmdParams)
{
if (!param.Contains(el.ToString()))
{
param.Add(el.ToString());
}
}
Int32 i = 0;
IDbDataParameter dp;
foreach (String el in param)
{
dp = cmd.CreateParameter();
dp.ParameterName = el;
dp.Value = Parameters[i++];
cmd.Parameters.Add(dp);
}
}
dt.Load(cmd.ExecuteReader(), LoadOption.OverwriteChanges);
}
return dt;
}
Thanks for ideas/links etc. :)
I believe IDbCommand has a CreateParameter() method:
var parameter = command.CreateParameter();
parameter.ParameterName = "#SomeName";
parameter.Value = 1;
command.Parameters.Add(parameter);
You could add the code of the accepted answer to an extension method:
public static class DbCommandExtensionMethods
{
public static void AddParameter (this IDbCommand command, string name, object value)
{
var parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
command.Parameters.Add(parameter);
}
}
I know it's not what you're asking, but I have a much simpler and more robust solution to offer.
The Microsoft Patterns and Practices library includes a Data Access Application block that is incredibly powerful and easy to use. A sample for executing a stored procedure and returning a dataset is shown below from our actual code:
object[] ParameterValues = new object[] {"1",DateTime.Now, 12, "Completed", txtNotes.Text};
Database db = DatabaseFactory.CreateDatabase("ConnectionStringName");
DataSet ds = = db.ExecuteDataSet("StoredProcName", ParameterValues);
It doesn't matter if the Connection is OleDb, ODBC, etc. The ConnectionStringName in the first line of code is just the name of the Consternating as defined in the .config file. You pass in a Connection String name, stored proc name, and an array of objects, which make up the parameters.
This is just one of the many sweet functions available.
You'll get everything you're trying to build and then some.
The official site is here: http://msdn.microsoft.com/en-us/library/ff648951.aspx
To save you some searching, the Data classes documentation are found here: http://msdn.microsoft.com/en-us/library/microsoft.practices.enterpriselibrary.data(PandP.50).aspx
(and it's free from Microsoft, and updated regularly.)
This answer is intended for slightly more specific purpose than what you're doing, but building on #Dismissile's answer, I used a Dictionary to supply the parameter name and value to a foreach loop in my personal project.
using( IDbCommand dbCommand = dbConnection.CreateCommand() )
{
dbCommand.CommandText = Properties.Settings.Default.UpdateCommand;
Dictionary<string,object> values = new Dictionary<string,object>()
{
{"#param1",this.Property1},
{"#param2",this.Property2},
// ...
};
foreach( var item in values )
{
var p = dbCommand.CreateParameter();
p.ParameterName = item.Key;
p.Value = item.Value;
dbCommand.Parameters.Add(p);
}
}
Your Parameters parameter needs to be of type IDataParameter[] and, given the error text, the concrete implementation needs be a SqlParameter[] type.
If you wish to keep your signature, you'll need a factory to derive the necessary concrete implementation.
Add using System.Data.SqlClient; and
cmd.Parameters.Add(new SqlParameter("#parameterName", value));
Can anyone show me a working example of using a cursor returned from PLSQL to C# code?
I found many examples showing how to fill a dataSet with returned data, but I cannot find how to use that cursor with a DataReader, so as a result I have {unnamed portal}.
NpgsqlTransaction tr = (NpgsqlTransaction) Connection.BeginTransaction();
NpgsqlCommand cursCmd = new NpgsqlCommand("someStoredProcedure(:inRadius)", (NpgsqlConnection) Connection);
cursCmd.Transaction = tr;
NpgsqlParameter rf = new NpgsqlParameter("ref", NpgsqlTypes.NpgsqlDbType.Refcursor);
rf.Direction = ParameterDirection.InputOutput;
cursCmd.Parameters.Add(rf);
I have to add this to use NpgsqlDataReader myReader; correctly:
tr.Commit();
When I wrote fetch after the sql command, it works but it is not suitable.
For your reference:
/// <summary>
/// Get data from the returning refcursor of postgresql function
/// </summary>
/// <param name="FunctionName">Function name of postgresql</param>
/// <param name="Parameters">parameters to pass to the postgresql function</param>
/// <param name="ErrorOccured">out bool parameter to check if it occured error</param>
/// <returns></returns>
public List<DataTable> GetRefCursorData(string FunctionName, List<object> Parameters, out bool ErrorOccured)
{
string connectstring = ""; //your connectstring here
List<DataTable > dtRtn =new List<DataTable>();
NpgsqlConnection connection = null;
NpgsqlTransaction transaction = null;
NpgsqlCommand command = null;
try
{
connection = new NpgsqlConnection(connectstring);
transaction = connection.BeginTransaction();
command = new NpgsqlCommand();
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = FunctionName;
command.Transaction = transaction;
//
if (Parameters != null)
{
foreach (object item in Parameters)
{
NpgsqlParameter parameter = new NpgsqlParameter();
parameter.Direction = ParameterDirection.Input;
parameter.Value = item;
command.Parameters.Add(parameter);
}
}
//
NpgsqlDataReader dr = command.ExecuteReader();
while (dr.Read())
{
DataTable dt = new DataTable();
command = new NpgsqlCommand("FETCH ALL IN " + "\"" + dr[0].ToString() + "\"", Connection); //use plpgsql fetch command to get data back
NpgsqlDataAdapter da = new NpgsqlDataAdapter(command);
da.Fill(dt);
dtRtn.Add(dt); //all the data will save in the List<DataTable> ,no matter the connection is closed or returned multiple refcursors
}
ErrorOccured = false;
transaction.Commit();
}
catch
{
//error handling ...
ErrorOccured = true;
if (transaction != null) transaction.Rollback();
}
finally
{
if (connection != null) connection.Close();
}
return dtRtn;
}
I have got some answers on my question.
Problem: I have a stored PLSQL procedure which returns refCursor. I have to get the returned data with a DataReader, but wwhen I added parameters, the db returned <unnamed portal>.
To walk through all returned data I have to write my code like so:
NpgsqlTransaction tr = (NpgsqlTransaction) Connection.BeginTransaction();
NpgsqlCommand cursCmd = new NpgsqlCommand("someStoredProcedure", (NpgsqlConnection) Connection);
cursCmd.Transaction = tr;
NpgsqlParameter rf = new NpgsqlParameter("ref", NpgsqlTypes.NpgsqlDbType.Refcursor);
rf.Direction = ParameterDirection.InputOutput;
cursCmd.Parameters.Add(rf);
NpgsqlParameter param2 = new NpgsqlParameter("param1", NpgsqlTypes.Int32);
rf.Direction = ParameterDirection.Input;
cursCmd.Parameters.Add(param2);
NpgsqlDataReader r = cmd.ExecuteReader();
while (r.Read())
{
; // r.GetValue(0);
}
r.NextResult();
while(r.Read())
{
;
}
tr.Commit();
Notice that you don't write your parameters in sql like func(:param1).
If you have parameters in your function, assign only the function name to the CommandText property and add parameters to the NpgsqlCommand.Parameters collection as usual. Npgsql will take care of binding your parameters correctly.
But now I have another problem. When I pass another output parameter to my CommandText, I have two fields in my result. One of them is 0{my first output param} and the other is <unnamed portal>.
In Oracle, I can directly convert a RefCursor parameter to a DataReader, but in postgresql, I cannot.
First of all, here is some documentation that could be useful: Npgsql doc
In this documentation you'll find a NpgsqlDataAdapter. This object also has a Fill() method (inherited from DbDataAdapter). This method can take a DataSet and a cursor. It will fill the DataSet with the data returned by your cursor.
You can't actually give a DataReader to this method, but you can give a DataTable, I think you can manage to do something with this.
I have solved the problem with Out parameters by using two commands in the same transaction.
In the first command, I read the out parameter and then execute the next command.
The second command looks like:
var cmd2 = new NpgsqlCommand("FETCH ALL FROM \"list\"", (NpgsqlConnection) Connection)
Where list the name of cursor created inside the stored procedure. As a result I get data selected from the db.