简体   繁体   中英

SQL Server conversion fidelity from nvarchar to varbinary, then from varbinary to nvarchar

I'm trying to convert back the nvarchar stored in CONTEXT_INFO.

declare @LanguageCode nvarchar(6) = 'en';
declare @binvar varbinary(128);
set @binvar = cast(@LanguageCode as varbinary);
set context_info @binvar;

select A = len(@LanguageCode);
select B = len(convert(nvarchar(6),@LanguageCode));

select C = convert(nvarchar(6),context_info());
select D = cast(CONTEXT_INFO() as nvarchar(5));
select E = len(convert(nvarchar(6),CONTEXT_INFO()));
select F =len(cast(CONTEXT_INFO() as nvarchar(6)));

select G = convert(nvarchar,context_info());
select H = cast(CONTEXT_INFO() as nvarchar);
select I = len(convert(nvarchar,CONTEXT_INFO()));
select J = len(cast(CONTEXT_INFO() as nvarchar));

The nvarchar obtained from casting varbinary to nvarchar is of length 6, it includes the \\0. Example, the language 'en' is obtained back as 'en\\0\\0\\0\\0'. This mucked up the key caching on NHibernate .

Is there a way we can convert back the nvarchar value stored in varbinary to its original value? ie, 'en' can be converted back to 'en'.

Output:

A
-----------
2

B
-----------
2

C
------
en    

D
-----
en   

E
-----------
6

F
-----------
6

G
------------------------------
en                            

H
------------------------------
en                            

I
-----------
30

J
-----------
30

CHOSEN ANSWER rationale

I chose HABO's answer as his solution is directly embeddable to consuming queries:

declare @LanguageCode nvarchar(6) = 'en';
declare @binvar varbinary(128);
set @binvar = cast(datalength(@LanguageCode) as varbinary) + cast(@LanguageCode as varbinary);
set context_info @binvar;
select dump = @binvar;



-- directly embeddable
select  recoveredLanguage = convert(nvarchar, substring(context_info(), 5, convert(int, substring(context_info(), 1, 4)) )  ) 
        , theLength =  len ( convert(nvarchar, substring(context_info(), 5, convert(int, substring(context_info(), 1, 4)) )  ) )


declare @buffer varbinary(128) = context_info();
declare @RecoveredLanguageCode nvarchar(6) = cast(substring(@buffer, 5, cast(substring(@buffer, 1, 4) as int)) as nvarchar(6));
select recoveredLanguage = @RecoveredLanguageCode, theLength = len(@RecoveredLanguageCode);

Output:

dump
--------------------
0x0000000465006E00

(1 row(s) affected)

recoveredLanguage              theLength
------------------------------ -----------
en                             2

(1 row(s) affected)

recoveredLanguage theLength
----------------- -----------
en                2

(1 row(s) affected)

While souplex's answer is correct, I'm not certain if SET ANSI_PADDING_OFF effects globally, or local only on one batch of statements. And placing all the castings in one statement has side effect, the restoration doesn't work, each casting must be done on separate statements. I upvote souplex's answer anyway

DECLARE @LanguageCode NVARCHAR(6) = 'en';
DECLARE @binvar VARBINARY(128);
SET @binvar = CAST(@LanguageCode AS VARBINARY(128));
SET context_info @binvar;
select dump = @Binvar;




SET ANSI_PADDING OFF;
select recoveredLanguage = cast( cast( cast(context_info() as binary(128)) as varbinary(128) ) as nvarchar(6) )
        , theLength = len( cast( cast( cast(context_info() as binary(128)) as varbinary(128) ) as nvarchar(6) ) );


DECLARE @binvar1 BINARY(128) = context_info();
DECLARE @binvar2 VARBINARY(128) = CAST(@binvar1 AS VARBINARY(128));
declare @c nvarchar(6) = cast(@binvar2 as nvarchar(6))
select recoveredLanguage = @c, theLength = len(@c);

Output:

dump
----------
0x65006E00

(1 row(s) affected)

recoveredLanguage theLength
----------------- -----------
en                6

(1 row(s) affected)

recoveredLanguage theLength
----------------- -----------
en                2

(1 row(s) affected)

Kudos to HABO and souplex for not bringing up XML PATH or XML AUTO solution :-) Thanks!

Prefix the string with the length before you save it in CONTEXT_INFO :

declare @LanguageCode nvarchar(6) = 'en';
declare @binvar varbinary(128);
set @binvar = cast(datalength(@LanguageCode) as varbinary) + cast(@LanguageCode as varbinary);
set context_info @binvar;

declare @buffer varbinary(128) = context_info();
declare @RecoveredLanguageCode nvarchar(6) = cast(substring(@buffer, 5, cast(substring(@buffer, 1, 4) as int)) as nvarchar(6));
select @RecoveredLanguageCode, len(@RecoveredLanguageCode);

A regular varbinary(128) would be perfectly castable.

DECLARE @LanguageCode NVARCHAR(6) = 'en';
DECLARE @binvar VARBINARY(128);
SET @binvar = CAST(@LanguageCode AS VARBINARY);
SELECT  CAST(@binvar AS NVARCHAR(6));

This returns the initial value 'en' back.

Now when you set the context value and retrieve it back, it seems to have turned into a binary(128) value rather then a varbinary(128).

SET context_info @binvar;
SELECT  CAST(context_info() AS NVARCHAR(6));

Now you have trailing char(0) characters.

When you cast the context_info() value to a varbinary and cast that to a nvarchar your issue is solved when you explicitly set ansi padding to off.

SET ANSI_PADDING OFF;
--
DECLARE @LanguageCode NVARCHAR(6) = 'en';
DECLARE @binvar VARBINARY(128);
SET @binvar = CAST(@LanguageCode AS VARBINARY(128));
SET context_info @binvar;

SET ANSI_PADDING ON;
DECLARE @binvar1 BINARY(128) = context_info();
DECLARE @binvar2 VARBINARY(128) = CAST(@binvar1 AS VARBINARY(128));
--> having trailing char(0)
SELECT  CAST(@binvar2 AS NVARCHAR(6));

GO

DECLARE @LanguageCode NVARCHAR(6) = 'en';
DECLARE @binvar VARBINARY(128);
SET @binvar = CAST(@LanguageCode AS VARBINARY(128));
SET context_info @binvar;

SET ANSI_PADDING OFF;
DECLARE @binvar1 BINARY(128) = context_info();
DECLARE @binvar2 VARBINARY(128) = CAST(@binvar1 AS VARBINARY(128));
--> no more trailing char(0)
SELECT  CAST(@binvar2 AS NVARCHAR(6));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM