Strange Behaviour moving from firebird sql version 2.5 to version 4 - c#

I have a Windows application written in C# using embedded firebird sql version 2.5.5.26952, which I am re-working it to update to embedded firebird sql version 4.0.0.2496. I have update the fdb file to the new version,and all the tables and sprocs, are there. When run a cmd.Fill() command for a selected statement rows are returned, if I do a update for a row in the table, I get the expected results back fine. but If I do a insert nothing is returned, and no errors are thrown, but the data is added to the database. If I run the sproc from the FireRobin application, the data is inserted, and a row is returned, so I'm at a loss to know why it is not working from my C# application. below is slimmed down version of the code.
The 2.5 version is using FirebirdSql.Data.FirebirdClient.4.10.0.0
The 4.0 version is using FirebirdSql.Data.FirebirdClient.9.0.2
using (var cmd = new FbDataAdapter("PROC_UPSERTPEOPLE", _connection)
{
DataTable data = new DataTable();
cmd.SelectCommand.Parameters.Add("SURNAME", FbDbType.Text).Value = item.Surname;
cmd.SelectCommand.Parameters.Add("FORENAMENAME", FbDbType.Text).Value = item.Forename);
var transaction = _connection.BeginTransaction();
cmd.SelectCommand.CommandType = CommandType.StoredProcedure;
cmd.SelectCommand.Transaction = transaction;
var result = cmd.Fill(data);
transaction.Commit();
}
On a update result contains 1, and data has the expected result, but on a insert result = 0, and data has no rows in.
Any help would be appreciated.
This is the simple version fo the sproc in question
CREATE OR ALTER PROCEDURE PROC_UPSERTPEOPLE_SLIM
(
RECID INTEGER,
SURNAME VARCHAR(100),
FORENAME VARCHAR(100)
)
RETURNS
(
ID INTEGER,
LSURNAME VARCHAR(100),
LFORENAME VARCHAR(100)
)
AS
DECLARE VARIABLE local_id integer;
DECLARE VARIABLE local_surname varchar(100);
DECLARE VARIABLE local_forename varchar(100);
BEGIN
select
ID,
FORENAME,
SURNAME
FROM
APA_PEOPLE
WHERE
(:RECID IS NOT NULL AND ID = :RECID)
OR (:RECID IS NULL
AND FORENAME = :FORENAME
AND SURNAME = :SURNAME)
INTO
:local_id,
:local_forename,
:local_surname;
IF (:local_id IS NULL) then
begin
UPDATE OR INSERT INTO APA_PEOPLE(FORENAME, SURNAME)
VALUES(:FORENAME, :SURNAME)
MATCHING (FORENAME, SURNAME);
end
else
begin
UPDATE APA_PEOPLE SET FORENAME = :FORENAME,
SURNAME = :SURNAME
WHERE ID = :local_id;
end
FOR
SELECT
ID,
SURNAME,
FORENAME
from
APA_PEOPLE
WHERE
(:RECID IS NOT NULL AND ID = :RECID)
OR (:RECID IS NULL
AND FORENAME = :FORENAME
AND SURNAME = :SURNAME)
INTO
:ID,
:LSURNAME,
:LFORENAME
DO
begin
suspend;
end
END;
Update
To answer my own question, being mainly a TSQL developer, DSQL seems strange, change the sproc to the following, which is simpler
CREATE OR ALTER PROCEDURE PROC_UPSERTPEOPLE_SLIM (
RECID integer,
SURNAME varchar(100),
FORENAME varchar(100)
)
RETURNS (ID integer)SQL SECURITY INVOKER
AS
BEGIN
UPDATE OR INSERT INTO APA_PEOPLE(FORENAME, SURNAME)
VALUES(:FORENAME, :SURNAME)
MATCHING (FORENAME, SURNAME)
RETURNING ID INTO :ID;
END;
but also had to change the way it was called, to use
EXECUTE PROCEDURE PROC_UPSERTPEOPLE_SLIM(#RECID, #SURNAME, #FORENAME)
This does seem counter intuitive, I had assumed a stored procedure was a stored procedure, and there are not two different flavors. Oh well it works now, so move on to getting the rest of the app to work.

Related

Stored procedure/query two variable comparison

I have created a stored procedure for SQL Server 2014.
There are two parameters: Name which is a user name and Hash which is password md5 hash. I check in the database if the md5 hashes are equal (first hash is from the program and the second one is already stored in the database).
If I just run a query (not a stored procedure) in the database (or in program using commandType.Text) - everything works and the user is being selected, but when I run the exact thing but using stored procedures, the SqlReader in C# has no elements returned, which most likely means that the conditions during those variable comparison were not met.
Maybe I am doing something wrong?
I also have about 10 other stored procedures for reading or/and writing to the database, everything works except this one.
Here is the procedure:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId, Employee.name, Employee.surname
FROM Orders
LEFT JOIN Employee ON Orders.orderId = Employee.id
WHERE batchName = '#Name' AND productCode = '#Hash'
END
GO
Code part:
public Boolean VerifyPassword(string name, string password)
{
var paramsList = new List<SqlParameter> { new SqlParameter("#Name", name), new SqlParameter("#Hash", GetMd5Hash(password)) };
const string ProcedureName = "GetHash";
var ActiveUser = new DBController().GetFromDatabase(ProcedureName, "Login", "EJL15_DB", paramsList).ToList();
return ActiveUser.Count > 0;
}
And from Database Controller
private void SetCommandProperties(string procedureName, IEnumerable<SqlParameter> paramsList)
{
this.sqlCommand.CommandText = procedureName;
this.sqlCommand.CommandType = CommandType.StoredProcedure;
foreach (var curParam in paramsList)
this.sqlCommand.Parameters.Add(curParam);
this.sqlCommand.CommandTimeout = 15;
}
You don't need to quote the parameters in the stored procedure. Do this instead:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId,
Employee.name,
Employee.surname
FROM Orders
LEFT JOIN Employee
ON Orders.orderId=Employee.id
WHERE batchName = #Name
AND productCode = #Hash
END
I just wonder, obviously your #Hash parameter passed to the stored
procedure is a user's password. But for some reason your WHERE clause
in the procedure goes like that:
"WHERE batchName='#Name' AND productCode='#Hash'"
Is there a chance your condition is incorrect? I guess it should be something like: Employee.password = #Hash
You should not put '' around your variables. Otherwise your comparison is totally wrong.

Uupdate values in two tables via stored procedure

I have two tables and I need to update values in them via a stored procedure. Tried too much to update but some times it update the first table only, others the second or even fail due to cannot allow duplicates. Also when it updates the WHOLE data in the table becomes the same as the new updated ones. I've now reached to this error after all these lines of codes
Cannot insert the value NULL into column 'Emp_ID',table 'DatePics'; column does not allow nulls. UPDATE fails.The statement has been terminated
Here is the SQL code :
ALTER procedure [dbo].[UpdateEmp]
#EmpName nvarchar(100),
#Nationality nvarchar(30),
#Passport nvarchar(20),
#ContractDate date,
#HealthDate date
AS
BEGIN
set nocount on;
DECLARE #IDs table (ID int )
UPDATE Employee SET
EmpName=#EmpName, Nationality=#Nationality, Visa=#Visa, Passport=#Passport,
ReceivedDate=#ReceivedDate,IDIssue=#IDIssue, IDExpiry=#IDExpiry, Sponsor=#Sponsor
output inserted.ID into #IDs (ID)
WHERE ID = #ID
UPDATE DatePics SET
FingerDate=#FingerDate, ContractDate=#ContractDate, HealthDate=#HealthDate
where Emp_ID in (select ID from #IDs);
END
After writing the stored procedure code, I wrote the C# code like this:
private void updatebtn_Click(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = db.con;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "UpdateEmp";
cmd.Parameters.AddWithValue("#EmpName", NameSeartxt.Text);
cmd.Parameters.AddWithValue("#Nationality", NatSeartxt.Text);
cmd.Parameters.AddWithValue("#Passport", PassSeartxt.Text);
cmd.Parameters.AddWithValue("#ContractDate", ContractSeartxt.Text);
cmd.Parameters.AddWithValue("#HealthDate", HealthSeartxt.Text);
db.con.Open();
int up = cmd.ExecuteNonQuery();
if (up > 0)
{
MessageBox.Show("Update done ", "DONE !");
SearNametxt.Text = "";
}
else
{
MessageBox.Show("Failed to update", "FAIL !");
SearNametxt.Text = "";
}
db.con.Close();
}
Any clue?
I can see three problems with your query. 1 You declare ID, but don't assign it before using it, so it will always be NULL for the first query, so this will never update any rows:
DECLARE #ID int
UPDATE FrstTable SET
EmpName=#EmpName, Nationality=#Nationality, Passport=#Passport
WHERE ID = #ID
Secondly, you are using SCOPE_IDENTITY to attempt to get the ID of the record that has been updated. You can't do that, SCOPE_IDENTITY will return the last inserted ID, it is not affected by updates. You will need to use OUTPUT to get the Updated ID:
DECLARE #IDs TABLE (ID INT);
UPDATE FirstTable
OUTPUT inserted.ID INTO #Ids (ID)
SET EmpName = #EmpName,
Nationality = #Nationality,
Passport = #Passport;
Thirdly, your second update statement has no where clause, so will update the entire table:
UPDATE ScndTable
SET Emp_ID=#ID, ContractDate=#ContractDate, HealthDate=#HealthDate
WHERE EmpID IN (SELECT ID FROM #Ids);
Your stored procedure looks weird to me. I believe there should be a WHERE cluase for the second UPDATE otherwise it will always update the whole ScndTable table. set #ID = SCOPE_IDENTITY(); seems to be reduntant here. Are you trying to perform insert into ScndTable if there's no corresponding Emp_ID there? Finnaly explicitly create a transaction to update either both tables or none.
Hope it helps!
Please assign the value of #ID variable, before executing the first update statement.
I think you are trying to update some row, so you can pass the 'id' value from the CSHARP code. When you use the SCOPE_IDENTITY, you will get the last inserted value. Try to pass the ID value from the front end.

EF reading return value Stored Proce RETURN(X)

Some stored procedures do some checks and when those fails, return a specific value. If nothing fails, some return a SELECT statement, some return NOTHING (like the sample below).
Note: Stored Procedures canNOT be changed at this point and the sample below is just a sample to show the RETURN and the SELECT differences.
/* THIS IS A SAMPLE PROC */
CREATE PROCEDURE [dbo].[Register]
(
#Email varchar(200)
)
AS
BEGIN
SET NOCOUNT ON ;
-- Check if user table has an Active record with this email
IF EXISTS(SELECT * FROM Users WHERE Email = #Email AND Activated = 0)
RETURN(1)
-- Check if user table has an Active record with this email
IF EXISTS(SELECT * FROM Users WHERE Email = #Email AND Activated = 1)
RETURN(2)
-- Create New User record
INSERT INTO Users (Email, xxx)
VALUES (#Email, xxxx)
END
Using EF5 and importing the stored procedures into the EDMX, how can I get the value 1 or 2 in case of problems or nothing in case the stored procedures went thru successfully?
USe out parameter in store procedure.
var result = dbContext.Database.SqlQuery<string>("QUERY TEXT OR PROUCEDURE TEXT").FirstOrDefualt();
Check if the result not null than it must be 1 or 2.
you can also call the stored procedure as following:
var firstName = "test";
var id = 12;
var sql = #"Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
But ExecuteSqlCommand does not return a result!
By default the SPROC will return 0 if no other value is given and no error is thrown. I don't know if that works in all of your scenarios (maybe you are explicitly returning zero in one of them) but in this one, you will still get the 0 response when you aren't explicitly returning anything.

How to write this stored procedure

I have a table Student. This table has a column ID which is auto-increment. I want to write a stored procedure to add a new student and return the ID. I'm newbie at this.
This is my code. Please check if it's wrong and fix for me and show me how to code to use it in C#. I used Entity Framework 4.
#Name NVARCHAR(50),
#Birthday DATETIME,
#ID bigint OUTPUT
AS
BEGIN
SET XACT_ABORT ON
BEGIN
INSERT INTO [Student](Name, Birthday) VALUES (#Name, #Birthday);
SELECT #ID = SCOPE_IDENTITY()
END
END
It is better you can C# code instead SP when your working with EF4.
Using Entity Framework, this is all done automagically for you.
using (MyEntities context = new MyEntities())
{
var student = new Student()
{
Name = "some value",
Birthday = "some Birthday"
};
context.Students.AddObject(student);
context.SaveChanges();
int ID = student.ID; // You will get here the Auto-Incremented table ID value.
}
Saving in Entity Framework will automatically update the "auto-increment" ID column. You just read it out after the call to .SaveChanges();
EDIT:
Also read this post if you encounter any issues getting the "auto-increment" ID value.
#Name NVARCHAR(50),
#Birthday DATETIME,
#ID bigint OUTPUT
AS
BEGIN
SET XACT_ABORT ON
BEGIN
INSERT INTO [Student](Name,Birthday)
VALUES(#Name,#Birthday);
SELECT #ID = SCOPE_IDENTITY()
END
I just added commas in between fields
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.aspx
This should provide you all the information you need to build a C# application calling your Stored Procedure.

C#/SQL get autoincremented field value

I have a table with autoincremented primary key. In my code I am trying to receive the new autoincremented value when I execute each 'insert' query. Is there a way to do it programatically?
Thanks.
UPD:
Assume I have a table:
TABLE User ( userID INT NOT NULL AUTO_INCREMENT, name VARCHAR( 25 ) NOT NULL , email VARCHAR( 50 ) NOT NULL , UNIQUE ( userID ) );
And I when I insert new values (name and email) to this table I want automatically receive newly generated userID. Ideally I am looking for any ways to do that with a single transaction and without stored procedures.
Have your sql/stored proc return scope_identity() or if you are using Linq2SQL or EF the entity used for insertion gets the new id.
In the stored proc it is:
ALTER proc [dbo].[SaveBuild](
#ID int = 0 output,
#Name varchar(150)=null,
#StageID int,
#Status char(1)=null
)
as
SET NOCOUNT ON
Insert into Builds
(name, StageID, status)
values (#Name, #StageID, #Status)
select #ID = scope_identity()
RETURN #ID
In the C# code you have:
public int SaveBuild(ref int id, ref string Name)
{
SqlCommand cmd = GetNewCmd("dbo.SaveBuild");
cmd.Parameters.Add("#ID", SqlDbType.Int).Value = id;
cmd.Parameters["#ID"].Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add("#Name", SqlDbType.VarChar).Value = Name;
cmd.Parameters.Add("#StageID", SqlDbType.Int).Value = 0;
ExecuteNonQuery(cmd);
id = (int)cmd.Parameters["#ID"].Value;
return id;
}
Dependent upon your situation, you might be better off using table-valued parameters to pass your inserts to a stored procedure, then use OUTPUT INSERTED to return a table-valued parameter from your stored procedure.
It will drastically reduce the number of hits required if you're processing multiple items.
Are you limited to building SQL on the client and sending it to the server? Cause if you can use a stored procedure, this is easy to do. In the stored proc, do the insert and then, either
Select Scope_Identity() as the last statement in the stored proc., or
Use a output parameter to the stored proc, (say named #NewPKValue) and make the last statement:
Set #NewPKValue = Scope_Identity()
Otherwise, you need to send a batch of commands to the server that include two statements, the insert, and Select Scope_Identity() and execute the batch as though it was a select statement
You could use the SQL statement SELECT scope_identity().

Categories

Resources