<TL; DR>ปัญหาค่อนข้างง่ายจริงๆแล้ว: คุณไม่ได้จับคู่การเข้ารหัสที่ประกาศ (ในการประกาศ XML) กับประเภทข้อมูลของพารามิเตอร์อินพุต หากคุณเพิ่ม<?xml version="1.0" encoding="utf-8"?><test/>
เข้าไปในสตริงด้วยตนเองการประกาศSqlParameter
เป็นประเภทSqlDbType.Xml
หรือSqlDbType.NVarChar
จะทำให้คุณเกิดข้อผิดพลาด "ไม่สามารถเปลี่ยนการเข้ารหัส" จากนั้นเมื่อแทรกด้วยตนเองผ่าน T-SQL เนื่องจากคุณเปลี่ยนการเข้ารหัสที่ประกาศให้เป็นutf-16
คุณได้แทรกVARCHAR
สตริงอย่างชัดเจน(ไม่ได้ขึ้นต้นด้วยตัวพิมพ์ใหญ่ "N" ดังนั้นการเข้ารหัสแบบ 8 บิตเช่น UTF-8) และไม่ใช่NVARCHAR
สตริง (ขึ้นต้นด้วยตัวพิมพ์ใหญ่ "N" ดังนั้นการเข้ารหัสแบบ 16 บิต UTF-16 LE)
การแก้ไขควรทำได้ง่ายๆดังนี้:
- ในกรณีแรกเมื่อเพิ่มคำประกาศที่ระบุ
encoding="utf-8"
: อย่าเพิ่มการประกาศ XML
- ในกรณีที่สองเมื่อเพิ่มคำประกาศที่ระบุ
encoding="utf-16"
: อย่างใดอย่างหนึ่ง
- อย่าเพิ่มการประกาศ XML หรือ
- เพียงเพิ่ม "N" ลงในประเภทพารามิเตอร์อินพุต:
SqlDbType.NVarChar
แทนSqlDbType.VarChar
:-) (หรืออาจเปลี่ยนไปใช้SqlDbType.Xml
)
(คำตอบโดยละเอียดอยู่ด้านล่าง)
คำตอบทั้งหมดนี้ซับซ้อนเกินไปและไม่จำเป็น (โดยไม่คำนึงถึงคะแนน 121 และ 184 สำหรับคำตอบของ Christian และ Jon ตามลำดับ) พวกเขาอาจให้รหัสที่ใช้งานได้ แต่ไม่มีใครตอบคำถามได้จริง ปัญหาคือไม่มีใครเข้าใจคำถามอย่างแท้จริงซึ่งท้ายที่สุดแล้วเกี่ยวกับวิธีการทำงานของประเภทข้อมูล XML ใน SQL Server ไม่มีอะไรเทียบกับคนฉลาดทั้งสองอย่างชัดเจน แต่คำถามนี้แทบไม่มีอะไรเกี่ยวข้องกับการทำให้เป็นอนุกรมกับ XML การบันทึกข้อมูล XML ลงใน SQL Server นั้นง่ายกว่าที่กล่าวโดยนัยที่นี่มาก
ไม่สำคัญว่าจะสร้าง XML อย่างไรตราบใดที่คุณปฏิบัติตามกฎของวิธีสร้างข้อมูล XML ใน SQL Server ฉันมีคำอธิบายอย่างละเอียดมากขึ้น (รวมถึงโค้ดตัวอย่างการทำงานเพื่อแสดงให้เห็นถึงประเด็นที่ระบุไว้ด้านล่าง) ในคำตอบสำหรับคำถามนี้: วิธีแก้ข้อผิดพลาด "ไม่สามารถสลับการเข้ารหัส" เมื่อใส่ XML ลงใน SQL Serverแต่พื้นฐานคือ
- การประกาศ XML เป็นทางเลือก
- ประเภทข้อมูล XML จะเก็บสตริงเป็น UCS-2 / UTF-16 LE เสมอ
- หาก XML ของคุณคือ UCS-2 / UTF-16 LE คุณจะ:
- ส่งผ่านข้อมูลเป็น
NVARCHAR(MAX)
หรือXML
/ SqlDbType.NVarChar
(maxsize = -1) หรือSqlDbType.Xml
หรือถ้าใช้สตริงลิเทอรัลข้อมูลนั้นจะต้องขึ้นต้นด้วยตัวพิมพ์ใหญ่ "N"
- หากระบุการประกาศ XML จะต้องเป็น "UCS-2" หรือ "UTF-16" (ไม่มีความแตกต่างจริงที่นี่)
- หาก XML ของคุณเข้ารหัสแบบ 8 บิต (เช่น "UTF-8" / "iso-8859-1" / "Windows-1252") คุณจะ:
- จำเป็นต้องระบุการประกาศ XML หากการเข้ารหัสแตกต่างจากโค้ดเพจที่ระบุโดยค่าเริ่มต้นการเรียงฐานข้อมูล
- คุณต้องส่งผ่านข้อมูลเป็น
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1) หรือหากใช้สตริงลิเทอรัลจะต้องไม่ขึ้นต้นด้วยตัวพิมพ์ใหญ่ "N"
- ไม่ว่าจะใช้การเข้ารหัสแบบ 8 บิตแบบใดก็ตาม "การเข้ารหัส" ที่ระบุไว้ในการประกาศ XML จะต้องตรงกับการเข้ารหัสไบต์ที่แท้จริง
- การเข้ารหัส 8 บิตจะถูกแปลงเป็น UTF-16 LE โดยประเภทข้อมูล XML
ด้วยประเด็นที่ระบุไว้ข้างต้นในใจและเนื่องจากสตริงใน. NET มักจะเป็นUTF-16 LE / UCS-2 LE (ไม่มีความแตกต่างระหว่างสิ่งเหล่านี้ในแง่ของการเข้ารหัส) เราสามารถตอบคำถามของคุณได้:
มีเหตุผลไหมที่ฉันไม่ควรใช้ StringWriter เพื่อทำให้เป็นอนุกรมของวัตถุเมื่อฉันต้องการเป็นสตริงในภายหลัง?
ไม่StringWriter
รหัสของคุณดูเหมือนจะใช้ได้ (อย่างน้อยฉันก็ไม่เห็นปัญหาใด ๆ ในการทดสอบแบบ จำกัด ของฉันโดยใช้บล็อกโค้ดที่ 2 จากคำถาม)
การตั้งค่าการเข้ารหัสเป็น UTF-16 (ในแท็ก xml) จะไม่ทำงานหรือ
ไม่จำเป็นต้องระบุการประกาศ XML เมื่อขาดหายไปการเข้ารหัสจะถือว่าเป็น UTF-16 LE หากคุณส่งสตริงไปยัง SQL Server เป็นNVARCHAR
(เช่นSqlDbType.NVarChar
) หรือXML
(เช่นSqlDbType.Xml
) การเข้ารหัสจะถือว่าเป็นโค้ดเพจเริ่มต้น 8 บิตหากส่งผ่านเป็นVARCHAR
(เช่นSqlDbType.VarChar
) หากคุณมีอักขระ ASCII ที่ไม่ได้มาตรฐาน (เช่นค่า 128 ขึ้นไป) และกำลังส่งผ่านในฐานะVARCHAR
คุณจะเห็น "?" สำหรับอักขระ BMP และ "??" สำหรับอักขระเสริมเป็น SQL Server จะแปลงสตริง UTF-16 จาก. NET เป็นสตริง 8 บิตของโค้ดเพจของฐานข้อมูลปัจจุบันก่อนที่จะแปลงกลับเป็น UTF-16 / UCS-2 แต่คุณไม่ควรได้รับข้อผิดพลาดใด ๆ
ในทางกลับกันถ้าคุณระบุการประกาศ XML คุณต้องส่งผ่านไปยัง SQL Server โดยใช้ประเภทข้อมูล 8 บิตหรือ 16 บิตที่ตรงกัน ดังนั้นถ้าคุณมีการประกาศที่ระบุว่าการเข้ารหัสเป็นทั้ง UCS 2 หรือ UTF-16 แล้วคุณจะต้องผ่านในฐานะหรือSqlDbType.NVarChar
SqlDbType.Xml
หรือหากคุณมีการประกาศที่ระบุว่าการเข้ารหัสเป็นหนึ่งในตัวเลือกที่ 8 บิต (เช่นUTF-8
, Windows-1252
, iso-8859-1
ฯลฯ ) แล้วคุณจะต้องSqlDbType.VarChar
ผ่านในขณะที่ ความล้มเหลวในการจับคู่การเข้ารหัสที่ประกาศกับประเภทข้อมูล SQL Server 8 หรือ 16 บิตที่เหมาะสมจะส่งผลให้เกิดข้อผิดพลาด "ไม่สามารถสลับการเข้ารหัส" ที่คุณได้รับ
ตัวอย่างเช่นการใช้StringWriter
รหัสซีเรียลไลเซชันตามของคุณฉันเพียงแค่พิมพ์สตริงผลลัพธ์ของ XML และใช้ใน SSMS ดังที่คุณเห็นด้านล่างการประกาศ XML รวมอยู่ด้วย (เนื่องจากStringWriter
ไม่มีตัวเลือกให้OmitXmlDeclaration
ชอบXmlWriter
) ซึ่งจะไม่มีปัญหาตราบใดที่คุณส่งสตริงเป็นประเภทข้อมูล SQL Server ที่ถูกต้อง:
DECLARE @Xml XML = N'<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
SELECT @Xml;
อย่างที่คุณเห็นมันยังจัดการกับอักขระที่อยู่นอกเหนือ ASCII มาตรฐานเนื่องจากሴ
เป็นจุดรหัส BMP U + 1234 และ😸
เป็นจุดรหัสอักขระเสริม U + 1F638 อย่างไรก็ตามสิ่งต่อไปนี้:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
ส่งผลให้เกิดข้อผิดพลาดต่อไปนี้:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo นอกเหนือจากคำอธิบายทั้งหมดแล้วคำตอบที่สมบูรณ์สำหรับคำถามเดิมของคุณคือ:
SqlDbType.VarChar
คุณได้อย่างชัดเจนผ่านสตริงเป็น เปลี่ยนไปใช้SqlDbType.NVarChar
และจะใช้งานได้โดยไม่จำเป็นต้องทำตามขั้นตอนเพิ่มเติมในการลบการประกาศ XML สิ่งนี้เป็นที่ต้องการมากกว่าการเก็บรักษาSqlDbType.VarChar
และลบการประกาศ XML เนื่องจากโซลูชันนี้จะป้องกันข้อมูลสูญหายเมื่อ XML มีอักขระ ASCII ที่ไม่ได้มาตรฐาน ตัวอย่างเช่น:
DECLARE @Xml2 XML = '<string>Test ሴ😸</string>';
SELECT @Xml2;
อย่างที่คุณเห็นไม่มีข้อผิดพลาดในครั้งนี้ แต่ตอนนี้มีข้อมูลสูญหาย🙀