DB2 dynamic sql, variables, returning scalar value - c#

I have a table with:
ID (int, generated)
OriginalID (int, nullable)
...
other columns.
When inserting a record I would like to set the "OriginalID" to the "ID" that was generated. Currently the script looks like this:
BEGIN ATOMIC
DECLARE i INT;
SET i = SELECT ID FROM NEW TABLE(INSERT INTO MyTable(...) VALUES (....));
UPDATE MyTable SET ORIGINALID=i WHERE ID=i;
END
now the problem I have is how to return the "i", to get the value in code, when calling ExecuteScalar method?
I have already tried "SELECT i FROM SYSIBM.SYSDUMMY1", "VALUES (i)" - both statements return a "null" in code.
I do get the value if I call ExecuteScalar with script like this:
SELECT ID FROM NEW TABLE(INSERT INTO MyTable(...) VALUES (....));
The query is executed from c# code (.net core/IBM.Data.DB2.Core 1.2.2.100) by using a DB2Connection and DB2Command:
var query = ...;
var connection = new DB2Connection(conn_string);
var command = new DB2Command(connection, query);
command.parameters.Add(...);
var returnValue = command.ExecuteScalar(); << the value here is always
null if trying to return the db2 variable; it works only if executing
"single statement"
**** edit ****
Got it working with help of Mark Barinstein:
last statement in query is now: "set #newId = i;" and in code:
var newId = new DB2Parameter("#newId", DB2Type.Integer);
newId.Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(newId);
command.ExecuteNonQuery();
var id = newId.Value;

Related

Passing an array to SQL parameter with Dapper

I want to insert into a table an array of values if they don't exist, the array is small and would not exceed 10 items, so it is safe to pass in an insert.
How can I execute this code with Dapper? I tried the following but I am getting an error:
const string sqlSymbolsInsert =
#"INSERT INTO Country (Name)
SELECT NewNames.Name FROM (VALUES(#Names)) AS NewNames (Name)
WHERE NOT EXISTS (SELECT 1 FROM Country AS C WHERE C.Name = NewNames.Name);";
await using var cn = new SqlConnection(CONNECTION_STRING);
await cn.ExecuteAsync(sqlSymbolsInsert, new { Names = countries.Select(x => x.Name) });
The error is:
Core Microsoft SqlClient Data Provider: Incorrect syntax near ','.
There is a similar problem on SO, but it is for the IN clause:
dapper "IN" clause not working with multiple values
Is there another way in Dapper to pass an array in my case?
What you are trying to do isn't possible. But instead of the array, you can just use your countries collection. It has a Name property and Dapper will run the query for each item in a suitable collection:
const string sqlSymbolsInsert =
#"INSERT INTO Country (Name)
SELECT #Name WHERE NOT EXISTS
(
SELECT 1
FROM Country
WHERE Name = #Name
)";
await using var cn = new SqlConnection(CONNECTION_STRING);
await cn.ExecuteAsync(sqlSymbolsInsert, countries);

How to get return value from query with Dapper?

I'm trying to get a return value from an insert query using Dapper.
Here's how I try to make it work:
// the query with a "returning" statement
// note : I have a trigger that sets the Id to a new value using the generator IF Id is null...
string SQL = "UPDATE OR INSERT INTO \"MyTable\" (\"Id\", \"Name\") " + "VALUES (#Id, #Name) RETURNING \"Id\"";
using (var conn = new FbConnection(MyConnectionString)) {
var parameters = new DynamicParameters();
parameters.Add("Id", null, System.Data.DbType.Int32);
parameters.Add("Name", "newName", System.Data.DbType.String);
// --- also add the returned parameters
parameters.Add("retval", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
// execute the query with Dapper....
conn.Execute(SQL, parameters);
// expecting the new ID here but it is ALWAYS null....!!!
var newId = parameters.Get<object>("retval");
}
Now to make sure my query is ok and not the source of the problem here, I implemented a similar code with my actual connector (Firebird in this case), as follows:
using (var conn = new FbConnection(MyConnectionString)) {
FbCommand cmd = new FbCommand(SQL, conn);
cmd.Parameters.Add("Id", null);
cmd.Parameters.Add("Name", "newName");
FbParameter pRet = cmd.Parameters.Add("retval", FbDbType.Integer);
pRet.Direction = ParameterDirection.ReturnValue;
conn.Open();
cmd.ExecuteNonQuery();
// => the new value is NOT null here, it returns the correct id!!
var newId = Convert.ToInt32(pRet.Value);
conn.Close();
}
What is my mistake in the Dapper code? Why is one version OK and NOT the other? I've read that Dapper executes ExecuteNonQuery() so I'm not expecting this to be the cause.
The returning clause acts like select, in that it returns data in a results grid. As such, your query should be executed as a query. This also has the advantage that it significantly simplifies the calling code:
var newId = conn.QuerySingle<int>(SQL, new { Id = (int?)null, Name = "newName" });
If you need additional fields, this can be extended to use a custom return type that matches the columns coming back, or a value-tuple. For example:
var row = conn.QuerySingle<MyTable>(SQL, new { Id = (int?)null, Name = "newName" });
or
var row = conn.QuerySingle<(int id, string name)>(SQL, new { Id = (int?)null, Name = "newName" });
-- edit
You can access the returned values by
int iVal = row.Result.id;
string sVal = row.Result.name;
The biggest drawback to Dapper's Execute() is that it returns "number of rows impacted" (by updates, deletes, etc)... even if all occurs in a transaction which, after an error occurred, was cancelled via ROLLBACK. The return-value still holds the impacted-row-number before Rollback, tho the transaction was not committed. Yikes!!
DynamicParameters() was more complex, but worked. But in Moq Tests, I encountered a number of exceptions that I couldn't easily resolve.
My solution (similar to Marc and neggenbe's) followed these steps:
In the SQL stored-procedure, return an integer-value via,
SELECT -1 -- 0 for success, -1 for error
note--> SQL-Returns (ie. RETURN(1)) are ignored for some reason.
Use Dapper as such,
int result = conn.QueryFirst<int>(SProcName, new { id = req.Id, value = req.Value }, commandType: CommandType.StoredProcedure);
note--> Other commands work as well with differing return types:
QueryFirst: result = key/value where value=[return value]
QueryFirst<int>: result = integer
QuerySingle: Detailed by Marc and neggenbe's answer.
Check result appropriately, as the above examples.

Why does a SQL Server string output parameter return 0 on an EF ExecuteSqlCommand call?

I have this code in C# and I want to use a string sReturn as output parameter in my code.
var sReturn = new SqlParameter();
sReturn.ParameterName = "#Return";
sReturn.SqlDbType = SqlDbType.VarChar;
sReturn.Size = 300;
sReturn.Direction = ParameterDirection.Output;
string query = "exec #Return = sqlServerProcedure #id, #dateBegin, #dateEnd";
_context.Database.CommandTimeout = timeout;
_context.Database.ExecuteSqlCommand
(query, sReturn,
new SqlParameter("#id", id),
new SqlParameter("#dateBegin", dateBegin),
new SqlParameter("#dateEnd", dateEnd) );
return sReturn.Value;
sReturn is always returning 0.
Procedure is something like this:
CREATE PROCEDURE [dbo].sqlServerProcedure
(#dateBegin DATETIME,
#dateEnd DATETIME,
#id NUMERIC)
AS
BEGIN
SELECT 'some random message'
END
Why is it always returning 0?
You really don't need to declare #return parameter, if you are expecting a single value from procedure, use ExecuteScalar() method of SQL command.
Also you can use stored procedure type instead of inline SQL query. Your ADO.net code will look like below, no changes in stored procedure.
_context.Database.Connection.Open();
var cmd = _context.Database.Connection.CreateCommand();
cmd.CommandTimeout = timeout;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "sqlServerProcedure";
cmd.Parameters.Add(new SqlParameter("#id", id));
cmd.Parameters.Add(new SqlParameter("#dateBegin", dateBegin));
cmd.Parameters.Add(new SqlParameter("#dateEnd", dateEnd));
var result = (string)cmd.ExecuteScalar();
The error is in the SQL Server procedure and the calling of it in ExecuteSqlCommand.
You have to do this in C#:
string query = "exec sqlServerProcedure #id, #dateBegin, #dateEnd, #Return OUTPUT";
In SQL Server:
CREATE PROCEDURE [dbo].sqlServerProcedure
(#dateBegin DATETIME,
#dateEnd DATETIME,
#id NUMERIC,
#Return VARCHAR(300) OUTPUT)
The documentation for EXECUTE (Transact-SQL) contains the following:
#return_status
Is an optional integer variable that stores the return status of a module. This variable must be declared in the batch, stored procedure, or function before it is used in an EXECUTE statement.
When used to invoke a scalar-valued user-defined function, the #return_status variable can be of any scalar data type.
Since you are invoking a stored procedure (not scalar-valued function), the return value is 0 (success) converted to string.
If you were using ADO.NET, then you could use ExecuteScalar as mentioned in another answer. However EF6 ExecuteSqlCommand method is equivalent of the ADO.NET ExecuteNonQuery and cannot be used to retrieve the returned single row single column record set.
The easiest way to execute your stored procedure and get the desired string result in EF6 is to use the combination of EF SqlQuery<string> method and LINQ FirstOrDefault:
var returnValue = _context.Database
.SqlQuery<string>("sqlServerProcedure #p0, #p1, #p2", dateBegin, dateEnd, id)
.FirstOrDefault();

Return ID if record exist, else Insert and return ID

I have the below C# code to check if the record does not exist, insert and return id. But also I need if the record exists, it return the value. What change should I make to C# and SQL part for this to happen? Database is sQL server. Do I still have to use ExecuteScalar() for this?
con.Open();
// Insert ClinRefFileTypeMaster
string command1 = string.Format(
"if NOT exists (select * from [ClinRefFileTypeMaster] where [ClinRefTypeName] = '{0}') Insert into [ClinRefFileTypeMaster] ([ClinRefTypeName]) output INSERTED.[ClinRefTypeID] VALUES('{0}')",
dataToParse[i][0]
);
SqlCommand ClinRefFileTypeMaster = new SqlCommand(command1, con);
// check if there is an value
object checkValue = ClinRefFileTypeMaster.ExecuteScalar();
if (checkValue != null)
ClinRefFileTypeId = (int)checkValue;
A stored procedure to do all the stuff for you would look something like.....
CREATE PROCEDURE usp_Get_ClinRefTypeID
#ClinRefTypeName VARCHAR(100),
#ClinRefTypeID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #NewID TABLE(ClinRefTypeID INT);
SELECT #ClinRefTypeID = [ClinRefTypeID]
FROM [ClinRefFileTypeMaster]
where [ClinRefTypeName] = #ClinRefTypeName;
IF (#ClinRefTypeID IS NULL)
BEGIN
INSERT INTO [ClinRefFileTypeMaster] ([ClinRefTypeName])
OUTPUT inserted.[ClinRefTypeID] INTO #NewID(ClinRefTypeID)
VALUES(#ClinRefTypeName)
SELECT #ClinRefTypeID = [ClinRefTypeID] FROM #NewID
END
END
And your C# code would look something like.....
con.Open();
// Insert ClinRefFileTypeMaster
SqlCommand cmd = new SqlCommand("usp_Get_ClinRefTypeID", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#ClinRefTypeID", SqlDbType.Int).Direction = ParameterDirection.Output;
cmd.Parameters.Add(new SqlParameter("#ClinRefTypeName", dataToParse));
// get the value back from the output parameter
cmd.ExecuteNonQuery();
int ClinRefTypeName = Convert.ToInt32(cmd.Parameters["#ClinRefTypeID"].Value);
There are many ways to achieve this. 1) You can do it all in inline Sql 2) you can do it all in stored proc. 3) You can do it all in code but split the code as this code is frankly doing too much. In general I would avoid insert/query in the same method.
Also try to use SqlParameters instead of building the query as string concat.
I would propose something like this which makes the code a bit more readable
public int InsertAndRetrieveClientRefId(string clientRefTypeName)
{
int id = GetIdIfRecordExists(clientRefTypeName);
if (id == 0)
{
// insert logic here
id = GetIdIfRecordExists(clientRefTypeName);
}
return id;
}
public int GetIdIfRecordExists(string clientRefTypeName)
{
int id = 0;
string command = "select id from ClinRefFileTypeMaster where ClinRefTypeName = #ClinRefTypeName";
SqlParameter nameParameter = new SqlParameter("#ClinRefTypeName", System.Data.SqlDbType.NVarChar, 10) { Value = clientRefTypeName };
using (SqlConnection connection = new SqlConnection("ConnectionString"))
{
using (SqlCommand cmd = new SqlCommand(command))
{
cmd.Parameters.Add(newParameter);
connection.Open();
cmd.Connection = connection;
int.TryParse(cmd.ExecuteScalar().ToString(), out id);
}
}
return id;
}
do all this in database i.e in store procedure
if not exists (select 1 from [ClinRefFileTypeMaster] where [ClinRefTypeName] =#name)
begin
Insert into [ClinRefFileTypeMaster] ([ClinRefTypeName]) values (#name)
end
else
begin
select (as desired) from ClinRefFileTypeMaster where where [ClinRefTypeName] =#name
end
this will either insert new record or it will select already inserted information
Youll need to add an IF EXISTS clause to the SQL statement as well, checking for the same conditions, and providing logic to return a value.
It seems using ExecuteReader would be better if you need it to return the value from the database.
2¢
I personally would split the logic into two queries and run the If statement within c# checking if the value is in the database, then updating the database else returning a value from the database
conn.open()
int CheckDb;
String Command1 = "select * from [ClinRefFileTypeMaster] where [ClinRefTypeName] = #ClinRefFileTypeId";
using (SqlCommand ClinRefFileTypeMaster = new SqlCommand(command1, con);
{
cmd.Parameters.AddWithValue("#ClinRefFileTypeId", {0});
CheckDb = (int)ClinRefFileTypeMaster.ExecuteScalar();
}
If (CheckDb != 0)
//Logic for returning the value from the database
Else
//Here you can request user check data or insert the value into the database.
if you want to perform Instert operation, I think its better you call a stored procedure and write your query in the procedure. It will be safer.
SqlCommand command = new SqlCommand("procedureName",con);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue(“#value1”, txtValue1.Text);
command.Parameters.AddWithValue(“#value2”, Value2);
int value = command.ExecuteScalar();
IF EXISTS (SELECT 1 FROM Table WHERE FieldValue='')
BEGIN
SELECT TableID FROM Table WHERE FieldValue=''
END
ELSE
BEGIN
INSERT INTO TABLE(FieldValue) VALUES('')
SELECT SCOPE_IDENTITY() AS TableID
END
If you want to pass the querystring, you can call select query and if it returns null perform a insert opeartion and use scope_Identity() to get the ID
INSERT INTO YourTable(val1, val2, val3 ...)
VALUES(#val1, #val2, #val3...);
SELECT SCOPE_IDENTITY();

What is wrong with my SQL function that it always returns null when I try to retrieve its value in C#?

What is wrong with my SQL function that it always returns null when I try to retrieve its value in C#? The function works pretty solid in the SQL designer but when I try to run it using C# it fails.
This is my function definition. First the Table Valued Function:
ALTER FUNCTION dbo.FGetNumberOfChikensInThisDate
(
#Date nvarchar(12),
#IDSource bigint
)
RETURNS Table
AS
RETURN SELECT SUM(NumberOfChicken) AS Number of Chickens
FROM tblChickens
WHERE (SourceID= #IDSource) AND (ReservedDate = #Date)
And this is the Scalar Valued Function :
ALTER FUNCTION dbo.FSGetNumberOfChikensInThisDate
(
#Date nvarchar(12),
#IDSource bigint
)
RETURNS bigint
AS
BEGIN
DECLARE #ret bigint;
SELECT #ret= SUM(NumberOfChicken)
FROM tblChickens
WHERE (ReserveDate = #Date) AND (SourceID= #IDSource)
RETURN #ret;
END;
I use these two methods to create the SQL command string for these functions and pass their parameters and execute them from C#: For table based functions I use:
public static DataTable ExecuteSqlFunction(string functionName, string[] Functionparamers)
{
SqlDataAdapter reader = new SqlDataAdapter();
DataTable table = new DataTable();
string query = "select * from " + functionName + "(";
int index = 0;
foreach (string item in Functionparamers)//{0},{0}
{
query += String.Format("{0}", item);
query += ++index >= Functionparamers.Length ? String.Format(")", item) : ",";
}
if (connection.State != ConnectionState.Open)
{ connection.Open(); }
cmd = new SqlCommand(query, connection);
reader.SelectCommand = cmd;
reader.Fill(table);
connection.Close();
return table;
}
and using it like this :
ExecuteSqlFunction("dbo.FGetNumberOfChikensInThisDate",new string[] { Date, API.CurrentSourceID });
will create this string :
select * from dbo.FGetNumberOfChikensInThisDate(1391/12/01,4) //the date is in Persian
The returning DataTable object has one row but when I write
dataTable.Rows[0][0].ToString();
I get a
""
string while when I run this SQL command on the SQL it runs just fine! (Actually I try to execute the SQL command of this function inside SQL designer and i get the results just fine but when i try to run it using c# it just acts like this).
And for the scalar valued function is use the same former method with a slight change at the end:
public static object ExecuteScalarSqlFunction(string functionName, string[] FunctionParameters)
{
string query = "select * from " + functionName + "(";
int index = 0;
object result;
foreach (string item in FunctionParameters)//{0},{0}
{
query += String.Format("{0}", item);
query += ++index >= FunctionParameters.Length ? String.Format(")", item) : ",";
}
if (connection.State != ConnectionState.Open)
{ connection.Open(); }
cmd = new SqlCommand(query, connection);
result = cmd.ExecuteScalar();
connection.Close();
return result;
}
Calling this function like this:
ExecuteScalarSqlFunction("dbo.FSGetNumberOfChikensInThisDate",new string[] { Date, API.CurrentSourceID });
Will create this SQL string:
"select * from dbo.FSGetNumberOfChikensInThisDate(1391/12/01,4)"
and when I get to the
result = cmd.ExecuteScalar();
section it generates an exception saying:
Invalid object name 'dbo.FSGetNumberOfChikensInThisDate'.
while it does exists ! and is there!.
The ExecuteSqlFunction() is working for the rest of my functions but these are exceptions which fail. The ExecuteScalarFunction is not tested since I don't know if I have missed anything in it but I'm sure the ExecuteSqlFunction which deals with table valued SQL functions is OK so it must be something else that I am missing here.
The function FSGetNumberOfChikensInThisDate returns a scalar value - a single result of type bigint - which cannot be used as the source of a SELECT statement. It can be used as a column source for a SELECT statement however.
Try this SQL statement instead:
SELECT dbo.FSGetNumberOfChikensInThisDate('1391/12/01', 4);
Should return a single row with a single unnamed BigInt column result.

Categories

Resources