EF6 fails to import stored procedure - c#

This is a simplified version of a stored procedure
ALTER PROCEDURE [dbo].[StoredProc1]
(
#PageIndex INT = 1,
#RecordCount INT = 20,
#Gender NVARCHAR(10) = NULL
)
AS
BEGIN
SET NOCOUNT ON ;
WITH tmp1 AS
(
SELECT u.UserId, MIN(cl.ResultField) AS BestResult
FROM [Users] u
INNER JOIN Table1 tbl1 ON tbl1.UserId = u.UserId
WHERE (#Gender IS NULL OR u.Gender = #Gender)
GROUP BY u.UserID
ORDER BY BestResult
OFFSET #PageIndex * #RecordCount ROWS
FETCH NEXT #RecordCount ROWS ONLY
)
SELECT t.UserId, t.BestResult, AVG(cl.ResultField) AS Average
INTO #TmpAverage
FROM tmp1 t
INNER JOIN Table1 tbl1 ON tbl1.UserId = t.UserId
GROUP BY t.UserID, t.BestResult
ORDER BY Average
SELECT u.UserId, u.Name, u.Gender, t.BestResult, t.Average
FROM #tmpAverage t
INNER JOIN Users u on u.UserId = t.UserId
DROP TABLE #TmpAverage
END
When I use EF6 to load the stored procedure, and then go to the "Edit Function Import" dialog, no columns are displayed there. Even after I ask to Retrieve the Columns, I get the message that the SP does not return columns. When I execute the SP from SMMS, I get the expected [UserId, Name, Gender, BestResult, Average] list of records.
Any idea how can I tweak the stored procedure or EF6 to make it work?
Thanks in advance

Thanks to the comments above, the answer is that unfortunately EF6 does not cope well with TMP tables on stored procedures.
One way around is the following:
1) Comment out all temp table calls inside the Stored Procedure.
2) Change the Stored Procedure to return a Fake a result with the same exact column's names that match the expected result
3) Import the Stored Procedure into EF6
4) Double Click on the Function Imports/Stored procedure name
5) On the Edit Function Import dialog, retrieve the Columns and the create a New Complex Type that will match the fake columns
6) CTRL+Save in order to generate all the C# code
7) Re-Update the Stored Procedure by removing the fake result set and un-comment the code with the Temp tables.
That should do the job.
P.S. Special thanks for the helpers that pointed me to the right place !!!

Sometimes it works to add the following statement to the stored proc:
set fmtonly off
But still, dont leave this statement in - only use it while generating the result set

Related

SQL Table Value Parameter only works when its data is copied to temporary table

I'm passing the data through c# to sql stored procedure using dapper.
c# (using dapper):
var dt = new DataTable("dbo.TVPtype");
dt.SetTypeName("dbo.TVPtype");
dt.Columns.Add("ID", typeof(int));
dt.Rows.Add(5);
_db.Execute("mysp", param: new { TVP = dt.AsTableValuedParameter("dbo.TVPtype"), commandType: CommandType.StoredProcedure);
The bellow SQL query will not work and I don't know Why! And it removes all data from mytable.
SQL stored procedure:
CREATE PROCEDURE [dbo].[mysp] #TVP dbo.TVPtype READONLY AS
DELETE FROM [dbo].[mytable] WHERE NOT EXISTS
(
SELECT NULL FROM #TVP na
WHERE ID = na.ID
)
To solve the problem I've used a temporary table in the stored procedure like bellow, and it works well.
My solution using temporary table:
CREATE table temptb (ID int)
insert into temptb select * from #TVP
Now I use temptb instead of #TVP in the delete query:
DELETE FROM [dbo].[mytable] WHERE NOT EXISTS
(
SELECT NULL FROM temptb na
WHERE ID = na.ID
)
It works well and delete specific data (not all data) !
So what's wrong with my first query?
I can't tell you, why it's working with temporary table, but the problem comes from the DELETE statement if you change it to:
DELETE t1
FROM [dbo].[mytable] AS t1 WHERE NOT EXISTS
(
SELECT NULL FROM #TVP na
WHERE t1.ID = na.ID
)
It will work. See, that when I add alias to mytable condition becomes clear which ID's it should compare. When in your example it might compare #TVP ID with it self(that's my guess).
I want to add two more points to your query:
It's better to select something that is not NULL when you check for existence, as NULL is special in SQL(although it is working in your example). For example SELECT 1 FROM #TVP... would be more readable
Why not to do it like this:
Snippet:
DELETE FROM [dbo].[mytable] AS t1 WHERE ID NOT IN
(
SELECT na.ID FROM #TVP na
)
Qualify your column names! You think you are writing:
DELETE FROM [dbo].[mytable]
WHERE NOT EXISTS (SELECT 1
FROM #TVP na
WHERE mytable.ID = na.ID
);
But you are not. The scoping rules of SQL interpret your query as:
DELETE FROM [dbo].[mytable]
WHERE NOT EXISTS (SELECT 1
FROM #TVP na
WHERE na.ID = na.ID
);
This is rather non-sensical. The WHERE is not correlated to the outer query. It will delete either all rows (if #TVP is empty or the ID is always NULL) or no rows (any other situation).
If you qualify all column references, you will never have this problem.

Batch delete operation procedure not working

I have a stored procedure which looks like following:
alter procedure [dbo].[zsp_deleteEndedItems]
(
#ItemIDList nvarchar(max)
)
as
delete from
SearchedUserItems
WHERE EXISTS (SELECT 1 FROM dbo.SplitStringProduction(#ItemIDList,',') S1 WHERE ItemID=S1.val)
The parameter IDList is passed like following:
124125125,125125125...etc etc
And the split string function look like following:
ALTER FUNCTION [dbo].[SplitStringProduction]
(
#string nvarchar(max),
#delimiter nvarchar(5)
) RETURNS #t TABLE
(
val nvarchar(500)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#string,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(500)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
This is supposed to delete all items from table "SearcheduserItems" under the IDs:
124125125 and 125125125
But for some reason after I do a select to check it out:
select * from SearchedUserItems
where itemid in('124125125','125125125')
The records are still there...
What am I doing wrong here? Can someone help me out?
As mentioned in the comments, a different option would be to use a table type parameter. This makes a couple of assumptions (some commented), however, should get you on the right path:
CREATE TYPE dbo.IDList AS TABLE (ItemID int NOT NULL); --Assumed int datatype;
GO
ALTER PROC dbo.zsp_deleteEndedItems #ItemIDList dbo.IDList READONLY AS
DELETE SUI
FROM dbo.SearchedUserItems SUI
JOIN #ItemIDList IDL ON SUI.ItemID = IDL.ItemID;
GO
--Example of usage
DECLARE #ItemList dbo.IDList;
INSERT INTO #ItemList
VALUES(123456),(123457),(123458);
EXEC dbo.zsp_deleteEndedItems #ItemList;
GO
In regards to the question of an inline table value function, one such example is the below, which I quickly wrote up, that provides a tally table of the next 1000 numbers:
CREATE FUNCTION dbo.NextThousand (#Start int)
RETURNS TABLE
AS RETURN
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 + #Start AS I
FROM N N1 --10
CROSS JOIN N N2 --100
CROSS JOIN N N3; --1,000
GO
The important thing about an iTVF is that it has only one statement, and that is the RETURN statement. Declaring the table as a return type variable, inserting data into it, and returning that variable turns it into a multi-line TVF; which perform far slower.

Entity Framework doesn't support stored procedures which build result sets from Dynamic queries or Temporary tables

I've written the following stored proc, which works fine. What I want to do with it though is use it an entity data model. However using it in the entity data model maps to a return type of integer, and a value of zero.
How do I get the SP to return the actual data instead of an integer using the DataContext ?
IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE TYPE = 'P' AND NAME = 'myProc') DROP PROCEDURE myProc;
GO
CREATE PROCEDURE [dbo].myProc
#START DateTime, #STOP DateTime
AS
BEGIN TRY
CREATE TABLE #Temp (download_Pk int);
INSERT INTO #Temp
SELECT download_pk FROM t1
UNION ALL
SELECT download_pk FROM t2;
WITH
x as
(
SELECT ID as Caps_Pk,
rootID as [Caps_RootId],
Case400Series as [Case],
SUBSTRING(c1, CHARINDEX('_', c1,1)+1, LEN(c1)) as [Customer],
run as Run,
SUBSTRING(c2, 1, CHARINDEX('_', c2, 1) -1) as [Sample],
SUBSTRING(c2, CHARINDEX('_', c2,1)+1, len(c2)) as [Amplification],
projectTitle,
DateAdded as [UploadTime],
UserId as [User]
FROM t3
WHERE DateAdded >= #START AND DateAdded <= #STOP AND
[User] in (SELECT name FROM ViewUsers WHERE Site = 'abc' AND Role = 'def')
)
SELECT *
FROM x
WHERE Caps_Pk NOT IN (Select download_Pk from #Temp)
DROP TABLE #Temp;
END TRY
BEGIN CATCH
DROP TABLE #Temp;
END CATCH
GO
Thanks in Advance.
Have you looked at this Code First Stored Procudure, this works great for me, it also has a NuGet Pckage that you can install.
found the answer here: EF4 - The selected stored procedure returns no columns
"EF doesn't support importing stored procedures which build result set from:
Dynamic queries
Temporary tables
The reason is that to import the procedure EF must execute it."

Merge tables(add, update and delete records from different sources)

I have three tables tb1,tb2 and tbTotal. They have the same schemas. The tables have three columns, MetricID, Descr and EntryDE.
What I want is to merge tb1 with tbTotal. I have done this and it works fines.
My stored procedure is:
CREATE PROCEDURE [dbo].[Admin_Fill]
-- Add the parameters for the stored procedure here
#MetricId INT,
#Descr VARCHAR(100),
#EntryDE VARCHAR(20)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET IDENTITY_INSERT dbo.tbTotal ON
-- Insert statements for procedure here
;WITH cte AS (SELECT MetricId=#MetricId,Descr=#Descr,EntryDE=#EntryDE)
MERGE tbTotal d
USING cte s
ON s.EntryDE = d.EntryDE
AND s.MetricId=d.MetricId
WHEN matched THEN UPDATE
set MetricId=s.MetricId,
Descr=s.Descr,
EntryDE=s.EntryDE
WHEN not matched BY TARGET THEN
INSERT(MetricId,Descr,EntryDE)
VALUES (s.MetricId,s.Descr,s.EntryDE);
END
My C# code:
foreach (DataRow row in dt.Rows) // pass datatable dt1
{
MetricId = Convert.ToInt32(row["MetricId"]);
Descr = row["Descr"].ToString();
EntryDE = row["EntryDE"].ToString();
parameters.Add("#MetricId", MetricId);
parameters.Add("#Descr", Descr);
parameters.Add("#EntryDE", EntryDE);
dbaccess.ExecuteNonQuery(strStoredProcedure, parameters); //cmd.ExecuteNonQuery();
parameters.Clear();
}
Also I want to remove all records in dt2 from dtTotal. I am not sure how to modify the stored procedure.
Thanks for help.
If i have understood what you are trying to do correctly, then this is potentially how I would prefer to implement the solution.
I would pass the 2 datatables as TABLE variables to an SP - similar to below and then use JOINs to both UPDATE and DELETE as required using SET operations - thus affecting multiple rows in one query and avoid looping through each row separately.
As mentioned by AdaTheDev in the related answer, you will end up creating a "TABLE" type but there is no drawback to having one extra type and this solution will scale a lot better than a looping approach would.
DISCLAIMER :- Code below may not be syntactically correct but i hope you get the picture of what I am proposing.
CREATE TYPE TableType AS TABLE
(
MetricId INT,
Descr VARCHAR(300) --or whatever length is appropriate,
EntryDE INT
);
GO
CREATE PROCEDURE [dbo].[Admin_Fill]
#RowsForUpdate TableType READONLY,
#RowsForDelete TableType READONLY
AS
BEGIN
-- Update all the Descriptions for all the rows
UPDATE
t
SET
t.Descr = u.Descr
FROM
tbTotal t
INNER JOIN #RowsForUpdate u
ON t.EntryDE = u.EntryDE AND t.MetricId = u.MetricId
-- Delete the rows to be deleted
DELETE t
FROM tbTotal t
INNER JOIN #RowsForDelete d
ON t.EntryDE = u.EntryDE AND t.MetricId = u.MetricId
END

Last record of orders for specific customer - SQL

i am trying to show the last order for the a specific customer on a grid view , what i did is showing all orders for the customer but i need the last order
here is my SQL code
SELECT orders.order_id, orders.order_date,
orders.payment_type, orders.cardnumber, packages.Package_name,
orders.package_id, packages.package_price
FROM orders INNER JOIN packages ON orders.package_id = packages.Package_ID
WHERE (orders.username = #username )
#username get its value from a cookie , now how can i choose the last order only for a cookie value " Tony " for example ?
To generalize (and fix a little bit) Mitch's answer, you need to use SELECT clause embellished with TOP(#N) and ORDER BY ... DESC. Note that I use TOP(#N), not TOP N, which means you can pass it as an argument to the stored procedure and return, say, not 1 but N last orders:
CREATE STORED PROCEDURE ...
#N int
...
SELECT TOP(#N) ...
ORDER BY ... DESC
SELECT top 1
orders.order_id,
orders.order_date,
orders.payment_type,
orders.cardnumber,
packages.Package_name,
orders.package_id,
packages.package_price
FROM orders
INNER JOIN packages ON orders.package_id = packages.Package_ID
WHERE (orders.username = #username )
ORDER BY orders.order_date DESC
In fact assuming orders.order_id is an Identity column:
SELECT top 1
orders.order_id,
orders.order_date,
orders.payment_type,
orders.cardnumber,
packages.Package_name,
orders.package_id,
packages.package_price
FROM orders
INNER JOIN packages ON orders.package_id = packages.Package_ID
WHERE (orders.username = #username )
ORDER BY orders.order_id DESC

Categories

Resources