38

I have a stored procedure that contains dynamic select. Something like this:

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

In SSMS the stored procedure runs fine and shows result set.

In C# using Entity Framework it shows returning an int instead of IEnumerable?

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;
}

Generated function for 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);
}
Rod
  • 14,529
  • 31
  • 118
  • 230
  • 1
    do you have a sample of the c# code that calls this stored procedure? – Claies Aug 15 '13 at 03:05
  • Yeah, post the code for `usp_GetTestRecords()` – Robert Harvey Aug 15 '13 at 03:13
  • possible duplicate of [Entity Framework Stored Procedures and POCO](http://stackoverflow.com/questions/9930408/entity-framework-stored-procedures-and-poco) – Hamish Smith Aug 15 '13 at 03:14
  • 2
    Why do you have your query stored in a variable then executed ? What am I missing here ? Why not just execute it straight away ? – Dimitar Dimitrov Aug 15 '13 at 03:42
  • It's more complicated with many joins, figured just show the gist. – Rod Aug 15 '13 at 03:47
  • we're still missing the code from the `usp_GetTestRecords()` function on the C# side. It's probably a generated item, if you used database first. – Claies Aug 15 '13 at 04:13
  • 1
    Probably should be using [SP_EXECUTESQL](http://blog.sqlauthority.com/2007/07/02/sql-server-2005-comparison-sp_executesql-vs-executeexec/). – Erik Philips Aug 15 '13 at 04:42
  • I think that the non-generic `ExecuteFunction` does not know what the result type is expected so cannot return an enumerable of the TestRecord. Try changing your call to `((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("usp_GetTestRecords", groupIdParameter);` and return `IEnumerable` from your method. If the code was generated for you by the designer it appears to me that since you are concatenating SQL (btw. this is suspectible to sql injection attack and seems to be not necessary) the DDEX provider could not figure out what the sproc really returns – Pawel Aug 15 '13 at 04:47
  • public virtual int usp_GetTestRecords(string groupId) // returns int – phil soady Aug 15 '13 at 05:17
  • Six years later and this is still a problem!! I have a no dynamic SQL SelectAll with no parameters but several joins. There are other sprocs that are not registering as int. What a joke EF is. Makes it easier? #FakeNews – Joe Johnston Oct 24 '19 at 16:21

17 Answers17

32

I get this when I have a stored procedure that includes an "exec" call into a temporary table, such as:

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

It appears that Entity Framework gets confused as to what should be returned by the procedure. The solution I've come across is to add this at the top of the sproc:

SET FMTONLY OFF

After this, all is well.

Yelnic
  • 541
  • 2
  • 6
  • 6
31

I got the same problem, and found solution here

  1. Move to your .edmx
  2. At Model Browser Window/Function Imports find your procedure then double click it
  3. Change the return type to you want
  4. Save .edmx and check the return type again.

Screenshot

It should be what you need now.

Koopakiller
  • 2,838
  • 3
  • 32
  • 47
Circle Hsiao
  • 1,497
  • 4
  • 22
  • 37
16

Entity Framework can't tell what your stored procedure is returning. I've had success creating a table variable that mirrors the data from your SELECT statement. Just insert into the table variable then do a select from that table variable. EF should pick it up.

Rob
  • 5,578
  • 3
  • 23
  • 28
  • 1
    This is really the way to go. You can even do it just temporarily, but beware that you could be in a bind if one were to regenerate the EF model. EF doesn't pick up dynamic SQL so you need a temp table or table variable for it to read from to generate the complex type. – wilsjd Aug 15 '13 at 18:56
  • 2
    Could you please explain this part a little clearer: "Just insert into the table variable then do a select from that table variable. EF should pick it up." ? I'm not sure what to do on that. – Termato Jul 14 '14 at 18:42
  • This didn't work, it even caused problems for the actual solution below, on the model you can double click functions and set the return type. If you use temp tables that doesn't work. – John Mar 03 '17 at 08:04
  • 1
    EF is a pain in the backside – djack109 Aug 01 '17 at 22:21
  • Here is a step by step solution of this issue which worked for me http://www.sandeepknarware.in/?p=247 – Sandeep Kumar Narware Dec 30 '17 at 12:30
11

See Ladislav Mrnka's answer in this Stack Overflow post https://stackoverflow.com/a/7131344/4318324

I had the same basic problem.

Adding

SET FMTONLY OFF

To a procedure you are trying to import during the import will address this problem. It's a good practice to remove the line afterwards unless the purpose of the database is solely to provide schema for EF (Entity Framework).

The main reason for caution is that EF uses this setting to prevent data mutations when trying to obtain metadata.

If you refresh your entity model from a database any procedures with this line in them can potentially update the data in that database just by trying to obtain the schema.

I wanted to add a further note on this so it's not needed to fully scan through the other link.

if you want to try to use FMTONLY here are a couple things to keep in mind.

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

Example

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

The above returns NO rows whatsoever and the metadata for each of the three queries

For this reason some people suggest toggling it off in a way that takes advantage of it's non-observance of flow control

if 1=0
begin
    set fmtonly off
end

In fact you could use this to introduce logic that tracks this

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

Think VERY CAREFULLY before trying to use this feature as it is both deprecated and potentially makes sql extremely hard to follow

the MAIN concepts that need to be understood are the following

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.
wode
  • 236
  • 3
  • 8
  • Also. it looks like FMTONLY is being deprecated https://learn.microsoft.com/en-us/sql/t-sql/statements/set-fmtonly-transact-sql so we may have to sit back and see how they change Entity Framework – wode Apr 06 '17 at 20:35
7

Entity Framework will automatically return a scalar value if your stored procedure doesn't have a primary key in your result set. Thus, you'd have to include a primary key column in your select statement, or create a temp table with a primary key in order for Entity Framework to return a result set for your stored procedure.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
hoangvu
  • 71
  • 1
  • 2
2

I had the same problem, I changed the name of return fields by 'AS' keyword and addressed my problem. One reason for this problem is naming column names with SQL Server reserved keywords.

The example is fallows:

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
2

Best solution I found is to cheat a little.

In the store procedure, comment everything, put a first line with a select [foo]='', [bar]='' etc... Now update the model, go to the mapped function, select complex type and click on Get Column Information and then Create Complex Type.

Now comment the fake select and un-comment the real store procedure body.

Max Favilli
  • 6,161
  • 3
  • 42
  • 62
1

When you generated your model class for your stored procedure, you chose scalar return result by mistake. you should remove your stored procedure from your entity model, then re-add the stored procedure. In the dialog for the stored procedure, you can choose the return type you are expecting. Do not just edit the generated code.. this may work now, but the generated code can be replaced if you make other changes to your model.

Claies
  • 22,124
  • 4
  • 53
  • 77
  • 2
    I generated the Model from the Database. It automatically gave it the int as a return type – Rod Aug 15 '13 at 15:29
  • 2
    Even if he chose a complex return type, EF wouldn't pick it up because EF doesn't recognize dynamic SQL. – wilsjd Aug 15 '13 at 18:58
1

I have pondered this a bit and I think I have a better/simpler answer

If you have a complex stored that gives entity framework some difficultly (for current versions of Entity Framework that are using the FMTONLY tag to aquire schema)

consider doing the folowing at the beginning of your stored procedure.

--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

if you are okay loading your result set into a table variable you can do the following to help keep your schema in sync

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

if 1=0
begin
    select * from @tablevar
end

...
-- load data into @tablevar
select * from @tablevar
wode
  • 236
  • 3
  • 8
  • the thing to look out for here is that if you change the schema your procedure returns, you will need to update the beginning piece to get EF to pick up those changes. – wode Aug 14 '17 at 20:04
  • this works since EF ignores flow control logic in it's search for the first returned dataset schema to build it's model with. – wode Aug 14 '17 at 20:05
  • added an option to address schema updates. note: "go" 's will cause problems for table variables and the table variable will work where a regular temp table will not (at least for my version SQL Server 2014 12.0.5000.0 anyone have confirmation for other versions?) – wode Aug 14 '17 at 20:22
1

If you need to do this, then you might be better off just making a partial of the dbcontext and creating the C# function yourself that will use SqlQuery to return the data you need. Advantages over some of the other options is:

  1. Don't have to change anything when the model updates
  2. Won't get overwritten if you do it directly in the generated class (someone above mention this as if it's an option :) )
  3. Don't have to add anything to the proc itself that could have side effects now or later on

Example Code:

 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();
       }
    }
user441521
  • 6,942
  • 23
  • 88
  • 160
0

Just change to

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
Nezam
  • 4,122
  • 3
  • 32
  • 49
0

I know this is an old thread but in case someone has the same problems I'll tell my woes.

As a help to find the issue, run sql profiler when you add your stored proc. Then you can see what entity framework is passing as parameters to generate your resultset. I imagine nearly always it will pass null parameter values. If you are generating sql on the fly by concatenating string values and parameter values and some are null then the sql will break and you wont get a return set.

I haven't needed to generate temp tables or anything just an exec command.

Hope it helps

Rob White
  • 950
  • 1
  • 6
  • 16
0

During import

SET FMTONLY ON can be used for taking the sp schema.

If you change the sp and want to update the new one, you should delete the old defined function from edmx file (from xml), because although deleting sp from model browser, it is not deleted in edmx. For example;

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

I had the same problem, and when I delete the FuctionImport tag of corresponding sp totally, the model updated right. You can find the tag by searching the function name from visual studio.

Betul Bas
  • 1
  • 1
0

You may have luck opening up the model browser, then going to Function Imports, double clicking the stored procedure in question and then manually clicking "Get Column Information" and then clicking "Create New Complex Type". This usually sorts out the problem.

Captain Kenpachi
  • 6,960
  • 7
  • 47
  • 68
0

Well I had this issue as well but after hours of online searching none of above methods helped. Finally I got to know that It will happen if your store procedure is getting some parameters as null and which generate any error in query execution. Entity Framework will generate method for store procedure by defining the complex entity model. Due to that null value your store procedure will return and int value.

Please check your store procedure either its providing empty result set with null values. It will fix your problem. Hopefully.

0

I think this is a problem of permissions on the database, I don't know what exactly could be, but, in my job we use Active Directory users to grant applications connect to databases, this accounts are specially created for the applications, each app has its own user account, well, as a developers I have permissions for read, write and other basic things, no alter, and no advanced features, and I have this same problem running Visual Studio with my normal account, then, what I did was to open Visual Studio selecting the option "as a different user" on the context menu, and I put the AD login granted for the application and voila!, now my Stored Procedures are loading with all the fields I was expected, before that, my Stored Procedures was returning as int. I hope this help someone, maybe the VIEW DEFINITION permissions on database account do the trick

0

If SQL Authentication is in place, verify that the user credential that is being used to connect Entity Framework to the database has the proper rights to read from CUSTOMERS table.

When Entity Framework uses SQL Authentication to map complex objects (i.e stored procedures that SELECTs more than one column), if any of the tables from within such stored procedure don't have set up the Read permission, the mapping will result in returning INT instead of the desired Result set.

Joseph L.
  • 431
  • 6
  • 7