<TL; DR> Thực ra vấn đề khá đơn giản: bạn không khớp kiểu mã hóa đã khai báo (trong khai báo XML) với kiểu dữ liệu của tham số đầu vào. Nếu bạn đã thêm vào <?xml version="1.0" encoding="utf-8"?><test/>
chuỗi theo cách thủ công , thì việc khai báo SqlParameter
thuộc loại SqlDbType.Xml
hoặc SqlDbType.NVarChar
sẽ cho bạn lỗi "không thể chuyển đổi mã hóa". Sau đó, khi chèn theo cách thủ công qua T-SQL, vì bạn đã chuyển mã hóa đã khai báo thành utf-16
, rõ ràng bạn đang chèn một VARCHAR
chuỗi (không có tiền tố bằng chữ hoa "N", do đó là mã hóa 8 bit, chẳng hạn như UTF-8) và không phải là mộtNVARCHAR
chuỗi (có tiền tố là "N" viết hoa, do đó là mã hóa UTF-16 LE 16 bit).
Cách khắc phục phải đơn giản như sau:
- Trong trường hợp đầu tiên, khi thêm khai báo nêu rõ
encoding="utf-8"
: chỉ cần không thêm khai báo XML.
- Trong trường hợp thứ hai, khi thêm khai báo nêu rõ
encoding="utf-16"
:
- đơn giản là không thêm khai báo XML, HOẶC
- chỉ cần thêm "N" vào kiểu tham số đầu vào:
SqlDbType.NVarChar
thay vì SqlDbType.VarChar
:-) (hoặc thậm chí có thể chuyển sang sử dụng SqlDbType.Xml
)
(Phản hồi chi tiết ở bên dưới)
Tất cả các câu trả lời ở đây đều quá phức tạp và không cần thiết (bất kể 121 và 184 phiếu thuận cho câu trả lời của Christian và Jon tương ứng). Họ có thể cung cấp mã làm việc, nhưng không ai trong số họ thực sự trả lời câu hỏi. Vấn đề là không ai thực sự hiểu câu hỏi, cuối cùng là về cách hoạt động của kiểu dữ liệu XML trong SQL Server. Không có gì chống lại hai người rõ ràng thông minh đó, nhưng câu hỏi này không liên quan gì đến việc tuần tự hóa sang XML. Lưu dữ liệu XML vào SQL Server dễ dàng hơn nhiều so với những gì đang được ngụ ý ở đây.
Việc tạo ra XML như thế nào không thực sự quan trọng, miễn là bạn tuân theo các quy tắc về cách tạo dữ liệu XML trong SQL Server. Tôi có giải thích kỹ lưỡng hơn (bao gồm mã ví dụ làm việc để minh họa các điểm được nêu bên dưới) trong câu trả lời cho câu hỏi này: Cách giải quyết lỗi "không thể chuyển đổi mã hóa" khi chèn XML vào SQL Server , nhưng những điều cơ bản là:
- Khai báo XML là tùy chọn
- Kiểu dữ liệu XML luôn lưu trữ các chuỗi dưới dạng UCS-2 / UTF-16 LE
- Nếu XML của bạn là UCS-2 / UTF-16 LE, thì bạn:
- chuyển vào dữ liệu dưới dạng
NVARCHAR(MAX)
hoặc hoặc XML
/ SqlDbType.NVarChar
(maxsize = -1) hoặc SqlDbType.Xml
, hoặc nếu sử dụng một chuỗi ký tự thì nó phải được đặt trước bằng chữ hoa "N".
- nếu chỉ định khai báo XML, nó phải là "UCS-2" hoặc "UTF-16" (không có sự khác biệt thực sự ở đây)
- Nếu XML của bạn được mã hóa 8 bit (ví dụ: "UTF-8" / "iso-8859-1" / "Windows-1252"), thì bạn:
- cần chỉ định khai báo XML NẾU mã hóa khác với trang mã được chỉ định bởi Đối chiếu mặc định của cơ sở dữ liệu
- bạn phải chuyển vào dữ liệu dưới dạng
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), hoặc nếu sử dụng một chuỗi ký tự thì nó không được bắt đầu bằng chữ hoa "N".
- Dù sử dụng cách mã hóa 8 bit nào, thì "mã hóa" được ghi chú trong khai báo XML phải khớp với mã hóa thực tế của các byte.
- Mã hóa 8 bit sẽ được chuyển đổi thành UTF-16 LE bởi kiểu dữ liệu XML
Với những điểm đã nêu ở trên và cho rằng các chuỗi trong .NET luôn là UTF-16 LE / UCS-2 LE (không có sự khác biệt giữa các chuỗi về mặt mã hóa), chúng tôi có thể trả lời câu hỏi của bạn:
Có lý do gì mà tôi không nên sử dụng StringWriter để tuần tự hóa một Đối tượng khi tôi cần nó dưới dạng chuỗi sau đó không?
Không, StringWriter
mã của bạn có vẻ ổn (ít nhất tôi không thấy có vấn đề gì trong thử nghiệm giới hạn của mình bằng cách sử dụng khối mã thứ 2 từ câu hỏi).
Sau đó, đặt mã hóa thành UTF-16 (trong thẻ xml) sẽ không hoạt động?
Không cần thiết phải cung cấp khai báo XML. Khi nó bị thiếu, mã hóa được giả định là UTF-16 LE nếu bạn chuyển chuỗi vào SQL Server dưới dạng NVARCHAR
(tức là SqlDbType.NVarChar
) hoặc XML
(tức là SqlDbType.Xml
). Mã hóa được giả định là Trang mã 8 bit mặc định nếu chuyển vào dưới dạng VARCHAR
(tức là SqlDbType.VarChar
). Nếu bạn có bất kỳ ký tự ASCII không chuẩn nào (tức là giá trị 128 trở lên) và đang chuyển vào dưới dạng VARCHAR
, thì bạn có thể sẽ thấy "?" cho các ký tự BMP và "??" đối với Ký tự bổ sung vì SQL Server sẽ chuyển đổi chuỗi UTF-16 từ .NET thành chuỗi 8 bit của Trang mã của cơ sở dữ liệu hiện tại trước khi chuyển đổi lại thành UTF-16 / UCS-2. Nhưng bạn sẽ không nhận được bất kỳ lỗi nào.
Mặt khác, nếu bạn chỉ định khai báo XML, thì bạn phải chuyển vào SQL Server bằng cách sử dụng kiểu dữ liệu 8 bit hoặc 16 bit phù hợp. Vì vậy, nếu bạn có một khai báo cho biết mã hóa là UCS-2 hoặc UTF-16, thì bạn phải nhập bằng SqlDbType.NVarChar
hoặc SqlDbType.Xml
. Hoặc, nếu bạn có một tuyên bố nói rằng các mã hóa là một trong những lựa chọn 8-bit (ví dụ UTF-8
, Windows-1252
, iso-8859-1
, vv), sau đó bạn phải vượt qua trong khi SqlDbType.VarChar
. Không khớp mã hóa đã khai báo với kiểu dữ liệu SQL Server 8 hoặc 16 bit thích hợp sẽ dẫn đến lỗi "không thể chuyển đổi mã hóa" mà bạn đang gặp phải.
Ví dụ, sử dụng StringWriter
mã tuần tự hóa dựa trên cơ sở của bạn , tôi chỉ cần in chuỗi kết quả của XML và sử dụng nó trong SSMS. Như bạn có thể thấy bên dưới, khai báo XML được bao gồm (vì StringWriter
không có tùy chọn OmitXmlDeclaration
thích XmlWriter
), điều này không có vấn đề gì miễn là bạn chuyển chuỗi vào dưới dạng kiểu dữ liệu SQL Server chính xác:
-- Upper-case "N" prefix == NVARCHAR, hence no error:
DECLARE @Xml XML = N'<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
SELECT @Xml;
-- <string>Test ሴ😸</string>
Như bạn có thể thấy, nó thậm chí còn xử lý các ký tự ngoài ASCII tiêu chuẩn, với điều kiện ሴ
là Điểm mã BMP U + 1234 và 😸
là Điểm mã ký tự bổ sung U + 1F638. Tuy nhiên, những điều sau:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
dẫn đến lỗi sau:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo, tất cả lời giải thích đó sang một bên, giải pháp đầy đủ cho câu hỏi ban đầu của bạn là:
Rõ ràng bạn đã chuyển chuỗi vào dưới dạng SqlDbType.VarChar
. Chuyển sang SqlDbType.NVarChar
và nó sẽ hoạt động mà không cần thực hiện thêm bước xóa khai báo XML. Điều này được ưu tiên hơn việc giữ SqlDbType.VarChar
và loại bỏ khai báo XML vì giải pháp này sẽ ngăn mất dữ liệu khi XML bao gồm các ký tự ASCII không chuẩn. Ví dụ:
-- No upper-case "N" prefix on the string literal == VARCHAR, and no XML declaration:
DECLARE @Xml2 XML = '<string>Test ሴ😸</string>';
SELECT @Xml2;
-- <string>Test ???</string>
Như bạn thấy, không có lỗi lần này, nhưng bây giờ có mất dữ liệu 🙀.