ExecuteStoreQuery with TVP parameters - entity-framework-4

I have a stored procedure in my database that takes a table value parameter, a list of IdTable objects which contain a single integer Id column.
I have an entity model for the database and want to do the following...
ProjectEntities projectEntities = new ProjectEntities ();
DataTable stationIds = new DataTable();
stationIds.Columns.Add("Id");
stationIds.Rows.Add(1);
stationIds.Rows.Add(2);
SqlParameter parameter = new SqlParameter("#stationIds",stationIds);
parameter.TypeName = "IdTable";
var parameters = new object[] {parameter};
var results = projectEntities .ExecuteStoreQuery<ProjectSummary>("exec ProjectSummary", parameters);
var count = results.Count();
This runs and returns no results, when it should return a bunch of ProjectSummary entities.
When I profile this in SQL Profiler, I get the following
declare #p3 IdTable
insert into #p3 values(N'1')
insert into #p3 values(N'2')
exec sp_executesql N'exec ProjectSummary',N'#stationIds [IdTable] READONLY',#stationIds=#p3
If I declare the stored procedure to be
ALTER PROCEDURE [dbo].[ProjectSummary]
#stationIds [dbo].[IdTable] READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT * FROM #stationIds
...
Then I get not results back, it looks like the TVP parameter is coming through empty.
Where as if I manually execute
declare #p3 IdTable
insert into #p3 values(N'1')
insert into #p3 values(N'2')
EXEC [ProjectSummary]
#stationIds = #p3
GO
I get the values 1 and 2 returned from the SELECT query.
So, it looks like I want to use EXEC rather than SP_EXECUTESQL when I run ExecuteStoreCommand. Given the code example above, how on earth do I do that?

Turns out the ExecuteStoreQuery call was incorrect, it should be
SqlParameter stations = new SqlParameter { ParameterName = "p0", Value = ids, TypeName = "[dbo].[IdTable]", SqlDbType = SqlDbType.Structured };
var parameters = new object[] { stations };
var results = projectEntities.ExecuteStoreQuery<ProjectSummary>("exec ProjectSummary #p0", parameters);
So I needed to name parameter and add the #p0 to the exec command.

Related

Executing sql from a javascipt UDF

I have a snowflake table being used to store records of sql being executed by a stored procedure and any error messages. The records in this table are being saved as a string with special chars escaped with javascripts escape('log') function. I then want to create a view to this log table that will return the records in an easily readable format.
My first attempt at this was to create an additional stored procedure to query the log table, pass the record to the unescape() function, then return it. Calling this procedure works as intended but we can't then create a view of this data say with something like
create view log_view as
select (call UNESCAPE_PROC());
The other idea was to use a UDF rather than a stored procedure. However this also fails as we can't execute sql code with a javascript UDF. This post touches on this idea.
My question is how can I record these executed statements in a table in such a way as they can be later viewed in a plain text readable format. I've outlined my attempts below but perhaps my approach is flawed, open to any suggestions.
Minimal working example below
Sample log table and procedure to write a statement to said table
create or replace table event_table(
event varchar,
event_stamp timestamp
);
create or replace procedure insert_to_event(stamp string)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
COMMENT = 'SP to log an event message with timestamp to event_table'
EXECUTE AS CALLER
AS
$$
// some variables to log in our event table
var str_stamp = (new Date()).toISOString();
to_log = `insert into dummy_table values(2, 'Bill', `+STAMP+`);`;
sql =
`INSERT INTO event_table (
event,
event_stamp
)
VALUES
('`+escape(to_log)+`', to_timestamp('`+str_stamp+`'));`;
var stmnt = snowflake.createStatement({ sqlText: sql });
stmnt.execute();
return "logged: "+ escape(to_log)
$$;
call insert_to_event(current_timestamp());
select * from event_table;
Stored procedure to return readable log records
CREATE OR REPLACE PROCEDURE UNESCAPE_PROC()
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
COMMENT = 'SP will select a chosen column from event_table table, pass it to javascripts unescape() fn and return it'
EXECUTE AS CALLER
AS
$$
unescape_sql =
`select event from event_table`
var errs_res = [];
try {
all_logs = snowflake.execute(
{ sqlText: unescape_sql }
);
// iterate over all columns
while (all_logs.next()) {
errs_res.push(all_logs.getColumnValue(1));
}
return unescape(errs_res)
}
catch(err){
return "something went wrong: " + err
}
$$;
call UNESCAPE_PROC();
Which returns the records in a readable form as expected
However this of course wont work as part of a view eg.
-- define a view calling this procedure??
create view log_view as
select (call UNESCAPE_PROC());
Javascript user defined function can be used in a view like this, however it cannot be used to execute sql as in the stored procedures
-- use a UDF instead
CREATE OR REPLACE FUNCTION UNESCAPE_UDF()
RETURNS string
LANGUAGE JAVASCRIPT
AS
$$
unescape_sql =
`select event from event_table`
var errs_res = [];
try {
all_logs = snowflake.execute(
{ sqlText: unescape_sql }
);
// iterate over all columns
while (all_logs.next()) {
errs_res.push(all_logs.getColumnValue(1));
}
return unescape(errs_res)
}
catch(err){
return "something went wrong: " + err
}
$$
;
select UNESCAPE_UDF();
Stored procedures will solve one half of my problem for me, whilst UDF's will solve the other half. How can I combine the functionality of these two methods to solve this issue?
A much cleaner approach using parameters binding:
create or replace procedure insert_to_event(stamp string)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
COMMENT = 'SP to log an event message with timestamp to event_table'
EXECUTE AS CALLER
AS
$$
// some variables to log in our event table
var str_stamp = (new Date()).toISOString();
to_log = `insert into dummy_table values(2, 'Bill', '${str_stamp}');`;
sql = `INSERT INTO event_table (event,event_stamp)
VALUES(?, try_to_timestamp(?));`;
var stmnt = snowflake.createStatement({sqlText: sql, binds:[to_log, str_stamp]});
stmnt.execute();
return "logged: "+ to_log
$$;
Call:
call insert_to_event(current_timestamp());
-- logged: insert into dummy_table values(2, 'Bill', '2022-02-03T17:45:44.140Z');
select * from event_table;
Found a solution/workaround.
Rather than using javascripts escape/unescape functions to remove special chars from the logs, we use a regex replace eg.
create or replace procedure insert_to_event(stamp string)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
COMMENT = 'SP to log an event message with timestamp to event_table'
EXECUTE AS CALLER
AS
$$
// some variables to log in our event table
var str_stamp = (new Date()).toISOString();
to_log = `insert into dummy_table values(2, 'Bill', `+STAMP+`);`;
to_log = to_log.replace(/[`~!##$%^&*|+=?'"<>\{\}\[\]\\\/]/gi, '');
sql =
`INSERT INTO event_table (
event,
event_stamp
)
VALUES
('`+to_log+`', to_timestamp('`+str_stamp+`'));`;
var stmnt = snowflake.createStatement({ sqlText: sql });
stmnt.execute();
return "logged: "+ to_log
$$;
call insert_to_event(current_timestamp());
select * from event_table;
Which writes to the log table in an easily readable format with no need for additional stored procedures/UDF's.

'System.Data.Entity.Core.EntityCommandExecutionException' occurred in EntityFramework.SqlServer.dll but was not handled in user code

I did raw SQL query below to select only certain fields from a table.
{
List<CustEmpVM> CustomerVMlist = new List<CustEmpVM>();
var cid = db.Customers.SqlQuery("select SchedDate from Customer where CustID = '#id'").ToList<Customer>();
}
But i keep getting the error of:
System.Data.Entity.Core.EntityCommandExecutionException occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The data reader is incompatible with the specified ALFHomeMovers.Customer. A member of the type, CustID, does not have a corresponding column in the data reader with the same name.
The exception message is pretty straightforward: the query expected to return full entity of Customer table but only SchedDate column returned, hence EF cannot done mapping other omitted columns including CustID.
Assuming Customers is a DbSet<Customer>, try return all fields from Customer instead:
// don't forget to include SqlParameter
var cid = db.Customers.SqlQuery("SELECT * FROM Customer WHERE CustID = #id",
new SqlParameter("id", "[customer_id]")).ToList();
If you want just returning SchedDate column, materialize query results and use Select afterwards:
var cid = db.Customers.SqlQuery("SELECT * FROM Customer WHERE CustID = #id",
new SqlParameter("id", "[customer_id]"))
.AsEnumerable().Select(x => x.SchedDate).ToList();
NB: I think you can construct LINQ based from the SELECT query above:
var cid = (from c in db.Customers
where c.CustID == "[customer_id]"
select c.SchedDate).ToList();
Similar issue:
The data reader is incompatible with the specified Entity Framework
Use below query instead of raw query:
{
List<CustEmpVM> CustomerVMlist = new List<CustEmpVM>();
var cid = db.Customers.Where(w=>w.Id == YOURCUSTOMERID).Select(s=>new Customer{SchedDate = s.SchedDate }).ToList();
}
It will give compile time error rather than run time error.

Getting value from database query

I am using this code to get some data from my database in my controller
var NewAspNetId = from a in db.CVVersjon
where a.CVVersjonId.Equals(Id)
select a.CVVersjonId;
And the value I get back is
SELECT
[Extent1].[CVVersjonId] AS [CVVersjonId]
FROM
[dbo].[CVVersjon] AS [Extent1]
WHERE
[Extent1].[CVVersjonId] = #p__linq__0
What am I doing wrong?
That is not the value you get back. That is the debugger showing you the query.
In order to execute the query and materialize the result, you need to enumerate NewAspNetId:
foreach (var versionId in NewAspNetId)
{
// do something with versionId
}
Or, if you're sure that query returns [0..1] records:
var newId = NewAspNetId.FirstOrDefault();

passing array as parameters, stored procedures don't update result

I have an int[] and pass it as parameter to a procedure, when the method is called, no error is reported but nothing is updated in the database. There may be something wrong in my Add() since the Stored Procedure is written by another programmer. I cannot modify this stored procedure so everything must be solved in the Add().
FYI, the Add() takes an array of userIDs and a groupID as parameters, format them and run the stored procedure. The stored procedure inserts userIDs and groupID into the DB
For example:
if userIDs=[1,2,3] and groupID=4,
then I want the following data to be inserted into the DB
userID groupID
1 4
2 4
3 4
The stored procedure
USE [xyz]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spSaveSomething]
#groupID INT,
#userIDs TEXT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #handle INT
EXEC sp_xml_preparedocument #handle OUTPUT, #userIDs
DELETE FROM tbl1 WHERE i_GroupID = #groupID AND i_NetworkUserID IN (SELECT [ID] FROM OPENXML (#handle, 'ROOT/VAL', 1) WITH ([ID] INT))
INSERT INTO tbl1 (i_NetworkUserID, i_GroupID)
SELECT [ID], #groupID FROM OPENXML (#handle, 'ROOT/VAL', 1) WITH ([ID] INT)
EXEC sp_xml_removedocument #handle
END
The Add()
public void AddUsers(int[] UserIDs, int GroupID)
{
List<int> testList = new List<int>();
foreach (int id in UserIDs)
{
testList.Add(id);
}
XmlSerializer xs = new XmlSerializer(typeof(List<int>));
MemoryStream ms = new MemoryStream();
xs.Serialize(ms, testList);
string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray());
SqlParameter param1 = new SqlParameter("#userIDs", resultXML);
SqlParameter param2 = new SqlParameter("#groupID", GroupID);
context.Database.ExecuteSqlCommand("spSaveSomething #groupID, #userIDs",
param2, param1);
}
Convert the int[] into a string with Comma seprated and then in Stored Prodedure, us in ie
WHERE i_NetworkUserID in #UserIds
for me, your approach a little bit complicated.
I suggest to rewrite your procedure like
alter procedure [dbo].[spSaveSomething]
(
#groupID int,
#userIDs nvarchar(max)
)
as
begin
set nocount on
declare #tmp_Users table (ID int primary key)
insert into #tmp_Users
select T.C.value('.', 'int')
from #userIDs.nodes('List/ID') as T(C)
delete from tbl1
where
i_GroupID = #group_ID and
i_NetworkUserID in (select ID from #tmp_Users)
insert into tbl1 (i_NetworkUserID, i_GroupID)
select ID, #groupID
from #tmp_Users
-- actually it's strange - you deleting users and then inserting
-- may be you wanted to do this:
-- delete from tbl1
-- where
-- i_GroupID = #group_ID and
-- i_NetworkUserID not in (select ID from #tmp_Users)
-- insert into tbl1 (i_NetworkUserID, i_GroupID)
-- select U.ID, #groupID
-- from #tmp_Users as U
-- where
-- not exists (
-- select T.*
-- from tbl1 as T
-- where T.i_GroupID = #group_ID and T.i_NetworkUserID = U.ID
-- )
end
and your xml generation like
var userIDs = new List<int>() { 1, 2, 3 };
var userXML = new XElement("List", a.Select(x => new XElement("ID", x))).ToString();
// so userXML = "<List><ID>1</ID><ID>2</ID><ID>3</ID></List>"
hope that helps

I cannot get the output parameter when use function import by Entity Framework

Here's my SQL Server stored procedure :
ALTER PROCEDURE [dbo].[SearchUser]
(#Text NVARCHAR(100),
#TotalRows INT = 0 OUTPUT)
AS
BEGIN
SELECT #TotalRows=1000
SELECT * from Users
END
And my C# code
using (var context = new TestDBEntities())
{
var outputParameter = new ObjectParameter("TotalRows", typeof(Int32));
context.SearchUser("", outputParameter);
Response.Write(outputParameter.Value);
}
However outputParameter.Value always is null.
Could anybody tell me why?
Output parameters filled by its actual values during the execution of the stored procedure.
But table-valued stored procedure actually get executed only in moment when you're trying to iterate resulting recordset, but not calling a wrapper method.
So, this DOES'T work:
using (var context = new TestDBEntities())
{
var outputParameter = new ObjectParameter("TotalRows", typeof(Int32));
context.SearchUser("", outputParameter);
// Paremeter value is null, because the stored procedure haven't been executed
Response.Write(outputParameter.Value);
}
This DOES:
using (var context = new TestDBEntities())
{
var outputParameter = new ObjectParameter("TotalRows", typeof(Int32));
// Procedure does not executes here, we just receive a reference to the output parameter
var results = context.SearchUser("", outputParameter);
// Forcing procedure execution
results.ToList();
// Parameter has it's actual value
Response.Write(outputParameter.Value);
}
When you're working with stored procedures what don't return any recordset, they execute immediately after a method call, so you have actual value in output parameter.
We had a simular issue due to defered excecution our unit tests failed. In short if you have a stored proc that does NOT return anything you need to be sure to set the response type as 'None' when set as 'None' it will be excecuted when called and not defered.
In case you return anything (E.g. Scalar type of String results) it will excecute it when you use the result even if that .Count() or .ToList() is outside of the method that contains the function call.
So try not to force excecution if not need, when needed it should excecute but be sure to declare it correctly or it might not work.
I have same problem before. The main reason I think that the entities framework has the bug in case the user stored procedure has output parameter and return a result set. For example:
ALTER PROCEDURE [dbo].[SearchTest]
(
#RowTotal INT = 0 OUTPUT,
#RowCount INT = 0 OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
SELECT * FROM SomeThing
SELECT #RowTotal = 1233, #RowCount = 5343
END
However if you change the user stored procedure as following, you can get the output params
ALTER PROCEDURE [dbo].[SearchTest]
(
#RowTotal INT = 0 OUTPUT,
#RowCount INT = 0 OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
SELECT #RowTotal = 1233, #RowCount = 5343
END
You can workaround as following:
ALTER PROCEDURE [dbo].[SearchTest]
AS
BEGIN
DECLARE #RowTotal INT, #RowCount INT
SET NOCOUNT ON
SELECT #RowTotal = 1233, #RowCount = 5343
SELECT #RowTotal AS RowTotal, #RowCount AS RowCount, s.*
FROM SomeThing s
END
If anybody has better solution, please tell me

Resources