The problem lies in how connection pooling works. A local temp table (#) exists as long as the current spid has a reference to it, either the duration of a stored procedure if the temp table is created within that procedure, or for the duration of the connection if the temp table was created outside of a procedure. A global temp table (using ##) exists as long as any spid still has a reference to the object.
The local temp table can only be accessed by the client thread that created it, because a new connection will use a different thread context. A global temp table can be used until the old connection is disposed(), which can range from milli-seconds to minutes.
The solution is to create and return the temp table within a stored procedure, or use one of the schemes that lets you use a regular table as though it were a temp table.
No difference that I can see caused by using the stored procedure and accessing it via UDL. Just create the procedure that you need, do your "data dancing" (creating the temp table, populating it, manipulating it) within the procedure, and life is good.
There are many different ways to treat a permanent table as a temp table. The key trick is to allow different sets of data to safely reside within the table simultaneously. The easiest way to do that is to add a column to the table to differentiate between different sets of data. My preference for a set id is to use a GUID column. If you do this, you need to periodically "flush" the table. My preference for the flush is to create a startup procedure that simply empties the table (when the database starts up, nothing could have been using it), although some people prefer a scheduled job to do this.