it-swarm.dev

Utilizzo di StringWriter per la serializzazione XML

Attualmente sto cercando un modo semplice per serializzare gli oggetti (in C # 3).

Ho cercato su google alcuni esempi e ho trovato qualcosa come:

MemoryStream memoryStream = new MemoryStream ( );
XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) );
XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 );
xs.Serialize ( xmlTextWriter, myObject);
string result = Encoding.UTF8.GetString(memoryStream .ToArray());

Dopo aver letto questo domanda Mi sono chiesto, perché non usare StringWriter? Sembra molto più facile.

XmlSerializer ser = new XmlSerializer(typeof(MyObject));
StringWriter writer = new StringWriter();
ser.Serialize(writer, myObject);
serializedValue = writer.ToString();

Un altro problema era che il primo esempio generava XML che non potevo semplicemente scrivere in una colonna XML di SQL Server 2005 DB.

La prima domanda è: c'è un motivo per cui non dovrei usare StringWriter per serializzare un oggetto quando ne ho bisogno come stringa in seguito? Non ho mai trovato un risultato usando StringWriter quando google.

Il secondo è, ovviamente: se non dovessi farlo con StringWriter (per qualsiasi motivo), quale sarebbe un modo valido e corretto?


Aggiunta:

Come è stato già menzionato da entrambe le risposte, entrerò ulteriormente nel problema da XML a DB.

Durante la scrittura nel database ho ricevuto la seguente eccezione:

System.Data.SqlClient.SqlException: analisi XML: riga 1, carattere 38, impossibile cambiare la codifica

Per stringa

<?xml version="1.0" encoding="utf-8"?><test/>

Ho preso la stringa creata da XmlTextWriter e ho appena inserito come xml lì. Questo non ha funzionato (né con l'inserimento manuale nel DB).

Successivamente ho provato l'inserimento manuale (solo scrivendo INSERT INTO ...) con encoding = "utf-16" che ha fallito. La rimozione della codifica ha funzionato totalmente allora. Dopo quel risultato sono tornato al codice StringWriter e voilà - ha funzionato.

Problema: non capisco davvero perché.

a Christian Hayter: Con quei test non sono sicuro di dover usare utf-16 per scrivere sul DB. L'impostazione della codifica su UTF-16 (nel tag xml) non funzionerebbe allora?

92
StampedeXV

<TL; DR> Il problema è piuttosto semplice, in realtà: non stai abbinando la codifica dichiarata (nella dichiarazione XML) con il tipo di dati dell'input parametro. Se hai aggiunto manualmente <?xml version="1.0" encoding="utf-8"?><test/> Alla stringa, quindi dichiarare SqlParameter di tipo SqlDbType.Xml O SqlDbType.NVarChar Ti darebbe l '"impossibile cambiare la codifica "errore. Quindi, quando si inserisce manualmente tramite T-SQL, poiché è stata cambiata la codifica dichiarata in utf-16, Si stava chiaramente inserendo una stringa VARCHAR (non preceduta da una "N" maiuscola, quindi una codifica a 8 bit, come UTF-8) e non una stringa NVARCHAR (preceduta da una "N" maiuscola, da cui la codifica LE a 16 bit UTF-16).

La correzione avrebbe dovuto essere semplice come:

  1. Nel primo caso, quando si aggiunge la dichiarazione che afferma encoding="utf-8": Semplicemente non aggiungere la dichiarazione XML.
  2. Nel secondo caso, quando si aggiunge la dichiarazione che afferma encoding="utf-16":
    1. semplicemente non aggiungere la dichiarazione XML, OR
    2. aggiungi semplicemente una "N" al tipo di parametro di input: SqlDbType.NVarChar invece di SqlDbType.VarChar :-) (o eventualmente passa a usare SqlDbType.Xml)

(La risposta dettagliata è sotto)


Tutte le risposte qui sono troppo complicate e non necessarie (indipendentemente dai 121 e 184 voti positivi per le risposte di Christian e Jon, rispettivamente). Potrebbero fornire un codice funzionante, ma nessuno di loro in realtà risponde alla domanda. Il problema è che nessuno ha veramente capito la domanda, che alla fine riguarda il funzionamento del tipo di dati XML in SQL Server. Nulla contro quelle due persone chiaramente intelligenti, ma questa domanda non ha nulla a che fare con la serializzazione in XML. Il salvataggio dei dati XML in SQL Server è molto più semplice di quello che è implicito qui.

Non importa in che modo viene prodotto l'XML purché si seguano le regole su come creare dati XML in SQL Server. Ho una spiegazione più approfondita (incluso il codice di esempio funzionante per illustrare i punti descritti di seguito) in una risposta a questa domanda: Come risolvere l'errore "impossibile cambiare la codifica" quando si inserisce XML in SQL Server , ma le basi sono:

  1. La dichiarazione XML è facoltativa
  2. Il tipo di dati XML memorizza le stringhe sempre come UCS-2/UTF-16 LE
  3. Se il tuo XML è UCS-2/UTF-16 LE, allora tu:
    1. passa i dati come NVARCHAR(MAX) o XML/SqlDbType.NVarChar (maxsize = -1) o SqlDbType.Xml, oppure se usi una stringa letterale allora devi essere preceduto da una "N" maiuscola.
    2. se si specifica la dichiarazione XML, deve essere "UCS-2" o "UTF-16" (qui nessuna differenza reale)
  4. Se il tuo XML è codificato a 8 bit (ad es. "UTF-8"/"iso-8859-1"/"Windows-1252"), allora:
    1. è necessario specificare la dichiarazione XML SE la codifica è diversa dalla tabella codici specificata dalla Fascicolazione predefinita del database
    2. devi inserire i dati come VARCHAR(MAX)/SqlDbType.VarChar (maxsize = -1), oppure se usi una stringa letterale allora devi not avere come prefisso una "N" maiuscola.
    3. Qualunque sia la codifica a 8 bit utilizzata, la "codifica" indicata nella dichiarazione XML deve corrispondere alla codifica effettiva dei byte.
    4. La codifica a 8 bit verrà convertita in UTF-16 LE dal tipo di dati XML

Tenendo presente i punti sopra indicati, e dato che le stringhe in .NET sono sempre UTF-16 LE/UCS-2 LE (non c'è differenza tra quelli in termini di codifica), possiamo rispondere alle tue domande:

C'è un motivo per cui non dovrei usare StringWriter per serializzare un oggetto quando ne ho bisogno come stringa in seguito?

No, il tuo codice StringWriter sembra andare bene (almeno non vedo problemi nei miei test limitati usando il secondo blocco di codice della domanda).

L'impostazione della codifica su UTF-16 (nel tag xml) non funzionerebbe allora?

Non è necessario fornire la dichiarazione XML. Quando manca, si presume che la codifica sia UTF-16 LE if si passa la stringa in SQL Server come NVARCHAR (ovvero SqlDbType.NVarChar) O XML (ovvero SqlDbType.Xml). Si presume che la codifica sia la pagina di codice predefinita a 8 bit se si passa come VARCHAR (ovvero SqlDbType.VarChar). Se hai caratteri ASCII non standard (ovvero valori 128 e superiori) e stai passando come VARCHAR, probabilmente vedrai "?" per BMP caratteri e "??" per i caratteri supplementari in quanto SQL Server convertirà la stringa UTF-16 da .NET in una stringa di 8 bit della codepage del database corrente prima di riconvertirla in UTF-16/UCS-2, ma non dovresti riscontrare errori.

D'altra parte, se si specifica la dichiarazione XML, allora must si passa a SQL Server utilizzando il tipo di dati corrispondente a 8 o 16 bit. Quindi se hai una dichiarazione che afferma che la codifica è UCS-2 o UTF-16, allora must passi come SqlDbType.NVarChar O SqlDbType.Xml. Oppure, se hai una dichiarazione che afferma che la codifica è una delle opzioni a 8 bit (es. UTF-8, Windows-1252, iso-8859-1, Ecc.), Allora tu must passa come SqlDbType.VarChar. La mancata corrispondenza della codifica dichiarata con il tipo di dati SQL Server a 8 o 16 bit corretto comporterà l'errore "impossibile cambiare la codifica" che si stava verificando.

Ad esempio, usando il tuo codice di serializzazione basato su StringWriter, ho semplicemente stampato la stringa risultante dell'XML e l'ho usata in SSMS. Come puoi vedere di seguito, la dichiarazione XML è inclusa (perché StringWriter non ha un'opzione per OmitXmlDeclaration come XmlWriter), il che non pone alcun problema finché passi la stringa in come tipo di dati SQL Server corretto:

-- 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>

Come puoi vedere, gestisce anche i caratteri oltre lo standard ASCII, dato che È BMP Codice Punto U + 1234 e ???? È Punto Codice carattere supplementare U + 1F638. Tuttavia, quanto segue:

-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ????</string>';

provoca il seguente errore:

Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding

Ergo, tutta questa spiegazione a parte, la soluzione completa alla tua domanda originale è:

Stavi chiaramente passando la stringa come SqlDbType.VarChar. Passa a SqlDbType.NVarChar E funzionerà senza la necessità di passare alla fase aggiuntiva di rimozione della dichiarazione XML. È preferibile mantenere SqlDbType.VarChar E rimuovere la dichiarazione XML perché questa soluzione eviterà la perdita di dati quando l'XML include caratteri ASCII non standard. Per esempio:

-- 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>

Come puoi vedere, questa volta non c'è nessun errore, ma ora c'è una perdita di dati ????.

1
Solomon Rutzky

Un problema con StringWriter è che per impostazione predefinita non ti consente di impostare la codifica che pubblicizza - quindi puoi finire con un documento XML che pubblicizza la sua codifica come UTF-16, il che significa che è necessario codificarlo come UTF-16 se lo si scrive in un file. Ho una piccola classe per aiutarlo però:

public sealed class StringWriterWithEncoding : StringWriter
{
    public override Encoding Encoding { get; }

    public StringWriterWithEncoding (Encoding encoding)
    {
        Encoding = encoding;
    }    
}

O se hai solo bisogno di UTF-8 (che è tutto ciò di cui ho spesso bisogno):

public sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Per quanto riguarda il motivo per cui non è stato possibile salvare l'XML nel database: dovrete fornirci maggiori dettagli su ciò che è accaduto quando avete provato, se volete che siamo in grado di diagnosticare/risolvere il problema.

203
Jon Skeet

Quando si serializza un documento XML su una stringa .NET, la codifica deve essere impostata su UTF-16. Le stringhe sono memorizzate come UTF-16 internamente, quindi questa è l'unica codifica che ha senso. Se si desidera archiviare i dati in una codifica diversa, utilizzare invece un array di byte.

SQL Server funziona secondo un principio simile; qualsiasi stringa passata in una colonna xml deve essere codificata come UTF-16. SQL Server rifiuterà qualsiasi stringa in cui la dichiarazione XML non specifica UTF-16. Se la dichiarazione XML non è presente, lo standard XML richiede che sia predefinito UTF-8, quindi anche SQL Server lo rifiuterà.

Tenendo presente questo, ecco alcuni metodi di utilità per eseguire la conversione.

public static string Serialize<T>(T value) {

    if(value == null) {
        return null;
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlWriterSettings settings = new XmlWriterSettings()
    {
        Encoding = new UnicodeEncoding(false, false), // no BOM in a .NET string
        Indent = false,
        OmitXmlDeclaration = false
    };

    using(StringWriter textWriter = new StringWriter()) {
        using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
            serializer.Serialize(xmlWriter, value);
        }
        return textWriter.ToString();
    }
}

public static T Deserialize<T>(string xml) {

    if(string.IsNullOrEmpty(xml)) {
        return default(T);
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlReaderSettings settings = new XmlReaderSettings();
    // No settings need modifying here

    using(StringReader textReader = new StringReader(xml)) {
        using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
            return (T) serializer.Deserialize(xmlReader);
        }
    }
}
126
Christian Hayter

Prima di tutto, fai attenzione a trovare vecchi esempi. Ne hai trovato uno che utilizza XmlTextWriter, che è obsoleto a partire da .NET 2.0. XmlWriter.Create dovrebbe essere usato invece.

Ecco un esempio di serializzazione di un oggetto in una colonna XML:

public void SerializeToXmlColumn(object obj)
{
    using (var outputStream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(outputStream))
        {
            var serializer = new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj);
        }

        outputStream.Position = 0;
        using (var conn = new SqlConnection(Settings.Default.ConnectionString))
        {
            conn.Open();

            const string INSERT_COMMAND = @"INSERT INTO XmlStore (Data) VALUES (@Data)";
            using (var cmd = new SqlCommand(INSERT_COMMAND, conn))
            {
                using (var reader = XmlReader.Create(outputStream))
                {
                    var xml = new SqlXml(reader);

                    cmd.Parameters.Clear();
                    cmd.Parameters.AddWithValue("@Data", xml);
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}
19
John Saunders
public static T DeserializeFromXml<T>(string xml)
{
    T result;
    XmlSerializerFactory serializerFactory = new XmlSerializerFactory();
    XmlSerializer serializer =serializerFactory.CreateSerializer(typeof(T));

    using (StringReader sr3 = new StringReader(xml))
    {
        XmlReaderSettings settings = new XmlReaderSettings()
        {
            CheckCharacters = false // default value is true;
        };

        using (XmlReader xr3 = XmlTextReader.Create(sr3, settings))
        {
            result = (T)serializer.Deserialize(xr3);
        }
    }

    return result;
}
1
Mashudu Nemukuka