首页 > 解决方案 > 将整数作为 varbinary 发送到 SQL 触发器

问题描述

我使用此代码在 SQL 触发器中设置 context_info

string strUser = _app.Settings.User;
string strInt = "";

// strInt += '\x0100'; => becomes 1 in trigger
strInt += '\x0000';
strInt += '\x0200'; //=> becomes two in the trigger
strInt += '\u0000';
strInt += '\u0300';//=> becomes 3 in the trigger

// strInt += '\x0000'; 
// strInt += '\x0210'; //=> becomes 4098 in the trigger( 4096=1 + 2=2)
// strInt += '\x0000'; 
// strInt += '\u1300';//=> becomes 19 in the trigger


// strInt += '\x0200'; // = 131072
// strInt += '\x0000'; 
// strInt += '\u0100';// = 65536
// strInt += '\u0000';

//strInt += '\x0000'; 
//strInt += '\x0003'; //768
//strInt += '\u0000';
//strInt += '\u0002'; //512

string strUser = "myself";
string strContextValue = strInt + strUser + "$";
string strContext = "declare @context_info varbinary(120) set @context_info =  CONVERT(varbinary(120), N'" + strContextValue + "') set context_info @context_info";

 SqlCommand scContext = new SqlCommand(strContext, conn, tran);
 scContext.ExecuteNonQuery();

在 Trigger 中,前 8 个字符被解释为两个数字:

declare @ctxt varbinary(128)
select @ctxt=context_info from master.dbo.sysprocesses where spid = @@spid
set @session=substring(@ctxt,1,4)
set @contextid=substring(@ctxt,5,4)

我不是触发器的作者,所以我无法更改它,但如果你能解释一下这里发生了什么样的魔法,那就太好了?如何确保前 8 个字符是数字?我如何对两个整数进行编码,以便它们在 SQL 触发器上变成 4 个字符长?

我已经做了很多编码的东西,但看不透。

当我将正确写入的(从另一个应用程序)数据加载回 ac#DataTable 时,我可以通过执行以下操作获得正确的数字值:

 byte[] binaryString = (byte[])dataTable.Rows[0][2];
 string x = Encoding.ASCII.GetString(binaryString);

 string strSubX2 = x.Substring(3, 4);
 int iResult = BitConverter.ToInt32(Encoding.Unicode.GetBytes(strSubX2), 0);

标签: c#sqlsql-serverunicodeado.net

解决方案


看起来你的困惑来自于误解是什么varbinaryvarbinary是一串字节,而不是字符。它甚至不是一个真正的字符串,它是一个字节数组。

varbinary可以表示为十六进制值的字符串,而varbinaryT-SQL 代码中的常量则表示为十六进制字符串。每个字节两个十六进制字符。

T-SQL 中的SUBSTRING函数可以处理字符串 ( varchar) 和字节 ( varbinary)。

句法

SUBSTRING(表达式,开始,长度)

start 和 length 的值必须以 、 或 数据类型的字符数和、、ntext或数据 char类型varchar字节数指定。textimagebinaryvarbinary

所以,下面的 T-SQL 触发代码

declare @ctxt varbinary(128)
select @ctxt=context_info from master.dbo.sysprocesses where spid = @@spid
set @session=substring(@ctxt,1,4)

从字节数组中获取前 4 个字节(不是 chars@ctxt并将它们分配给@session变量。什么类型@session?很可能是int

在 SSMS 中运行此示例代码,您将看到一些整数的十六进制表示:

DECLARE @T TABLE (I int);
INSERT INTO @T VALUES
(0),
(1),
(3),
(19),
(255),
(256),
(512),
(768),
(4098),
(65535),
(65536),
(131072),
(1234567890),
(-1),
(-65535);

SELECT
    I, CONVERT(varbinary(120), I) AS BinI
FROM @T;

结果

+------------+------------+
|     I      |    BinI    |
+------------+------------+
|          0 | 0x00000000 |
|          1 | 0x00000001 |
|          3 | 0x00000003 |
|         19 | 0x00000013 |
|        255 | 0x000000FF |
|        256 | 0x00000100 |
|        512 | 0x00000200 |
|        768 | 0x00000300 |
|       4098 | 0x00001002 |
|      65535 | 0x0000FFFF |
|      65536 | 0x00010000 |
|     131072 | 0x00020000 |
| 1234567890 | 0x499602D2 |
|         -1 | 0xFFFFFFFF |
|     -65535 | 0xFFFF0001 |
+------------+------------+

您在问题中的 C# 代码没有多大意义。将两者都包裹strInt + strUser起来CONVERT(varbinary(120), N'" + strContextValue + "')没有意义。

C# 代码应该构造二进制数组。T-SQL 触发器代码期望该数组的前 4 个字节是@session,接下来的 4 个字节是@contextid字节,而不是字符

所以,如果你想传递,比如说,131072小数 as @session1234567890小数 as @contextid,加上myself$字符串作为额外信息,你应该运行以下 T-SQL 代码:

declare @context_info varbinary(128); -- note 128 length here
set @context_info = 0x00020000;
-- note, there are no quotes around the constants, they are numbers, not strings
set @context_info = @context_info + 0x499602D2;
set @context_info = @context_info + CONVERT(varbinary(120), N'myself$');
-- note 120 length here, first 8 bytes are used by two integers
set context_info @context_info;

在 SSMS 中运行此代码,但将最后一行替换为SELECT @context_info;以查看它的作用。您将得到以下结果:

+------------------------------------------------+
|                (No column name)                |
+------------------------------------------------+
| 0x00020000499602D26D007900730065006C0066002400 |
+------------------------------------------------+

您可以看到两个整数的二进制表示加上 unicode 文本字符串的二进制表示。

在 C# 中,您通过连接整数的十六进制表示来构造此代码。请注意,您不应将0x00020000和括0x499602D2在引号或CONVERT(varbinary, ...). 您应该只将myself$字符串包含在CONVERT(varbinary, ...).

C# 代码应如下所示:

string strUser = "myself$";
int iSession = 131072;
int iContextID = 1234567890;

StringBuilder sb = new StringBuilder();
sb.Append("declare @context_info varbinary(120);");
sb.Append("set @context_info = 0x");
sb.Append(iSession.ToString("X8"));
sb.Append(";");
sb.Append("set @context_info = @context_info + 0x");
sb.Append(iContextID.ToString("X8"));
sb.Append(";");
sb.Append("set @context_info = @context_info + CONVERT(varbinary(120), N'");
sb.Append(strUser);
sb.Append("');");
sb.Append("set context_info @context_info;");

string strContext = sb.ToSTring();

SqlCommand scContext = new SqlCommand(strContext, conn, tran);
scContext.ExecuteNonQuery();

另请参阅C# 将整数转换为十六进制并再次返回


推荐阅读