I'd like to execute Sybase stored procedure the same way I am doing in a SQL IDE, i.e. something like this in SQL:
exec sp_GetCMyDataPerRegion JAPAN'
However, instead of this, in the C# code I am forced to define each parameter individually, each having all those types defined:
AseCommand command = new AseCommand(spName, DbConnection);
command.CommandType = CommandType.StoredProcedure;
AseParameter param = command.CreateParameter();
param.ParameterName = "#region";
param.AseDbType = AseDbType.VarChar;
param.Direction = ParameterDirection.Input;
param.Value = myValue;
command.Parameters.Add(param);
Quite a pain and not finding anyway so far to have it "generic", i.e. would just like to wrap the store procedure call in a method with this kind of signature:
public AseDataReader ExecuteStoredProcedure(string spExecutionString){}
Would you have any way on doing so?
Thank you in advance!
I do have an example of an SqlDataReader where the Function call is
ExecuteNonQuery("dbo.[Sp_Skp_UpdateFuncties]", parameters);
This is in a class DataBaseManager which hold the databaseconnectionstring
public classDataBaseManager
{
...
public int ExecuteStoredProcedure(string storedprocedureNaam, IEnumerable<KeyValuePair<string, object>> parameters)
{
var sqlCommand = new SqlCommand
{
Connection = DatabaseConnectie.SqlConnection,
CommandType = CommandType.StoredProcedure,
CommandText = storedprocedureNaam,
};
foreach (KeyValuePair<string, object> keyValuePair in parameters)
{
sqlCommand.Parameters.Add(
new SqlParameter { ParameterName = "#" + keyValuePair.Key, Value = keyValuePair.Value ?? DBNull.Value }
);
}
if (sqlCommand == null)
throw new KoppelingException("Stored procedure ({0}) aanroepen lukt niet", storedprocedureNaam);
return sqlCommand.ExecuteNonQuery();
}
....
}
Related
I made a generic function to call stored procedures from C#. The code looks like this:
public static void executeStoredProcedure(string SPName, Dictionary<string, object> parameters, string connectionStringName)
{
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(SPName, con))
{
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null)
{
foreach (KeyValuePair<string, object> kvp in parameters)
cmd.Parameters.Add(new SqlParameter(kvp.Key, kvp.Value));
}
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
}
And the code when I use it goes like this:
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("UserId", UserId);
parameters.Add("UserName", UserName);
GlobalClass.executeStoredProcedure("UpdateUserName", parameters, "userDB");
How do I modify this to be able to work with a stored procedure that has an output parameter, and store the output in a C# variable?
You can use SqlParameter array instead of Dictionary<string, object>. For example
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter() {ParameterName = "#UserId", SqlDbType = SqlDbType.NVarChar, Direction = ParameterDirection.Input, Value= UserId},
new SqlParameter() {ParameterName = "#UserName", SqlDbType = SqlDbType.NVarChar, Direction = ParameterDirection.Input, Value = UserName},
new SqlParameter() {ParameterName = "#OutValue", SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Output},
};
GlobalClass.executeStoredProcedure("UpdateUserName", parameters, "userDB");
To store output in C# variable use
int OutVal = Convert.ToInt32(cmd.Parameters["#OutValue"].Value);
This value you can return as the executeStoredProcedure function result or assign this value to an external variable.
To get the output you have to specify an output parameter with the return direction instead of the default input direction.
SqlParameter retVal = new SqlParameter("#retVal", SqlDbType.YOURTYPE);
retVal.Direction = ParameterDirection.ReturnValue;
parameters.Add(parm);
//your code
cmd.ExecuteNonQuery();
object val = retVal.Value;
//your code
Then, pass a bool to your executeStoredProcedure and return an object OR create a new method that has a return value and takes a bool for returning a value.
I have a method similar. I used to List return type and I get sp result with dynamic expandoObject. When I searching this issue, that answer helped me.
I've done some trouble shooting by taking out the parameters and replacing them with text, and the only parameter that is not working is #seat and I can't figure out why.
allSeats is an array of custom controls. I've tried replacing the parameter contents with an actual string ie. "A1" and that still doesn't work. If I remove the #seat parameter and replace it with A1, it works, but I need to be able to set the column name dynamically.
myConnection.Open();
SqlCommand myCommand = new SqlCommand("UPDATE Events SET #seat = #truefalse WHERE Name = #name", myConnection);
SqlParameter param = new SqlParameter();
SqlParameter param2 = new SqlParameter();
SqlParameter param3 = new SqlParameter();
param.ParameterName = "#seat";
param2.ParameterName = "#truefalse";
param2.DbType = System.Data.DbType.Boolean;
param3.ParameterName = "#name";
param.Value = allSeats[i].Name;
param2.Value = allSeats[i].taken;
param3.Value = name;
myCommand.Parameters.Add(param);
myCommand.Parameters.Add(param2);
myCommand.Parameters.Add(param3);
myCommand.ExecuteNonQuery();
Any help is appreciated. If I need to post more relevant code please let me know and I shall add it.
In your
SET #seat = #truefalse
part, you try to parameterize your column name. You can't do that. You only can parameterize your values, not column name or table names.
You can use dynamic SQL for such a case but it is not recommended. Read
The Curse and Blessings of Dynamic SQL
SELECT * FROM #tablename
As a recommendation, use a white list such a case. I hope, there can only be a fixed set of possible correct values for the column name. Of course, this requires strong validation in your inputs part.
Agree with Soner. Change the string before you create the command
string cmdStr = string.Format("UPDATE Events SET {0} = #truefalse WHERE Name = #name", allSeats[i].Name)
Then
only use 2 parameters.
SqlCommand myCommand = new SqlCommand(cmdStr, myConnection);
SqlParameter param = new SqlParameter();
SqlParameter param2 = new SqlParameter();
etc.
cmd.parameter.addwithvalue("#param1", value1);
cmd.parameter.addwithvalue("#param2", value2);
use like this.
As Soner has mentioned, columns cannot be parameterized. This means you will either have to create dynamic queries, or create all the parameterized once at the startup, one query per column name.
this can be done in the following example:
private static Dictionary<string, SqlCommand> parameterizedCommands = new Dictionary<string,SqlCommand>();
public static void CreateparameterizedCommandsy(string[] colums)
{
parameterizedCommands = new Dictionary<string,SqlCommand>();
foreach (string colname in colums)
{
parameterizedCommands.Add(colname, CreateCommandForColumn(colname));
}
}
public static SqlCommand CreateCommandForColumn(string columnName)
{
SqlCommand myCommand = new SqlCommand(string.Format("UPDATE Events SET {0} = #truefalse WHERE Name = #name",columnName));
// the following statement creates the parameter in one go. Bit = boolean
myCommand.Parameters.Add("#truefalse", SqlDbType.Bit);
myCommand.Parameters.Add("#name", SqlDbType.Text);
return myCommand;
}
public int ExccuteColumnUpdate(string columnName, bool setToValue, string name, SqlConnection connection)
{
connection.Open();
try
{
SqlCommand command;
if (parameterizedCommands.TryGetValue(columnName, out command))
{
command.Connection = connection;
command.Parameters["#truefalse"].Value = setToValue;
command.Parameters["#name"].Value = name;
return command.ExecuteNonQuery();
}
}
finally
{
connection.Close();
}
return 0;
}
I am trying to create a SqlParameterCollection, but gives error while adding some SqlParameter in sp.Add() method.
Please help me how to add parameter and how to pass it to my another function where I declare a SqlConnection and SqlCommand.
SqlParameterCollection sp = null;
sp.Add(new SqlParameter("#CmpyCode", SqlDbType.NVarChar)).Value = CV.Global.CMPYCODE;
sp.Add(new SqlParameter("#Code", SqlDbType.NVarChar)).Value = codeName;
sp.Add(new SqlParameter("#DisplayCode", SqlDbType.NVarChar)).Value = codeName + "-";
sp.Add(new SqlParameter("#TotalDigit", SqlDbType.Int)).Value = CV.Global.PARAMTOTALDIGIT;
insertData("<Sp Name>", sp);
My another function is insertData(...)
internal static int insertData(string spName, SqlParameterCollection sp)
{
int retObj = 0;
using (SqlConnection con = new SqlConnection(CV.Global.CONSTRING))
{
try
{
con.Open();
SqlCommand cmd = new SqlCommand(spName, con);
cmd.CommandType = CommandType.StoredProcedure;
if (sp.Count > 0)
{
foreach (SqlParameter param in sp)
cmd.Parameters.Add(param);
}
retObj = cmd.ExecuteNonQuery();
}
catch (Exception ev)
{
Util.Log(ev);
throw;
}
finally
{
try
{
con.Close();
}
catch (Exception ev) { Util.Log(ev); throw; }
}
}
return retObj;
}
I am trying to create a SqlParameterCollection and passed it to the insertData function. But it throws an error while I am calling sp.Add() method in my first function.
The error is
Object reference not set to an instance of an object
You cannot use any variable like SqlParameterCollection (a reference object) without a call to its constructor (new), but the SqlParameterCollection is an object that cannot be initialized directly with a new. It has no public constructor and can be retrieved only from the property of an existant SqlCommand.
SqlCommand cmd = new SqlCommand(commandText, connection);
SqlParameterCollection sp = cmd.Parameters;
I suggest to change your InsertData method to accept a List<SqlParameter> and let it handle the adding of the parameters to the SqlCommand that executes the command text
List<SqlParameter> sp = new List<SqlParameter>()
{
new SqlParameter() {ParameterName = "#CmpyCode", SqlDbType = SqlDbType.NVarChar, Value= CV.Global.CMPYCODE},
new SqlParameter() {ParameterName = "#Code", SqlDbType = SqlDbType.NVarChar, Value = codeName},
new SqlParameter() {ParameterName = "#DisplayCode", SqlDbType = SqlDbType.NVarChar, Value = codeName + "-"},
new SqlParameter() {ParameterName = "#TotalDigit", SqlDbType = SqlDbType.Int, Value = CV.Global.PARAMTOTALDIGIT}
};
insertData(CV.Sps.SP_INSERT_PARAM_TABLE, sp);
and insertData simply receives an optional list of SqlParameter and add them to the internal SqlCommand parameter collection if needed
internal static int insertData(string spName, List<SqlParameter> sp = null)
{
....
if(sp != null)
cmd.Parameters.AddRange(sp.ToArray());
....
}
Here is a simplified answer. I use this type of thing for a dynamic SQL query with dynamic parameters. Sometimes you don't need all parameters if you are writing a dynamic sqlquery when determining if a variable has a value.
List<SqlParameter> paramList = new List<SqlParameter>();
paramList.Add(new SqlParameter("#StartDate", StartDate));
paramList.Add(new SqlParameter("#EndDate", EndDate));
if (TicketID != "" && TicketID != null && TicketID != "undefined")
{
paramList.Add(new SqlParameter("#TicketID", TicketID));
SQLQuery = SQLQuery + " AND A.TicketID = #TicketID";
}
var Parameters = paramList.ToArray();
List<Report> ReportList = db.Database.SqlQuery<Report>(SQLQuery, Parameters).ToList();
I have a method like this one:
private void SetDataSet(string sqlString, params SqlParameter[] parameters)
{
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(cs))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
//cmd.CommandType = CommandType.
cmd.CommandText = sqlString;
if (parameters != null)
{
foreach (SqlParameter parm in parameters)
{
cmd.Parameters.Add(parm);
}
}
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
cmd.ExecuteScalar();
}
}
}
}
Now, from the other method called testMethod, I'd like to call "SetDataSet" with all needed arguments. Unfortunately I don't know how to "construct" the needed sqlparameters and how, later, pass them info "SetDataSet" method.
private void testMethod()
{
string sqlString = .... .
//here should be the code, which will create sql parameters
//and now we call the SetDataSet with all needed arguments:
SetDataSet(sqlString, ?!);
}
I was thinking about some kind of loop, which could create sqlparameters array(?) in testMethod, and then pass it to the SetDataSet method, however dont know how to accomply that.
Any thoughts?
You need to create SQL parameters?
Like this:
SqlParameter SqlParm = new SqlParameter("ID", SqlDbType.Int);
SqlParm.Value = 100;
If you think you are making some kind of generic way to do all your database stuff, you will end up creating a new layer but not avoiding the nuts and bolts of accessing a database.
All you have to do is provide the parameters. The params parameter will automatically create the array.
SetDataSet(sqlString, param1, param2, param3, ..., paramN);
You need to create SQL parameters?
Like this:
SqlParameter sqlP1= new SqlParameter("Id", SqlDbType.Int);
sqlP1.Value = 200;
So what you are trying to do is something like this:
private void testMethod()
{
string sqlString = .....
//here should be the code, which will create sql parameters
// An varchar(80) parameter called #Name with the value "Chuck".
SqlParameter paramName = new SqlParameter("#Name", SqlDbType.VarChar, 80);
paramName.Value = "Chuck";
// An int parameter called #Age with the value 49.
SqlParameter paramAge = new SqlParameter("#Age", SqlDbType.Int);
paramAge.Value = 49;
// Create more parameters here, as many as you want.
// You could also create a SqlParameter[] array and send in instead.
//and now we call the SetDataSet with all needed arguments:
SetDataSet(sqlString, paramName, paramAge); // just add all parameters one after another.
}
Since you are using a params parameter in SetDataSet(string sqlString, params SqlParameter[] parameters) you can add zero or how many parameters you want after the sqlString parameter.
I am trying to pass a table-value parameter to a stored procedure, but I keep getting an exception (see below).
SqlCommand c = new SqlCommand("getPermittedUsers", myConn) { CommandType = CommandType.StoredProcedure };
c.Parameters.AddWithValue("#intNotifyingUserId", notifyingUserId);
c.Parameters.AddWithValue("#tSelectedPdfIds", sharedPdfs).SqlDbType = SqlDbType.Structured;
SqlDataReader dr = c.ExecuteReader();
The type is defined on the server like this:
CREATE TYPE [dbo].[IdList] AS TABLE(
[Id] [int] NOT NULL
)
I have tried passing sharedPdfs as a List<int>, and IQueryable<int>, but keep getting the following exception:
Object must implement IConvertible.
Anyone know what I am doing wrong? The documentation implies that I should be able to pass a list as a TVP but doesn't give any examples.
Thank you.
The following example illustrates using either a DataTable or an IEnumerable<SqlDataRecord>:
SQL Code
CREATE TABLE dbo.PageView
(
PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
#Display dbo.PageViewTableType READONLY
AS
BEGIN
MERGE INTO dbo.PageView AS T
USING #Display AS S
ON T.PageViewID = S.PageViewID
WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END
C# Code
private static void ExecuteProcedure(bool useDataTable, string connectionString, IEnumerable<long> ids) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
using (SqlCommand command = connection.CreateCommand()) {
command.CommandText = "dbo.procMergePageView";
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter;
if (useDataTable) {
parameter = command.Parameters.AddWithValue("#Display", CreateDataTable(ids));
}
else {
parameter = command.Parameters.AddWithValue("#Display", CreateSqlDataRecords(ids));
}
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.PageViewTableType";
command.ExecuteNonQuery();
}
}
}
private static DataTable CreateDataTable(IEnumerable<long> ids) {
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(long));
foreach (long id in ids) {
table.Rows.Add(id);
}
return table;
}
private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) {
SqlMetaData[] metaData = new SqlMetaData[1];
metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
SqlDataRecord record = new SqlDataRecord(metaData);
foreach (long id in ids) {
record.SetInt64(0, id);
yield return record;
}
}
You can pass the parameter as a DataTable, IEnumerable<SqlDataRecord>, or DbDataReader.
Adding a new answer with updated links.
According to the documentation (learn.microsoft.com), you can use one of the following parameter types:
SqlClient supports populating table-valued parameters from DataTable, DbDataReader or IEnumerable \ SqlDataRecord objects. You must specify a type name for the table-valued parameter by using the TypeName property of a SqlParameter. The TypeName must match the name of a compatible type previously created on the server.
Not included in the docs but important for high-performance apps, a sample using IEnumerable<SqlDataRecord> (.NET Core 3.1, async):
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using SqlConnection connection = this.GetConnection();
await connection.OpenAsync(timeout.Token);
using SqlCommand command = connection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "Mycommand";
IEnumerable<SqlDataRecord> records = // ...
SqlParameter parameter = command.Parameters.Add("#MyObjects", SqlDbType.Structured);
parameter.TypeName = "MyCustomTableType";
parameter.Value = records;
await command.ExecuteNonQueryAsync(timeout.Token);
Example using a DataTable:
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
Example using DbDataReader:
// Assumes connection is an open SqlConnection.
// Retrieve data from Oracle.
OracleCommand selectCommand = new OracleCommand(
"Select CategoryID, CategoryName FROM Categories;",
oracleConnection);
OracleDataReader oracleReader = selectCommand.ExecuteReader(
CommandBehavior.CloseConnection);
// Configure the SqlCommand and table-valued parameter.
SqlCommand insertCommand = new SqlCommand(
"usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam =
insertCommand.Parameters.AddWithValue(
"#tvpNewCategories", oracleReader);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();