Why SqlParameter always set parameter is null? - c#

using (SqlCommand cmd = new SqlCommand())
{
DataTable dt = new DataTable();
cmd.CommandText = p;
cmd.CommandType = CommandType.Text;
SqlConnection con = new SqlConnection("--");
cmd.Connection = con;
cmd.Connection.Open();
foreach (var parameter in filters)
{
var type = parameter.Value.GetType();
var param = new SqlParameter(parameter.Id, parameter.Value);
param.Direction = ParameterDirection.Input;
param.Value = parameter.Value;
cmd.Parameters.Add(param);
}
dt.Load(cmd.ExecuteReader());
cmd.Connection.Close();
return dt;
}
Here is my code.
Variable "p" is my sqlQuery string.
Variable "filters" is my parameter list.
For example: parameter.Id = "#offerId" (as string) and parameter.Value = 1230 (as Integer)
Also my query is like that : "select * from Offers where ID = #offerID and IsActive = #isActive"
when pass into cmd.ExecuteReader(), in IntelliTrace shows my query like that:
--The data may be truncated and may not represent the query that was run on the server
USE [DB];
GO
--Type and value data was not available for the following variables. Their values have been set to defaults.
DECLARE #offerID AS SQL_VARIANT;
DECLARE #isActive AS SQL_VARIANT;
SET #offerID = NULL;
SET #isActive = NULL;
select * from Offers where ID = #offerID and IsActive = #isActive
We tried lots of method for set. But always variables set null.

IntelliTrace currently only supports this kind of type information for log files from MMA scenarios. In your scenario, the type information from SqlParameter isn't collected; as a result all the variables in the query default to SQL_VARIANT with null values.

Using your code on Visual Studio 2010 and SQL Server 2008 I tried to reproduce your scenario (or at least what I thought your scenario was as you weren't very specific).
I created these tables: Q25682067 using int for the offer id and bit for the 'isActive' field; and Q25682067_Offers with sql_variant for each field, as suggested by your post.
CREATE TABLE [Q25682067]([ID] [int] NOT NULL, [IsActive] [bit] NOT NULL) ON [PRIMARY]
CREATE TABLE [Q25682067_Offers]([ID] [sql_variant] NOT NULL,[IsActive] [sql_variant] NOT NULL ) ON [PRIMARY]
Data pairs (1,false) and (1,true) added to each table.
Now considering your filters are something like:
var filters = new Parameter[] {
new Parameter() {Id="#offerID ", Value=1},
new Parameter() {Id="#isActive", Value=false}
};
where a Parameter might very swiftly be (without any consideration to OOP practices):
internal class Parameter
{
public string Id;
public object Value;
}
Now, this populates the data table:
cmd.CommandText = "select * from Q25682067 where ID = #offerID and IsActive = #isActive";
this does not:
cmd.CommandText = "select * from Q25682067_Offers where ID = #offerID and IsActive = #isActive";
Using the SqlParameter constructor like that binds your parameters to SqlDbType.Int and SqlDbType.Bit instead of SqlDbType.Variant, which seems to be your weapon of choice. That's why your code works with the first table definition, and not the second.

Related

How to get output parameter and also a table when executing a stored procedure

Scenario
I'm working with SQL Server 2017 (not possible to change)
I'm using Visual Studio 2019 in C# console and .NET Framework 4.5 (possible to change)
I'm using ADO.NET because several years before we couldn't use Entity Framework, as the system is made to work with a stored procedure that returns at least 100k rows (possible to change)
Situation
I have an USP that returns a table that is at least 100k of rows by 20 fields. I need to add an output parameter in order to get also an ID created by the USP itself. So, the situation is that I need return a table and an ID (called ProcMonitorId). I don't know if this is even so possible (See workarounds section)
At the SQL level is seems to be so far so good:
CREATE PROCEDURE [myschema].[mystore]
#ProcMonitorId BIGINT OUTPUT
AS
BEGIN
BEGIN TRANSACTION
(...)
SELECT fields FROM myTable
SELECT #ProcMonitorId = #internalVariable
SQL execution:
And at repository layer (only relevant lines, someone were surprised for health of example):
var command = new SqlCommand("myStoreProcedure", mycon);
command.CommandType = CommandType.StoredProcedure;
SqlParameter outPutParameter = new SqlParameter();
outPutParameter.ParameterName = "#ProcMonitorId";
outPutParameter.SqlDbType = System.Data.SqlDbType.BigInt;
outPutParameter.Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(outPutParameter);
// Open connection etc-etc that works
SqlDataAdapter da = new SqlDataAdapter(command);
DataTable dt = new DataTable();
string ProcMonitorId = outPutParameter.Value.ToString();
da.Fill(dt);
Everything worked fine until the addition of the output at C# level. It returns in the line:
string ProcMonitorId = outPutParameter.Value.ToString();
it returns NullReferenceException because Value is null (that can't be) and of course, can't convert to String. I would solve this situation by adding a ? but if that's situation happens for real, I need catch it any way as error. The main idea is that Value can not be null.
As I don't have any ORM map, (and my expertise is not ADO.NET but Entity Framework) I can't understand why is null (No, is not null at SQL layer, always return a value)
Question
How can I solve this error or how can I return a BIGINT parameter and ALSO a table result?
Workarounds
As I first glance I have to solve it quickly, I made a:
SELECT 1 as type, #procID as procid, null as data1, null as data2
UNION ALL
SELECT 2 as type, null as procid, data1, data2
in order to simulate a "header" and "data" rows on one single table.
But I don't like this solution and is not very elegant and flexible. I've to parse the header every time.
Thanks in advance and please comment anything, tip, help, workaround, I will be glade to update my answer if more information is needed.
Also I can make my Framework to .NET Core or change to Entity Framework. That I can't change is my SQL version
Update #2
No changes in SQL - Still working as screenshot
In C# - Hangs out for ever
SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings["DbConnection"]);
connection.Open();
var command = new SqlCommand("myUSP", connection);
command.CommandType = CommandType.StoredProcedure;
command.CommandTimeout = Convert.ToInt16(ConfigurationManager.AppSettings["DataBaseTimeOut"]);
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
SqlParameter r = command.Parameters.Add("#ProcMonitorId", SqlDbType.BigInt);
r.Direction = ParameterDirection.Output;
DataTable dt = new DataTable();
using (var rdr = command.ExecuteReader())
{
dt.Load(rdr);
long id = (long)r.Value;
}
SqlDataAdapter da = new SqlDataAdapter(command);
da.Fill(dt);
The parameter value won't be available until after you consume the resultset, eg
var cmd0 = new SqlCommand("create or alter procedure pFoo #id int output as begin select * from sys.objects; set #id = 12; end", con);
cmd0.ExecuteNonQuery();
var cmd = new SqlCommand("pFoo", con);
cmd.CommandType = CommandType.StoredProcedure;
var p1 = cmd.Parameters.Add("#id", SqlDbType.Int);
p1.Direction = ParameterDirection.Output;
var dt = new DataTable();
using (var rdr = cmd.ExecuteReader())
{
dt.Load(rdr);
var id = (int)p1.Value;
}
You should use a Parameter with the Direction property set to ReturnValue, and, inside the sp, declare an internal variable and set it to the value you want.
Then call the RETURN statement before leaving the StoredProcedure.
As an example, see this SP:
ALTER PROCEDURE [GetTimeZoneGMT]
#TimeZone NVARCHAR(128)
AS
BEGIN
DECLARE #timeZoneNumber as INT = -20;
IF #TimeZone ='Pacific/Midway'
SET #timeZoneNumber = -11
ELSE IF #TimeZone ='Pacific/Niue'
SET #timeZoneNumber = -11
ELSE IF #TimeZone ='Pacific/Pago_Pago'
SET #timeZoneNumber = -11
SELECT 1 -- or whatever you need to have as result set
RETURN #timeZoneNumber;
END
The stored procedure ends with a (bogus) SELECT statement but also has a RETURN statement with the parameter set inside the SP logic.
Now from the C# side you could call it in this way (LinqPad example)
using (var connection = new SqlConnection("Data Source=(LOCAL);Initial Catalog=LinqPADTest;Integrated Security=True;"))
{
connection.Open();
SqlCommand cmd = new SqlCommand("GetTimeZoneGMT", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#TimeZone", SqlDbType.NVarChar).Value = "Asia/Kuala_Lumpur";
SqlParameter r = cmd.Parameters.Add("#p2", SqlDbType.BigInt);
r.Direction = ParameterDirection.ReturnValue;
DataTable dt = new DataTable();
dt.Load(cmd.ExecuteReader());
r.Value.Dump(); // Prints -20
dt.Dump(); // Prints a row with a single column with 1 as value
}

How can I get multiple output parameters from a stored procedure in a web service in C#?

I have a web service in C#, I use it to consults from tables, but I want to create a WebMethod to call a stored procedure and get back multiples output parameters. I can execute it with output parameters, it doesn't work when I try to call it whit outputs parameters.
This is a sample, I want to get back more that 2 parameters.
Stored procedure:
CREATE OR REPLACE PROCEDURE O_CAPEREZ.GIO_SP (
VNOMBRE IN VARCHAR2,
SALUDO OUT VARCHAR2 )
AS
BEGIN
INSERT INTO G_PRUEBA_SP(NOMBRE)
VALUES (vNOMBRE);
SALUDO:= ('Hello: ' || vNOMBRE);
END;
And this is my code in the web service, when I execute it using output variables I get this error
[HYC00] [Oracle][ODBC]Optional feature not implemented
C# code:
[WebMethod]
public string AP_Data(string curp)
{
string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
using (OdbcConnection con = new OdbcConnection(constr))
{
OdbcCommand cmd = new OdbcCommand("{CALL GIO_SP(?,?)}", con);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#vNOMBRE", (curp));
cmd.Parameters.Add("#vNOMBRE", OdbcType.VarChar, 18);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Parameters["#SALUDO"].Direction = ParameterDirection.ReturnValue;
cmd.Connection.Close();
string ret = Convert.ToString(cmd.Parameters["#SALUDO"].Value);
return ret;
}
}
You have to add the parameter to the list even if you're not going to set a value there:
cmd.Parameters.Add("#SALUDO", OdbcType.VarChar, 18).Direction = ParameterDirection.Output;
I don't know the the Oracle flavor is different, but in SQL I use ParameterDirection.ReturnValue rather than ParameterDirection.Output.
here's how i do it in MS SQL server 2008 But notice the data type and the lenth of the variables your create must be the same in your table
the stored proc create code
USE DATABASE DATABASE_NAME
GO
CREATE PROC SP_METHOD
#ID_CATIGORY INT,
#NAME VARCHAR (50),
#DESCRIPTION VARCHAR (50)
AS
INSERT INTO TABLE_NAME
([ID_CAT]
,[NAME_PRODUCT]
,[DESC_PRODUCT]
)
VALUES
( #ID_CATIGORY
,#NAME
,#DESCRIPTION )
GO
in the c# code
// Create SqlConnection
SqlConnection conn= new SqlConnection(#"Server=server_name;
DataBase=your_data_base_name;Integrated Security=false;User
Id=user_id;Password=password");
// Open the Connection
if (sqlconnection.State != ConnectionState.Open)
{
conn= .Open();
}
// execute stored_procedure method don't change this
public void ExecuteCommand(string stored_procedure, SqlParameter[] param)
{
SqlCommand sqlcomd = new SqlCommand();
sqlcomd.CommandType = CommandType.StoredProcedure;
sqlcomd.CommandText = stored_procedure;
sqlcomd.Connection = sqlconnection;
if (param !=null)
{
sqlcomd.Parameters.AddRange(param);
}
sqlcomd.ExecuteNonQuery();
}
// close connection method
public void close_conn()
{
if (sqlconnection.State == ConnectionState.Open)
{
sqlconnection.Close();
}
}
// execute and retrieving data Method
public void Add_product(int ID_cat ,string Name_Product,string
Des_Product)
{
SqlParameter[] param = new SqlParameter[3];
param[0] = new SqlParameter("#ID_CAT", SqlDbType.Int);
param[0].Value = ID_cat;
param[1] = new SqlParameter("#NAME_PRODUCT", SqlDbType.VarChar, 50);
param[1].Value = Name_Product;
param[2] = new SqlParameter("#DESC_PRODUCT", SqlDbType.VarChar, 50);
param[2].Value = Des_Product;
ExecuteCommand("StoredProcedure_name", param);
close_conn();
}
and finally you can call this function
Add_product(Convert.ToInt32(ComboBox.SelectedValue),txt_name.Text,
txt_desc.Text);
if there is any part you don't understand lemme know
I've seen many ways to accomplish this.
One way is to Pipe Delimit your select statement in your stored procedure and then use "Value1|Value2".Split('|')[0] to get Value1.
You could also return a table instead of using multiple parameters
DataTable table = new DataTable();
DataAdapter adapter = new DataAdapter(cmd);
adapter.fill(table);
return table.Rows[0]["Greeting"] + table.Rows[0]["Name"];
In the second example you can return as many 'Parameters' as you want, but you will have to assign them to their rightful spots later in your code.
I've also seen an XML way to do this same feature but I won't provide the code here since I don't personally think it is a very good way to do it. The way I've seen done was adding a bunch of XML attributes to a parent tag, and then coming back later and finding the value of each tag later in the code.
In MYSQL it would go like this
CREATE PROCEDURE O_CAPEREZ.GIO_SP (
#vNOMBRE VARCHAR(50))
AS
BEGIN
INSERT INTO G_PRUEBA_SP(NOMBRE)
VALUES (#vNOMBRE);
select 'Hola' as Greeting, #vNOMBRE as Name
END
Also note what Marc_s commented
You need to set the .Direction of the parameter BEFORE making the call to .ExecuteNonQuery()

Must declare the table variable "#TableName"

I am getting this error in Visual Studio for this part of my code. Why? I do declare #TableName below as FormField.
Must declare the table variable "#TableName".
Code:
using (SqlCommand sqlCmd2 = new SqlCommand())
{
sqlCmd2.Connection = sqlConn2;
sqlCmd2.CommandType = System.Data.CommandType.Text;
sqlCmd2.CommandText = string.Format("SELECT DisplayName AS MyColumn FROM #TableName WHERE EventId = 1 AND Visible = 1");
sqlCmd2.Parameters.Add("#TableName", SqlDbType.NVarChar).Value = "FormField";
sqlCmd2.ExecuteNonQuery();
}
The table name can not be resolved by using parameter. For this purpose you have to prepare your statement when passing to SqlCommand:
string tableName = "FormField";
using (SqlCommand sqlCmd2 = new SqlCommand())
{
sqlCmd2.Connection = sqlConn2;
sqlCmd2.CommandType = System.Data.CommandType.Text;
sqlCmd2.CommandText = string.Format("SELECT DisplayName AS MyColumn FROM {0} WHERE EventId = 1 AND Visible = 1", tableName );
sqlCmd2.ExecuteNonQuery();
}
SqlParameter can only be used for passing parameter. E.g. in insert or update statements. But if you want to do this, be sure tableName can not be changed from outside your source code to prevent from sql injection in any case. Youre maybe able to filter acceptable values before executing any queries.
You cannot specify a table name (or column name or function or operator) as a parameter.
Hence, in your query, #TableName is interpreted as a table variable, rather than a parameter and the table variable is not defined.
Alas, you need to put the table in explicitly, using string operations.

Must declare scalar variaable #ID when inserting record

I am inserting a record in DB, I am using inline query, I have to get the generated ID, this is how I am doing this
var query = "INSERT INTO [Users] ([Username],[Password]) VALUES(#username,#pwd) SELECT #ID = SCOPE_IDENTITY()";
OleDbParameter[] queryparam = new OleDbParameter[3];
queryparam[0] = new OleDbParameter("#username", OleDbType.VarChar);
queryparam[0].Value = "username";
queryparam[1] = new OleDbParameter("#pwd", OleDbType.VarChar);
queryparam[1].Value = "123456";
queryparam[2] = new OleDbParameter("#ID", OleDbType.Integer);
queryparam[2].Direction = ParameterDirection.Output;
OleDbCommand myCommand = new OleDbCommand();
myCommand.Connection = DBConnectionHelper.getConnection();
myCommand.CommandText = query;
myCommand.Parameters.AddRange(queryparam);
adapter.InsertCommand = myCommand;
myCommand.ExecuteNonQuery();
I am getting error:
Must declare the scalar variable "#ID".
Must declare the scalar variable "#username".
I also tried using (int)myCommand.ExecuteScalar() but no luck.
How can I get generated ID in this case?
From OleDbCommand.Parameters:
The OLE DB .NET Provider does not support named parameters for passing parameters to an SQL statement or a stored procedure called by an OleDbCommand when CommandType is set to Text1. In this case, the question mark (?) placeholder must be used. For example:
SELECT * FROM Customers WHERE CustomerID = ?
So, for your code your query should be:
INSERT INTO [Users] ([Username],[Password]) VALUES(?,?) SELECT ? = SCOPE_IDENTITY()
1Which is the default, so is what your code is using.
From what I can make out of your question, you have declared a variable called query and written your query inside it. Also, your parameter declaration seems to be correct.
Now the problem might be in this line :
myCommand.CommandText = _query;
The variable is query and here you are using _query. So that is probably why the parameters are not added for that particular query and hence the error. It should be like this :
myCommand.CommandText = query;
Hope this clears.
Instead of Select Use Set #Id=Scope_Identity()

Error on SQL Server Insert stored procedure

I'm trying to simply insert data into a table in SQL Server from a C# winforms application.
Currently when the executing the query I'm receiving the error:
Incorrect syntax near 'soAddItems'
Here is my insert code of the class handling my database CRUD operations:
Note: As test data:
itemName = "test"
skuNo = "a123"
itemPrice = 2.99
stockItemToAdd = 3
itemPic = "C:\Users\Name\Pictures\pic.png"**
Code:
public virtual void AddItem(string itemName, string skuNo, double itemPrice, int stockAmountToAdd, string itemPic)
{
using (SqlConnection open = new SqlConnection(connectionString))
{
SqlCommand insertCommand = new SqlCommand("soAddItems", open);
open.Open();
insertCommand.Parameters.Add("#itemName", SqlDbType.NChar).Value = itemName;
insertCommand.Parameters.Add("#skuNo", SqlDbType.VarChar).Value = skuNo;
insertCommand.Parameters.Add("#itemPrice", SqlDbType.Decimal).Value = itemPrice;
insertCommand.Parameters.Add("#instockAmount", SqlDbType.BigInt).Value = stockAmountToAdd;
insertCommand.Parameters.Add("#lastSold", SqlDbType.DateTime).Value = DateTime.Today;
insertCommand.Parameters.Add("#itemPic", SqlDbType.NChar).Value = itemPic;
//***** Error on the execute*****
insertCommand.ExecuteNonQuery();
};
}
SQL Server stored procedure:
ALTER PROC [dbo].[soAddItems]
#itemName nchar,
#skuNo varchar,
#itemPrice float,
#instockAmount bigint,
#lastSold dateTime,
#itemPic varchar
AS
BEGIN
INSERT INTO items (itemName, skuNo, itemPrice, instockAmount, lastSold, itemPic)
VALUES (#itemName, #skuNo, #itemPrice, #instockAmount, #itemPic)
END
Can anyone tell me where I'm going wrong?
You need to tell the SqlCommand command type as stored procedure.
Try this:
insertCommand.CommandType = CommandType.StoredProcedure;
It looks like you forget to assign your SqlCommand.CommandType property.
Because it is Text as a default.
insertCommand.CommandType = CommandType.StoredProcedure;
Besides the missing CommandType.StoredProcedure, you have a second major flaw: if you define a parameter like this: #skuNo varchar - then you get a string of 1 character length!
That's usually not what you want - you should ALWAYS specify a length when defining parameters and variables in T-SQL!
Use: #skuNo varchar(50) or whatever length you need

Categories

Resources