Calling Stored Procedure In Entity Framework - c#

I have the following part in the end of a SQL Server stored procedure:
if(#someValue < 0)
begin
SELECT #resultIsSuccess = 0
Return #resultIsSuccess
end
else
begin
SELECT #resultIsSuccess = 1
Return #resultIsSuccess
end
where #resultIsSuccess is of type bit.
So, basically I am returning a bool to indicate if the procedure yielded the intended result.
On the EF side, I configured the Function Import's return type as boolean.
When I call:
bool isSuccess = context.MyFunctionImport(arg1, arg2).FirstOrDefault().Value;
I get the following exception:
The data reader returned by the store data provider does not have
enough columns for the query requested.
What is the problem here?

If you are returning a value, you need a return parameter. Therefore you aren't using a datareader to get your value. You aren't selecting anything therefore you would need to have access to that return parameter.
Instead select your values to populate the datareader since you are probably getting an error when you try to read values from a noninstantiated datareader through EF.
if(#someValue < 0)
SELECT 0
else
SELECT 1
I'm unsure if return values are supported, reference, so you may be trying to do the impossible unless fixed in a newer version.

You have to keep in mind that you are NOT returning a bit datatype from your procedure. The return datatype of a stored procedure is int.
You could greatly simplify your code to a single line return statement.
return case when #someValue < 0 then 0 else 1 end
Then in your code you would need to parse the 0 or 1 to a boolean.
--EDIT--
Since you are looking for the first value you will need to use a select statement in your procedure. Something like this.
Select isSuccess = case when #someValue < 0 then 0 else 1 end

Related

Can't get stored procedure results with Entity Framework 6

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

C# Getting Return Value from Stored Procedure Is Not Working

I am trying to run a stored procedure returning a single integer value and I can't figure out where its going wrong. From what I've read the following should work but is not and I can't see where I'm going wrong.
Here is my stored procedure:
ALTER PROCEDURE [dbo].[getDDNTempID]
AS
BEGIN
declare #tempID int
select top 1 #tempID = tempID from tblDDNHdr order by tempID asc
if #tempID is null
return 0
else
return #tempID
END
Here is the code where I try to get the return value:
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString))
{
SqlCommand cmd = new SqlCommand("getDDNTempID", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection.Open();
Int32 tempID = (Int32)cmd.ExecuteScalar();
if (tempID == 0) { tempID = -1; }
return tempID;
}
When this procedure is called I get a NullReferenceException and the line giving the error is:
Int32 tempID = (Int32)cmd.ExecuteScalar();
I would appreciate any guidance you guys could give.
Thanks
The return function in SQL Server is specifically to return completion codes to the calling function. As such, the values available to be returned are limited. What you need to do instead is to SELECT #tempID and treat it as a result set.
ExecuteScalar returns the value of the first column of the first row of the results. Your stored procedure does not return a result set.
Add a parameter to the SqlCommand.Parameters collection and set the Direction to ReturnValue. It will receive the return value from the stored procedure.
Please note that the return value is intended only for returning a status. You should use an OUTPUT parameter to return #TempId.

How to access 'Results', 'Messages', and 'Return Value' of a Stored Procedure using Entity Framework 4?

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.

Assign a stored procedure result as a bool variable on C#

Hello Is it possible to store the result of a stored procedure as a true/false value in a bool variable? Something like:
1)
bool var = ExecuteScalar("sp_name", parameters);
or
2)
bool var = ClassName.getValue(parameters);
where
getValue() is a method that has this code:
return dataBase.ExecuteDataSet("sp_name", parameters);
I want to know if a customer has a card or not. I was thinking of using what I have right now which is basically the second option above, and then create a condition whether the row count is more than zero. But I also want to know if there are any other methods I can use.
Thanks in advance.
You can simply do:
DataSet ds = dataBase.ExecuteDataSet("sp_name", parameters);
return ds.Tables[0].Rows.Count>0; //true if record found; false if no rows
Assuming your proc is simply doing a select using the parameters in the where clause. Something like:
select col1 from table a where condition=#parameter
You don't say which DBMS you are using. SQL Server, for example, does not have a bool data type, but you can make your sproc return an int, which you can test for nonzero, e.g.
bool result = ((int) ExecuteScalar("sp_name", parameters)) != 0;
NOTE: I have not tested this, but you could try making the sproc return a bit instead. This is still an integer value as far as SQL Server is concerned, but ADO.NET should detect it and automatically cast it to bool for you. It certainly does for sproc parameters.
ExecuteScalar returns an Object. So no, #1 above would throw an error. Assuming your stored procedure returns a value that can be converted to true/false, you'd have to do something like this:
bool var;
bool.TryParse(ExecuteScalar("sp_name", paramaters).ToString(), out var);

Best practice for returning type from SQL Server to ExecuteScalar?

In C# SqlCommand - ExecuteScalar is :
private object CompleteExecuteScalar(SqlDataReader ds, bool returnSqlValue)
now let's go to SQL Server.
If I want to return a value from the select (which goes to ExecuteScalar), e.g. :
if record already exists select `-1`
else select `0`
Question :
what is the preferred (by best practices) type for returning from SQL Server in order to reduce the amount of casting & tostring() in C#:
if exists(select ....) select '-1' -- string
or
if exists(select ....) select -1 -- int
You are returning number so int is preferable here by meaning.
Futher more, when parsing error in C# code you will be albe to do somethinng like:
enum Error { NO_ERROR = -1, ERROR 1 };
....
Error e = (Error)cmd.ExcecuteScalar();
Which is pretty clean and self explaining code as oposite to parsing result to int, or comparing string values.

Categories

Resources