I am having a somewhat frustrating issue.
On the lower level: I want to be able to know whether an INSERT or UPDATE query in a proc was successful or not. I am not 100% sure if there is a status that is returned on all queries (apart from the SELECT). I know SQL Server gives a return type to all stored procs, and currently all mine have a return type of Integer.
On the middle level: In my repository, I want to use Entity Framework to call my stored procs and return the status as a converted (from int) boolean from the proc execution to my service.
On the higher level: I want to be able to use the returned boolean from my service to report back to an MVC controller on the task that was being performed.
On the most important layer of my issue, I have the following code:
public virtual ObjectResult<int> Proc_AddApprovalProcessor(string userId, string approverId, int approvalOrder)
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<int>(
"EXECUTE [dbo].[Proc_AddApprovalProcessor] #userId, #approverId, #approvalOrder",
new SqlParameter { ParameterName = "userId", Value = userId },
new SqlParameter { ParameterName = "approverId", Value = approverId },
new SqlParameter { ParameterName = "approvalOrder", Value = approvalOrder }).FirstOrDefault();
}
Make sure your insert/update stored procedures end with the following line:
SELECT ##ROWCOUNT
Then examine the return code and check that it is 1 (or however many rows you were expecting to insert/update).
You can use ##ROWCOUNT server variable immediately after the insert/update query to check number of affected rows by using the insert/update operation.
declare #fName varchar(50) = 'my name',
#lName varchar(50) = 'your name'
INSERT INTO myTable(fName,lName) values(#fName,#lName)
SELECT ##ROWCOUNT --> 0 - means no rows affected/nothing inserted or updated
--> 1 - means row has been inserted or updated successfully
if you are using transaction then have to use the below code to return the transaction count.
return ##TRANCOUNT;
Related
I have a web service API that uses a list of item ID as input parameter and a data table as output parameter (among other parameters irrelevant to this question). This API calls an Oracle stored procedure within a package to get the content of the output data table.
The stored procedure loops through each item ID and determines an outcome for it. It then uses a temp table to store the results for each item ID (Item ID, outcome, sysdate). A the end, a cursor is used to query this temp table and get the result.
My question is that as time goes by content of this data table gets too big (millions of records). I know I can have a clean up process but was wondering if it acceptable to delete the content after cursor is created.
This is a watered version of web service API and stored procedure:
public static EnumGlobal.Errorcode GetOutcomeByItem(string itemIDs, out DataTable dtOutcome, ...)
{
OracleDbContext dbContext = new OracleDbContext();
List<OracleParameter> spParams = new List<OracleParameter>();
DataSet dsOutcome = new DataSet();
...
try
{
spParams.Add(new OracleParameter("IPSITEMIDS", OracleDbType.Varchar2, itemIDs, ParameterDirection.Input));
...
spParams.Add(new OracleParameter("CUR_OUT", OracleDbType.RefCursor, ParameterDirection.Output));
try
{
dbContext.Open();
dbContext.ExecuteStoredProcedure("PKGSOMEQUERY.USPGETOUTCOMEBYITEM", spParams, ref dsOutcome);
}
}
}
PROCEDURE USPGETOUTCOMEBYITEM
(
IPSITEMIDS VARCHAR2,
...
CUR_OUT OUT GETDATACURSOR
)
IS
LVSQUERY VARCHAR2(4000):='';
V_OUTCOME VARCHAR2(5);
V_NEWITEMSLIST VARCHAR2(4000) := REPLACE(IPSITEMIDS, '''', '');
CURSOR cur IS
SELECT REGEXP_SUBSTR(V_NEWITEMSLIST, '[^,]+', 1, LEVEL) V_NEWITEM2 FROM DUAL CONNECT BY instr(V_NEWITEMSLIST, ',',1, LEVEL -1) > 0;
BEGIN
-- Loop thorugh each ITEM ID and determine outcome, add ITEM ID and OUTCOME to temp table
FOR rec IN cur LOOP
V_NEWITEM := rec.V_NEWITEM2;
...
-- Determine V_OUTCOME
...
INSERT INTO TEMPOUTCOME
(
ITEMID,
OUTCOME,
ORIGINDATE
)
VALUES
(
V_NEWITEM,
V_OUTCOME,
SYSDATE
);
COMMIT;
END LOOP;
LVSQUERY:='SELECT ITEMID, OUTCOME, ORIGINDATE FROM TEMPOUTCOME WHERE ITEMID IN (' || IPSITEMIDS || ')';
OPEN CUR_OUT FOR LVSQUERY;
COMMIT;
-- Can I do this?
-- Delete from temp table all item IDs used in this session, in one shot
-- DELETE FROM TEMPOUTCOME WHERE ITEMID IN (select REGEXP_SUBSTR(IPSITEMIDS, '\''(.*?)\''(?:\,)?', 1, LEVEL, NULL, 1) FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(IPSITEMIDS, '''(?: +)?(\,)(?: +)?''', 1) + 1);
EXCEPTION WHEN OTHERS THEN
PKGHANDLEERROR.USPHANDLEERROR('USPGETOUTCOMEBYITEM', LVIERRORCODE);
OPIERRORCODE:=LVIERRORCODE;
END USPGETOUTCOMEBYITEM;
I haven't really tested that, but from general ORACLE knowledge perspective, as soon as you open a cursor, you are no longer dealing with stored data. Instead you are iterating an in-memory snapshot. So I believe it should work. Unless there's a huge amount of data and oracle tries to page the results (not sure if it actually happens though)...
As a simple/safe option you can delete the records that are a day/hour/minute old (depending on the utilization).
Also as a suggestion, if you get sysdate once into a variable and use it in your insert, it may be much easier to deal with the dataset. as you may just query by origindate.
It will also make it a bit faster to insert
One more thing to take a look at (maybe even the best one) is Oracle Temporary tables.
I have a stored procedure which returns a 0 or a 1 depending on whether or not a specified email address exists in my database:
CREATE PROCEDURE [DatabaseSchema].[EmailAddressIsDuplicate] (#emailAddress nvarchar(255))
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS(
SELECT *
FROM [DatabaseSchema].[EmailUpdatesRegistrant]
WHERE EmailAddress = #emailAddress
)
RETURN 1
ELSE
RETURN 0
RETURN 0
END
GO
And I'm trying to derive the results of this stored procedure from an Entity Framework 6 database context:
using (DatabaseContext dbContext = new DatabaseContext())
{
ObjectParameter param = new ObjectParameter("emailAddress", typeof(bool));
var result = dbContext.EmailAddressIsDuplicate(emailAddress);
}
I'm getting lots of errors.
Error #1: Using the code above, var result is always set to -1.
Error #2: I tried navigated to Edit Function Import and set the Returns a Collection Of to a Boolean scalar value. This throws the following error:
The data reader returned by the store data provider does not have enough columns for the query requested.
Error #3: I went back and set the Edit Function Import return value to None. Then I tried the following code from this answer:
using (DatabaseContext dbContext = new DatabaseContext())
{
var p = new SqlParameter("#emailAddress", emailAddress);
var result = dbContext.Database.SqlQuery<bool>("DatabaseSchema.EmailAddressIsDuplicate", p);
}
No immediate errors thrown, but I have no idea whether or not I can derive useful data from var result. Trying to cast result to bool throws the following error:
Cannot convert type 'System.Data.Entity.Infrastructure.DbRawSqlQuery' to 'bool'
Any ideas on how I can see the results of this stored procedure (0 or 1)?
You could try adding an output parameter (#result) in the stored procedure signature:
CREATE PROCEDURE [DatabaseSchema].[EmailAddressIsDuplicate]
(#emailAddress nvarchar(255), #result bit out)
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS(SELECT *
FROM [DatabaseSchema].[EmailUpdatesRegistrant]
WHERE EmailAddress = #emailAddress)
SET #result = 1
ELSE
SET #result = 0
RETURN #result
END
GO
(you'll have to re-define your EF Model Function definition accordingly)
using (DatabaseContext dbContext = new DatabaseContext())
{
ObjectParameter isDuplicate = new ObjectParameter("isDuplicate", typeof(bool));
var result = dbContext.EmailAddressIsDuplicate(emailAddress, isDuplicate);
bool emailIsDuplicate = (bool)isDuplicate.Value;.
}
If you want to call the stored procedure directly with an out parameter you could follow this suggestion:
Database.SqlQuery calling stored procedure that has multiple output parameters
REASON - The template builder for EF (including v6) incorrectly sets the SP up as returning an INT containing the row count rather than the return value because it incorrectly calls the wrong ObjectContext.ExecuteFunction (found in the template-generated class YourDatabaseEntities that is the child of the DBContext).
Why wrong ExecuteFunction? - The result set incorrectly says the row count of changed rows rather than the return value or output parameters because it calls a different ExecuteFunction that discards the results. The flyover intellisense hint of the ObjectContext.ExecuteFunction says "Executes a stored procedure ….; discards any results returned from the function; and returns the number of rows affected by the execution" rather than the usual "Executes a stored procedure …. with the specified parameters".
WHY -1: I believe the SET NOCOUNT ON is causing the SP to return no count result and that Microsoft's ExecuteFunction returns that as error code.
SP FIXES - 1) You have to comment out SET NOCOUNT ON .
2) You have to change stored procedure to do the SELECT command as last statement instead of the RETURN command.
SOLUTION FIX - 1) After fixing SP, delete SP from Function Imports folder and the Data Store's SP folder. 2) Reload the SP into the EDMX by using the "Update Model from Database" 3) Rebuild all of your data project where the EDMX resides. 4) Exit Visual Studio and return. 5) Rebuild overall solution.
See: Entity Framework (Database first) has incorrect return result from stored procedure
Implement the stored procedure in C# to a value using parameters.
Resource: https://msdn.microsoft.com/en-us/library/yy6y35y8(v=vs.110).aspx
This way, the values can be stored to a variable from the ExecuteReader.
Add the value to model similar to adding a value to a property. The stored procedure could be called from ActionResult. Though this may require adding the stored procedure to a separate layer, that simply runs the stored procedure and adds the value to model afterwards.
try this
CREATE PROCEDURE [DatabaseSchema].[EmailAddressIsDuplicate] (#emailAddress nvarchar(255))
AS
BEGIN
SELECT *
FROM [DatabaseSchema].[EmailUpdatesRegistrant]
WHERE EmailAddress = #emailAddress
SELECT ##ROWCOUNT
END
GO
using (DatabaseContext dbContext = new DatabaseContext())
{
var result = dbContext.Database.SqlQuery<int32>("exec DatabaseSchema.EmailAddressIsDuplicate {0}", emailAddress).FirstOrDefault();
}
Anything other 0 in the return value indicates there is a match and the number indicates the number of matches
i'm calling this stored procedure to check where Username, Email Address and Phone Number is already register or not. if this values are not registered in my database then this Stored Procedure return Available Value
Create procedure [dbo].[proc_CheckingUserEmailMobile]( #chkUserEmailPhone varchar(35))
as
begin
declare #User varchar(35)
declare #Email varchar(35)
declare #Phone varchar(15)
declare #StatusAvailablevarchar(15)
set #StatusAvailable='NotAvailable'
set #User=(select username from users where username=#chkUserEmailPhone)
set #Email=(select emailid from users where emailid=#chkUserEmailPhone)
set #Phone=(select phone from users where phone=#chkUserEmailPhone)
if(#User is not null)
begin
select Username=#User
end
else if(#Email is not null)
begin
select Email=#Email
end
else if (#Phone is not null)
begin
select Phone=#Phone
end
else
begin
select StatusAvailable=#StatusAvailable
end
end
In Asp.net Page :
DataSet dsUEM = businessLogic.CheckUsernameEmailPhoneBAL(ChkUserBO);
if (dsUEM.Tables[0].Rows.Count > 0)
{
if (dsUEM.Tables[0].Rows[0]["StatusAvailable"].ToString() != "NotAvailable")
{
}
}
Error :
Column StatusAvailable does not belong to table Table.
Please any one suggest me how to achieve this...
This whole setup seems to just be making this logic a lot more complicated than it needs to be. All you need to do is check if a matching record exists or not. That's one SELECT statement:
SELECT id FROM users
WHERE username = #chkUserEmailPhone
OR emailid = #chkUserEmailPhone
OR phone = #chkUserEmailPhone
If there's a matching record, the resulting record count will be greater than 0. So...
DataSet dsUEM = businessLogic.CheckUsernameEmailPhoneBAL(ChkUserBO);
if (dsUEM.Tables[0].Rows.Count > 0)
{
// a matching record was found
}
(Now, the fact that the one input value can be in any of those three columns seems like another problem entirely. But that's outside the scope of what's being asked.)
To start I really suggest to change the stored procedure. As now it executes three queries just to check for the existance of the data over the same table
Create procedure [dbo].[proc_CheckingUserEmailMobile]( #chkUserEmailPhone varchar(35))
as
begin
if EXISTS(select 1 from users
where username=#chkUserEmailPhone OR
emailid=#chkUserEmailPhone OR
phone=#chkUserEmailPhone)
SELECT 1 ELSE SELECT 0
end
In this way the stored procedure returns just one row with one column, as ExecuteScalar requires. The return is 1 if one of your fields contains the value passed as parameter or 0 if no field matches the parameter
Now in your BAL methods you run an ExecuteScalar call
public bool IsUserAvailable(string chkUserEmailPhone)
{
using(SqlConnection cnn = new SqlConnection(....))
using(SqlCommand cmd = new SqlCommand("proc_CheckingUserEmailMobile", cnn))
{
cnn.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#chkUserEmailPhone", SqlDbType.NVarChar).Value = chkUserEmailPhone;
int result = Convert.ToInt32(cmd.ExecuteScalar())
// SP return 1 if the data exists, so we need to negate
// to get true if the user data is available for a new record.
return !(result == 1)
}
}
QUESTION
How do I access the 'Results', 'Messages', and 'Return Value' of a Stored Procedure using Entity Framework 4.4 and C# 4.0?
Below is the Stored Procedure that takes three parameters. One way or another when I run the Store Procedure I should, I hope, be able to access all three values for 'Results', 'Messages', and 'Return Value'.
Can someone help me figure out how to do that with EF? Using the code that is generated out of EF all I seem to be able to access is the 'Results' of the query ( returned rows )
Stored Procedure
USE [THIS_DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[THIS_PROCEDURE]
#FIRST_PARAM CHAR(17) = NULL,
#SECOND_PARAM CHAR(2) = NULL,
#THIRD_PARAM CHAR(5) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ReturnValue INT = 0;
IF COALESCE(#SECOND_PARAM, 'XX') NOT IN ('XX', 'YY')
BEGIN
RAISERROR('Invalid #SECOND_PARAM value: %s; #SECOND_PARAM mXXt be XX or YY.', 2, 1, #SECOND_PARAM ) WITH SETERROR;
SET #ReturnValue = -50100;
END
IF COALESCE(#SECOND_PARAM, 'XX') = 'YY'
BEGIN
RAISERROR('#SECOND_PARAM value: %s; YY is valid, but currently is not supported, returning XX results.', 2, 1, #SECOND_PARAM) WITH SETERROR;
SET #ReturnValue = -50105;
END
IF COALESCE(#THIRD_PARAM, 'XX-EN') NOT IN ('XX-EN')
BEGIN
RAISERROR('Invalid #THIRD_PARAM value: %s; #THIRD_PARAM mXXt be XX-EN.', 2, 1, #THIRD_PARAM) WITH SETERROR;
SET #ReturnValue = -50101;
END
SELECT DISTINCT
THESE.VALUES
FROM dbo.THIS_TABLE
WHERE THESE.CONDITIONS;
IF ##ROWCOUNT = 0
BEGIN
DECLARE #SP_MATCHCOUNT INT
EXEC #SP_MATCHCOUNT = [dbo].[MATCHTABLE] #PATTERNH = #PATTERN
IF #SP_MATCHCOUNT > 0
BEGIN
RAISERROR('Mapping from HERE to HERE not found for PATTERN: %s.', 2, 1, #PATTERN) WITH SETERROR
SET #ReturnValue = -50103;
END
ELSE
BEGIN
RAISERROR('PATTERN Pattern not found for PATTERN: %s.', 2, 1, #PATTERN) WITH SETERROR
SET #ReturnValue = -50104;
END
END
RETURN #ReturnValue
END
CODE
public virtual ObjectResult<THIS_PROCEDURE_RESULT> THIS_PROCEDURE_METHOD(string FIRST, string SECOND, string THIRD)
{
var FIRST_PARAM = FIRST != null ?
new ObjectParameter("FIRST", FIRST) :
new ObjectParameter("FIRST", typeof(string));
var SECOND_PARAM = SECOND != null ?
new ObjectParameter("SECOND", SECOND) :
new ObjectParameter("SECOND", typeof(string));
var THIRD_PARAM = THIRD != null ?
new ObjectParameter("THIRD", THIRD) :
new ObjectParameter("THIRD", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<THIS_PROCEDURE_RESULT>("THIS_PROCEDURE", FIRST_PARAM, SECOND_PARAM, THIRD_PARAM);
}
So, first things first :-) Just want to make sure we're on the same page before I answer the 3 parts of the question. EF is designed to be an ORM (object-relational-mapper). That means its purpose for being is to translate relational data to code objects (and vice-versa). The mechanism it uses for this is result sets (not return values). So most of the plumbing inside EF is specifically designed to operate on result sets, and also to automatically generate SQL for getting those result sets. However, since people requested it, EF now has the capability to execute stored procedures, but that ability is not comprehensive, and is sort of a side-effect to the main capabilities of the product. Having said that, EF does use ADO.NET under the covers, and that's where you are going to get your answers because ADO.NET does handle all your scenarios.
First problem - how to get results. EF will execute the SP in this case, and presumably, it's mapped to some object that has properties that match the result columns. That means that EF will create a collection (enumerable query result set to be more precise) of objects, each of which represents a row of data in the results. In your case, the return of your method is ObjectResult. ObjectResult is a collection of objects, and each item is of type THIS_PROCEDURE_RESULT, which in turn has a property for each mapped column of the result.
Second problem - how to get messages. If Raiserror is used with a certain range of severity, will cause ADO.NET to throw and exception (of type SqlException). EF will just just surface (pass through) that error. That SQLException instance will contain all the error & message information. To see it, you just have to catch the error:
try
{
// call EF SP method here...
}
catch(SqlException se)
{
Debug.WriteLine(se.Message);
}
catch(Exception e)
{
// all non-DB errors will be seen here...
}
However, if the Raiserror statement is of a warning or info severity, ADO.NET will not throw an exception. In that case, you have to use an event of the connection object to see info and warning messages from the databse. To do this in EF, you have to get the EntityConnection from the EF object context, and then get the Store Connection from the EntityConnection. If you are using SQL Server (SqlClient ADO.NET provider), this will be a SqlConnection instance. That instance contains an event called InfoMessage. You can hook up an event handler to that event to trap messages. More info here: http://support.microsoft.com/kb/321903
Last problem - how to get Return Value. This one is going to suck. Based on my first paragraph, EF isn't really designed to arbitrarily handle SP calls. While it will map result sets to object collections, it doesn't handle return values from SPs. You will have to use ADO.NET without the EF layer in order to access the Parameters collections of the SqlCommand object. One of the parameters is of parameter-type ReturnValue, and it will contain the return value itself.
I use Enterprise Library and I have one problem:
string sql = "
UPDATE StackOverflow SET UserName = #UserName
WHERE Id = #Id
";
DbCommand cmd = base.Database.GetSqlStringCommand(sql);
base.Database.AddInParameter(cmd, "Id", DbType.Int32, StackOverflow.Id);
base.Database.AddInParameter(cmd, "UserName", DbType.Int32, StackOverflow.UserName);
int val = Convert.ToInt32(base.Database.ExecuteScalar(cmd));
Convert.ToInt32(base.Database.ExecuteScalar(cmd)) //returns 0.
I've read this article http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx
The article says:
The function returns the new Identity column value if a new row was inserted, 0 on failure.
but I did not insert into that table - I only want to update and return updated row Id.
You should use ExecuteNonQuery in your case.
ExecuteScalar
Executes the query, and returns the first column of the first row in the result set returned by the query
ExecuteNonQuery
Executes a Transact-SQL statement against the connection and returns the number of rows affected
Your query doesn't return anything, so ExecuteScalar is not the right method to work with.
ExecuteNonQuery on the other side will give the correct information if your query has updated anything.
If you modify your SQL statement to the following, I think this will give you the result your expecting:
string sql = "
UPDATE StackOverflow SET UserName = #UserName
WHERE Id = #Id
RETURN #Id
";