<TL; DR> Le problème est plutôt simple, en fait: vous ne faites pas correspondre le codage déclaré (dans la déclaration XML) avec le type de données du paramètre d'entrée. Si vous avez ajouté manuellement <?xml version="1.0" encoding="utf-8"?><test/>
à la chaîne, alors déclarer le SqlParameter
comme étant de type SqlDbType.Xml
ou SqlDbType.NVarChar
vous donnerait l'erreur «impossible de changer de codage». Ensuite, lors de l'insertion manuelle via T-SQL, puisque vous avez changé le codage déclaré pour être utf-16
, vous insérez clairement une VARCHAR
chaîne (non préfixée par un «N» majuscule, d'où un codage 8 bits, tel que UTF-8) et non une NVARCHAR
chaîne (préfixée par un "N" majuscule, d'où le codage UTF-16 LE 16 bits).
Le correctif aurait dû être aussi simple que:
- Dans le premier cas, lors de l'ajout de la déclaration déclarant
encoding="utf-8"
: n'ajoutez simplement pas la déclaration XML.
- Dans le second cas, lors de l'ajout de la déclaration indiquant
encoding="utf-16"
: soit
- n'ajoutez simplement pas la déclaration XML, OU
- ajoutez simplement un "N" au type de paramètre d'entrée:
SqlDbType.NVarChar
au lieu de SqlDbType.VarChar
:-) (ou éventuellement passez à using SqlDbType.Xml
)
(La réponse détaillée est ci-dessous)
Toutes les réponses ici sont trop compliquées et inutiles (indépendamment des 121 et 184 votes positifs pour les réponses de Christian et Jon, respectivement). Ils peuvent fournir un code fonctionnel, mais aucun d'entre eux ne répond réellement à la question. Le problème est que personne n'a vraiment compris la question, qui porte finalement sur le fonctionnement du type de données XML dans SQL Server. Rien contre ces deux personnes clairement intelligentes, mais cette question n'a rien à voir avec la sérialisation vers XML. L'enregistrement des données XML dans SQL Server est beaucoup plus facile que ce qui est sous-entendu ici.
La façon dont le XML est produit n'a pas vraiment d'importance tant que vous suivez les règles de création de données XML dans SQL Server. J'ai une explication plus approfondie (y compris un exemple de code de travail pour illustrer les points décrits ci-dessous) dans une réponse à cette question: Comment résoudre l'erreur «impossible de changer de codage» lors de l'insertion de XML dans SQL Server , mais les bases sont:
- La déclaration XML est facultative
- Le type de données XML stocke toujours les chaînes sous la forme UCS-2 / UTF-16 LE
- Si votre XML est UCS-2 / UTF-16 LE, alors vous:
- transmettez les données sous la forme
NVARCHAR(MAX)
ou XML
/ SqlDbType.NVarChar
(maxsize = -1) ou SqlDbType.Xml
, ou si vous utilisez une chaîne littérale, elle doit être précédée d'un "N" majuscule.
- si vous spécifiez la déclaration XML, elle doit être "UCS-2" ou "UTF-16" (pas de vraie différence ici)
- Si votre XML est codé 8 bits (par exemple, "UTF-8" / "iso-8859-1" / "Windows-1252"), vous:
- besoin de spécifier la déclaration XML SI l'encodage est différent de la page de codes spécifiée par le classement par défaut de la base de données
- vous devez transmettre les données sous la forme
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), ou si vous utilisez une chaîne littérale, elle ne doit pas être précédée d'un «N» majuscule.
- Quel que soit le codage 8 bits utilisé, le "codage" noté dans la déclaration XML doit correspondre au codage réel des octets.
- Le codage 8 bits sera converti en UTF-16 LE par le type de données XML
Avec les points décrits ci-dessus à l'esprit, et étant donné que les chaînes dans .NET sont toujours UTF-16 LE / UCS-2 LE (il n'y a aucune différence entre celles-ci en termes d'encodage), nous pouvons répondre à vos questions:
Y a-t-il une raison pour laquelle je ne devrais pas utiliser StringWriter pour sérialiser un objet lorsque j'en ai besoin sous forme de chaîne par la suite?
Non, votre StringWriter
code semble très bien (au moins je ne vois aucun problème dans mes tests limités en utilisant le 2ème bloc de code de la question).
La définition de l'encodage sur UTF-16 (dans la balise xml) ne fonctionnerait-elle pas alors?
Il n'est pas nécessaire de fournir la déclaration XML. Lorsqu'il est manquant, le codage est supposé être UTF-16 LE si vous passez la chaîne dans SQL Server en tant que NVARCHAR
(ie SqlDbType.NVarChar
) ou XML
(ie SqlDbType.Xml
). Le codage est supposé être la page de codes 8 bits par défaut s'il est transmis en tant que VARCHAR
(c.-à-d.SqlDbType.VarChar
). Si vous avez des caractères ASCII non standard (c'est-à-dire des valeurs de 128 et plus) et que VARCHAR
vous passez en tant que , vous verrez probablement "?" pour les caractères BMP et "??" pour les caractères supplémentaires, car SQL Server convertira la chaîne UTF-16 de .NET en une chaîne 8 bits de la page de codes de la base de données actuelle avant de la reconvertir en UTF-16 / UCS-2. Mais vous ne devriez pas avoir d'erreur.
En revanche, si vous spécifiez la déclaration XML, vous devez passer à SQL Server en utilisant le type de données 8 bits ou 16 bits correspondant. Donc, si vous avez une déclaration indiquant que le codage est soit UCS-2, soit UTF-16, vous devez passer en tant que SqlDbType.NVarChar
ou SqlDbType.Xml
. Ou, si vous avez une déclaration indiquant que le codage est ( à savoir l' une des options 8 bits UTF-8
, Windows-1252
, iso-8859-1
, etc.), alors vous devez passer comme SqlDbType.VarChar
. Le fait de ne pas faire correspondre le codage déclaré avec le type de données SQL Server 8 ou 16 bits approprié entraînera l'erreur «Impossible de changer le codage» que vous obteniez.
Par exemple, en utilisant votre StringWriter
code de sérialisation basé sur votre code, j'ai simplement imprimé la chaîne résultante du XML et l'ai utilisée dans SSMS. Comme vous pouvez le voir ci-dessous, la déclaration XML est incluse (car elle StringWriter
n'a pas d'option pour OmitXmlDeclaration
aimer XmlWriter
), ce qui ne pose aucun problème tant que vous passez la chaîne en tant que type de données SQL Server correct:
-- 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>
Comme vous pouvez le voir, il gère même les caractères au-delà de l'ASCII standard, étant donné qu'il ሴ
s'agit du point de code BMP U + 1234 et du 😸
point de code de caractère supplémentaire U + 1F638. Cependant, ce qui suit:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
entraîne l'erreur suivante:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo, toutes ces explications mises à part, la solution complète à votre question initiale est:
Vous passiez clairement la chaîne en tant que SqlDbType.VarChar
. Basculez vers SqlDbType.NVarChar
et cela fonctionnera sans avoir à passer par l'étape supplémentaire de suppression de la déclaration XML. Cela est préférable à la conservation SqlDbType.VarChar
et à la suppression de la déclaration XML car cette solution empêchera la perte de données lorsque le XML comprend des caractères ASCII non standard. Par exemple:
-- 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>
Comme vous pouvez le voir, il n'y a pas d'erreur cette fois, mais maintenant il y a une perte de données 🙀.