存储过程返回 int 而不是结果集

Stored procedure returns int instead of result set

提问人:Rod 提问时间:8/15/2013 最后编辑:Rod 更新时间:5/14/2020 访问量:58260

问:

我有一个包含动态选择的存储过程。像这样的东西:

ALTER PROCEDURE [dbo].[usp_GetTestRecords] 
    --@p1 int = 0, 
    --@p2 int = 0
    @groupId nvarchar(10) = 0
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @query  NVARCHAR(max)

    SET @query = 'SELECT * FROM CUSTOMERS WHERE Id = ' + @groupId
    /* This actually contains a dynamic pivot select statement */

    EXECUTE(@query);
END

在 SSMS 中,存储过程运行良好并显示结果集。

在使用实体框架的 C# 中,它显示返回一个而不是?intIEnumerable

private void LoadTestRecords()
{
    TestRecordsDBEntities dataContext = new TestRecordsDBEntities();
    string id = ddlGroupId.SelectedValue;

    List<TestRecord> list = dataContext.usp_GetTestRecords(id); //This part doesn't work returns int
    GridView1.DataSource = list;
}

生成的函数usp_GetTestRecords

public virtual int usp_GetTestRecords(string groupId)
{
    var groupIdParameter = groupId != null ?
        new ObjectParameter("groupId", groupId) :
        new ObjectParameter("groupId", typeof(string));

    return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("usp_GetTestRecords", groupIdParameter);
}
C# SQL 实体框架

评论

1赞 Claies 8/15/2013
是否有调用此存储过程的 C# 代码示例?
0赞 Robert Harvey 8/15/2013
是的,发布代码usp_GetTestRecords()
0赞 Hamish Smith 8/15/2013
实体框架存储过程和 POCO 的可能副本
2赞 Dimitar Dimitrov 8/15/2013
为什么将查询存储在变量中然后执行?我在这里错过了什么?为什么不直接执行呢?
1赞 Erik Philips 8/15/2013
可能应该使用SP_EXECUTESQL

答:

1赞 Claies 8/15/2013 #1

在为存储过程生成模型类时,错误地选择了标量返回结果。应从实体模型中删除存储过程,然后重新添加存储过程。在存储过程的对话框中,可以选择所需的返回类型。不要只是编辑生成的代码。这现在可能有效,但如果对模型进行其他更改,则可以替换生成的代码。

评论

2赞 Rod 8/15/2013
我从数据库中生成了模型。它自动给它 int 作为返回类型
2赞 wilsjd 8/16/2013
即使他选择了复杂的返回类型,EF 也不会选择它,因为 EF 无法识别动态 SQL。
17赞 Rob 8/15/2013 #2

实体框架无法判断存储过程返回的内容。我成功地创建了一个表变量,该变量反映了 SELECT 语句中的数据。只需插入到表变量中,然后从该表变量中进行选择即可。EF 应该捡起它。

评论

1赞 wilsjd 8/16/2013
这确实是要走的路。您甚至可以暂时执行此操作,但请注意,如果要重新生成 EF 模型,您可能会陷入困境。EF 不会选取动态 SQL,因此需要一个临时表或表变量,以便从中读取以生成复杂类型。
2赞 Termato 7/15/2014
您能否更清楚地解释这部分:“只需插入表变量,然后从该表变量中进行选择。EF应该捡起来。我不确定该怎么做。
0赞 John 3/3/2017
这不起作用,它甚至给下面的实际解决方案带来了问题,在模型上您可以双击函数并设置返回类型。如果您使用临时表,则不起作用。
1赞 djack109 8/2/2017
EF是背部的疼痛
0赞 Sandeep Kumar Narware 12/30/2017
这是对我有用的此问题的分步解决方案 sandeepknarware.in/?p=247
33赞 Yelnic 10/21/2014 #3

当我有一个包含对临时表的“exec”调用的存储过程时,我会得到这个,例如:

insert into #codes (Code, ActionCodes, Description)
exec TreatmentCodes_sps 0

实体框架似乎对过程应返回的内容感到困惑。我遇到的解决方案是在 sproc 的顶部添加它:

SET FMTONLY OFF

在此之后,一切都很好。

0赞 Nezam 5/16/2015 #4

只需更改为

ALTER PROCEDURE [dbo].[usp_GetTestRecords] 
    --@p1 int = 0, 
    --@p2 int = 0
    @groupId nvarchar(10) = 0
AS
BEGIN
    SET NOCOUNT ON;


    SELECT * FROM CUSTOMERS WHERE Id =  @groupId

END
34赞 Circle Hsiao 12/8/2015 #5

我遇到了同样的问题,并在这里找到了解决方案

  1. 移至 .edmx
  2. 在“模型浏览器窗口/函数导入”中,找到您的过程,然后双击它
  3. 将返回类型更改为所需的返回类型
  4. 保存 .edmx 并再次检查返回类型。

Screenshot

它应该是你现在需要的。

评论

1赞 Ami Schreiber 10/20/2016
这解决了我的问题。谢谢。
1赞 djack109 8/2/2017
真是个彻头彻尾的傻瓜。每次我的模型更新时,我都必须这样做。上帝首先帮助任何工作代码的人。这每天都要花费我几个小时
0赞 Rob White 12/9/2015 #6

我知道这是一个古老的线程,但如果有人遇到同样的问题,我会告诉我的困境。

为了帮助查找问题,请在添加存储的过程时运行 sql profiler。然后,您可以看到哪个实体框架作为参数传递以生成结果集。我想它几乎总是会传递 null 参数值。如果您通过连接字符串值和参数值来动态生成 sql,并且有些值为 null,则 sql 将中断,您将不会获得返回集。

我不需要生成临时表或任何东西,只需要一个 exec 命令。

希望对您有所帮助

2赞 Armen Hovsepian 6/21/2016 #7

我遇到了同样的问题,我通过“AS”关键字更改了返回字段的名称并解决了我的问题。此问题的一个原因是使用 SQL Server 保留关键字命名列名。

这个例子是休耕:

ALTER PROCEDURE [dbo].[usp_GetProducts]

AS
BEGIN

 SET NOCOUNT ON;

 SELECT 
      , p.Id
      , p.Title
      , p.Description AS 'Description'

    FROM dbo.Products AS p
END
7赞 hoangvu 10/11/2016 #8

如果存储过程在结果集中没有主键,则实体框架将自动返回标量值。因此,必须在 select 语句中包含主键列,或者创建具有主键的临时表,以便 Entity Framework 返回存储过程的结果集。

11赞 wode 4/7/2017 #9

请参阅 Ladislav Mrnka 在此 Stack Overflow 帖子中的回答 https://stackoverflow.com/a/7131344/4318324

我有同样的基本问题。

添加

SET FMTONLY OFF

在导入过程中尝试导入的过程将解决此问题。 最好在之后删除该行,除非数据库的目的只是为 EF(实体框架)提供架构。

谨慎的主要原因是 EF 在尝试获取元数据时使用此设置来防止数据突变。

如果从数据库刷新实体模型,则任何包含此行的过程都可能通过尝试获取架构来更新该数据库中的数据。

我想对此添加进一步的注释,因此不需要完全扫描另一个链接。

如果您想尝试使用 FMTONLY,请记住以下几点。

when FMTONLY is on:
  1) only the schema is returned (no) rows.
     similar to adding a blanket false statement to your where clause (ie "where 1=0")
  2) flow control statements are ignored

set fmtonly on

if 1=1
begin
    select 1 a
end
else
begin
    select 1 a,2 b
end

while 1=1
select 1 c

上面返回 NO 行和三个查询中每个查询的元数据

出于这个原因,有些人建议以一种利用它不遵守流量控制的方式将其关闭

if 1=0
begin
    set fmtonly off
end

事实上,你可以用它来引入跟踪这一点的逻辑

set fmtonly off
declare @g varchar(30)
set @g = 'fmtonly was set to off'

if 1=0
begin
    set fmtonly off
    set @g = 'fmtonly was set to on' 
end

select @g

在尝试使用此功能之前,请仔细考虑,因为它已被弃用,并且可能使 sql 极难遵循

需要理解的主要概念如下

1. EF turns FMTONLY on to prevent MUTATING data from executing stored procedures
   when it executes them during a model update.
   (from which it follows)

2. setting FMTONLY off in any procedure that EF will attempt to do a schema scan
   (potentially ANY and EACHONE) introduces the potential to mutate database
   data whenever *anyone* attempts to update their database model.

评论

0赞 wode 4/7/2017
也。看起来 FMTONLY learn.microsoft.com/en-us/sql/t-sql/statements/ 被弃用了...... 所以我们可能不得不坐下来看看他们如何改变实体框架
1赞 wode 8/15/2017 #10

我对此进行了一些思考,我认为我有一个更好/更简单的答案

如果你有一个复杂的存储,这给实体框架带来了一些困难(对于使用 FMTONLY 标记来获取架构的当前版本的实体框架)

请考虑在存储过程开始时执行以下操作。

--where [columnlist] matches the schema you want EF to pick up for your stored procedure

if 1=0
begin
    select 
       [columnlist]
    from [table list and joins]
    where 1=0
end

如果可以,可以将结果集加载到表变量中 您可以执行以下操作来帮助使架构保持同步

declare @tablevar as table
(
    blah int
    ,moreblah varchar(20)
)

if 1=0
begin
    select * from @tablevar
end

...
-- load data into @tablevar
select * from @tablevar

评论

0赞 wode 8/15/2017
这里需要注意的是,如果更改过程返回的架构,则需要更新开头部分,以使 EF 获取这些更改。
0赞 wode 8/15/2017
这之所以有效,是因为 EF 在搜索第一个返回的数据集架构时会忽略流控制逻辑来构建其模型。
0赞 wode 8/15/2017
添加了一个选项以解决架构更新问题。注意:“go”会导致表变量出现问题,并且表变量将在常规临时表无法工作的地方工作(至少对于我的版本SQL Server 2014 12.0.5000.0有人确认其他版本?
1赞 user441521 1/9/2018 #11

如果需要执行此操作,则最好只创建 dbcontext 的一部分,然后自己创建 C# 函数,该函数将使用 SqlQuery 返回所需的数据。与其他一些选项相比,优点是:

  1. 模型更新时无需更改任何内容
  2. 如果您直接在生成的类中执行此操作,则不会被覆盖(上面有人提到这一点,就好像这是一个选项:))
  3. 不必向过程本身添加任何现在或以后可能产生副作用的内容

示例代码:

 public partial class myEntities
    {
       public List<MyClass> usp_GetTestRecords(int _p1, int _p2, string _groupId)
       {
          // fill out params
          SqlParameter p1 = new SqlParameter("@p1", _p1);
          ...
          obj[] parameters = new object[] { p1, p2, groupId };

          // call the proc
          return this.Database.SqlQuery<MyClass>(@"EXECUTE usp_GetTestRecords @p1, @p2, @groupId", parameters).ToList();
       }
    }
0赞 Betul Bas 1/22/2018 #12

导入期间

将 FMTONLY 设置为 ON 可用于采用 SP 架构。

如果更改 sp 并想要更新新函数,则应从 edmx 文件(从 xml)中删除旧的定义函数,因为尽管从模型浏览器中删除了 sp,但在 edmx 中不会删除它。例如;

    <FunctionImport Name="GetInvoiceByNumber"     ReturnType="Collection(Model.Invoice_Result)">
       <Parameter Name="InvoiceNumber" Mode="In" Type="Int32" />
    </FunctionImport>

我遇到了同样的问题,当我完全删除相应 sp 的 FuctionImport 标签时,模型更新正确。可以通过在 Visual Studio 中搜索函数名称来查找标记。

0赞 Captain Kenpachi 8/3/2018 #13

您可能很幸运地打开模型浏览器,然后转到“函数导入”,双击有问题的存储过程,然后手动单击“获取列信息”,然后单击“创建新的复杂类型”。这通常可以解决问题。

0赞 Murxiii Javed 8/29/2018 #14

好吧,我也遇到了这个问题,但是经过数小时的在线搜索,上述方法都无济于事。 最后,我知道如果您的存储过程将某些参数获取为 null 并在查询执行中生成任何错误,就会发生这种情况。 实体框架将通过定义复杂的实体模型来生成存储过程的方法。由于该 null 值,存储过程将返回 int 值。

请检查您的存储过程,它是否提供带有 null 值的空结果集。它将解决您的问题。希望。

2赞 Max Favilli 11/28/2018 #15

我发现的最好的解决方案是作弊一点。

在存储过程中,注释所有内容,将第一行与等... 现在更新模型,转到映射函数,选择复杂类型,然后单击 ,然后单击 。select [foo]='', [bar]=''Get Column InformationCreate Complex Type

现在对假选择进行注释,并取消对真实存储过程正文进行注释。

0赞 Carl0s_ 10/8/2019 #16

我认为这是数据库权限的问题,我不知道到底是什么,但是,在我的工作中,我们使用 Active Directory 用户授予应用程序连接到数据库的权限,这些帐户是专门为应用程序创建的,每个应用程序都有自己的用户帐户,好吧,作为开发人员,我有读取权限, 编写和其他基本的东西,没有更改,也没有高级功能,我用我的普通帐户运行 Visual Studio 时遇到了同样的问题,然后,我所做的是打开 Visual Studio,在上下文菜单上选择“作为其他用户”选项,然后我把授予应用程序的 AD 登录名,瞧! 现在我的存储过程正在加载我预期的所有字段,在此之前,我的存储过程以 int 形式返回。我希望这对某人有所帮助,也许数据库帐户的VIEW DEFINITION权限可以解决问题

0赞 Joseph L. 5/14/2020 #17

如果 SQL 身份验证已到位,请验证用于将 Entity Framework 连接到数据库的用户凭据是否具有从 CUSTOMERS 表中读取的适当权限。

当实体框架使用 SQL 身份验证映射复杂对象(即 SELECT 多列的存储过程)时,如果此类存储过程中的任何表未设置“读取”权限,则映射将导致返回 INT 而不是所需的结果集。