我有一个 C# 应用程序,使用 ADO.Net 连接到 MSSQL
我需要创建表(具有动态的列数),然后插入许多记录,然后从表中执行选择操作。
每个步骤都必须是单独的 C# 调用,尽管我可以在持续时间内保持连接/事务打开。
问题是#Temp 表仅存在于连接和执行范围内。 当从 C# 对 SQL 的第一次调用完成时,控制权将传递到更高级别的范围。
这就像您有一个调用两个存储过程的 T-SQL 脚本一样。每个 SP 创建一个名为#MyTable 的表。第二个 SP 引用的表与第一个 SP 完全不同。
但是,如果父 T-SQL 代码创建了该表,则两个 SP 都可以看到该表,但彼此看不到对方。
这里的解决方案是使用##Temp 表。它们跨越范围和联系。 但危险在于,如果您使用硬编码名称,那么同时运行的程序的两个实例可能会看到同一个表。因此,动态地将表名称设置为始终唯一的名称。
SQL Server中有两种类型的临时表,本地临时表和全局临时表。来自 BOL:
在本地临时表名称前添加单个数字符号 (#tablename),并在全局临时表名称前添加两个数字符号 (##tablename)。
本地临时表将仅适用于您当前的连接。全局变量将可用于所有连接。因此,如果您在相关调用中重复使用(并且您确实说过可以)相同的连接,则可以只使用本地临时表,而不必担心同时进程会干扰彼此的临时表。 您可以从
BOL 文章获取更多信息,特别是在“临时表”部分的中间位置。
以下测试方法通过,证明#temp表内容在执行之间仍然存在。
这可能比使用双磅临时表更好,因为 ##temp 表是全局对象。如果您有多个客户端恰好使用相同的 ##temp 表名称,那么它们可能会互相踩踏。此外,##temp 表不会在服务器重新启动后继续存在,因此从技术上讲,它们的生命周期并不是永远的。恕我直言,最好控制#temp 表的范围,因为它们是有限的。
using System.Transactions;
using Dapper;
using Microsoft.Data.SqlClient;
using IsolationLevel = System.Data.IsolationLevel;
namespace TestTempAcrossConnection
{
[TestClass]
public class UnitTest1
{
private string _testDbConnectionString = @"Server=(localdb)\mssqllocaldb;Database=master;trusted_connection=true";
class TestTable1
{
public int Col1 { get; set; }
public string Col2 { get; set; }
}
[TestMethod]
public void TempTableBetweenExecutionsTest()
{
using var conn = new SqlConnection(_testDbConnectionString);
conn.Open();
var tran = conn.BeginTransaction(IsolationLevel.ReadCommitted);
conn.Execute("create table #test1(col1 int, col2 varchar(20))", transaction: tran);
conn.Execute("insert into #test1(col1,col2) values (1, 'one'),(2,'two')", transaction: tran);
var tableResult = conn.Query<TestTable1>("select col1, col2 from #test1", transaction: tran).ToList();
Assert.AreEqual(1, tableResult[0].Col1);
Assert.AreEqual("one", tableResult[0].Col2);
tran.Commit();
}
[TestMethod] public void TempTableBetweenExecutionsScopeTest()
{
using var scope = new TransactionScope();
using var conn = new SqlConnection(_testDbConnectionString);
conn.Open();
conn.Execute("create table #test1(col1 int, col2 varchar(20))");
conn.Execute("insert into #test1(col1,col2) values (1, 'one'),(2,'two')");
var tableResult = conn.Query<TestTable1>("select col1, col2 from #test1").ToList();
Assert.AreEqual(2, tableResult[1].Col1);
Assert.AreEqual("two", tableResult[1].Col2);
scope.Complete();
}
}
}
查询创建临时表(!),那么该表将在后续命令期间持续存在(只要连接保持打开状态)
cn.Execute(@"CREATE TABLE #TempTbl (ID int)"); //no parameters in the SQL
cn.Execute(@"INSERT INTO #TempTbl (ID) VALUES (@param1)"); //table is still there!
原因是参数化查询通过
sp_executesql
(又名
CommandType.StoredProcedure
)使用batchRpc模式,这缩小了范围。虽然非参数查询是通过普通的 CommendType.Text
直接执行的,这避免了嵌套范围。