Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment résoudre l'impossibilité de changer l'erreur d'encodage lors de l'insertion de XML dans SQL Server

Cette question est presque un doublon de 2 autres, et étonnamment - bien que celle-ci soit la plus récente - je pense qu'il manque la meilleure réponse.

Les doublons, et ce que je pense être leurs meilleures réponses, sont :

  • Utilisation de StringWriter pour la sérialisation XML (2009-10-14)
    • https://stackoverflow.com/a/1566154/751158
  • La tentative de stockage du contenu XML dans SQL Server 2005 échoue (problème d'encodage) (2008-12-21)
    • https://stackoverflow.com/a/1091209/751158

Au final, peu importe l'encodage déclaré ou utilisé, tant que le XmlReader peut l'analyser localement au sein du serveur d'application.

Comme cela a été confirmé dans Méthode la plus efficace pour lire XML dans ADO.net à partir d'une colonne de type XML dans SQL Server ?, SQL Server stocke XML dans un format binaire efficace. En utilisant le SqlXml , ADO.net peut communiquer avec SQL Server dans ce format binaire et ne pas demander au serveur de base de données d'effectuer une sérialisation ou une désérialisation de XML. Cela devrait également être plus efficace pour le transport sur le réseau.

En utilisant SqlXml , XML sera envoyé pré-analysé à la base de données, puis la base de données n'a pas besoin de savoir quoi que ce soit sur les encodages de caractères - UTF-16 ou autre. En particulier, notez que les déclarations XML ne sont même pas conservées avec les données de la base de données, quelle que soit la méthode utilisée pour les insérer.

Veuillez vous référer aux réponses ci-dessus pour les méthodes qui ressemblent beaucoup à cela, mais cet exemple est le mien :

using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Xml;

static class XmlDemo {
    static void Main(string[] args) {
        using(SqlConnection conn = new SqlConnection()) {
            conn.ConnectionString = "...";
            conn.Open();

            using(SqlCommand cmd = new SqlCommand("Insert Into TestData(Xml) Values (@Xml)", conn)) {

                cmd.Parameters.Add(new SqlParameter("@Xml", SqlDbType.Xml) {
                    // Works.
                    // Value = "<Test/>"

                    // Works.  XML Declaration is not persisted!
                    // Value = "<?xml version=\"1.0\"?><Test/>"

                    // Works.  XML Declaration is not persisted!
                    // Value = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><Test/>"

                    // Error ("unable to switch the encoding" SqlException).
                    // Value = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Test/>"

                    // Works.  XML Declaration is not persisted!
                    Value = new SqlXml(XmlReader.Create(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Test/>")))
                });

                cmd.ExecuteNonQuery();
            }
        }
    }
}

Notez que je ne considérerais pas le dernier exemple (non commenté) comme "prêt pour la production", mais je l'ai laissé tel quel pour être concis et lisible. Si cela est fait correctement, le StringReader et le XmlReader créé doit être initialisé dans using déclarations pour s'assurer que leur Close() les méthodes sont appelées lorsqu'elles sont terminées.

D'après ce que j'ai vu, les déclarations XML ne sont jamais persistantes lors de l'utilisation d'une colonne XML. Même sans utiliser .NET et en utilisant simplement cette instruction d'insertion SQL directe, par exemple, la déclaration XML n'est pas enregistrée dans la base de données avec le XML :

Insert Into TestData(Xml) Values ('<?xml version="1.0" encoding="UTF-8"?><Test/>');

Maintenant, en ce qui concerne la question de l'OP, l'objet à sérialiser doit encore être converti en une structure XML à partir de MyMessage objet et XmlSerializer est encore nécessaire pour cela. Cependant, au pire, au lieu de sérialiser en une chaîne, le message pourrait plutôt être sérialisé en un XmlDocument - qui peut ensuite être passé à SqlXml via un nouveau XmlNodeReader - éviter un voyage de désérialisation/sérialisation vers une chaîne. (Voir http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx pour plus de détails et un exemple .)

Tout ici a été développé et testé avec .NET 4.0 et SQL Server 2008 R2.

Veuillez ne pas gaspiller en exécutant XML via des conversions supplémentaires (dé-désérialisations et sérialisations - vers DOM, chaînes ou autres), comme indiqué dans d'autres réponses ici et ailleurs.