Problems executing SQL Server Stored Procedures from EFCore - c#

I have a database with several tables, and I have prepared a stored procedure (basically consisting of delete * from ... statements) to delete rows from these tables. When I run the stored procedure from SQL Server Management Studio, it works just fine.
Now, I'd like to execute the same stored procedure from my C# application. I use the below code to do this:
using (var context = new DBContext())
{
var param = new SqlParameter[] {...}; //create parameters here
//trigger the stored procedure for deletion from the first table here
context.Database.ExecuteSqlRaw("[dbo].[delete_first] #id,#date", param);
//trigger the stored procedure for deletion from the hts_clients table here
context.Database.ExecuteSqlRaw("[dbo].[delete_second] #id,#date", param);
}
When executed, the first invocation seems to work fine. However, the second one always throws an exception with the following message:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1. Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1
(Note: Yes, the exception message appears exactly like this. I didn't paste it twice.)
However, I'm definitely not using any transactions in the bodies of the stored procedures i.e I have no Begin Transaction/Commit Transaction/Rollback Transactions statements anywhere in either of them, nor did I wrap the invocation in a transaction from EFCore (as can be seen from the code segment above). Can anyone tell me why this is happening?
Thanks in advance.

The dbContext is not getting refreshed automatically after executeSql. You have to reload the entry that was changed.
try to use this:
context.Database.ExecuteSqlRaw("[dbo].[delete_first] #id,#date", param);
_context.Entry(...deletionClass... ).Reload();
context.Database.ExecuteSqlRaw("[dbo].[delete_second] #id,#date", param);
//and maybe if you need you can try it again
_context.Entry(...deletionClass... ).Reload();
but in your case maybe easier to create a new DbContext for the second stored procedure.
using (var context1 = new DBContext())
{
var param = new SqlParameter[] {...}; //create parameters here
//trigger the stored procedure for deletion from the first table here
context1.Database.ExecuteSqlRaw("[dbo].[delete_first] #id,#date", param);
}
using (var context2 = new DBContext())
{
var param = new SqlParameter[] {...}; //create parameters here
//trigger the stored procedure for deletion from the hts_clients table here
context2.Database.ExecuteSqlRaw("[dbo].[delete_second] #id,#date", param);
}

Related

Entity Framework Core using stored procedure to delete entity with bool return

I have an entity, lets just call it "Entity", that I want to delete with a stored procedure. The "Entity" entity is relatively complex with a lot of related entities - Hence why, I want to use a stored procedure to delete the Entity.
CREATE PROCEDURE dbo.spDeleteEntity
#EntityId int,
#ServiceResult bit output
AS
BEGIN
.... Delete logic here ...
IF ##ERROR = 0
SET #ServiceResult = 1
ELSE SET #ServiceResult = 0
END
As you can see, the stored procedure takes in an EntityId for the entity, performs my delete logic, and returns a bit - Which in this case is my ServiceResult. Here the ServiceResult is "True"/1 if no errors occur while executing the query, and "False"/0 if errors occur. The problem is now, that I want to be able to execute this stored procedure from .NET Core. My Initial idea was to do something like this
public bool DeleteEntity(Entity Entity)
{
return _context.Entity.FromSqlRaw<bool>("spDeleteEntity {0}", Entity.Id);
}
I believe this doesn't work, because Entity Framework Core does not know what datatype it should expect. From what I can read, Entity Framework Core only accepts types of TEntity. So my question really is, how do I call a stored procedure with Entity Framework Core, so that I can pass an Id and get a bool value returned.
While in your case, you could simply RAISERROR in your procedure to indicate failure.;
try{
_context.Database.ExecuteSqlInterpolatedAsync($"spDeleteEntity {Entity.Id}");
return true;
}catch(...){
return false;
}
There is a way to pass sql parameters in / out of raw sql commands using EF Core with something like;
var entityId = new SqlParameter("#entityId", Entity.Id);
var result = new SqlParameter("#result", SqlDbType.Bit)
{ Direction = ParameterDirection.Output };
_context.Database.ExecuteSqlRaw("EXEC #result = spDeleteEntity #entityId", entityId, result);
return (bool)result.Value;
Call your stored procedure in the try catch and add your SqlParameter that you want to pass to sp like this :
try
{
using(var context = new SampleContext())
{
//Declare storedprocedure parameter
var Idp = new SqlParameter("#IdParam", "Idp");
//Call stored procedure(For async call use ExecuteSqlCommandAsync method)
context.Database.ExecuteSqlCommand("EXEC spName #IdParam", Idp);
}
Return true:
}
catch
{
Return false;
}
In this way if execution of sp occurred with error, it goes to catch and return false and if not it's return true.
Note: if you declare raiseerror in your storedprocedure, you can generates an error message and send it to your application try catch.
More details about raiseerror :
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql?view=sql-server-ver15

Pass two parameters to stored procedure and grab value

First check my controller code below. Then also check the bottom picture which contains my stored procedure code in SQL Server Management Studio.
Now the issue is: my PagedSearchedUserItems procedure needs two int parameters and it will return output of few columns of data (you can see data sample from screen shot picture below).
In my controller, I am not getting idea how I can pass two parameters and get back that data in variable Items. Can you please fix my controller code to pass two parameters correctly, and grab all values on my variable "Items" ?
Controller code:
using (var ctx = new db_demoEntities())
{
var Items = ctx.Database.SqlQuery<SearchedUserItems>("EXEC PagedSearchedUserItems #TakeFrom",2).ToList<SearchedUserItems>();
}
Stored procedure code:
CREATE PROCEDURE PagedSearchedUserItems
#TakeFrom INT,
#TakePerPage INT
AS
BEGIN
SELECT *
FROM SearchedUserItems
ORDER BY Id
OFFSET #TakeFrom ROWS
FETCH NEXT #TakePerPage ROWS ONLY;
END
PagedSearchedUserItems 2,5
SQL Server Management Studio screenshot
I think you are looking for something like that. This code should work fine with passing two parameters. I am assuming that "SearchedUserItems" object is maching your Data model.
using (var ctx = new db_demoEntities())
{
object[] xparams = {
new SqlParameter("#TakeFrom", 2),
new SqlParameter("#TakePerPage", 5)};
var Items = ctx.Database.SqlQuery<SearchedUserItems>("EXEC PagedSearchedUserItems #TakeFrom, #TakePerPage", xparams).ToList<SearchedUserItems>();
}

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

Read a non typed result of a stored procedure with Entity Framework

I am using Entity Framework for calling a stored procedure.
When I call a stored procedure that returns a result set from a table, it works:
var user= context.Database.SqlQuery<User>("get_My_User, #Name, #Mail",
new SqlParameter("#Name", 'Maurice'),
new SqlParameter("#Mail", 'maurice.abruti#yahoo.com')
).FirstOrDefault();
Unfortunately when I call a stored procedure with a result set that is not a table, I cannot get any result.
This attempt always returns -1:
var result = context.Database.ExecuteSqlCommand("my_stored_procedure #Param1, #Param2",
new SqlParameter("#Param1", "value1"),
new SqlParameter("#Param2", "value2"));
This other attempt returns a list of null objects:
var generatedDesignations = context.Database.SqlQuery<Object>(
"my_stored_procedure #Param1, #Param2",
new SqlParameter("#Param1", "value1"),
new SqlParameter("#Param2", "value2")).ToList();
Do you know how to do this?
I do not konow use of SqlQuery and ExecuteSqlComman methods.Bu i can recomment another way.
If you define the Stored Procedure in the VS's Server Explorer,its usage is easier.You can use it as a method.In order to define it;
1) open Server Explorer,
2) Find Stored Procedure of Data Connection.
3) Right click and add write the stored procedure.
4) Update it.
5) Update the EDMX
6) Use the Stored procedure context.StoredProcedureName(paramters)

How can I run a stored procedure that has no return values from C# with my db context?

I am using the following code to run a stored procedure:
MyContext db = new MyContext();
var a = db.Database.SqlQuery<string>("dbo.getQuestionUIdsByTopicId #TopicId",
new SqlParameter { ParameterName = "TopicId", Value = testTopicId });
This works good but now I will have a stored procedure that does not return any data other than a return code.
How can I execute a stored procedure with a parameter using my context db.Database and have the stored procedure return only a return code? If someone could give an example of a 3-4 line SP and how it returns a return code that would also be a great help.
You can use ExecuteSqlCommand to send non-query commands to the database.
int result = db.Database.ExecuteSqlCommand("exec sproc...");
See this link for more info.

Categories

Resources