2011-04-13 6 views
8

nchar/nvarcharフィールドには、SQL ServerがUnicode UCS-2(2バイトの固定長文字エンコード)を使用しているようです。一方、C#では、文字列にUnicode UTF-16エンコーディングを使用しています(注:UCS-2はUnicodeとは見なされませんが、Unicodeサブセット0〜0xFFFFのUTF- SQL Serverに関する限り、それは文字列に関してネイティブにサポートする「Unicode」に最も近いものです。C#文字列(UTF-16)をSQL Serverのnvarchar(UCS-2)列に格納すると、どのような影響がありますか?

UCS-2は、基本多言語面でUTF-16と同じ基本コードポイントをエンコードしますが(BMP)では、サロゲートペアのためにUTF-16が許可するビットパターンは予約されていません。

SQL Server nvarchar(UCS-2)フィールドにC#文字列を書き込み、それを読み取ると、常に同じ結果が返されますか?

UTF-16はUTF-16がより多くのコードポイント(例えば0xFFFF以上)をエンコードする意味ではUCS-2のスーパーセットですが、実際には2バイトのUCS-2のサブセットですそれはより制限的なので、レベル。

私のC#文字列に0xFFFF(文字のペアで表される)以上のコードポイントが含まれていれば、これらはデータベースに格納され、正しく検索されていると思われますが、 SQL Serverにサロゲートペアを認識し、nchar/nvarchar文字列をUTF-16として効果的に処理しない限り、後で文字列を表示する際にいくつかの問題が発生する可能性があります(たとえば、TOUPPERを呼び出したり、1文字おきに空白を入れようとするなど) 。

答えて

3

本当にすごいです。

まず類似

  • 2バイト文字の文字列としてSQL Serverのnchar/nvarchar/ntextデータ型ストアのテキスト。検索や並べ替えが行われるまでは、実際には何を入れても問題ありません(適切なUnicode照合順序が使用されます)。
  • データ型は、テキストを2バイトの文字列として格納します(Char)。それはまた、あなたが検索とソートをするようになるまで(それは適切な文化特有の方法を使用する)、あなたがそれに入れたものを本当に気にしません。

  • .NETの違いは、あなたがStringInfoクラス経由でCLR文字列に実際のUnicodeコードポイントにアクセスすることができます。
  • .NETには、さまざまなエンコーディングでテキストデータをエンコードおよびデコードするためのさまざまなサポートがあります。任意のバイトストリームをStringに変換すると、その文字列は常にUTF-16としてエンコードされます(完全な多言語プレーンをサポートします)。要するに

限り、あなたはテキストの全体の塊としてCLRとSQL Serverの文字列変数の両方を扱うように、あなたが自由に情報を失うことなく一方から他方に割り当てることができます。上に重なる抽象レイヤーがわずかに異なっていても、基礎となるストレージフォーマットはまったく同じです。

+0

オクラホマので、読み出し/書き込み文字列としてサロゲートペアとして解釈されるものが含まれていても、nvarcharフィールドへのエンティティ全体で問題や情報の損失は発生しません。今、char列にC#文字列を書くのはどうですか?私は、いくつかの解釈と変換が必要であると考えています。 – Triynko

+0

シングルバイト列には、検索および並べ替え規則を定義するだけでなく、文字は許可されます。列のコードページの値にマップされているUnicodeコードポイントはすべて保持され、残りは破棄されます。 –

+0

破棄されました...または特定のダミーまたは "非文字"バイトに置き換えられましたか? 1バイトのコードページは、非文字のために特定のバイトを予約しますか?ターゲットコードスペースで定義されていないUnicode文字が疑問符に置き換えられていることを示すいくつかの例を見てきましたが、おそらく非文字がどのように表示されるのでしょうか? – Triynko

4

私は、テキストをUCS-2として扱うことは多くの問題を引き起こすとは考えていません。

(AFAIK)BMPの上に大文字と小文字のマッピングがありません(もちろん、IDマッピングを除いて)、大文字と小文字がマップされるため、大文字と小文字の変換に問題はありません。

他のすべての文字を空白にするだけで問題が発生することがあります。実際には、文字の値を考慮せずにこれらの変換を行うことは、常に危険な活動です。文字列の切り捨てが正当に起こっているのがわかります。しかし、他に類のないサロゲートが結果に現れた場合、これ自体は巨大な問題ではありません。そのようなデータを受け取ったシステムは、恐らくそれについて何かをするのが面倒であれば、代替えされたサロゲートを置換文字で置き換えるだけでしょう。

明らかに、文字列の長さは文字数ではなく2バイトになりますが、Unicodeコードチャートの深さに配管を開始すると文字数はあまり有用ではありません。たとえば、文字、RTL言語、方向制御文字、タグ、およびいくつかの種類のスペース文字を組み合わせるため、ASCII範囲を離れるとモノスペース表示で良好な結果を得ることはできません。高いコードポイントはあなたの問題の中で最も少なくなるでしょう。

ちょうどいい側にいるためには、おそらく、考古学者の名前とは異なる欄に楔形文字のテキストを保存してください。 :D

ここで、経験的データを更新してください!

大文字と小文字の変換で何が起こるかを確認するためのテストを実行しました。私は英語の単語TESTを大文字のラテン文字で2回、次にDeseretスクリプトで作成しました。 .NETとSQL Serverでこの文字列に小文字の変換を適用しました。

.NETバージョンでは、両方のスクリプトのすべての文字を正しく小文字にしました。 SQL Serverのバージョンでは、ラテン文字のみが小文字になり、Deseret文字は変更されませんでした。これは、UTF-16のUCS-2の取り扱いに関する期待に応えます。

using System; 
using System.Data.SqlClient; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     string myDeseretText = "TEST\U00010413\U00010407\U0001041D\U00010413"; 
     string dotNetLower = myDeseretText.ToLower(); 
     string dbLower = LowercaseInDb(myDeseretText); 

     Console.WriteLine(" Original: {0}", DisplayUtf16CodeUnits(myDeseretText)); 
     Console.WriteLine(".NET Lower: {0}", DisplayUtf16CodeUnits(dotNetLower)); 
     Console.WriteLine(" DB Lower: {0}", DisplayUtf16CodeUnits(dbLower)); 
     Console.ReadLine(); 
    } 

    private static string LowercaseInDb(string value) 
    { 
     SqlConnectionStringBuilder connection = new SqlConnectionStringBuilder(); 
     connection.DataSource = "(local)"; 
     connection.IntegratedSecurity = true; 
     using (SqlConnection conn = new SqlConnection(connection.ToString())) 
     { 
      conn.Open(); 
      string commandText = "SELECT LOWER(@myString) as LoweredString"; 
      using (SqlCommand comm = new SqlCommand(commandText, conn)) 
      { 
       comm.CommandType = System.Data.CommandType.Text; 
       comm.Parameters.Add("@myString", System.Data.SqlDbType.NVarChar, 100); 
       comm.Parameters["@myString"].Value = value; 
       using (SqlDataReader reader = comm.ExecuteReader()) 
       { 
        reader.Read(); 
        return (string)reader["LoweredString"]; 
       } 
      } 
     } 
    } 

    private static string DisplayUtf16CodeUnits(string value) 
    { 
     System.Text.StringBuilder sb = new System.Text.StringBuilder(); 

     foreach (char c in value) 
      sb.AppendFormat("{0:X4} ", (int)c); 
     return sb.ToString(); 
    } 
} 

出力:

Original: 0054 0045 0053 0054 D801 DC13 D801 DC07 D801 DC1D D801 DC13 
.NET Lower: 0074 0065 0073 0074 D801 DC3B D801 DC2F D801 DC45 D801 DC3B 
    DB Lower: 0074 0065 0073 0074 D801 DC13 D801 DC07 D801 DC1D D801 DC13 

場合誰がデセレット文字のフォントがインストールされているだけに、ここにあなたの楽しみのために、実際の文字列です:

Original: TEST 
.NET Lower: test 
    DB Lower: test 
+0

お返事ありがとうございます。私は大文字小文字の変換が問題ではないとは思っていません。たとえば、データベースの文字列でTOUPPERを呼び出すと、C#の文字列でToUpperを呼び出すのとは異なるバイトシーケンスが生成されます。これは、サロゲートペアが存在する場合、TSQL TOUPPERは2バイトのCLR String.ToUpperはおそらくサロゲートペアを考慮し、大文字を表す新しいペアを生成します(2番目の2バイトシーケンスはBMP 0〜0xFFFFの範囲にあり、おそらく大文字になります) 。 – Triynko

+0

私は恐らく、「どのような文字列変換が中立代理であるか」といった全く異なる質問をすることができます。大文字/小文字の変更、文字の長さの検索、文字列の比較/並べ替え、反転などはおそらく中立代理ではありませんが、トリミングはどうですか?私はおそらく何もないと思います。なぜなら、「文字の価値を考慮せずにこれらの種類の変換を行うことは、常に危険な活動です」というあなたの陳述に同意する理由です。 – Triynko

+0

@Triynko - サロゲートコードポイントは、UCS-2で透明になるように特別に割り当てられます。先頭のサロゲートまたは後続のサロゲートのいずれかを大文字にしようとすると、それらのコードポイントに対して大文字と小文字の変換が定義されていないため、常に元の文字に戻されます。ハイプレーンで大文字と小文字の変換が定義されていると仮定すると、CLRとTSQLは変換を別々に実行しますが、どちらの操作も迷惑データを生成しません(TSQLではこれらの文字は変更されません)。 ... –