EF ExecuteStoredCommand with ReturnValue parameter - sql-server

I'm creating a new application which needs to interface with legacy code :(.
The stored procedure I'm attempting to call uses RETURN for its result. My attempts to execute and consume the return value result in the exception:
InvalidOperationException: When executing a command, parameters must be exclusively database parameters or values.
Changing the stored proc to return the value another way isn't desired, since it either requires updating the legacy app or maintaining a nearly duplicate stored proc.
The legacy stored proc synopsis:
DECLARE #MyID INT
INSERT INTO MyTable ...
SELECT #MyID = IDENTITY()
RETURN #MyID
My Entity Framework / DbContext work, which yields the above InvalidOperationException.
SqlParameter parm = new SqlParameter() {
ParameterName = "#MyID",
Direction = System.Data.ParameterDirection.ReturnValue
};
DbContext.Database.ExecuteSqlCommand("EXEC dbo.MyProc", parm);
Looking for any and all solutions which don't require the stored proc to be modified.

You can capture the return value of the stored procedure into an output parameter instead:
SqlParameter parm = new SqlParameter() {
ParameterName = "#MyID",
SqlDbType = SqlDbType.Int,
Direction = System.Data.ParameterDirection.Output
};
Database.ExecuteSqlCommand("exec #MyId = dbo.MyProc", parm);
int id = (int)parm.Value;

I know it's a bit late, but this works for me:
var param = new SqlParameter("#Parameter1", txtBoxORsmth.text);
someVariable = ctx.Database.SqlQuery<int>("EXEC dbo.MyProc", param).First();

You don't have to use ExecuteSqlCommand.
You can just get the underlying connection from DbContext.Database.Connection and use raw ADO.NET (CreateCommand(), ExecuteNonQuery(), ...)

The error message
InvalidOperationException: When executing a command, parameters must be exclusively database parameters or values.
means that you're not providing the right type (or something else which isn't shown in your code snippet) in the params list of SQLParameters.
In my case I had forgotten to remove a MergeOption because I changed the way the SQL command was executed.

This extension method will do all the dirty work for you now. See fuller description on SO here.

This will return the int from a stored proc using DBContext:
var newId = DbContext.Database.SqlQuery<int>("EXEC dbo.MyProc #MyID = {0}", parm).First();

Create table for With parameter example for testing or change the second sql query according to yours.
CREATE TABLE [dbo].[Test](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
//========================= =================================//
public void Test()
{
using (var db = new DbContext())
{
string sql = "dbo.MyProc"; //With Out Parameter
int id1 = (int)db.Database.SqlQuery<decimal>(sql).FirstOrDefault();
db.SaveChanges();
//Or
sql = "INSERT Test(Name) values({0}) SELECT SCOPE_IDENTITY();"; //With Parameter
int id2 = (int)db.Database.SqlQuery<decimal>(sql, new object[] { "Thulasi Ram.S" }).FirstOrDefault();
db.SaveChanges();
}
}

I tried the ways above, but only this way works out for me(Must have the 'ToList()' function):
SqlParameter res = new SqlParameter()
{
ParameterName = "Count",
Value=1,
Direction = System.Data.ParameterDirection.Output
};
db.Database.SqlQuery<object>(
"[dbo].[GetWorkerCountBySearchConditions] #Count ,
res
).ToList();
return Convert.ToInt32(res.Value);

I'm sure this isn't the only valid answer, but one that I ultimately used and has been working successfully.
The key seemed to be naming the ReturnValue parameter RetVal.
SqlParameter id = create.Parameters.Add("RetVal", System.Data.SqlDbType.Int);
id.Direction = System.Data.ParameterDirection.ReturnValue;
putting it all together:
SqlCommand proc = new SqlCommand("dbo.MyProc", new SqlConnection(<connection string>));
proc.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter id = create.Parameters.Add("RetVal", System.Data.SqlDbType.Int);
id.Direction = System.Data.ParameterDirection.ReturnValue;
proc.ExecuteNonQuery();
int newId = Convert.ToInt32(id.Value);

Related

ASP.NET Core MVC and Entity Framework output param not returning when calling stored procedure

I have a procedure which returns the identity of the record added. I am using Entity Framework to call the procedure and retrieve the value, but it is always 0.
This is the code - can you figure out why it is not returning the identity value?
C# Entity Framework domain code:
var cNumber = new SqlParameter("CNumber", acctSl.cNumber);
var fId = new SqlParameter("FId", acctSl.FId);
var splAmt = new SqlParameter("SplAmt", acctSl.SplAmt);
var frDt = new SqlParameter("FrDt", acctSl.FrDate);
var toDt = new SqlParameter("ToDt", acctSl.ToDate);
var user = new SqlParameter("User", acctSl.User);
var id = new SqlParameter("Id", "")
{
Direction = ParameterDirection.Output,
SqlDbType = SqlDbType.VarChar
};
var sql = "EXECUTE [dbo].[InsertAcctSpl] #CNumber, #FID, #SplAmt, #FrDt, #ToDt, #User, #Id OUTPUT";
var result = DbContext.Database.ExecuteSqlRaw(sql, cNumber, fId, splAmt, frDt, toDt, user, id);
int rowsAffected;
var yourOutput = Convert.ToInt32(id.Value);
if (result > 0)
{
acctSl.AcctId = yourOutput;
}
else
{
acctSl.AcctId = 0;
}
SQL Server procedure:
ALTER PROCEDURE [dbo].[InsertAccountsSpend]
#CNumber varchar(15),
#FId bigint,
#SplAmt money,
#FrDt date,
#ToDt date,
#User bigint,
#Id bigint OUTPUT
AS
INSERT INTO AcctSpend (CNmbr, FID, SplAmt, FrDt, ToDt,
Cr8Dt, Cr8User_ID, UpdtDt, UpdtUser_ID)
VALUES (#CNumber, #FId, #splAmt, #FroDt,# ToDt,
GETDATE(), #User, GETDATE(), #User)
SET #id = SCOPE_IDENTITY()
RETURN #id
The issue was with the data type it needed be long
var id = new SqlParameter("Id", "")
{
Direction = ParameterDirection.Output,
**SqlDbType = SqlDbType.BigInt**
};
Your problem seems to be with this section of your code:
int rowsAffected;
var yourOutput = Convert.ToInt32(id.Value);
if (rowsAffected > 0)
{
acctSl.AcctId = yourOutput;
}
else
{
acctSl.AcctId = 0;
}
You are basing your if-else logic off of the rowsAffected variable, but that variable is never assigned the value from your stored procedures output. Since rowsAffected is declared as an int type variable, it cannot be null, so it is automatically set to 0.
To get the actual value for rowsAffected you will need to utilize the data returned in your result variable that you have declared here:
var result = DbContext.Database.ExecuteSqlRaw(sql, cNumber, fId, splAmt, frDt, toDt, user, id);
EDIT
It appears that the syntax around your id SQL parameter object is incorrect. Try creating this object in the following manner:
var id = new SqlParameter
{
ParameterName = "Id",
DbType = System.Data.DbType.String,
Direction = System.Data.ParameterDirection.Output
};

TVP does not conform to table type

Below is the function that inserts my data.
using (SqlCommand insSwipeDataCommand = connection.CreateCommand())
{
insSwipeDataCommand.Transaction = transaction;
insSwipeDataCommand.CommandType = CommandType.StoredProcedure;
insSwipeDataCommand.CommandText = "dbo.insSwipeData_sp";
SqlParameter attendeeTableParam = insSwipeDataCommand.Parameters.Add("#AttendeeTable", SqlDbType.Structured);
attendeeTableParam.Value = this.dataTable;
attendeeTableParam.TypeName = "AttendeeTableType";
// add orgid parameter
insSwipeDataCommand.Parameters.Add("#orgId", SqlDbType.UniqueIdentifier).Value = this.organizationId;
insSwipeDataCommand.ExecuteNonQuery();
}
insSwipeData_sp
create PROC dbo.insSwipeData_sp
(#AttendeeTable AttendeeTableType READONLY
,#orgId UNIQUEIDENTIFIER
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #enteredUserId UNIQUEIDENTIFIER
SET #enteredUserId = 'xxxxxxxxx-xxxx-xxx-xxxx-xxxxxxxxxx'
-- Delete old Swipe records
DELETE FROM dbo.swipeData_tbl
WHERE orgId = #orgId
-- CREATE Swipe Records
INSERT INTO dbo.swipeData_tbl
(orgId, sdid, rawData, enteredUserId, enteredUtc, manualEntry)
SELECT #orgId, attendeeId, barcode
,#enteredUserId, GETUTCDATE(), 0 -- Consider ( datepart , date ) if date here is needed as NVARCHAR
FROM #AttendeeTable
WHERE barcode IS NOT NULL and LTRIM(RTRIM(barcode)) <> '';
END
Here is an image of my AttendeeTableType schema.
and here is an image of my this.datatable that i am using for my attendeeTableParam
On the insSwipeDataCommand.ExecuteNonQuery(); line i get the following error.
The data for table-valued parameter "#AttendeeTable" doesn't conform to the table type of the parameter.
Per the error, your data does not conform to the table type exactly. Note "exactly" -- if you do not specify types for the columns, they will be inferred, and they can easily be inferred incorrectly. The best approach here is to create a table that you know matches the table type definition:
var dt = new DataTable();
dt.Columns.Add("firstName", typeof(string)).MaxLength = 100;
dt.Columns.Add("lastName", typeof(string)).MaxLength = 100;
dt.Columns.Add("studentId", typeof(string)).MaxLength = 10;
dt.Columns.Add("email", typeof(string)).MaxLength = 100;
dt.Columns.Add("barcode", typeof(string)).MaxLength = 100;
dt.Columns.Add("dob", typeof(string)).MaxLength = 200;
dt.Columns.Add("major", typeof(string)).MaxLength = 200;
dt.Columns.Add("gender", typeof(string)).MaxLength = 200;
dt.Columns.Add("classCode", typeof(string)).MaxLength = 15;
dt.Columns.Add("currentclassCode", typeof(string)).MaxLength = 15;
dt.Columns.Add("entranceCode", typeof(string)).MaxLength = 15;
dt.Columns.Add("attendeeId", typeof(Guid));
And then use .Clone() to create a new table with the correct schema when you need to insert data. This way, if you have a type or length mismatch, it will be caught on the client end.
There is another approach you can take that does not rely on embedding the table definition into the application, which is fetching it from the database. There are pros and cons to this -- it requires an extra roundtrip to the database and it's not as easy to spot mistakes in the application logic if the types or columns don't match, but it does give you additional flexibility to change the type without having to change the application (adding a new, nullable column, for example).
var dt = new DataTable();
using (var connection = new SqlConnection(...)) {
connection.Open();
using (var command = new SqlCommand()) {
command.Connection = connection;
command.CommandText = "DECLARE #t dbo.AttendeeTableType; SELECT * FROM #t;"
using (var reader = command.ExecuteReader()) {
dt.Load(reader);
}
}
}
Obviously you probably want to cache the results of this and .Clone(), rather than doing it for every command involving the table type parameter.
I reached this page while searching for similar issue that I was experiencing, but none of the replies did help me. After some head beating I found that the error is being generated in case when the data table being passed from the code has some data that does not match the TVP type specification.
For example if you defined the following type:
CREATE TYPE EmployeeType AS TABLE
(
EmpID BigInt, EmpName VARCHAR(100)
)
and suppose the data table that you are passing has (say) one EmpName that has more than 100 characters then "*** does not conform to table type" error is generated.
This solved my issue. Hope it will help others as well.
Even though Jeroen Mostert answer helped me solve one piece of the puzzle, this error was still popping up.
Here is what I learned;
Datatable created and passed as table value param should
correspond to the sequence / order of field in table value type defined
in sql.
So if your table type looks like below :
CREATE TYPE [dbo].[tvp_mytabletype] AS TABLE(
[ID] BIGINT,
[AddressCode] BIGINT,
[Address] NVARCHAR(200) NOT NULL
)
then define your DT with columns in exact same order/sequence like below :
DataTable dataTable = new DataTable();
//DataTable definition
dataTable.Columns.Add("ID", typeof(long));
dataTable.Columns.Add("AddressCode", typeof(long));
dataTable.Columns.Add("Address", typeof(string)).MaxLength = 200;
Your attendeeId is looks strange. It must be Guid in C# side.

Cannot understand how will Entity Framewrok generate a SQL statement for an Update operation using timestamp?

I have the following method inside my asp.net mvc web application :
var rack = IT.ITRacks.Where(a => !a.Technology.IsDeleted && a.Technology.IsCompleted);
foreach (var r in rack)
{
long? it360id = technology[r.ITRackID];
if (it360resource.ContainsKey(it360id.Value))
{
long? CurrentIT360siteid = it360resource[it360id.Value];
if (CurrentIT360siteid != r.IT360SiteID)
{
r.IT360SiteID = CurrentIT360siteid.Value;
IT.Entry(r).State = EntityState.Modified;
count = count + 1;
}
}
IT.SaveChanges();
}
When I checked SQL Server profiler I noted that EF will generated the following SQL statement:
exec sp_executesql N'update [dbo].[ITSwitches]
set [ModelID] = #0, [Spec] = null, [RackID] = #1, [ConsoleServerID] = null, [Description] = null, [IT360SiteID] = #2, [ConsoleServerPort] = null
where (([SwitchID] = #3) and ([timestamp] = #4))
select [timestamp]
from [dbo].[ITSwitches]
where ##ROWCOUNT > 0 and [SwitchID] = #3',N'#0 int,#1 int,#2 bigint,#3 int,#4 binary(8)',#0=1,#1=539,#2=1502,#3=1484,#4=0x00000000000EDCB2
I can not understand the purpose of having the following section :-
select [timestamp]
from [dbo].[ITSwitches]
where ##ROWCOUNT > 0 and [SwitchID] = #3',N'#0 int,#1 int,#2 bigint,#3 int,#4 binary(8)',#0=1,#1=539,#2=1502,#3=1484,#4=0x00000000000EDCB2
Can anyone advice?
Entity Framework uses timestamps to check whether a row has changed. If the row has changed since the last time EF retrieved it, then it knows it has a concurrency problem.
Here's an explanation:
http://www.remondo.net/entity-framework-concurrency-checking-with-timestamp/
This is because EF (and you) want to update the updated client-side object by the newly generated rowversion value.
First the update is executed. If this succeeds (because the rowversion is still the one you had in the client) a new rowversion is generated by the database and EF retrieves that value. Suppose you'd immediately want to make a second update. That would be impossible if you didn't have the new rowversion.
This happens with all properties that are marked as identity or computed (by DatabaseGenertedOption).

Getting a result feedback from a stored procedure in Entity Framework

In a SQL Server 2008 I have a simple stored procedure moving a bunch of records to another table:
CREATE PROCEDURE [dbo].MyProc(#ParamRecDateTime [datetime])
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].Table2
SELECT
...,
...
FROM [dbo].Table1
WHERE RecDateTime <= #ParamRecDateTime
DELETE FROM [dbo].Table1
WHERE RecDateTime <= #ParamRecDateTime
END
Running it from within SQL Server Management Studio, I get the job done and return value = 0
DECLARE #return_value int
EXEC #return_value = dbo.MyProc #ParamRecDateTime = '2011-06-25 11:00:00.000'
SELECT 'Return Value' = #return_value
But when I call the same stored procedure from an app using Entity framework, I also get the job done but the return value is "-1":
int result = myrepository.MyProc(datetimePar);
MessageBox.Show(result.ToString());
I didn't manage to find an explanation for this error, but found this discouraging post, where it's said that there is no standard for this type of return codes in SQL Server.
What is the good, reliable way of getting know of a Stored Procedure execution result when calling it from Entity Framework and when the Stored Procedure doesn't return any entities?
One way to do it is to call ExecuteStoreCommand, and pass in a SqlParameter with a direction of Output:
var dtparm = new SqlParameter("#dtparm", DateTime.Now);
var retval = new SqlParameter("#retval", SqlDbType.Int);
retval.Direction = ParameterDirection.Output;
context.ExecuteStoreCommand("exec #retval = MyProc #dtparm", retval, dtparm);
int return_value = (int)retval.Value;
Originally I tried using a direction of ReturnValue:
retval.Direction = ParameterDirection.ReturnValue;
context.ExecuteStoreCommand("MyProc #dtparm", retval, dtparm);
but retval.Value would always be 0. I realized that retval was the result of executing the MyProc #dtparm statement, so I changed it to capture the return value of MyProc and return that as an output parameter.
using (dbContext db = new dbContext())
{
var parameters = new[]
{
new SqlParameter("#1","Input Para value"),
new SqlParameter("#2",SqlDbType.VarChar,4){ Value = "default if you want"},
new SqlParameter("#3",SqlDbType.Int){Value = 0},
new SqlParameter("#4","Input Para Value"),
new SqlParameter("#5",SqlDbType.VarChar,10 ) { Direction = ParameterDirection.Output },
new SqlParameter("#6",SqlDbType.VarChar,1000) { Direction = ParameterDirection.Output }
};
db.ExecuteStoreCommand("EXEC SP_Name #1,#2,#3,#4,#5 OUT,#6 OUT", parameters);
ArrayList ObjList = new ArrayList();
ObjList.Add(parameters[1].Value);
ObjList.Add(parameters[2].Value);
}
See OUTPUT attribute for SQL param of store procedure,
here
For future reference: I had the same issue but needed multiple OUTPUT variables. The solution was a combination of both answers. Below is a complete sample.
public void MyStoredProc(int inputValue, out decimal outputValue1, out decimal outputValue2)
{
var parameters = new[] {
new SqlParameter("#0", inputValue),
new SqlParameter("#1", SqlDbType.Decimal) { Direction = ParameterDirection.Output },
new SqlParameter("#2", SqlDbType.Decimal) { Direction = ParameterDirection.Output }
};
context.ExecuteStoreCommand("exec MyStoredProc #InParamName=#0, #OutParamName1=#1 output, #OutParamName2=#2 output", parameters);
outputValue1 = (decimal)parameters[1].Value;
outputValue2 = (decimal)parameters[2].Value;
}
Please note the Types used (decimal.) If another type is needed, remember to not only change it in the method argument list but also the SqlDbType.XXX.

How to save byte[] using a procedure?

This stored procedure does not save the data, it seems to be a problem with the VARBINARY. I am passing a byte[] to it, but then it doesn't work. If I send this parameter as NULL it works.
I'm calling the procedure with the following code:
public Community AddCommunity(string name, string description, byte[] picture, User owner, int? venue, int communityID)
{
using (var database = new Database())
{
return database.Scope.GetSqlQuery<Community>("QP_AddCommunity ?, ?, ?, ?, ?, ?", "VARCHAR Name, VARCHAR Description, VARBINARY Picture, INTEGER Owner, INTEGER Venue, INTEGER ID").GetResult(name, description, picture, owner.ID, venue, communityID);
}
}
The procedure is the following:
CREATE PROCEDURE [dbo].[QP_AddCommunity]
#Name VARCHAR(120),
#Description VARCHAR(MAX),
#Picture VARBINARY(MAX),
#Owner INTEGER,
#Venue INTEGER,
#ID INTEGER
AS
BEGIN
SET NOCOUNT ON;
IF(SELECT COUNT(*) FROM QT_Community WHERE ID = #ID) = 0
INSERT INTO QT_Community(Name, [Description], Picture, [Owner], Venue) VALUES(#Name, #Description, #Picture, #Owner, #Venue);
ELSE
UPDATE QT_Community SET Name = #Name, [Description] = #Description, Picture = #Picture, [Owner] = #Owner, Venue = #Venue WHERE ID = #ID;
SELECT * FROM QT_Community WHERE ID = ##IDENTITY;
END
What's wrong with this code? Isn't VARBINARY a byte[] ?
This code works when executing on SQL Server Management Studio.
DECLARE #X varbinary(20)
Set #X = CAST('Testing' As varbinary(20))
EXECUTE [QP_AddCommunity] 'aaaaa', 'descricao', #X, 216, NULL, 0;
But when calling from the GetSqlQuery method with something on the byte[] the transaction says it's not active and not dirty. BUT if the byte[] is null it works as it should.
i found that it is impossible as this answer shows
Hello gaurav, currently our
GetSqlQuery method cannot operate
properly with parameters of type
LongVarBinary or VarBinary, thus
making it impossible for the stored
procedure to work as expected. We are
aware of this problem and we are
working on fixing it. As a work around
you should try and use Linq to achieve
your goal. Greetings, Petar the
Telerik team
Accordingly to this table it seems either BLOB, BINARY, VARBINARY would be valid types for [] of primitive type.
You could try to ask on their forums, maybe someone will be able to help you.
Try using the .WRITE method. On your INSERT, insert 0x for Picture, then update independently.
UPDATE QT_Community
SET Picture.Write (#Picture, 0, DATALENGTH(Picture))
WHERE ID = #ID
Example (Ado.Net):
byte[] ba = UlongsToBytes(ul);
try
{
string source = #"packet size=4096;integrated security=SSPI;data source=MyPC\MyNamedInstance;persist security info=False;initial catalog=Sandbox";
SqlConnection conn = new SqlConnection(source);
conn.Open();
SqlCommand a = new SqlCommand("INSERT BigintsTarget(bi) SELECT * FROM dbo.ParseImageIntoBIGINTs(#BIGINTs)", conn);
a.CommandType = System.Data.CommandType.Text;
a.Parameters.Add(new SqlParameter("#BIGINTs", System.Data.SqlDbType.Image,2147483647));
for(int q=0; q<10; q++)
{
a.Parameters[0].Value = ba;
int res = a.ExecuteNonQuery();
}
d2 = DateTime.Now;
SqlCommand b = new SqlCommand("INSERT BigintsTarget1(bi) SELECT * FROM dbo.ParseVarcharMAXIntoBIGINTs(#BIGINTs)", conn);
b.CommandType = System.Data.CommandType.Text;
b.Parameters.Add(new SqlParameter("#BIGINTs", System.Data.SqlDbType.VarChar,2147483647));
for(int q=0; q<10; q++)
{
b.Parameters[0].Value = sss;
int res = b.ExecuteNonQuery();
}
//b.ExecuteNonQuery();
conn.Close();
}
catch(Exception ex)
{
string s = ex.Message;
int t=0;
t++;
}
}

Resources