15

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

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Bob Wolfson
  • 281
  • 2
  • 9
  • Does it work on vanilla SqlConnection? – Marc Gravell Feb 12 '14 at 18:09
  • It is a vanilla SqlConnection. I'll edit the code above to show it. – Bob Wolfson Feb 12 '14 at 18:27
  • no, what I mean is: if you use vanilla ado.net (sqlcommand etc): does it work? If not, there is nothing dapper can do to make it work. If it does, then thay is curious and I'll happily investigate. – Marc Gravell Feb 12 '14 at 18:30
  • I am not asking dapper to do anything special. It's just a sequence of SQL steps. They work fine if executed w/i SQL Manager. – Bob Wolfson Feb 12 '14 at 18:32
  • And indeed iirc we do something similar in the test suite - I suspect I know what is "wrong" though... – Marc Gravell Feb 12 '14 at 18:59
  • I did some more looking: I still can't get it to not work. Do you have a runnable (short but complete) illustration of the issue? Also: is it even remotely possible that you are creating the temporary table in an inner-context? (i.e. a stored procedure or usage of `exec` in TSQL) Because inner-contexts do not propagate their temporary tables to outer-contexts (meaning: temporary tables created in a stored procedure or `exec` are deleted at the end of that operation) – Marc Gravell Feb 19 '14 at 09:01
  • ***créate table*** and ***insert into*** without `; or GO` ? – Kiquenet Jul 05 '19 at 09:18

3 Answers3

13

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.

Bob Wolfson
  • 281
  • 2
  • 9
  • 7
    I found this reason this works is related to the answer found here: https://stackoverflow.com/a/6632837/1371084 When you include parameters in the command, .net wraps your sql with an "exec sp_executesql" call, which makes your temp table accessible only within that context. If you don't include parameters, your sql is executed directly, so it is available for subsequent commands. – Brian Aug 29 '17 at 19:54
9

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • OK, I just tried this. I.e. I made the first statement w/i the "using" block be "connection.Open();". But no joy. I get the error "The connection was not closed. The connection's current state is open." – Bob Wolfson Feb 12 '14 at 19:16
  • @user I will check tomorrow then - sounds odd – Marc Gravell Feb 12 '14 at 19:46
  • 1
    It is odd. But I've found a clue. If I create the temp table in an Execute all its own, the rest works. SO won't let me answer my own question for a few more hours since my reputation is less than 10, but I'll post a full explanation of my findings as soon as SO lets me. – Bob Wolfson Feb 12 '14 at 21:14
3

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Can you insert in one sql statement as well. Presumably a bulk insert like this (separate) insert statements would take a long time ... – cs0815 Oct 20 '14 at 17:18
  • @csetzkorn that depends a bit on how many rows; TCP is supported for large queries. Additionally, there is ExecuteReader which can be used with SqlBulkCopy for cross-server pumps. If you use the async API and enable MARS, you can also use "pipeline" mode, whereby multiple inserts can be queued without waiting for a response per insert. – Marc Gravell Oct 21 '14 at 03:30
  • @MarcGravell. Just curious to know - Why does the first Execute statement that only creates the temp table persist the existence of the table whereas when we combine it with insert it does not persist it and gives an exception of invalid object name? – Oxygen Sep 19 '19 at 16:27
  • @Oxygen what makes you think it doesn't? can you show some code that does what you describe? – Marc Gravell Sep 19 '19 at 16:29
  • 1
    @Oxygen can I make a wild guess? your connection isn't open; if dapper finds a closed connection, it opens it *for the duration of a single operation*; which means that any transients will evaporate when dapper closes it; if you want to span multiple operations in a single session, you'll need to open and close the connection *yourself* – Marc Gravell Sep 19 '19 at 16:31
  • @MarcGravell. This doesn't work. In this case we have a open connection. string sql = @" SELECT EvId INTO #EvIds FROM Evevnts WHERE EvId> @Evid"; _dataContext.Connection.Execute(evGLQuerySql, new {Evid = Evid}); _dataContext.Connection.Query("SELECT EvId FROM #EvIds"); . ( This is not complete code so please consider just a flow. of the statements) – Oxygen Sep 19 '19 at 16:46
  • @Oxygen if you think it might be a dapper bug, I *really* need a full example that I can repro locally – Marc Gravell Sep 19 '19 at 17:05