views:

1892

answers:

3

I'm currently searching for an easy way to serialize objects (in C# 3).

I googled some examples and came up with something like:

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());

After reading this question I asked myself, why not using StringWriter? It seems much easier.

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

Another Problem was, that the first example generated XML I could not just write into an XML column of SQL Server 2005 DB.

The first question is: Is there a reason why I shouldn't use StringWriter to serialize an Object when I need it as a string afterwards? I never found a result using StringWriter when googling.

The second is, of course: If you should not do it with StringWriter (for whatever reasons), which would be a good and correct way?


Addition:

As it was already mentioned by both answers, I'll further go into the XML to DB problem.

When writing to the Database I got the following exception:

System.Data.SqlClient.SqlException: XML parsing: line 1, character 38, unable to switch the encoding

For string

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

I took the string created from the XmlTextWriter and just put as xml there. This one did not work (neither with manual insertion into the DB).

Afterwards I tried manual insertion (just writing INSERT INTO ... ) with encoding="utf-16" which also failed. Removing the encoding totally worked then. After that result I switched back to the StringWriter code and viola - it worked.

Problem: I don't really understand why.

at Christian Hayter: With those tests I'm not sure that I have to use utf-16 to write to the DB. Wouldn't setting the encoding to UTF-16 (in the xml tag) work then?

+6  A: 

One problem with StringWriter is that by default it doesn't let you set the encoding which it advertises - so you can end up with an XML document advertising its encoding as UTF-16, which means you need to encode it as UTF-16 if you write it to a file. I have a small class to help with that though:

public sealed class StringWriterWithEncoding : StringWriter
{
    private readonly Encoding encoding;

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

    public override Encoding Encoding
    {
        get { return encoding; }
    }
}

Or if you only need UTF-8 (which is all I often need):

public sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

As for why you couldn't save your XML to the database - you'll have to give us more details about what happened when you tried, if you want us to be able to diagnose/fix it.

Jon Skeet
I went into more detail for the database problem now. See question.
StampedeXV
+2  A: 

When serialising an XML document to a .NET string, the encoding must be set to UTF-16. Strings are stored as UTF-16 internally, so this is the only encoding that makes sense. If you want to store data in a different encoding, you use a byte array instead.

SQL Server works on a similar principle; any string passed into an xml column must be encoded as UTF-16. SQL Server will reject any string where the XML declaration does not specify UTF-16. If the XML declaration is not present, then the XML standard requires that it default to UTF-8, so SQL Server will reject that as well.

Bearing this in mind, here are some utility methods for doing the conversion.

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

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

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

    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Encoding = new UnicodeEncoding(false, false); // no BOM in a .NET string
    settings.Indent = false;
    settings.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 null;
    }

    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);
        }
    }
}
Christian Hayter
See question addition. I don't understand my test results, it _seems_ to contradict your statement that the DB always wants/takes/needs UTF-16.
StampedeXV
You *don't* have to encode as UTF-16 - but you have to make sure that the encoding you use matches what the `StringWriter` expects. See my answer. The internal storage format is irrelevant here.
Jon Skeet
ok that I understand. In my new example: leaving the encoding completely out made the DB decide for itself which encoding was used - thats why it worked. Do I understand it correct now?
StampedeXV
or as Christian sais: it decides which is used and then converts it to UTF-16
StampedeXV
I don't know for sure what heuristics the SQL Server XML parser is using. I would *hope* that it uses the official W3C rules (http://www.w3.org/TR/2000/REC-xml-20001006#sec-guessing-no-ext-info), but it could be doing something else.
Christian Hayter
+1  A: 

First of all, beware of finding old examples. You've found one that uses XmlTextWriter, which is deprecated as of .NET 2.0. XmlWriter.Create should be used instead.

Here's an example of serializing an object into an XML column:

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();
                }
            }
        }
    }
}
John Saunders