C# – How to get Linq to SQL to recognize the result set of a dynamic Stored Procedure


I'm using Linq-to-SQL with a SQL Server backend (of course) as an ORM for a project. I need to get the result set from a stored procedure that returns from a dynamically-created table. Here's what the proc looks like:

CREATE procedure [RetailAdmin].[TitleSearch] (
@isbn varchar(50), @author varchar(50),
 @title varchar(50))

declare @L_isbn varchar(50)
declare @l_author varchar(50)
declare @l_title varchar(50)
declare @sql nvarchar(4000)

set  @L_isbn = rtrim(ltrim(@isbn))
set @l_author = rtrim(ltrim(@author))
set @l_title = rtrim(ltrim(@title))

    [storeid] int not NULL,
    [Author] [varchar](100) NULL,
    [Title] [varchar](400) NULL,
    [ISBN] [varchar](50) NULL,
    [Imprint] [varchar](255) NULL,
    [Edition] [varchar](255) NULL,
    [Copyright] [varchar](100) NULL,
    [stockonhand] [int] NULL

set @sql  = 'select  a.storeid, Author,Title, thirteendigitisbn ISBN,  
Imprint,Edition,Copyright ,b.stockonhand from ods.items a join ods.inventory b on     
a.itemkey = b.itemkey where b.stockonhand <> 0  ' 

if len(@l_author) > 0
set @sql =  @sql + ' and author like ''%'+@L_author+'%'''

if len(@l_title) > 0
set @sql =  @sql + ' and title like ''%'+@l_title+'%'''

if len(@L_isbn) > 0
set @sql =  @sql + ' and thirteendigitisbn like ''%'+@L_isbn+'%'''

print @sql

if len(@l_author) <> 0 or len(@l_title) <>  0 or len(@L_isbn) <> 0 

    insert into #mytemp
    EXECUTE sp_executesql  @sql

select * from #mytemp
drop table #mytemp

I didn't write this procedure, but may be able to influence a change if there's a really serious problem.

My present problem is that when I add this procedure to my model, the designer generates this function:

public int TitleSearch([Parameter(DbType="VarChar(50)")] string isbn,  
  [Parameter(DbType="VarChar(50)")] string author, 
  [Parameter(DbType="VarChar(50)")] string title)
    IExecuteResult result = this.ExecuteMethodCall(this, 
        ((MethodInfo)(MethodInfo.GetCurrentMethod())), isbn, author, title);

    return ((int)(result.ReturnValue));

which doesn't look anything like the result set I get when I run the proc manually:

Result Set

Can anybody tell me what's going wrong here?

This is basically the same problem as this question but due to the poor phrasing from the OP it was never really answered.

Thanks Marc for your reply. I will see about making the changes you suggested.

The problem was the temp table. Linq to Sql just doesn't know what to do with them. This was particularly difficult to diagnose, because Visual Studio caches information about stored procs, so when it initially failed to find a result set it set the return as a default integer type and didn't update when I made changes to the stored proc. Getting VS to recognize a change requires you to:

  • Delete proc from the dbml
  • delete the server connection from Server Explorer
  • save the dbml to force a recompile
  • close the project and restart VS
  • recreate the server connection and import the proc

You might not have to do every one of those steps, but that's what worked for me. What you need to do, if you must use a temp table, is to create a barebones proc that simply returns the correct schema, and then alter it to do what you want after you've imported it into the OR Designer.

Best Solution

First - IMPORTANT - your SQL is vulnerable to injection; the inner command should be parameterized:

if len(@l_author) > 0
set @sql =  @sql + ' and author like ''%''+@author+''%'''

EXECUTE sp_executesql  @sql, N'@author varchar(100)', @L_author

This passes the value of @L_author in as the @author parameter in the dynamic command - preventing injection attacks.

Second - you don't really need the temp table. It isn't doing anything for you... you just INSERT and SELECT. Perhaps just EXEC and let the results flow to the caller naturally?

In other circumstances a table-variable would be more appropriate, but this doesn't work with INSERT/EXEC.

Are the columns the same for every call? If so, either write the dbml manually, or use a temp SP (just with "WHERE 1=0" or something) so that the SET FMT_ONLY ON can work.

If not (different columns per usage), then there isn't an easy answer. Perhaps use regular ADO.NET in this case (ExecuteReader/IDataReader - and perhaps even DataTable.Fill).

Of course, you could let LINQ take the strain... (C#):

if(!string.IsNullOrEmpty(author)) {
    query = query.Where(row => row.Author.Contains(author));