Calling oracle stored procedure repeatedly - c#

Is it a good practice to call a stored procedure again and again inside a foreach loop to insert data into an Oracle table? Or is there an alternate way to do this?
I have the following procedure:
procedure proc1 (id in varchar2,
level in varchar2,
title in varchar2,
p_id in varchar2,
url in varchar2)
This is the code calling it:
foreach (var c in xDoc.Descendants("cat"))
{
// call store procedure provide all values
foreach (var a in xDoc.Descendants("abc"))
{
// call store procedure provide values
foreach (var d in xDoc.Descendants("def"))
{
// call stored procedure provide values
}
}
}
Is there a better way to do this?

I would personally architect the Stored Procedure to give you the table that you want so that you only have to call it once. Calling the procedure multiple times like this is horribly inefficient because the database is generating a result set multiple times and you have the network overhead. If you create the procedure to return the table that you need, instead of bits and pieces of it, you can call the procedure once and iterate through the table with the cursor.

Assuming you are using ODP.NET, you can use array binding to call your procedure multiple times during a single database round-trip. You pretty much bind your parameters as you normally would, except you assign an array (instead of just one value) to OracleParameter.Value and set OracleCommand.ArrayBindCount accordingly.
Let me give you a simplified example and I'm sure you won't have trouble adapting it to your needs...
Oracle:
CREATE TABLE TEST (
ID INT PRIMARY KEY
);
CREATE OR REPLACE PROCEDURE TEST_INSERT (ID IN NUMBER) AS
BEGIN
INSERT INTO TEST VALUES(ID);
END TEST_INSERT;
C#:
using (var conn = new OracleConnection("your connection string")) {
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "TEST_INSERT";
var param = cmd.Parameters.Add("ID", OracleDbType.Int32, System.Data.ParameterDirection.Input);
int[] arr = { 1, 2, 3, 4, 5, 6 };
param.Value = arr;
cmd.ArrayBindCount = arr.Length;
cmd.ExecuteNonQuery();
}
After this piece of code executes, the TEST table will contain all six values from the arr.
So, instead of calling your procedure in each iteration, simply memorize values in an array then pass it to your procedure in one big call at the very end.

Related

EF Core, Postgres call a stored procedure with a string array parameter

I can do:
await _sensorContext.Database.ExecuteSqlRawAsync("call testproc()");
And I can do:
System.Data.Common.DbConnection? c = _sensorContext.Database.GetDbConnection();
c.Open();
var cmd = c.CreateCommand();
cmd.CommandText = "call dewpointaverage('{\"70B3D52DD50003AC\",\"70B3D52DD5000452\"}'::varchar[])";
cmd.ExecuteNonQuery();
Which works, but I cannot figure out how to build and pass the parameter array in either (though preferably the first) approach.
There is no returned result set, the proc uses the parameters to calculate values and populate a table.
How can I pass the parameter array into either of these code examples?
await this._sensorContext.Database
.ExecuteSqlRawAsync("EXEC StoredProcedureName #FirstParameter, #SecondParameter",
new SqlParameter("FirstParameter", "ParameterValue"),
new SqlParameter("SecondParameter", "SecondParameterValue"));
To pass an array of elements, you can create an UserDataTable and follow this guide to give a list to your stored procedure.
Table-valued parameters

Pass integer array to the SQL Server stored procedure

Well this well answered question however I can't get it right for me. What I am trying to do call a stored procedure from .NET Core project using Entity Framework with some parameters. One of those parameter should be array (which I consider table type in SQL Server by create a custom table data type) type. I followed this Stackoverflow link. But got an error when I tried to execute my SQL command.
Here is my code:
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
foreach (var section in model.VMSectionIds) //model.VMSectionIds contains set of integers
{
dt.Rows.Add(section);
}
and finally I call stored procedure like this:
var sectiolist = new SqlParameter("#Sections", SqlDbType.Structured)
{
TypeName = "[dbo].[SectionList]",
Value = dt
};
_db.ExecuteSqlCommand("EXEC [SP_GenerateRegularEmployeeSalary] "+mastermodel.ID+","+ fromdate + "," + todate + ",1," + sectiolist + ""); //don't worry I took care of SQL injection for others parameter
But this execution throws an exception
SqlException: Must declare the scalar variable "#Sections"
I can't figure it out where exact problem is. Here call of stored procedure (with some static test parameter) from SQL for clear understanding of my stored procedure call mechanism:
DECLARE #data [SectionList]
INSERT #data (Id) VALUES (2, 3)
EXEC [SP_GenerateRegularEmployeeSalary] 2,'20190401','20190430','1',#data
Looks that you are using the ExecuteSqlCommand incorrectly. Try this way and don't use string concatenation in your code to avoid SQL Injection attacks in your application. Read more about it here.
Also put the correct expected parameter names from the stored procedure: SP_GenerateRegularEmployeeSalary.
Option 1
_db.ExecuteSqlCommand("EXEC [SP_GenerateRegularEmployeeSalary] #ID, #FromDate, #ToDate, #Flag, #Sections",
new SqlParameter("#ID", mastermodel.ID),
new SqlParameter("#FromDate", fromdate),
new SqlParameter("#ToDate", todate),
new SqlParameter("#Flag", 1),
new SqlParameter("#Sections", sectiolist));
Option 2
_db.ExecuteSqlCommand("EXEC [SP_GenerateRegularEmployeeSalary] #ID = {0}, #FromDate = {1}, #ToDate = {2}, #Flag = 1, #Sections = {4}", mastermodel.ID, fromdate, todate, sectiolist);
Please read this documentation about this method.
he using ExecuteSqlCommand incorrectly. he should not used string concatenation to avoid SQL Injection attacks in th application
_db.ExecuteSqlCommand("EXEC SP_GenerateRegularEmployeeSalary #YOUR_PARAM_ON_STOREPROCEDURE", sectiolist);

How can i get #rErr / Output Parameter value in c#?

I create a SQL stored procedure
create proc p1
(
#name1 nvarchar(50),
#rErr int OUTPUT
)
as
begin Transaction
insert into test (name1)
values (#name1)
if #name1 = 'n100'
Begin
Rollback Transaction
Set #rErr=50001
Return #rErr
End
commit Transaction
How can I get the #rErr value in C#?
Accessing output parameters is a little awkward; you need to add them to the parameters collection in the usual way, with with a .Direction of Output. Then you can read the .Value of the parameter object after executing the method. However: it is much easier to use select and process it as a result. Note that return values can be done in a similar way, but with the appropriate .Direction. The fact that you both output and return it, in this case, makes it even more fun... I'd just use the output part, personally. Or ... throw an exception (raiserrror).
Something like:
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "p1";
cmd.CommandType = CommandType.StoredProcedure;
var name = cmd.CreateParameter();
name.ParameterName = "#name1";
name.Value = "abc"; // etc
cmd.Parameters.Add(name);
var err = cmd.CreateParameter();
err.ParameterName = "#rErr";
err.Direction = ParameterDirection.Output;
cmd.Parameters.Add(err);
cmd.ExecuteNonQuery();
if (err.Value is int i)
{
// error i happened
}
}
However: if you'd just used:
raiserror (50001, 16, 1) -- severity needs to be at least 16 here; typically = 16
or (in more recent SQL Server versions):
throw 50001, 'oops', 1
you can get a similar result much more easily; this will result in an Exception direction from the ADO.NET layer.
(note that you should add the custom error message formally to sysmessages when using raiserror - throw doesn't require that step)
If you used the throw (or raiserror) approach, and removed the output parameter, this entire piece of code could become, with some help from Dapper:
conn.Execute("p1", new { name1 = "abc" }, commandType: CommandType.StoredProcedure);
which is a lot easier to get right!
You don't need transaction for a single insert, you can refactor your SP like following. To return the code as output parameter, you can simply use RETURN(50001)
CREATE PROC P1 (#name1 NVARCHAR(50),
#rErr INT output)
AS
IF #name1 <> 'n100'
BEGIN
INSERT INTO test(name1)
VALUES (#name1)
RETURN(0)
END
RETURN(50001)

Stored Procedure is not updating database C#

This is my first time posting, so please let me know if I made a mistake or if my question is poorly worded. I am currently working on an utility app that allows data from several different csv files to be uploaded into a database for reporting purposes.The App takes the files, combines them, and removes duplicates. Once the database is generated any new file can be uploaded to update the database or insert new records.
I looked around and I couldn't find a solution for my problem. At the moment I am having trouble updating the database with new data.
For some reason, whenever I try to update the data using the following stored procedure, none of the changes are saved in the database.
CREATE PROCEDURE [dbo].[UpdateDeviceReport]
#SerialNumber AS VARCHAR(32),
#DeviceType AS VARCHAR(8),
#InstalledOn AS VARCHAR(32),
#PortStatus AS CHAR(6)
AS
BEGIN
UPDATE computer
SET deviceType = #DeviceType, deviceInstalledOn = #InstalledOn
WHERE serialID = #SerialNumber
UPDATE computer_localblocker
SET portStatus = #PortStatus
WHERE computer_serialid = #SerialNumber;
END
GO
Here is the C# code that executes the procedure.
public void updateOrgDevices()
{
using (var conn = new SqlConnection(PopulateDatabase.connectionString))
{
int debug = 0;
conn.Open();
foreach (KeyValuePair<string, DictionaryObjs> element in GlobalVariables.UpdateData)
{
OrgDevRepObj obj = element.Value as OrgDevRepObj;
SqlCommand sqlCommand = new SqlCommand("UpdateDeviceReport", conn);
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.Add("#SerialNumber", SqlDbType.VarChar, 32).Value = obj.SerialNumber.Trim().ToUpper();
sqlCommand.Parameters.Add("#DeviceType", SqlDbType.VarChar, 8).Value = obj.DeviceType;
sqlCommand.Parameters.Add("#InstalledOn", SqlDbType.VarChar, 32).Value = obj.InstalledOn;
sqlCommand.Parameters.Add("#PortStatus", SqlDbType.Char, 6).Value = obj.PortStatus;
debug = sqlCommand.ExecuteNonQuery();
}
}
}
The parameters come from a dictionary/hashmap, GlobalVariables.UpdateData, that stores data rows as objects where the key is the serial number from that row of data(e.g. Dictionary<string[serial Number], DictionaryObjs>) and the values of the fields for each data row are stored as strings, in a class called DictionaryObjs. This is the parent class for all record types. In this case the record type is OrgDevRepObj.
I was able to get the code to work once, but I haven't been able to replicate it. It doesn't throw any errors, but I know its not working because sqlCommand.ExecuteNonQuery(); returns a -1 for each dictionary key value pair. I've also tried using
sqlCommand.Parameters.AddWithValue('#Param', [value]);
Any ideas?
Could you possibly have set NOCOUNT ON globally? See msdn.microsoft.com/en-us/library/ms176031.aspx
Try adding SET NOCOUNT OFF to your stored procedure.
An idea to test according to your situation, create a table called test with a single varchar type field and inside the procedure you make an insert to that table by inserting the SerialNumber. This way you can tell if it is filtering by the correct serial and if the procedure is executed. Luckā€¦

C# running temporary stored procedure

I have a SQL statement that I need to run in C# and would need to get parameters from C# code. I know stored procedures are preferred to avoid SQL injection but I am just looking to do this in C#.
I am translating this SQL to C# but I encountered an error even though the query works in SQL Server Management Studio. It uses temporary stored procedure and temp table below:
-- 1.) Declare a criteria table which can be any number of rows
BEGIN TRY
DROP TABLE #CriteriaTable
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #CriteriaTable (ParameterCode VARCHAR(64), Value VARCHAR(64))
-- 2.) Declare a procedure to add criteria table
BEGIN TRY
DROP PROCEDURE #AddCriteriaTable
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #AddCriteriaTable
(#ParameterCode VARCHAR(64), #Value VARCHAR(64))
AS
INSERT #CriteriaTable
VALUES(#ParameterCode, #Value)
GO
-- 3.) Do a computation which accesses the criteria
BEGIN TRY
DROP PROCEDURE #ComputeBasedOnCriteria
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #ComputeBasedOnCriteria
(#product VARCHAR(36) = 'ABC',
#currency VARCHAR(3) = 'USD',
#zScore FLOAT = .845)
AS
-- Code inside this procedure is largely dynamic sql.
-- This is just a quick mock up
SELECT
#Product ProductCode,
#currency Currency,
950 ExpectedRevenue,
*
FROM
#CriteriaTable c
PIVOT
(min (Value) FOR ParameterCode IN
([MyParam1], MyParam2, MyParam3)
) AS pvt
GO
--End of code for Configuration table
-- Samples: Execute this to add criteria to the temporary table that will be used by #ComputeBasedOnCriteria
EXEC #AddCriteriaTable 'MyParam1', 'MyValue1'
EXEC #AddCriteriaTable 'MyParam2', 'MyValue3'
EXEC #AddCriteriaTable 'MyParam3', 'MyValue3'
--Execute the procedure that will return the results for the screen
EXEC #ComputeBasedOnCriteria
Now trying this in C# I encounter an error when I try to run the #AddCriteriaTable procedure. When I try to run the ExecuteQuery on the second to the last line it throws:
Exception: System.Data.SqlClient.SqlException, Incorrect syntax near the keyword 'PROC'.
Why does it work in SQL Server but not in C# code? Is there another way to do this in C#? Let me know if there are c# guidelines I should follow as I am still learning this c# - db work.
EDIT:
I know I could do this as a normal stored proc and pass in a DataTable however there are team issues I cannot say and it forces me to use the sp as a text.
The reason that it is failing is you are passing parameters to the CREATE PROC section here:
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
It does not make sense to pass the values here, since you are just creating the procedure, you only need to pass them when executing the procedure. If you run a trace you will see something like this being executed on the server:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)',
N'#ParameterCode VARCHAR(64),#Value VARCHAR(64)',
#ParameterCode = 'MyParam1',
#Value = 'MyValue1'
Which will throw the same incorrect syntax error when run in SSMS. All you need is:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)';
So in c# you would need:
//First Create the procedure
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.ExecuteNoneQuery();
//Update the command text to execute it, then add parameters
cmd.CommandText = "EXECUTE #AddCriteriaTable #ParameterCode, #Value;";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
I think you are over complicating everything, a temporary stored procedure to add data to a temporary table seems over kill.
If you are executing from code it seems likely that you need to reuse everything, so why not just have a permanent procedure for your computation,
and then use a defined type to manage instances of the execution.
So first create your type:
CREATE TYPE dbo.CriteriaTableType AS TABLE (ParameterCode VARCHAR(64), Value VARCHAR(64));
Then create your procdure:
CREATE PROC dbo.ComputeBasedOnCriteria
(
#product VARCHAR(36)='ABC',
#currency VARCHAR(3)='USD',
#zScore FLOAT = .845,
#CriteriaTable dbo.CriteriaTableType READONLY
)
AS
--Code inside this proc is largely dynamic sql. This is just a quick mock up
SELECT
#Product ProductCode
,#currency Currency
,950 ExpectedRevenue
,*
FROM #CriteriaTable c
PIVOT (MIN (Value) FOR ParameterCode IN (MyParam1, MyParam2,MyParam3)) AS pvt;
GO
Then finally to run:
DECLARE #Criteria dbo.CriteriaTableType;
INSERT #Criteria
VALUES
('MyParam1', 'MyValue1'),
('MyParam2', 'MyValue2'),
('MyParam3', 'MyValue3');
EXECUTE dbo.ComputeBasedOnCriteria #CriteriaTable = #Criteria;
You can even populate the criteria table in c#, and just pass this from c# to the procedure.
var table = new DataTable();
table.Columns.Add("ParameterCode", typeof(string)).MaxLength = 64;
table.Columns.Add("Value", typeof(string)).MaxLength = 64;
foreach (var criterion in request.Criteria)
{
var newRow = table.NewRow();
newRow[0] = criterion.Key;
newRow[1] = criterion.Value;
table.Rows.Add(newRow);
}
using (var connection = new SqlConnection("connectionString"))
using (var command = new SqlCommand("dbo.ComputeBasedOnCriteria", connection))
{
var tvp = command.Parameters.Add("#CriteriaTable", SqlDbType.Structured);
tvp.TypeName = "dbo.CriteriaTableType";
tvp.Value = table;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
//Do Something with your results
}
}
}
If you are executing SQL to create a stored procedure via C# then you might as well just execute your SQL via C# and forget about the Procedure.
The point of using a stored procedure to avoid SQL Injection only applies when the stored procedure already exists on the server and you are not creating it via the code.
You can avoid SQL injection here by using a Parameterised query.
Parameters prevent sql injection by validating the data type. So if you insert a integer in your code then someone attempting injection cannot supply a string with special characters which changes your expected result.
BUT apart from all that, you're getting an error because you have CREATE PROC in your SQL in C# instead of CREATE PROCEDURE

Categories

Resources