I am creating a method that can be called from anywhere in my application that will take in the named of a stored procedure and a list of parameters to pass to it.
In doing this I ran across the Parameters.AddWithValue command, but also ran across a blog posts and some SO posts that say this is bad due to conversion issues. They all recommended to add parameters using
Parameters.Add(PARAMETER, SqlDbType.TYPE);
but the problem with this is if I have a method like mine how do I properly use the Parameters.Add method when I don't know what type the parameters are when they come in? What is a good way to address this, or am I being overly paranoid and just should stick with Parameters.AddWithValue?
For reference here is the base method right now that I am attempting to update so it can handle parameters
public static DataTable ExecuteDynamicsStoredProc(string procedureName)
{
var dataTable = new DataTable();
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DynamicsDB"].ToString()))
{
using (var command = new SqlCommand("c2s_ProjectPerformanceReport", connection))
{
connection.Open();
command.CommandType = CommandType.StoredProcedure;
var dataAdapter = new SqlDataAdapter();
dataAdapter.SelectCommand = command;
dataAdapter.Fill(dataTable);
return dataTable;
}
}
}
You can make the method accept a SqlParameter[] and use command.Parameters.AddRange().
public static DataTable ExecuteDynamicsStoredProc(string procedureName, SqlParameter[] args) {
var dataTable = new DataTable();
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DynamicsDB"].ToString())) {
using (var command = new SqlCommand(procedureName, connection)) { //use passed in proc name
connection.Open();
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(args); //add all the parameters
var dataAdapter = new SqlDataAdapter();
dataAdapter.SelectCommand = command;
dataAdapter.Fill(dataTable);
return dataTable;
}
}
}
public static void ExecuteProcOne(string name, int age, bool alive) {
SqlParameter p1 = new SqlParameter("name", name);
SqlParameter p2 = new SqlParameter("age", age);
SqlParameter p3 = new SqlParameter("alive", alive);
var result = ExecuteDynamicsStoredProc("ExecuteProcOne", new SqlParameter[] { p1, p2, p3 });
}
Use methods like ExecuteProcOne() to handle the individual procedures with their respective datatypes. You can extract this out further to make a method return the SqlParameter[].
This way you can just call MyProcName and you know what parameters you need from intellisense.
Related
I wrote up this function to return a dataset, I was expecting a smaller dataset as there's only one value I was expecting back, but I get a rather bloated object back which I cannot find the value I am looking for, this is causing problems as I intend to use this function heavily.
I was hoping someone could spot what I am doing wrong, I have included the code, a screenshot of the returned object and what I am expecting. Any help would be greatly appreciated.
If I have not phrased anything in this question correctly feel free to let me know, I struggle to express my thoughts well.
public DataSet getPartnerParameter(string parameter)
{
using (var dbConnection = new SqlConnection(UnityHelper.IocContainer.Resolve<IConfigHelperService>().GetConnectionString("CASConnectionString")))
{
dbConnection.Open();
using (var dbCommand = new SqlCommand("GETPARTNERPARAMETER"))
{
dbCommand.CommandType = CommandType.StoredProcedure;
dbCommand.Connection = dbConnection;
SqlParameter lstrParameter = new SqlParameter("#Parameter", SqlDbType.VarChar);
lstrParameter.Value = parameter;
dbCommand.Parameters.Add(lstrParameter);
var ldaDPS = new SqlDataAdapter(dbCommand);
var ldstParameterValues = new DataSet();
ldaDPS.Fill(ldstParameterValues);
return ldstParameterValues;
}
}
}
This is what I am expecting to find
edit//
changed my code slightly but still not working.
public String[] getPartnerParameter(string parameter)
{
using (var dbConnection = new SqlConnection(UnityHelper.IocContainer.Resolve<IConfigHelperService>().GetConnectionString("CASConnectionString")))
{
dbConnection.Open();
SqlCommand dbCommand = new SqlCommand("GETPARTNERPARAMETER", dbConnection);
dbCommand.CommandType = CommandType.StoredProcedure;
SqlParameter lstrParameter = new SqlParameter("#Parameter", SqlDbType.VarChar);
lstrParameter.Value = parameter;
dbCommand.Parameters.Add(lstrParameter);
SqlDataReader reader = dbCommand.ExecuteReader();
string[] results = new string[2];
while (reader.Read())
{
results[0] = reader[0].ToString();
results[1] = reader[1].ToString();
}
if (results.Length < 1)
{
results[0] = "Cannot find Value";
results[1] = "S";
return results;
}
else
{
return results;
}
}
The error is this:
{"Procedure or function 'GETPARTNERPARAMETER' expects parameter '#Parameter', which was not supplied."}
The values you are looking for are probably in the dataSet.Tables[0].Rows[0] row.
However, if you are expecting one row back, a DataSet object seems like overkill. I would recommend avoiding the SqlDataAdapter/DataSet and instead use a SqlDataReader.
Untested code, but should give you the gist of how to use it:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand dbCommand = new SqlCommand("GETPARTNERPARAMETER", connection);
dbCommand.CommandType = CommandType.StoredProcedure;
SqlParameter lstrParameter = new SqlParameter("#Parameter", SqlDbType.VarChar);
lstrParameter.Value = "LexisNexisCreditConsentRequired";
dbCommand.Parameters.Add(lstrParameter);
SqlDataReader reader = dbCommand.ExecuteReader();
while (reader.Read())
{
var yourValue = reader[0];
var yourDataType = reader[1];
}
}
A DataSet is an object which can contain many tables. It doesn't have to, but it can, and so it also has a number of fields, properties, and methods to support that role.
For this query, look at ldstParameterValues.Tables[0].Rows[0]. Within that row, you can also see the columns with another level of bracket-indexing:
DataRow row = ldstParameterValues.Tables[0].Rows[0];
var column0Value row[0];
var column1Value = row[1];
However, the type for these results is object. You'll need to either cast the values or use one of the GetX() methods on the datarow to get results with a meaningful type.
I know people are going to scream that this topic is all over the internet. I know, I've read them. And I still don't understand it. I simply want to populate my object from the results of a stored procedure. Now this stored procedure takes at least 2 parameters - an action and what to find.
#Action
#customername
The #Action simply determine what the stored procedure needs to do and returns the results. So for example if I want to update a customer, I'd call the sp through my object:
public class Customer()
{
//properties
public int UpdateCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
SQLCommand = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
SqlParameterCollection parameterCollection = command.Parameters;
parameterCollection.Add("#Action", SqlDbType.NVarChar, -1).Value = "Update"
//complete the rest of it....
}
}
}
This works well. So the problem arises when I simply want to populate the object with the results of the sp. In this case I would pass "Retrieve" as the #Action parameter and this.customer_name as the #customername parameter to the sp.
But how do I put the results from the stored procedure into the object?
I have this...
public void GetCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
var retrieval = new DynamicParameters();
retrieval.Add("#Action", "Retrieve");
retrieval.Add("#name", this.customer_Name);
connection.Open():
connection.Execute(Helper.Procedure("Customer"), retrieval, commandType: CommandType.StoredProcedure);
}
}
But I don't think it's working.
Back a long time ago I used to run a "fetch" for PHP when I needed to populate an object. Should I go back to this?
You need to execute a SqlReader on the command, Something like this:
using (var connection = new SqlConnection("Connection"))
using (var command = new SqlCommand("Retrieve", connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_Name);
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var item = new YourClass();
// You can use GetX() methods to get the value of the fields
item.Name = reader.GetString("name");
// You can also access the fields by using bracket notation
item.Age = (int)reader["age"];
// Be careful of nullable fields though!
}
}
}
Using #Encrypt0r advice and guidance I got it working:
public void GetCustomer() {
using (SqlConnection connection = new SqlConnection(Helper.CnnVal("DataConnection"))) {
SqlCommand command = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_name);
connection.Open();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
this.tbl_id = (int)reader["tbl_id"];
this.customer_name = (string)reader["customer_name"];
this.customer_id = reader.GetInt32(customer_id);
this.customer_address = (string)reader["customer_address"];
this.customer_city = (string)reader["customer_city"];
this.customer_state = (string)reader["customer_state"];
this.customer_zip = reader.GetInt32(customer_zip);
}
}
In my main form, I have implemented this code..
void SampleMethod(string name, string lastName, string database)
{
SqlParameter sqlParam = new SqlParameter();
sqlParam.ParameterName = "#name";
sqlParam.Value = name;
sqlParam.SqlDbType = SqlDbType.NVarChar;
SqlParameter sqlParam1 = new SqlParameter();
sqlParam1.ParameterName = "#lastName";
sqlParam1.Value = lastName;
sqlParam1.SqlDbType = SqlDbType.NVarChar;
SqlParameter sqlParam2 = new SqlParameter();
sqlParam2.ParameterName = "#database";
sqlParam2.Value = database;
sqlParam2.SqlDbType = SqlDbType.NVarChar;
SampleClass sampleClass = new SampleClass(new DBConn(#serverName, tableName, userName, password));
sampleClass.executeStoredProc(dataGridView1, "sp_sampleStoredProc", sqlParam, sqlParam1, sqlParam2);
}
And in my SampleClass, I have this kind of method.
public DataGridView executeStoredProc(DataGridView dtgrdView, string storedProcName, params SqlParameter[] parameters)
{
try
{
DataTable dt = new DataTable();
sqlDA = new SqlDataAdapter(storedProcName, sqlconn);
sqlDA.SelectCommand.CommandType = CommandType.StoredProcedure;
sqlDA.SelectCommand.CommandTimeout = 60;
// Loop through passed parameters
if (parameters != null && parameters.Length > 0)
{
foreach (var p in parameters)
sqlDA.SelectCommand.Parameters.Add(p);
}
sqlDA.Fill(dt);
dtgrdView.DataSource = dt;
sqlconn.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
sqlconn.Close();
}
return dtgrdView;
}
What I am trying to do is avoid multiple
SqlParameter sqlParam = new SqlParameter()
in my code, I have tried so many solutions for this problem but I didn't get the right answer. I have also tried to research about this but I still couldn't get the right answer.
Please don't mind my naming convention and other codes as I intentionally change many of them :) Thanks.
As an alternative to your solution, try to use already existing one instead, using Dapper (https://github.com/StackExchange/dapper-dot-net).
You still need to use multiple parameters if your stored procedure or query needs it, but this is nicely abstracted for you and this will definatelly reduce the amount of code.
void SampleMethod(string name, string lastName, string database)
{
using(var connection = new SqlConnection(MY_CONNECTION_STRING))
{
var resultListOfRows = connection.Query<ReturnObject>(MY_STORED_PROCEDURE, new {
name = name,
lastName = lastName,
database = database}, commandType: System.Data.CommandType.StoredProcedure);
}
}
First of all, the easiest option would be to use a microORM like Dapper, and retrieve a strongly-typed collection. Gridviews can bind to anything, including strongly typed collections. All this code could become:
using(var con=new SqlConnection(myConnectionString))
{
con.Open();
var result= connection.Query<ResultClass>("sp_MySproc",
new { Name= name, LastName= lastName,Database=database},
commandType: CommandType.StoredProcedure);
return result;
}
Even when using raw ADO.NET, you can create a SqlParameter in one line by using the appropriate constructor . For example, you can create a new nvarchar(n) parameter with:
var myParam=new SqlParameter("#name",SqlDbType.NVarchar,20);
or
var myParam=new SqlParameter("#name",SqlDbType.NVarchar,20){Value = name};
A better idea though is to create the SqlCommand object just once and reuse it. Once you have an initialized SqlCommand object, you can simply set a new connection to it and change the parameter values, eg:
public void Init()
{
_loadCustomers = new SqlCommand(...);
_loadCustomers.Parameters.Add("#name",SqlDbType.NVarChar,20);
...
}
//In another method :
using(var con=new SqlConnection(myConnectionString)
{
_loadCustomers.Connection=con;
_loadCustomers.Parameters["#name"].Value = myNameParam;
con.Open();
using(var reader=_load.ExecuteReader())
{
//...
}
}
You can do the same thing with a SqlDataAdapter, in fact that's how Windows Forms and Data Adapters are meant to be used since .NET 1.0 .
Instead of creating a new one each time you want to fill your grid, create a single one and reuse it by setting the connection and parameters before execution. You can use the SqlDataAdapter(SqlCommand) constructor to make things a bit cleaner:
public void Init()
{
_loadCustomers = new SqlCommand(...);
_loadCustomers.Parameters.Add("#name",SqlDbType.NVarChar,20);
....
_myGridAdapter = new SqlDataAdapter(_loadCustomers);
...
}
And call it like this:
using(var con=new SqlConnection(myConnectionString))
{
_myGridAdapter.SelectCommand.Connection=con;
_myGridAdapter.SelectCommand.Parameters["#name"].Value =....;
con.Open();
var dt = new DataTable();
_myGridAdapter.Fill(dt);
dtgrdView.DataSource = dt;
return dtgrdView;
}
Separate your Database logic at one place(put sqladapter, sqlcommand etc at one place), Then encapsulate parameters within your command like mentioned below and you don't need to declare sqlparameter separately, add it inside parameters list.
cmdToExecute.Parameters.Add(new SqlParameter("#parameter", value));
Take a look at the complete example below
public DataTable ProdTypeSelectAll(string cultureCode)
{
SqlCommand cmdToExecute = new SqlCommand();
cmdToExecute.CommandText = "dbo.[pk_ProdType_SelectAll]";
cmdToExecute.CommandType = CommandType.StoredProcedure;
DataTable toReturn = new DataTable("ProdType");
SqlDataAdapter adapter = new SqlDataAdapter(cmdToExecute);
cmdToExecute.Connection = _mainConnection;
cmdToExecute.Parameters.Add(new SqlParameter("#CultureName", cultureCode));
_mainConnection.Open();
adapter.Fill(toReturn);
return toReturn;
}
You may be able to use the SqlParameter Constructor (String, Object). Replace:
sampleClass.executeStoredProc(dataGridView1,
"sp_sampleStoredProc",
sqlParam,
sqlParam1,
sqlParam2);
With:
sampleClass.executeStoredProc(dataGridView1,
"sp_sampleStoredProc",
new SqlParameter("#name", (object)name),
new SqlParameter("#lastName", (object)lastName),
new SqlParameter("#database", (object)database));
In my project, I have a DBAdapter class that deals with database queries.
public class DBAdapter
{
private OleDbConnection _connection;
private void _Connect()
{
this._connection = new OleDbConnection();
_connection.ConnectionString = ConfigurationManager.AppSettings["Accessconnection"];
_connection.Open();
}
private void _Disconnect()
{
_connection.Close();
}
public DataTable Select(string query, OleDbParameterCollection parameters = null)
{
OleDbCommand cmd = new OleDbCommand();
OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
DataSet dataSet = new DataSet();
DataTable dataTable = new DataTable();
query = "SELECT " + query;
if (parameters != null)
{
foreach (OleDbParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
}
this._Connect();
cmd.Connection = _connection;
cmd.CommandText = query;
if (parameters != null) {
cmd.Prepare();
}
dataAdapter.SelectCommand = cmd;
dataAdapter.Fill(dataSet, "results");
this._Disconnect();
dataTable = dataSet.Tables["results"];
return dataTable;
}
}
In order to perform prepared queries, the Select method has an optionnal OleDBParameterCollection parameter.
Then, I have multiple Mappers for each domain object in my project, for example UserMapper, that use DataAdapter class to run queries (for example find user by id).
public class UserMapper : DataMapperAbstract
{
public User findByID(int id)
{
User user = new User()
string query = "* FROM USER WHERE idUser = ?";
OleDbParameterCollection parameters = new OleDbParameterCollection();
parameters.Add(new OleDbParameter("idUser", OleDbType.Integer).Value = id);
// Prepared query
DataTable results = adapter.Select(query, parameters);
this._populateData(user, results.Rows[0]);
return user;
}
}
Unfortunately, I have an error at this line
OleDbParameterCollection parameters = new OleDbParameterCollection();
VS says that the type OleDbParameterCollection has no constructor defined, and I don't really understand what is the problem here. Maybe I don't have rights to instantiate OleDbParameterCollection, but in that case, how should I pass a collection of parameters to my DBAdapter's method ?
OleDbParameterCollection doesn't expose public constructor we can access. It doesn't meant to be used that way so, simply change your method parameter to accept list of OleDbParameter instead of OleDbParameterCollection :
public DataTable Select(string query, List<OleDbParameter> parameters = null)
{
}
Then use it accordingly :
List<OleDbParameter> parameters = new List<OleDbParameter>();
parameters.Add(new OleDbParameter("idUser", OleDbType.Integer){ Value = id });
// Prepared query
DataTable results = adapter.Select(query, parameters);
OleDbParameterCollection has no public constructors.
When the OleDbDataAdapter object is instantiated it automatically creates an OleDbParameterCollection object for you within the SelectCommand object.
Here is some code from MSDN that I found:
OleDbDataAdapter adapter =
new OleDbDataAdapter(queryString, connection);
// Set the parameters.
adapter.SelectCommand.Parameters.Add(
"#CategoryName", OleDbType.VarChar, 80).Value = "toasters";
Here is the link:
http://msdn.microsoft.com/en-us/library/system.data.oledb.oledbparametercollection(v=vs.110).aspx
You may need to modify the way you run the query a little bit. But, it looks like you've already got an adapter object in there, so it shouldn't be too hard.
I'm writing an application which first connect to the database and retrieves a dt containing a list of all the stored procedures, inputs and their associated datatypes. The user then selected a SProc from the combobox and has to enter in the necessary inputs. The app will then connect to the database and run the selected SProc with the user specified inputs and return the results in a datatable.
What I'm unsure about is if I need to write a specific method for each SProc. I'm assuming so since I don't see how I could state what the parameters are otherwise.
Apologies for not making this clear the first time. Let me know if this still isn't clear enough.
Example is shown below (this is someone else's code)
public static GetDaysDTO GetDays(int offset)
{
GetDaysDTO ret = new GetDaysDTO { TODAY = DateTime.Now, TOMORROW = new DateTime(2012, 01, 01) };
SqlConnection con = new System.Data.SqlClient.SqlConnection(#"Server = FrazMan-pc\Programming; Database = master; Trusted_Connection = True");
SqlCommand cmd = new System.Data.SqlClient.SqlCommand
{
CommandText = "GetDays",
CommandType = System.Data.CommandType.StoredProcedure,
CommandTimeout = 1,
Connection = con,
Parameters = { new System.Data.SqlClient.SqlParameter("#offset", System.Data.SqlDbType.Int) { Value = offset } }
};
using (con)
{
con.Open();
using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
ret.TODAY = DateTime.Parse(reader[0].ToString());
ret.TOMORROW = DateTime.Parse(reader["TOMORROW"].ToString());
}
}
}
return ret;
}
What you're looking for is a design pattern called Factory and a way to tell which typed data table to create on each SP call
If you have the list of the parameters for each procedure, u could instantiate the Parameters object via a loop:
This class will be used to fill the params of the sp received from the db
class ParamData
{
public object Data;
public SqlDbType type;
public string ParamName;
}
and then later on, when calling the sp, u should also pass thie ParamData object to the method, and used it to fill the params of ur sp dynamicly in a loop:
List<ParamData> list = new List<ParamData>();
//initialize command here as u did
SqlCommand cmd;
foreach (ParamData param in list)
{
SqlParameter sqlParam = new SqlParameter(param.ParamName, param.type);
sqlParam.Value = param.Data;
cmd.Parameters.Add(sqlParam);
}
//execute the command
//fill the datatable with result
DataTable dt = GetTableBySPName("GetDays");
SqlDataReader reader = cmd.ExecuteReader();
dt.Load(reader);
The only thing you need to add is the mapping between ur typed datatables and the returned table by the procedure.
You can add a method to do this:
private DataTable GetTableBySPName(string name)
{
DataTable dt = null;
switch (name)
{
case "GetDays":
{
dt = new GetDatsDTO();
break;
}
}
return dt;
}