Scope_Identity not working with GUID on c# - c#

I've been trying to insert some data on a database and at the same time get the identifier.
The identifier type is a Guid.
I already tried some resolutions.
I was searching but I couldn't found any that worked with a Guid in C#.
The last one I tried was like this:
dbAccess db = new dbAccess();
SqlConnection con = db.openConnection();
Guid retVal = Guid.Empty;
try
{
SqlCommand cmd = new SqlCommand(#"insert into Comment(IdDiscussion,UserId,Description,DateTime)
values(#IdDiscussion,#UserId,#description,#dateTime);
set #ret=SCOPE_IDENTITY();", con);
cmd.Parameters.AddWithValue("#IdDiscussion", discussionId);
cmd.Parameters.AddWithValue("#UserId", userId);
cmd.Parameters.AddWithValue("#description", comment);
cmd.Parameters.AddWithValue("#dateTime", dateTime);
SqlParameter retParameter = new SqlParameter("#ret", SqlDbType.UniqueIdentifier);
retParameter.Direction = ParameterDirection.Output;
cmd.Parameters.Add(retParameter);
cmd.ExecuteNonQuery();
retVal = (Guid)retParameter.Value;
}
catch (Exception e)
{
//doesn't really matter
}
db.closeConnection();
return retVal;
In this case I get the following error on executeNonQuery(): "Operand type clash: numeric is incompatible with uniqueidentifier".
Any suggestion that can help?

scope_identity() only returns the last (scoped) identity value - this value datatype has to be numeric.
So unfortunetly only fix that comes to my mind would be something like this:
declare #op table
(
ColGuid uniqueidentifier
)
insert into Comment(IdDiscussion,UserId,Description,DateTime)
output inserted.CommentId -- GUID column
into #op
values (#IdDiscussion,#UserId,#description,#dateTime)
select top 1 o.ColGuid
from #op o
and then in your code:
var guid = (Guid)cmd.ExecuteScalar();
One more mention: this solution should work in SQL Server 2005 and above

I tried to use SCOPE_IDENTITY() to retrieve Guid values, but not worked with me. You can try declare a SQL variable and generate a new guid with the command NEWID() and in the end do a SELECT command in this variable executing ExecuteScalar method on the command object.
DECLARE #Id UNIQUEIDENTIFIER; SET #Id = NEWID();
INSERT INTO [tablename] ([Id], [Field1], [Field2]) VALUES (#Id, #Field1, #Field2);
You must add your parameters on the command normally.
cmd.Parameters.AddWithValue("#Field1", "Field 1 Value");
cmd.Parameters.AddWithValue("#Field2", "Field 2 Value"));
So, execute and retrieve the value.
var obj = cmd.ExecuteScalar();
Guid guid = (Guid)obj;

Related

How should I get the values from the select query of the stored procedure in c#

I want the date and the name from the select query which if I run as normal query I get the results but i when I try to get the results in C# all I get is count=0. Can anyone tell me what wrong am I doing?
Here is the C# code
private List<CertificationSummary> GetLastAccessData (string taskOwner)
{
List<CertificationSummary> lastAccessedResult = new List<CertificationSummary>();
string connectionString = SqlPlusHelper.GetConnectionStringByName("MetricRepositoryDefault");
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlParameter[] sqlParams = new SqlParameter[1];
sqlParams[0] = new SqlParameter("#taskOwner", SqlDbType.NVarChar);
sqlParams[0].Value = taskOwner;
connection.Open();
SqlCommand cmd = connection.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "GetLastAccessedCertificationData";
cmd.Parameters.AddRange(sqlParams);
cmd.ExecuteNonQuery();
}
return lastAccessedResult;
}
And here is the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetLastAccessedCertificationData]
(#taskOwner nvarchar(255))
AS
BEGIN
DECLARE #name nvarchar(100)
DECLARE #lastAccessedDate [datetime]
SELECT #name = Name
FROM CertificationReviewCycles
INNER JOIN UserReviewCycleAccess ON CertificationReviewCycles.CertificationReviewCycleID = UserReviewCycleAccess.LastAccessedReviewCycleID
WHERE USERID = #taskOwner
SELECT #lastAccessedDate = LastAccessedDate
FROM UserReviewCycleAccess
WHERE UserID = #taskOwner
CREATE TABLE #tempTable
(
name [nvarchar](255) NULL,
[LastAccessedDate] [datetime] NULL,
)
INSERT INTO #tempTable VALUES (#name, #lastAccessedDate)
SELECT TOP(1) name, LastAccessedDate
FROM #tempTable
END
GO
You are returning lastAccessedResult which is has just been set to new List<CertificationSummary>(). This list has no items, so it has a count of 0.
Use ExecuteReader instead of ExecuteNonQuery and you can then read the data returned and store them into your lastAccessedResult list.
Read here for more info.
ExecuteNonQuery will not return results, and should only be used when you don't expect rows back. This is common for UPDATE statements.
Since you're interested in reading the rows returned by the stored procedure, use ExecuteReader, e.g var reader = cmd.ExecuteReader();
See here for more:
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader?view=dotnet-plat-ext-3.1
You're using ExecuteNonQuery, which discards any grids from the query. You need to use ExecuteReader to consume grids, but it is a lot of mess and ceremony - the API is verbose. Frankly, I'd recommend a tool like "Dapper" (freely available on NuGet), then this becomes just
private List<CertificationSummary> GetLastAccessData (string taskOwner)
{
string connectionString = SqlPlusHelper.GetConnectionStringByName("MetricRepositoryDefault");
using var connection = new SqlConnection(connectionString);
return connection.Query<CertificationSummary>(
"GetLastAccessedCertificationData",
new { taskOwner }, // <== parameters
commandType: CommandType.StoredProcedure).AsList();
}

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

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...

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

Getting primary key value of recently added record in SQL Server database

For the web app that I am working on, whenever a user adds a new record to the database, the system validates if the record has successfully been added or not:
if (txnHnd.AddNewTxnDetails(txnDetails) == true && txnHnd.AddNewTxnName(txnName) == true)
{
string message = "Record has been saved.";
lblErrorMsg.Text = message;
Response.Redirect("FXTxnList.aspx");
}
else
{
string message = "Saving record failed.";
lblErrorMsg.Text = message;
}
Now, IF the record has been successfully added, only then will the system insert a sort of Reference Number to the newly-added record. My table has a primary key called TxnID of type int. It's an auto-increment number BTW. The question is, how do I extract the TxnID of the newly added record from the database? I need to put the code inside the if part of the if-else statement, where it says that the Record has been saved.
Update/Create the SP like
CREATE PROC SP_Insert
(
#name VARCHAR(50),
#id INT OUTPUT
)
AS
INSERT INTO Table (ColumnName) VALUES (#name)
SET #id=SCOPE_IDENTITY()
and then call this sp from ado.net like below
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "SP_Insert";
SqlParameter param = new SqlParameter();
param.Name = "#id";
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
int id = (int)param.Value;
If you can modify the stored proc/command you use to insert a new record, you should use SCOPE_IDENTITY() to return the last generated identity value:
SELECT CAST(SCOPE_IDENTITY() AS INT)
If you're using a stored proc and cannot modify it, you need to use ##IDENTITY, as SCOPE_IDENTITY() works only in the same scope (in this case within a stored proc). Be careful though, as ##IDENTITY will return the last identity not limited by scope which can be affected e.g. by triggers.
SELECT CAST(##IDENTITY AS INT)
More on MSDN.

How to insert a record and return the newly created ID using a single SqlCommand?

I'm using an SqlCommand object to insert a record into a table with an autogenerated primary key. How can I write the command text so that I get the newly created ID when I use the ExecuteScalar() method?
INSERT INTO YourTable(val1, val2, val3 ...)
VALUES(#val1, #val2, #val3...);
SELECT SCOPE_IDENTITY();
Don't forget the semicolons at the end of each statement.
Add the following line to the end of the Sql Query...
SELECT SCOPE_IDENTITY()
And then use the ExecuteScalar method on the SqlCommand object...
var rowCount = command.ExecuteScalar()
insert into Yourtable()
values()
SELECT SCOPE_IDENTITY()
I just ran a test and verified that the semi-colons are optional using SQL Server 2005 SP2, and .Net 3.5
Add an output parameter to the command object and then set the value to the new ID in the stored procedure.
Stored Procedure:
#ID AS INT OUTPUT
[Insert Command]
SET #ID = SCOPE_IDENTITY()
.NET:
cmd.CommandText = "stored_procedure";
SqlParameter pID = new SqlParameter("ID", DBType.Int32, 4);
pID.Direction = ParameterDirection.Output;
cmd.ExecuteScalar();
int id = Convert.ToInt32(cmd.Parameters["ID"].Value.ToString());
Don't use ##IDENTITY, however simple it may seem. It can return incorrect values.
SELECT SCOPE_IDENTITY()
appears to be the obvious choice.
Although I like Dave Markle's answer, ( and I see you did too, since you marked it as your answer ), that method can fail if you have triggers on your database, that audit CUD operations, and your audit table has an IDENTITY column. It would return the value of the Audit table's identity, not the table you just inserted into, since the audit table actualy happen after.
In that case, a more generic method can be used that will work in both cases, regardless of any auditing. Its a bit more wordy, but you get what you pay for.
example:
#"DECLARE #tmp AS TABLE ( id int )
INSERT INTO case
(
caseID,
partID,
serialNumber,
hardware,
software,
firmware
)
OUTPUT Inserted.ID into #tmp
VALUES
(
#caseID,
#partItemID,
#serialNumber,
#hardware,
#software,
#firmware
)
Select ID from #tmp" )
Immediately after your insert stmt, use
SELECT CAST(scope_identity() AS bigint) ---- incase you have a return result as int64
This will return the column created id/identity.
If your id is a Guid, then I found this solution to be best:
INSERT INTO YourTable (val1, val2, val3)
OUTPUT inserted.id
VALUES (#val1, #val2, #val3)
Thanks #Scott Ivey
Full demo:
internal static Guid InsertNote(Note note)
{
Guid id;
using (
var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["dbconn"].ConnectionString))
{
connection.Open();
using (
var command =
new SqlCommand(
"INSERT INTO Notes ([Title],[Text]) " +
"OUTPUT inserted.id " +
$"VALUES ('{title}','{text}');", connection))
{
command.CommandType = CommandType.Text;
var reader = command.ExecuteReader();
reader.Read();
id = reader.GetGuid(reader.GetOrdinal("id"));
}
connection.Close();
}
return id;
}
I would recommend using a Stored Procedure, but this is for unit testing our repository.
Straight out of the Whirlpool:
If you're using MS SQL you can use "SELECT ##IDENTITY as Value" after your insert to get the last ID generated
and:
##IDENTITY and SCOPE_IDENTITY return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; ##IDENTITY is not limited to a specific scope.
Edit: As pointed out in the comments, you should always use SCOPE_IDENTITY, not ##IDENTITY.

Categories