2011-04-13 27 views
8

Dường như SQL Server sử dụng Unicode UCS-2, mã hóa ký tự có độ dài cố định 2 byte, cho các trường nchar/nvarchar. Trong khi đó, C# sử dụng mã hóa Unicode UTF-16 cho chuỗi của nó (lưu ý: Một số người không coi UCS-2 là Unicode, nhưng mã hóa tất cả các điểm mã giống như UTF-16 trong tập con Unicode 0-0xFFFF, và như xa như SQL Server là có liên quan, đó là điều gần nhất với "Unicode" nó tự nhiên hỗ trợ trong điều khoản của chuỗi ký tự.)Hậu quả của việc lưu trữ chuỗi C# (UTF-16) trong cột nvarchar SQL Server (UCS-2) là gì?

Trong khi UCS-2 mã hóa các điểm mã cơ bản giống như UTF-16 trong Basic Multilingual Plane (BMP), nó không dự trữ các mẫu bit nhất định mà UTF-16 thực hiện để cho phép các cặp thay thế.

Nếu tôi viết chuỗi C# vào trường SQL Server nvarchar (UCS-2) và đọc lại, điều này sẽ luôn trả về cùng một kết quả không? Có vẻ như trong khi UTF-16 là một siêu của UCS-2 theo nghĩa là UTF-16 mã hóa nhiều điểm mã hơn (ví dụ trên 0xFFFF), nó thực sự là một tập con của UCS-2 tại 2-byte mức độ, vì nó hạn chế hơn.

Để trả lời câu hỏi của mình, tôi nghi ngờ rằng nếu chuỗi C# của tôi chứa các điểm mã trên 0xFFFF (được biểu diễn bằng cặp ký tự), chúng sẽ được lưu trữ và truy xuất tốt trong cơ sở dữ liệu, nhưng nếu tôi cố gắng thao tác chúng cơ sở dữ liệu (ví dụ: có thể gọi TOUPPER hoặc cố gắng xóa bỏ mọi ký tự khác), sau đó tôi có thể gặp phải một số vấn đề hiển thị chuỗi sau ... trừ khi SQL Server có chức năng xác nhận cặp thay thế và xử lý hiệu quả các chuỗi nchar/nvarchar như UTF-16 .

Trả lời

3

Đó là tất cả một chút của một fudge thực sự.

Đầu những điểm tương đồng

  • Các SQL Server nchar/nvarchar/ntext kiểu dữ liệu lưu trữ văn bản như là một chuỗi ký tự 2 byte. Nó không thực sự quan tâm những gì bạn đưa vào chúng cho đến khi bạn tìm kiếm và sắp xếp (sau đó nó sử dụng chuỗi đối chiếu Unicode thích hợp).
  • CLR String loại dữ liệu cũng lưu trữ văn bản dưới dạng chuỗi 2 byte Char s. Nó cũng không thực sự quan tâm những gì bạn đưa vào nó cho đến khi bạn đến để tìm kiếm và phân loại (sau đó nó sử dụng các phương pháp văn hóa cụ thể thích hợp).

Bây giờ sự khác biệt

  • .NET cho phép bạn truy cập vào các điểm mã Unicode thực tế trong một chuỗi CLR thông qua các lớp StringInfo.
  • .NET có rất nhiều hỗ trợ mã hóa và giải mã dữ liệu văn bản trong nhiều loại mã hóa. Khi chuyển đổi một dòng byte tùy ý thành String, nó sẽ luôn mã hóa chuỗi là UTF-16 (với hỗ trợ đa ngôn ngữ đa ngôn ngữ).

Nói tóm lại, chừng nào bạn đối xử với cả hai CLR và SQL server chuỗi biến như toàn bộ các đốm màu của văn bản, sau đó bạn có thể tự do chuyển nhượng từ một đến khác mà không làm giảm thông tin. Định dạng lưu trữ cơ bản giống hệt nhau, mặc dù các lớp trừu tượng được xếp lớp trên đầu hơi khác một chút.

+0

Ok, vậy đọc/viết một chuỗi như một thực thể toàn bộ đến một trường nvarchar sẽ không gây ra vấn đề hoặc mất thông tin, ngay cả khi nó chứa những gì sẽ được hiểu là cặp thay thế. Bây giờ, những gì về việc viết một chuỗi C# vào một cột char? Tôi nghi ngờ rằng sẽ liên quan đến một số giải thích và chuyển đổi và sẽ gây ra mất dữ liệu ... – Triynko

+0

Các cột một byte có một chuỗi đối chiếu không phải Unicode được xác định trên chúng, không chỉ định nghĩa các quy tắc tìm kiếm và sắp xếp, mà còn là trang mã định nghĩa ký tự được cho phép. Bất kỳ điểm mã Unicode nào được ánh xạ tới một giá trị trong trang mã của cột sẽ được giữ nguyên và phần còn lại sẽ bị hủy bỏ. –

+0

Bị hủy bỏ ... hoặc được thay bằng một ký tự giả hoặc byte "không phải ký tự" cụ thể? Các trang mã byte đơn có dự trữ một byte nhất định cho các ký tự không? Tôi đã thấy một số ví dụ cho thấy rằng các ký tự Unicode không được định nghĩa trong không gian mã đích được thay thế bằng dấu chấm hỏi, nhưng có lẽ đó chỉ là cách các ký tự không được hiển thị? – Triynko

4

Tôi không nghĩ rằng việc xử lý văn bản dưới dạng UCS-2 sẽ gây ra nhiều sự cố.

Chuyển đổi trường hợp không phải là một vấn đề, bởi vì (AFAIK) không có ánh xạ trường hợp nào trên BMP (ngoại trừ ánh xạ danh tính!), Và rõ ràng là các ký tự thay thế sẽ tự ánh xạ.

Tẩy trống mọi nhân vật khác chỉ yêu cầu sự cố. Trong thực tế, việc thực hiện các loại biến đổi này mà không xem xét các giá trị ký tự luôn là một hoạt động nguy hiểm. Tôi có thể thấy nó xảy ra một cách hợp pháp với các chuỗi cắt ngắn. Nhưng nếu bất kỳ đại diện chưa từng có nào xuất hiện trong kết quả, bản thân điều này không phải là vấn đề lớn lớn. Bất kỳ hệ thống nào nhận dữ liệu đó — và quan tâm — có lẽ sẽ chỉ thay thế người đại diện chưa từng có bằng một nhân vật thay thế, nếu nó làm phiền bất cứ điều gì về nó cả. Rõ ràng, độ dài chuỗi sẽ là byte/2 chứ không phải là số ký tự, nhưng số ký tự không phải là một giá trị rất hữu ích, khi bạn bắt đầu sửa ống nước độ sâu của biểu đồ mã Unicode. Ví dụ: bạn sẽ không nhận được kết quả tốt trong hiển thị đơn cách một khi bạn rời khỏi phạm vi ASCII, vì kết hợp các ký tự, ngôn ngữ RTL, ký tự điều khiển hướng, thẻ và một số loại ký tự khoảng trắng. Các điểm mã cao sẽ là ít nhất của các vấn đề của bạn.

Chỉ để an toàn, bạn nên lưu trữ các văn bản cuneiform của bạn trong một cột khác với tên của nhà khảo cổ học. : D

CẬP NHẬT ngay bây giờ với dữ liệu thực nghiệm!

Tôi vừa chạy thử nghiệm để xem điều gì xảy ra với các biến đổi trường hợp. Tôi đã tạo một chuỗi có từ TEST bằng tiếng Anh trong chữ hoa hai lần — đầu tiên bằng chữ Latin, sau đó trong tập lệnh Deseret. Tôi áp dụng một chuyển đổi trường hợp thấp hơn cho chuỗi này trong .NET và trong SQL Server.

Phiên bản .NET đã hạ thấp chính xác tất cả các chữ cái trong cả hai tập lệnh. Phiên bản SQL Server chỉ hạ thấp các ký tự Latinh và để lại các ký tự Deseret không thay đổi. Điều này đáp ứng các kỳ vọng về việc xử lý các câu 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(); 
    } 
} 

Output:

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 

Chỉ trong trường hợp ai đã cài đặt một phông chữ Deseret, đây là những chuỗi thực tế cho mái của bạn:

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

Cảm ơn bạn đã phản hồi. Tôi không đồng ý rằng các chuyển đổi trong trường hợp không phải là vấn đề. Ví dụ, gọi TOUPPER trên một chuỗi trong cơ sở dữ liệu sẽ tạo ra một chuỗi byte khác với việc gọi ToUpper trên một chuỗi trong C#, chính xác bởi vì nếu có cặp thay thế, TSQL TOUPPER sẽ ở trên mỗi chuỗi 2 byte của ghép đôi riêng lẻ (do đó chuỗi thứ hai 2 byte sẽ nằm trong dải 0-0xFFFF của BMP và có khả năng được xếp chồng lên trên), trong khi chuỗi CLR String.ToUpper có thể lấy cặp thay thế để xem xét và tạo ra một cặp mới đại diện cho chữ hoa . – Triynko

+0

Tôi có thể hỏi một câu hỏi hoàn toàn khác, chẳng hạn như "Biến đổi chuỗi nào là trung tính thay thế?". Thay đổi trường hợp, tìm chiều dài ký tự, so sánh/sắp xếp chuỗi, đảo ngược nó, v.v. có lẽ sẽ không thay thế trung lập, nhưng về cắt tỉa thì sao? Tôi nghĩ có lẽ không có, đó là lý do tại sao tôi đồng ý với tuyên bố của bạn rằng "làm những loại biến đổi này mà không xem xét các giá trị nhân vật luôn luôn là một hoạt động nguy hiểm". – Triynko

+0

@Triynko - Các điểm mã thay thế được phân bổ cụ thể để chúng được minh bạch trong UCS-2. Cố gắng viết hoa hoặc đại diện hàng đầu hoặc đại diện thay thế sẽ luôn luôn ánh xạ trở lại ký tự gốc, bởi vì không có chuyển đổi trường hợp được xác định cho các điểm mã đó. Nếu chúng ta giả định rằng có các chuyển đổi trường hợp được xác định trong các mặt phẳng cao (mà tôi nghi ngờ), thì CLR và TSQL sẽ thực hiện chuyển đổi khác nhau, nhưng không hoạt động nào sẽ tạo ra dữ liệu rác (vì TSQL sẽ để lại các ký tự đó không thay đổi). ... –

Các vấn đề liên quan