Data commit fails for "Upsert" / Merge function when calling through Npgsql - c#

I am using Npgsql 3.0.3 with Postgres 9.4. Here is my code in Postgres:
CREATE TABLE temp_test
(
id serial NOT NULL,
name text,
CONSTRAINT temp_test_pk PRIMARY KEY (id)
)
and the "Upsert" / merge function that returns the changed record as refcursor:
CREATE OR REPLACE FUNCTION test_save(
v_ref refcursor,
iv_id integer,
v_name character varying)
RETURNS refcursor AS
$BODY$
DECLARE
v_ref alias for $1;
v_id integer := iv_id;
BEGIN
UPDATE onepm.temp_test
SET name = v_name
WHERE id = v_id;
IF NOT FOUND THEN
INSERT INTO onepm.temp_test
(name)
VALUES
(v_name)
RETURNING id INTO v_id;
END IF;
OPEN v_ref FOR
SELECT id
, name
FROM onepm.temp_test
WHERE id = v_id;
RETURN v_ref;
END;
$BODY$
LANGUAGE plpgsql;
In my .net project I have the following function that returns a IDatareader:
public static IDataReader ExecuteReader()
{
NpgsqlConnection conn = new NpgsqlConnection(connectionString);
conn.Open();
NpgsqlTransaction _tran = conn.BeginTransaction();
NpgsqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT onepm.test_save(#ref, #id, #name)";
NpgsqlParameter _p = new NpgsqlParameter();
_p.ParameterName = "#ref";
_p.NpgsqlDbType = NpgsqlDbType.Refcursor;
_p.NpgsqlValue = "ref";
_p.Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add(_p);
cmd.Parameters.Add(new NpgsqlParameter("#id", 1));
cmd.Parameters.Add(new NpgsqlParameter("#name", "test"));
cmd.ExecuteNonQuery();
cmd.CommandText = "fetch all in \"ref\"";
cmd.CommandType = CommandType.Text;
return cmd.ExecuteReader();
}
This all works fine, I do receice the inserted or updated record in the reader, except that the data is never committed to the table - no data found in pgAdmin. If I call the same function in pgAdmin everything works fine - records are committed:
SELECT onepm.test_save('v_ref', 1, 'xxxxxx');
FETCH ALL IN "v_ref";
Thankful for any help!

Ummm, I think you need to commit the transaction you started...!
Regardless, you may also want to look at PostgreSQL 9.5's new built-in upsert functionality...

Related

Stored Procedure doesn't return records when called from C#, but works in SSMS

I have a stored procedure that correctly returns records when I call it from a SSMS query.
Here is the stored procedure:
CREATE PROCEDURE [dbo].[q_CheckRecords]
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
SELECT *
FROM q_Warehouse80_OOS_ItemsNeedingNotification
WHERE item_id = #ItemIDS
END
Calling this from a SSMS query like this:
exec [q_CheckOOSWarehouse80ItemsNeedingNotification] 'B30-R10000-B001'
It correctly returns a row, however when I use this C# code to call the stored procedure, I never get any rows returned.
SqlCommand cmd = null;
SqlDataReader myReader = null;
System.Data.SqlClient.SqlConnection conn = null;
conn = new System.Data.SqlClient.SqlConnection("Data Source=" + sSessionServer + ";database=" + sSessionDatabase + "; Integrated Security=SSPI");
String SQL = "[q_CheckOOSWarehouse80ItemsNeedingNotification]";
cmd = new SqlCommand();
cmd.CommandText = SQL;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Connection = conn;
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
conn.Open();
myReader = cmd.ExecuteReader();
// check to see if any rows were returned.
if (myReader.HasRows)
{
while (myReader.Read())
{
// code to read fields in returned rows here
}
}
conn.Close();
It appears to be a problem with how C# defines the datatype being passed to the stored procedure, but I haven't found any information online on how to solve this problem.
If I were to changed the stored procedure so it's "hard coded"
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
select * from q_Warehouse80_OOS_ItemsNeedingNotification where item_id = 'B30-R10000-B001'
END
then the C# call to it correctly indicates that a row was "found".
Any help would be greatly appreciated.
When you don't specify the length of a varChar sql treats it as length 1.
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
Your variable ItemsToBeChecked will be truncated, and I suspect there is nothing matching in your database with just the first character of that value.
Specify the length of the varchar
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar, 40).Value = ItemsToBeChecked;
You can verify this is the case by putting a profiler on sql, and executing your c#. You will see the value passed to the #ItemIDS parameter is only 1 character long.
The issue you are facing is because you are not calling your stored procedure in your C# Code.

PgSqlParameter error using dotConnect Postgresql in Visual Studio

I have a stored procedure . The input is 'id', output 'n'.
But when I try to run it in Visual Studio , I have an error: The value for the output parameter 'n' is absent in the command execution result.
Here is my code:
int id = Convert.ToInt32(this.textBox1.Text);
PgSqlConnection con = new PgSqlConnection();
con.ConnectionString = Properties.Settings.Default.DBConnectionString;
PgSqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "getcountmaterials";
PgSqlParameter param = new PgSqlParameter("n", SqlDbType.Int);
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);
cmd.Parameters.Add(new PgSqlParameter("#id", id));
con.Open();
cmd.ExecuteNonQuery();
string kolvo = cmd.Parameters["n"].Value.ToString();
con.Close();
this.result.Text = kolvo;
Stored Procedure:
CREATE OR REPLACE FUNCTION public.getcountmaterials(id integer)
RETURNS integer AS
$BODY$
declare n integer;
begin n := (select sum(count) from materials_in_warehouses
where id_materials = id);
return n;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public.getcountmaterials(integer)
OWNER TO postgres;
I've never used dotConnect for Pg (I am, however, a huge fan of it for Oracle), so I can't verify that I have the syntax right on all of this.
That said, I think I see your core issue. The lines between functions and "stored procedures" is somewhat blurred with Postgresql.
All you really want to do is run a select on the function above. As such, I believe the following will work. I know this would work with NpgSql, and I am hopeful it will translate properly to dotConnect:
PgSqlCommand cmd = new PgSqlCommand("select getcountmaterials(:ID)", con);
cmd.Parameters.AddWithValue("ID", id);
string kolvo = cmd.ExecuteScalar().ToString();

Stored procedure doesn't return an int value

MySql Procedure Code:
CREATE DEFINER=`root`#`localhost` PROCEDURE `USP_CreateCliente`(IN nome_cliente VARCHAR(45))
BEGIN
Select 20;
INSERT INTO clienti ( nome_cliente )
VALUES ( nome_cliente );
Select id_cliente from clienti;
END
C# code in the controller page:
ClienteInfo CI = new ClienteInfo();
DboUser objdbo = new DboUser();
int id_cliente = 0;
CI.nome_cliente = txtNomeCliente.Text;
id_cliente = objdbo.CreateClienteInfo(CI);
DboUser class:
public int CreateClienteInfo(ClienteInfo CI)
{
int result;
MySqlConnection conn = new MySqlConnection();
DbConnection db = new DbConnection();
conn = db.ConnessioneDb();
MySqlCommand cmd = new MySqlCommand(Costanti.StoredProcedures.USP_CreateCliente, conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#nome_cliente", CI.nome_cliente);
result = cmd.ExecuteNonQuery();
conn.Close();
return result;
}
I want my C# code to retrieve the id of my customer after inserting it into the database, so I can save it to the session and retrieve it again later in a page that will print a document with the customer's informations.
My id_cliente returns 0, do you notice any syntax error?
Did I do something wrong?
I'm 90% sure it's a problem dealing with the stored precedure tough, cause the customer is inserted correctly
Change this line
result = cmd.ExecuteNonQuery();
to
result = Convert.ToInt32(cmd.ExecuteScalar());
but you should also change your stored procedure because it doesn't return the last id generated for you by the AUTO_INCREMENT column id_cliente
CREATE DEFINER=`root`#`localhost` PROCEDURE `USP_CreateCliente`(IN nome_cliente VARCHAR(45))
BEGIN
INSERT INTO clienti ( nome_cliente ) VALUES ( nome_cliente );
Select LAST_INSERT_ID();
END
In MySql, to get the generated auto_increment value, you could use LAST_INSERT_ID(), next, your C# code don't need to use ExecuteNonQuery, which returns just the number of rows that you have changed, added or deleted, but you use ExecuteScalar which returns the first column of the first row of the last SELECT command executed by your stored procedure. (It is the SELECT LAST_INSERT_ID())
Also, to complete the answer, you don't really need a stored procedure for this kind of simple work. The advantages should be minimal while the problems related to a different piece of software to maintain are self evident.
Your C# code could be (removed the usage of your DbConnection class because it is not clear what it does)
public int CreateClienteInfo(ClienteInfo CI)
{
int result;
string cmdText = #"INSERT INTO clienti ( nome_cliente ) VALUES ( nome_cliente );
Select LAST_INSERT_ID();";
using(MySqlConnection conn = new MySqlConnection(....connectionstring .....))
using(MySqlCommand cmd = new MySqlCommand(cmdText, conn);
{
conn.Open()
cmd.Parameters.AddWithValue("#nome_cliente", CI.nome_cliente);
result = Convert.ToInt32(cmd.ExecuteScalar())
return result;
}
}
Here you use the possibility to pass batch commands to your MySql engine, meaning two commandtexts with the same MySqlCommand separating them with the semicolon

Insert 10000 records from C# into SQL Server

I have a C# console program
Selects 10000 records from Input table (Keyid varchar(4), Address varchar(100), Name varchar(100)).
for each record from Input table, it calls an API that returns data (if person works in that address, status is OK else NOT OK, also returns address type-place of interest or residential, commercial etc.) that needs to be saved in one main table and a detail table.
Main table:
1001|JOE STILTON| 2 MAIN ST, SALEM,PA| OK|4/15/2014
Detail table:
1001|PHARMACY
1001|COMMERCIAL
i.e Joe works in a pharmacy which is also a commercial bldg.
Right now, I call the API. then I call a method,
private static void insertTable(string keyid, DateTime updDate, string name, string address,string status)
{
Int32 rowsAffected = 0;
string connectionString = GetConnectionString();
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
SqlCommand cmd = new SqlCommand("google.usp_InsertCompanyAddrComponents", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 90;
cmd.Parameters.Add("#keyid", SqlDbType.VarChar);
cmd.Parameters["#keyid"].Value = keyid;
cmd.Parameters.Add( new SqlParameter("#dateverified", updDate));
cmd.Parameters.Add("#name", SqlDbType.VarChar);
cmd.Parameters["#name"].Value = name;
cmd.Parameters.Add("#address", SqlDbType.VarChar);
cmd.Parameters["#address"].Value = address;
cmd.Parameters.Add( new SqlParameter("#status", status));
try
{
rowsAffected = cmd.ExecuteNonQuery();
}
catch (Exception ep)
{
Console.WriteLine(ep.Message);
}
connection.Close();
}
Then, I call another similar method that inserts into detail table.
Since I have to do this for 10,000 records at a time, there is lot of I/O. how can I change to do batch insert? all 10000 insert at a time?
Thanks
R
You can also take a look into table types in SQL Server. You can pass two table types in stored procedure and do required operations directly over there.
Here is my sample stored proc
CREATE PROCEDURE [dbo].[usp_AssociateTags]
#Tags AS UDT_Tag READONLY
AS
SET XACT_ABORT ON
BEGIN TRAN
--Insert into Tag Master
INSERT INTO dbo.TagMaster
(
Name
,IsActive
)
VALUES ( '', -- Name - varchar(50)
1 -- IsActive - bit
)
DECLARE #TagId AS INT
SET #TagId=SCOPE_IDENTITY()
INSERT INTO dbo.TagCollection
( TagNumber, TagId )
SELECT TagNumber, #TagId FROM #Tags t
WHERE NOT EXISTS(SELECT * FROM dbo.TagCollection WHERE TagNumber = t.TagNumber)
COMMIT TRAN
SET XACT_ABORT OFF
Script to test this stored procedure
--DECLARE #hello as UDT_Tag
--INSERT INTO #hello VALUES('vaibhav')
--INSERT INTO #hello VALUES('Shantanu')
--INSERT INTO #hello VALUES('Sam')
--INSERT INTO #hello VALUES('Aakash')
--EXEC usp_AssociateTags #hello
--SELECT * FROM dbo.TagCollection
C# code to consume this procedure
SqlParameter Tags = new SqlParameter { ParameterName = "#Tags"
, Value = entity.Tags.ToDataTable()
, Direction = ParameterDirection.Input
, SqlDbType = SqlDbType.Structured, TypeName="UDT_Tag" };
SqlHelper.ExecuteNonQuery(tran, CommandType.StoredProcedure
, "usp_AssociateTags", Tags);
CodeProject

C# Oracle Stored Procedure Parameter Order

With this
PROCEDURE "ADD_BOOKMARK_GROUP" (
"NAME" IN VARCHAR2,
"BOOKMARK_GROUP_ID" IN NUMBER,
"STAFF_ID" IN VARCHAR2,
"MAX_NO" IN INT,
"NUMFOUND" OUT INT,
"NEW_ID" OUT NUMBER) IS
BEGIN
NEW_ID := -1;
SELECT COUNT(*) INTO NUMFOUND FROM BOOKMARK_GROUP_TABLE WHERE STAFF_ID = STAFF_ID;
IF NUMFOUND < MAX_NO THEN
INSERT INTO BOOKMARK_GROUP_TABLE (NAME, BOOKMARK_GROUP_ID, STAFF_ID) VALUES(NAME, BOOKMARK_GROUP_ID, STAFF_ID);
SELECT BGT_SEQUENCE.currval INTO NEW_ID FROM dual;
END IF;
END;
I find it interesting that if I don't add parameters in the order they were defined in, e.g.
OracleCommand cmd = new OracleCommand("ADD_BOOKMARK_GROUP", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("NAME", name));
...
cmd.Parameters.Add(new OracleParameter("NEW_ID", OracleDbType.Decimal)).Direction = ParameterDirection.Output;
cmd.Parameters.Add(new OracleParameter("NUMFOUND", OracleDbType.Int32)).Direction = ParameterDirection.Output;
instead of
OracleCommand cmd = new OracleCommand("ADD_BOOKMARK_GROUP", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("NAME", name));
...
cmd.Parameters.Add(new OracleParameter("NUMFOUND", OracleDbType.Int32)).Direction = ParameterDirection.Output;
cmd.Parameters.Add(new OracleParameter("NEW_ID", OracleDbType.Decimal)).Direction = ParameterDirection.Output;
The values returned by
cmd.Parameters["NEW_ID"].Value.ToString()
and
cmd.Parameters["NUMFOUND"].Value.ToString()
get swapped, although running the procedure through the VS2008 Server Explorer returns correct data.
Why is this?
You can probably set the BindByName parameter on the OracleCommand object. This works for straight SQL queries with parameters, I've not tried it with stored procedures but it would be logical...
cmd.BindByName = true;
I'm not an Oracle buff, so I can't verify - but it sounds like they are being passed by position (rather than passed by name). The moral equivelent to:
EXEC SomeProc 'Foo', 'Bar'
instead of:
EXEC SomeProc #arg1='Foo', #arg2='Bar'
This isn't hugely uncommon - for years (in the COM days) a lot of my code had to work with a pass-by-position ADODB driver.
In this case, the name that you give serves only as a local key to lookup the value from the collection collection. You can verify easily by inventing a name:
cmd.Parameters.Add(new OracleParameter("BANANA", ...
cmd.Parameters.Add(new OracleParameter("GUITAR", ...
...
cmd.Parameters["BANANA"].Value.ToString()
cmd.Parameters["GUITAR"].Value.ToString()
If the above runs without error, it is passing by position. And it they are passed by position... then simply add them in the right order ;-p And never add new parameters except at the end...
Not an answer to the question but you can use 'insert ... returning ... into ' in stead of select bgt_sequence.currval from dual, for example:
begin
insert into test (id)
values(test_seq.nextval)
returning id into p_id;
end;
See http://www.adp-gmbh.ch/ora/sql/insert_into_x_returning_y.html

Categories