How can i call OUT parameter in PROCEDURE via Npgsql - c#

How can i call OUT parameter in PROCEDURE via Npgsql
I am getting this error when i run below code
-- PostgreSQL 14 -> PROCEDURE
CREATE OR REPLACE PROCEDURE sp_add_users_new(arrJsnUsers JSON[], jOut OUT JSON) AS $$
DECLARE
intSpStatus INT;
v json;
BEGIN
FOREACH v IN ARRAY arrJsnUsers
LOOP
INSERT INTO tbl_user (vhr_name, vhr_password, sin_record_status)
VALUES(v->>'strName', v->>'strPassword', (v->>'intRecordStatus')::SMALLINT);
END LOOP;
COMMIT;
intSpStatus = 1;
jOut['intSpStatus'] = intSpStatus;
END;
$$ LANGUAGE plpgsql
--Dot net core 5
using (NpgsqlConnection objCon = new NpgsqlConnection("..."))
{
objCon.Open();
using (NpgsqlCommand objSqlCommand = new NpgsqlCommand("CALL sp_add_users_new(:p1, :p2)", objCon))
{
// Parameters
NpgsqlParameter[] lstSqlParameter = {
new NpgsqlParameter("p1", NpgsqlDbType.Array|NpgsqlDbType.Json) {
Value = lstUsers,
DataTypeName = "arrJsnUsers"
},
new NpgsqlParameter("p2", NpgsqlDbType.Json) {
Value = jOut,
DataTypeName = "jOut",
Direction = ParameterDirection.Output
},
};
objSqlCommand.Parameters.AddRange(lstSqlParameter);
// Execute
IDataReader objDataReader = objSqlCommand.ExecuteReader();
}
objCon.Close();
}
--Error
42703: column "p2" does not exist

Below is a sample which works - it's recommended to read the Npgsql docs on this. Some comments:
You don't specify the output parameter in SQL - just pass a NULL as per the PostgreSQL docs.
Since #p2 is an output parameter, it makes no sense to specify its value (jOut1 above).
DataTypeName (jOut above) also doesn't make sense here; that's used to indicate to Npgsql which PG type to send for input parameters (similar to NpgsqlDbType).
using var objCon = new NpgsqlConnection("Host=localhost;Username=test;Password=test");
objCon.Open();
using var objSqlCommand = new NpgsqlCommand("CALL sp_add_users_new(#p1, NULL)", objCon);
// Parameters
var lstUsers = new[] { #"{ ""Foo"": ""Bar"" }" };
NpgsqlParameter[] lstSqlParameter = {
new NpgsqlParameter("p1", NpgsqlDbType.Array|NpgsqlDbType.Json) { Value = lstUsers, DataTypeName = "arrJsnUsers"},
new NpgsqlParameter("p2", NpgsqlDbType.Json) { Direction = ParameterDirection.Output},
};
objSqlCommand.Parameters.AddRange(lstSqlParameter);
// Execute
IDataReader objDataReader = objSqlCommand.ExecuteReader();
Console.WriteLine(objSqlCommand.Parameters[1].Value);

Related

How can I provide an JSON fragment as an value to SqlParameter in CosmosDB

In Cosmos DB I can use Json fragments as part of a where clause for a query such that
SELECT * FROM c WHERE c.path.to.propery={"where":{"value":{"is":true}}}
yields results (of course if there are any)
now I want to use the same idea when querying using SqlParamters
var stmt = new SqlQuerySpec
{
QueryText = "SELECT * FROM c WHERE c.path.to.propery=#myjson",
Parameters = new SqlParameterCollection()
{
new SqlParameter{ Name = "#myjson", Value = ??? }
}
};
Any idea what (if any at all) needs to go instead of the ??? to have the json fragment {"where":{"value":{"is":true}}} in the query
Edit
Simply adding a string as a param value will not work as strings correctly will be enclosed in " to prevent SQL injection attacks.
using an anonymous object like
myjson = new { where = new { value = new { #is = true } } }
might work (I havent tried it out) but wont work in my specif case as the JSON and its structure are unknown at compile time.
I found the solution in the form of cosmos DBs StringToObject function so that
var myjson = #"{""where"":{""value"":{""is"":true}}}"
var stmt = new SqlQuerySpec
{
QueryText = "SELECT * FROM c WHERE c.path.to.propery=StringToObject(#myjson)",
Parameters = new SqlParameterCollection()
{
new SqlParameter{ Name = "#myjson", Value = myjson }
}
};

Using output parameters when calling stored procedures with AsyncPoco

I'm executing a SQL Server stored procedure which returns a single output parameter using AsyncPoco:
CREATE PROCEDURE [dbo].[GenerateId]
(#RETVAL VARCHAR(12) OUTPUT)
AS
DECLARE #pkgID VARCHAR(12)
BEGIN
SELECT #pkgID = 'ABC' + '000'
SELECT #RETVAL = #pkgID
END
GO
Here's how I'm calling it:
var spOutput = new SqlParameter("#RETVAL", System.Data.SqlDbType.VarChar)
{
Direction = System.Data.ParameterDirection.Output,
Size = 12,
};
var sql = $";EXEC [dbo].[GenerateId] #0 OUTPUT";
var response = await _dbAdapter.FetchAsync<dynamic>(sql, new object[] { spOutput });
return (string)spOutput.Value;
This is the error I get:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Object.GetType()
at AsyncPoco.Database.FormatCommand(String sql, Object[] args) in C:\Aldenteware\AsyncPoco\code\AsyncPoco\Database.cs:line 2279
I figured it out:
var spOutput = new SqlParameter("#RETVAL", System.Data.SqlDbType.VarChar)
{
Direction = System.Data.ParameterDirection.Output,
Size = 12,
};
var sql = $";EXEC [dbo].[GenerateId] ##RETVAL = #0 OUTPUT";
var sqlClass = new Sql();
var s = sqlClass.Append(sql, spOutput);
var response = await _dbAdapter.ExecuteAsync(s);
return (string)spOutput.Value;

IBM db2Command SQL SELECT * FROM IN #namedParameter

When using the C# code below to construct a DB2 SQL query the result set only has one row. If I manually construct the "IN" predicate inside the cmdTxt string using string.Join(",", ids) then all of the expected rows are returned. How can I return all of the expected rows using the db2Parameter object instead of building the query as a long string to be sent to the server?
public object[] GetResults(int[] ids)
{
var cmdTxt = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #ids ) ";
var db2Command = _DB2Connection.CreateCommand();
db2Command.CommandText = cmdTxt;
var db2Parameter = db2Command.CreateParameter();
db2Parameter.ArrayLength = ids.Length;
db2Parameter.DB2Type = DB2Type.DynArray;
db2Parameter.ParameterName = "#ids";
db2Parameter.Value = ids;
db2Command.Parameters.Add(db2Parameter);
var results = ExecuteQuery(db2Command);
return results.ToArray();
}
private object[] ExecuteQuery(DB2Command db2Command)
{
_DB2Connection.Open();
var resultList = new ArrayList();
var results = db2Command.ExecuteReader();
while (results.Read())
{
var values = new object[results.FieldCount];
results.GetValues(values);
resultList.Add(values);
}
results.Close();
_DB2Connection.Close();
return resultList.ToArray();
}
You cannot send in an array as a parameter. You would have to do something to build out a list of parameters, one for each of your values.
e.g.: SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #id1, #id2, ... #idN )
And then add the values to your parameter collection:
cmd.Parameters.Add("#id1", DB2Type.Integer).Value = your_val;
Additionally, there are a few things I would do to improve your code:
Use using statements around your DB2 objects. This will automatically dispose of the objects correctly when they go out of scope. If you don't do this, eventually you will run into errors. This should be done on DB2Connection, DB2Command, DB2Transaction, and DB2Reader objects especially.
I would recommend that you wrap queries in a transaction object, even for selects. With DB2 (and my experience is with z/OS mainframe, here... it might be different for AS/400), it writes one "accounting" record (basically the work that DB2 did) for each transaction. If you don't have an explicit transaction, DB2 will create one for you, and automatically commit after every statement, which adds up to a lot of backend records that could be combined.
My personal opinion would also be to create a .NET class to hold the data that you are getting back from the database. That would make it easier to work with using IntelliSense, among other things (because you would be able to auto-complete the property name, and .NET would know the type of the object). Right now, with the array of objects, if your column order or data type changes, it may be difficult to find/debug those usages throughout your code.
I've included a version of your code that I re-wrote that has some of these changes in it:
public List<ReturnClass> GetResults(int[] ids)
{
using (var conn = new DB2Connection())
{
conn.Open();
using (var trans = conn.BeginTransaction(IsolationLevel.ReadCommitted))
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
var parms = new List<string>();
var idCount = 0;
foreach (var id in ids)
{
var parm = "#id" + idCount++;
parms.Add(parm);
cmd.Parameters.Add(parm, DB2Type.Integer).Value = id;
}
cmd.CommandText = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( " + string.Join(",", parms) + " ) ";
var resultList = new List<ReturnClass>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var values = new ReturnClass();
values.Id = (int)reader["ID"];
values.Col1 = reader["COL1"].ToString();
values.Col2 = reader["COL2"].ToString();
resultList.Add(values);
}
}
return resultList;
}
}
}
public class ReturnClass
{
public int Id;
public string Col1;
public string Col2;
}
Try changing from:
db2Parameter.DB2Type = DB2Type.DynArray;
to:
db2Parameter.DB2Type = DB2Type.Integer;
This is based on the example given here

EF Core ExecuteSqlCommand and parameters

I have to execute an sql command on ef core 1.1.2:
var projectParam = new SqlParameter("#projectid", SqlDbType.UniqueIdentifier).Value = inventory.ProjectId;
var locationParam = new SqlParameter("#locationid", SqlDbType.UniqueIdentifier).Value = location.Id;
var scanOrderParam = new SqlParameter("#scanorder", SqlDbType.Int).Value = scanOrder;
_ctx.Database.ExecuteSqlCommand("update Inventories set ScanOrder=ScanOrder+1 where ProjectId = '#projectid' AND LocationId = '#locationid' AND ScanOrder>'#scanorder';",
parameters: new[] {
projectParam, locationParam, scanOrderParam
});
It throws the exception:
An unhandled exception has occurred: Conversion failed when converting from a character string to uniqueidentifier.
If I write ScanOrder>#scanorder into the command it says that scanorder parameter must be declared. Which is an int.
If I don't use apostrophes at projectid and locationid it throws the exception parameter must be declared.
What is the proper way for using parameters with ef core?
Edit (solution):
The problem was the way I declared parameters and give them values. If I use this form it works:
var projectParam = new SqlParameter("#projectid", SqlDbType.UniqueIdentifier);
projectParam.Value = inventory.ProjectId;
var locationParam = new SqlParameter("#locationid", SqlDbType.UniqueIdentifier);
locationParam.Value = location.Id;
var scanOrderParam = new SqlParameter("#scanorder", SqlDbType.Int);
scanOrderParam.Value = scanOrder;
var queryItem = new SqlParameter("#table", SqlDbType.Char) {Value = yourValue};

Use a stored procedure in entity framework

I have a model-first EF model. I just imported the first stored procedure: cpas_POIDVendorProjectDate
I imported it as a function. It has three input parameters: #ProjectID(int), #VendorID(int), and #Workdate(datetime), and returns #POID(int).
Here's the SQL code:
CREATE PROCEDURE [dbo].[cpas_POIDVendorProjectDate]
#VendorID int,
#ProjectID int,
#WorkDate datetime,
#PO_ID int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #RowCount int;
SELECT #PO_ID = ID FROM tblPO WHERE
VendorID = #VendorID
AND ExpirationDate >= #WorkDate
AND (ProjectID IS NULL OR ProjectID = #ProjectID)
AND CapitalExpense = (
SELECT CapitalExpense FROM tblProjects WHERE ID=#ProjectID)
AND GroupCode in (1,3,5);
SET #RowCount = ##RowCount;
IF (#RowCount != 1)
SET #PO_ID = -1*#RowCount;
END
I called it in my c# program as follows:
context.cpas_POIDVendorProjectDate(
currVendorID, currProjectID, currWorkDate, currPOID);
Intellisense says my use of "context" is wrong...It's a "variable", and I'm using it as a "method".
In addition, currPOID is rejected because it's looking for a system.data.objects.OjbectParameter, not an int. Intellisense is happy with the function name and other parameters (strangely...)
What am I doing wrong here?
You can always do this if nothing else works:
using(var context = new MyDataContext())
{
using(var cmd = context.Database.Connection.CreateCommand())
{
cmd.CommandText = "cpas_POIDVendorProjectDate";
cmd.CommandType = CommandType.StoredProcedure;
//if the stored proc accepts params, here is where you pass them in
cmd.Parameters.Add(new SqlParameter("VendorId", 10));
cmd.Parameters.Add(new SqlParameter("ProjectId", 12));
cmd.Parameters.Add(new SqlParameter("WorkDate", DateTimw.Now));
var poid = (int)cmd.ExecuteScalar();
}
}
If you would like an object orientated way, then Mindless passenger has a project that allows you to call a stored proc from entity frame work like this....
using (testentities te = new testentities())
{
//-------------------------------------------------------------
// Simple stored proc
//-------------------------------------------------------------
var parms1 = new testone() { inparm = "abcd" };
var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
var r1 = results1.ToList<TestOneResultSet>();
}
... and I am working on a stored procedure framework (here) which you can call like in one of my test methods shown below...
[TestClass]
public class TenantDataBasedTests : BaseIntegrationTest
{
[TestMethod]
public void GetTenantForName_ReturnsOneRecord()
{
// ARRANGE
const int expectedCount = 1;
const string expectedName = "Me";
// Build the paraemeters object
var parameters = new GetTenantForTenantNameParameters
{
TenantName = expectedName
};
// get an instance of the stored procedure passing the parameters
var procedure = new GetTenantForTenantNameProcedure(parameters);
// Initialise the procedure name and schema from procedure attributes
procedure.InitializeFromAttributes();
// Add some tenants to context so we have something for the procedure to return!
AddTenentsToContext(Context);
// ACT
// Get the results by calling the stored procedure from the context extention method
var results = Context.ExecuteStoredProcedure(procedure);
// ASSERT
Assert.AreEqual(expectedCount, results.Count);
}
}
internal class GetTenantForTenantNameParameters
{
[Name("TenantName")]
[Size(100)]
[ParameterDbType(SqlDbType.VarChar)]
public string TenantName { get; set; }
}
[Schema("app")]
[Name("Tenant_GetForTenantName")]
internal class GetTenantForTenantNameProcedure
: StoredProcedureBase<TenantResultRow, GetTenantForTenantNameParameters>
{
public GetTenantForTenantNameProcedure(
GetTenantForTenantNameParameters parameters)
: base(parameters)
{
}
}
If either of those two approaches are any good?

Categories