EF4-所选的存储过程未返回任何列

71

我在一个调用一些链接服务器和动态SQL的存储过程中有一个查询。我知道EF不喜欢这样,所以我明确列出了所有将返回的列。然而,它仍然不喜欢。我在这里做错了什么?我只是希望EF能够检测从存储过程返回的列,以便我可以创建所需的类。

请查看以下代码,它组成了我的存储过程的最后几行:

SELECT
    #TempMain.ID,
    #TempMain.Class_Data,
    #TempMain.Web_Store_Class1,
    #TempMain.Web_Store_Class2,
    #TempMain.Web_Store_Status,
    #TempMain.Cur_1pc_Cat51_Price,
    #TempMain.Cur_1pc_Cat52_Price,
    #TempMain.Cur_1pc_Cat61_Price,
    #TempMain.Cur_1pc_Cat62_Price,
    #TempMain.Cur_1pc_Cat63_Price,
    #TempMain.Flat_Length,
    #TempMain.Flat_Width,
    #TempMain.Item_Height,
    #TempMain.Item_Weight,
    #TempMain.Um,
    #TempMain.Lead_Time_Code,
    #TempMain.Wp_Image_Nme,
    #TempMain.Wp_Mod_Dte,
    #TempMain.Catalog_Price_Chg_Dt,
    #TempMain.Description,
    #TempMain.Supersede_Ctl,
    #TempMain.Supersede_Pn,
    TempDesc.Cust_Desc,
    TempMfgr.Mfgr_Item_Nbr,
    TempMfgr.Mfgr_Name,
    TempMfgr.Vendor_ID
FROM
    #TempMain
        LEFT JOIN TempDesc ON #TempMain.ID = TempDesc.ID
        LEFT JOIN TempMfgr ON #TempMain.ID = TempMfgr.ID

16个回答

163

EF不支持导入通过以下方式构建结果集的存储过程:

  • 动态查询
  • 临时表

原因在于要导入存储过程,EF必须执行它。这样的操作可能很危险,因为它可能会触发数据库中的一些更改。因此,EF在执行存储过程之前使用特殊的SQL命令:

SET FMTONLY ON

执行这个存储过程命令,它将仅返回结果集中列的“元数据”,而不会执行其逻辑。但是因为逻辑没有被执行,所以没有临时表(或构建的动态查询),因此元数据为空。

你有两个选择(除了需要重新编写存储过程以不使用这些功能的选择):

  • 手动定义返回的复杂类型(我猜应该可以工作)
  • 使用一个技巧,在存储过程开头添加SET FMTONLY OFF。这将允许你的SP代码正常执行。只要确保你的SP不修改任何数据,因为这些修改将在导入期间执行!成功导入后,请删除这个技巧。

9
这适用于我的一个程序,但不适用于另一个程序。我最终修改了我的程序,返回一个与原来数据类型相同的空结果集,同时将该程序添加到设计器中,并在成功添加程序后恢复原来的操作。 - cjbarth
3
对我来说关键点是“EF必须执行它”,我的存储过程在使用空参数时出现了错误。 - RMalke
3
针对我的情况,实际上是将 SET FMTONLY OFF 设置为开启状态。 - Roger
1
"SET FMTONLY OFF" 在大多数情况下对我也起作用,但我遇到了一个问题,唯一解决问题的方法是消除不必要的临时表并使用 WITH 子句代替!希望能帮助到某些人。 - Gelásio
2
如果您以正确的方式创建它们,则支持临时表:declare @MyTemporaryTable TABLE( - Luis Gouveia
显示剩余5条评论

33

1
经过数个小时的努力,这终于解决了我的问题,非常感谢。 - Reza
确保在执行此操作后,您的函数可以使用所有NULL值实际执行。EF将尝试使用所有NULL值实际运行查询。进行跟踪;你会看到。我们不知道该函数在输入NULL时会引发整数溢出错误。可能从未意识到这是真正的答案。 - Bluebaron
如果这个解决方案不能解决你的问题,有可能是由于 EF 中的“获取列信息”向导在你的存储过程完成之前超时了 - 这也会产生“未返回列”的结果。 解决方案是为你的存储过程临时添加一个WHERE子句,例如只返回一个有限的数据集,从而暂时加快它的速度。这对我有用。别忘了在EF检索/保存列信息后删除WHERE子句。 - JimSTAT

15

或者您可以创建一个用户定义的表类型并返回该类型。

CREATE TYPE T1 AS TABLE 
( ID bigint NOT NULL
  ,Field1 varchar(max) COLLATE Latin1_General_CI_AI NOT NULL
  ,Field2 bit NOT NULL
  ,Field3 varchar(500) NOT NULL
  );
GO

然后在程序中:

DECLARE @tempTable dbo.T1

INSERT @tempTable (ID, Field1, Field2, Field3)
SELECT .....

....

SELECT * FROM @tempTable

现在,EF应该能够识别返回的列类型。


4

正如其他人指出的那样,请确保该过程实际运行。尤其是在我的情况下,我在SQL Server Management Studio中快乐地运行该过程,完全忘记了我以管理员权限登录。当我尝试使用我的应用程序主要用户运行该过程时,我发现查询中有一张表该用户无权访问。


2
有趣的副作用:我曾经遇到过同样的问题,最初是通过使用表变量而不是临时表(仅用于导入)来解决的。这对我来说并不特别直观,在最初观察我的两个SProcs时让我感到困惑:一个使用临时表,另一个使用表变量。
(SET FMTONLY OFF 对我从未起作用,因此我只是暂时更改了我的SProcs以获取列信息,而不必在EF方面烦恼地进行黑客攻击,仅供参考。)
我最好的选择实际上是手动创建复杂类型并将函数导入映射到它。非常有效,唯一的区别在于设计器中包含了一个额外的FactoryMethod来创建属性。

1

两种解决方案: 1- 手动定义返回的复杂类型(我猜应该可以工作) 2- 使用一个hack,只需在存储过程开头添加SET FMTONLY OFF。

然而,在某些存储过程中它对我不起作用,但在其他存储过程中它有效!

我的存储过程以这行代码结束:

SELECT machineId, production [AProduction]
        , (select production FROM #ShiftBFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [BProduction]
        , (select production FROM #ShiftCFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [CProduction]
     FROM #ShiftAFinalProd
     ORDER BY machineId

谢谢


1
除了@tmanthley所说的,一定要确保你的存储过程实际上能在SSMS中运行。我曾经导入了一些存储过程,忘记了一些相关的标量函数,这导致EF判断该过程没有返回列。看起来这是我早些时候应该发现的错误,但在这种情况下EF不会给出错误消息。

1
Entity Framework会执行你的存储过程,对于每个参数传递NULL,以获取列信息。
请确保存储过程在所有情况下都会返回结果。请注意,让Entity Framework使用默认值而不是NULL来执行存储过程可能更明智。
Entity Framework通过以下方式获取表的元数据:
SET FMTONLY ON
这将在各种情况下破坏你的存储过程,特别是如果它使用临时表。
因此,为了获得复杂类型的结果,请尝试添加:
SET FMTONLY OFF;
这对我有效 - 希望对您也有效。

引用自https://social.msdn.microsoft.com/Forums/en-US/e7f598a2-6827-4b27-a09d-aefe733b48e6/entity-model-add-function-import-stored-procedure-returns-no-columns?forum=adodotnetentityframework


1
我要补充的是:
如果存储过程具有参数并且对于默认参数值不返回结果集,则导入也会失败。
我的存储过程有2个浮点参数,当这两个参数都为0时不会返回任何内容。
因此,为了将此存储过程添加到实体模型中,我在存储过程中设置了这些参数的值,以确保无论实际参数是什么,它都能返回一些行。
然后,在将此存储过程添加到实体模型后,我撤消了更改。

也许你们中的一些人正在运行复杂的光标类型存储过程,需要超过15秒才能返回结果,而数据集表适配器向导在15秒内未收到任何行时会超时。一个简单的解决方法是针对存储过程中的复杂选择语句,并在其前缀中加上“top 1”,以便立即返回单行。使用该语句构建您的数据集适配器。然后将存储过程修改回完整的选择语句,这样就可以了。 - Rudy Hinojosa

0
在我的情况下,SET FMTONLY OFF 没有起作用。我采用的方法是,我备份了原始存储过程,并仅替换了列名,如下面的查询所示。
Select Convert(max,'') as Id,Convert(max,'') as Name

进行这个更改后,在实体框架中创建新的函数导入,复杂类型。 一旦创建了函数导入和复杂类型,请使用您原始的存储过程替换上面的查询。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接