Using dapper, why is a temp table created in one use of a connection not available in a second use of the same connection

c# dapper sql temp-tables

Question

I'm trying to perform a series of SQL*Server steps using dapper from C#. One step creates a temp table and populates it. Following steps query data from the temp table. The create/populate seems to run successfully, but the first query from the temp table fails saying:

"Invalid object name '#GetPageOfGlobalUsers'."

        using (SqlConnection connection = DBConnectionProvider.CreateConnection())
        {
            ... misc setup stuff...

            connection.Execute(@"
                create table #GetPageOfGlobalUsers(row int, EmailAddress nvarchar(max), LastName nvarchar(max), FirstName nvarchar(max), Id uniqueidentifier)
                insert into #GetPageOfGlobalUsers
                SELECT ROW_NUMBER() OVER (order by LastName, FirstName, EmailAddress) row,
                    EmailAddress, LastName, FirstName, Id 
                    FROM Users 
                    WHERE LastName like @search or FirstName like @search or EmailAddress like @search
            ", new { search = search }
            );

            int count = connection.Query<int>(@"
                SELECT count(*) from tempdb..#GetPageOfGlobalUsers
            ").Single<int>();

... more queries from the temp table follow

Above, the Execute works, but the Query fails with the error I mentioned above. (Note that I get the same error whether or not I use the "tempdb.." prefix.) If I create a permanent table instead (i.e. if I remove the leading hash) or if I make it a global temp table (i.e. prefix the name with two hashes) everything works fine.

My understanding is that temp tables named with a single hash are scoped by the duration of the connection, so I don't know what's going on. But I'm sure someone can tell me!

(BTW, I would appreciate it if no one tells me "don't do it this way" unless it simply can't be done.)

Accepted Answer

I don't understand exactly what's going on, but I am able to work around the problem by creating the temp table in an Execute of its own, as opposed to in an Execute that both creates the table and populates it, as in the code shown in my question.

That is, the following works:

            connection.Execute(@"
                create table #PagesOfUsers(row int, 
                                           EmailAddress nvarchar(max), 
                                           LastName nvarchar(max), 
                                           FirstName nvarchar(max), 
                                           Id uniqueidentifier)"
                );

            connection.Execute(@"
                insert into #PagesOfUsers
                SELECT ROW_NUMBER() OVER (order by LastName, FirstName, EmailAddress) row,
                    EmailAddress, LastName, FirstName, Id 
                    FROM Users 
                    WHERE LastName like @search or FirstName like @search or EmailAddress like @search
            ", new { search = search }
            );

            int count = connection.Query<int>(@"
                SELECT count(*) from #PagesOfUsers
            ").Single<int>();

This isn't horrible, but it is inconvenient. It's worth noting that I'd rather not have to explicitly create the temp table at all. Indeed, I'd originally coded the create/populate operation as a SELECT INTO so I didn't have to itemize the temp table's columns. But that also ran into the "invalid object" error on the subsequent query, so I tried the explicit CREATE TABLE to see if it made a difference and posted my question here after finding that it didn't.

The behavior I'm seeing is that when the temp table is created and populated in the same Execute, it really isn't in tempdb after the Execute ends, ostensibly successfully. That leaves me to wonder if the Execute in my original code was doing anything at all! For all I can tell, it amounted to a NOOP.


Expert Answer

The following works perfectly for me:

db.Open();
db.Execute(
    @"create table #foo (val int not null);
      insert #foo (val) values (123)");
db.Execute(
    @"insert #foo (val) values (456)");
var vals = db.Query<int>(
    @"select * from #foo").ToList();
foreach(var val in vals)
    Console.WriteLine(val);

It also works perfectly if I use:

@"select * from tempdb..#foo"

The only way I can cause it to stop working is, as per my previous answer, to not open the connection first.


Expert Answer

I suspect the connection is not open. If so, dapper will open and close (back to the pool) the connection as needed. This will reset the connection, losing any temporary tables between commands.

Just explicitly open the connection.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why