[c#] XML 직렬화에 StringWriter 사용

현재 개체를 직렬화하는 쉬운 방법을 찾고 있습니다 (C # 3).

몇 가지 예를 검색하여 다음과 같은 결과를 얻었습니다.

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

질문을 읽은 후 StringWriter를 사용하지 않는 이유는 무엇입니까? 훨씬 쉬워 보입니다.

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

또 다른 문제는 첫 번째 예제에서 생성 된 XML이 SQL Server 2005 DB의 XML 열에 쓸 수 없다는 것입니다.

첫 번째 질문은 다음과 같습니다. 나중에 문자열로 필요할 때 StringWriter를 사용하여 개체를 직렬화하면 안되는 이유가 있습니까? 인터넷 검색을 할 때 StringWriter를 사용하여 결과를 찾지 못했습니다.

두 번째는 물론입니다. 어떤 이유로 든 StringWriter를 사용하지 말아야한다면 어떤 방법이 좋을까요?


덧셈:

두 답변에서 이미 언급했듯이 XML to DB 문제에 대해 더 자세히 살펴 보겠습니다.

데이터베이스에 쓸 때 다음 예외가 발생했습니다.

System.Data.SqlClient.SqlException : XML 구문 분석 : 줄 1, 문자 38, 인코딩을 전환 할 수 없습니다.

문자열

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

XmlTextWriter에서 만든 문자열을 가져 와서 거기에 xml로 넣었습니다. 이것은 작동하지 않았습니다 (DB에 수동으로 삽입하지 않음).

그 후 수동 삽입 (INSERT INTO … 작성)을 encoding = “utf-16″으로 시도했지만 실패했습니다. 그런 다음 인코딩을 완전히 제거했습니다. 그 결과 나는 StringWriter 코드로 다시 전환하고 짜잔-작동했습니다.

문제 : 나는 그 이유를 정말로 이해하지 못합니다.

at Christian Hayter : 이러한 테스트를 통해 DB에 쓰기 위해 utf-16을 사용해야할지 모르겠습니다. 인코딩을 UTF-16 (xml 태그에서)으로 설정하면 작동하지 않습니까?



답변

<TL; DR> 문제는 사실 다소 간단합니다. 선언 된 인코딩 (XML 선언에서)이 입력 매개 변수의 데이터 유형과 일치하지 않습니다. 수동으로 추가 한 경우 <?xml version="1.0" encoding="utf-8"?><test/>문자열로, 다음은 선언 SqlParameter형으로 SqlDbType.Xml또는 SqlDbType.NVarChar당신에게 “인코딩을 전환 할 수 없습니다”오류를 줄 것이다. 그런 다음 T-SQL을 통해 수동으로 삽입 할 때 선언 된 인코딩을로 전환했기 때문에 문자열 utf-16을 명확하게 삽입했습니다 VARCHAR(대문자 “N”접두어가 없으므로 UTF-8과 같은 8 비트 인코딩). NVARCHAR문자열이 아닙니다 (대문자 “N”이 접두사로 붙으므로 16 비트 UTF-16 LE 인코딩).

수정은 다음과 같이 간단해야합니다.

  1. 첫 번째 경우에는 다음과 같은 선언을 추가 할 때 encoding="utf-8" XML 선언을 추가하지 마십시오.
  2. 두 번째 경우에는 다음과 같은 선언을 추가 할 때 encoding="utf-16":
    1. 단순히 XML 선언을 추가하지 마십시오. 또는
    2. 입력 매개 변수 유형에 “N”을 추가하기 만하면됩니다 : SqlDbType.NVarChar대신 SqlDbType.VarChar🙂 (또는를 사용하여 전환 할 수도 있습니다 SqlDbType.Xml)

(자세한 답변은 아래 참조)


여기에있는 모든 답변은 지나치게 복잡하고 불필요합니다 (각각 Christian의 답변과 Jon의 답변에 대한 121 개 및 184 개의 찬성 투표에 관계없이). 작동하는 코드를 제공 할 수 있지만 실제로 질문에 대답하는 사람은 없습니다. 문제는 아무도 진정으로 질문을 이해하지 못했다는 것입니다. 궁극적으로 SQL Server의 XML 데이터 유형이 어떻게 작동하는지에 대한 것입니다. 이 두 명의 똑똑한 사람을 상대로 한 것은 아니지만이 질문은 XML로 직렬화하는 것과는 거의 관련이 없습니다. XML 데이터를 SQL Server에 저장하는 것은 여기에 암시 된 것보다 훨씬 쉽습니다.

SQL Server에서 XML 데이터를 만드는 방법에 대한 규칙을 따르는 한 XML이 어떻게 생성되는지는 실제로 중요하지 않습니다. 이 질문에 대한 대답에 대한 자세한 설명 (아래에 설명 된 요점을 설명하는 작업 예제 코드 포함)이 있습니다. XML을 SQL Server에 삽입 할 때 “인코딩을 전환 할 수 없습니다”오류를 해결하는 방법 이지만 기본 사항은 다음과 같습니다.

  1. XML 선언은 선택 사항입니다.
  2. XML 데이터 유형은 문자열을 항상 UCS-2 / UTF-16 LE로 저장합니다.
  3. XML이 UCS-2 / UTF-16 LE이면 다음을 수행합니다.
    1. 데이터를 NVARCHAR(MAX)또는 XML/ SqlDbType.NVarChar(maxsize = -1) 또는 SqlDbType.Xml으로 전달하거나 문자열 리터럴을 사용하는 경우 대문자 “N”접두사를 붙여야합니다.
    2. XML 선언을 지정하는 경우 “UCS-2″또는 “UTF-16″이어야합니다 (실제 차이는 없음).
  4. XML이 8 비트로 인코딩 된 경우 (예 : “UTF-8″/ “iso-8859-1″/ “Windows-1252”) 다음을 수행합니다.
    1. 인코딩이 데이터베이스의 기본 데이터 정렬에 지정된 코드 페이지와 다른 경우 XML 선언을 지정해야합니다.
    2. 데이터를 VARCHAR(MAX)/ SqlDbType.VarChar(maxsize = -1) 로 전달해야합니다 . 또는 문자열 리터럴을 사용하는 경우 대문자 “N”을 접두사로 사용 하지 않아야 합니다 .
    3. 어떤 8 비트 인코딩이 사용 되든 XML 선언에 명시된 “인코딩”은 바이트의 실제 인코딩과 일치해야합니다.
    4. 8 비트 인코딩은 XML 데이터 유형에 의해 UTF-16 LE로 변환됩니다.

점은 염두에 위에서 설명한, 함께 하고 주어진 .NET에서 문자열이 있음을 항상 UTF-16 LE / UCS-2 LE, 우리는 당신의 질문에 대답 할 수 있습니다 (인코딩 측면에서 그 사이에는 차이가 없다)

나중에 문자열로 필요할 때 StringWriter를 사용하여 Object를 직렬화하면 안되는 이유가 있습니까?

아니요, 귀하의 StringWriter코드는 괜찮은 것 같습니다 (적어도 질문의 두 번째 코드 블록을 사용하는 제한된 테스트에서 문제가 없음).

인코딩을 UTF-16 (xml 태그에서)으로 설정하면 작동하지 않습니까?

XML 선언을 제공 할 필요는 없습니다. 누락 된 경우 문자열을 NVARCHAR(예 SqlDbType.NVarChar) 또는 XML(예 SqlDbType.Xml) 로 SQL Server에 전달 하면 인코딩이 UTF-16 LE로 간주됩니다 . 인코딩은 VARCHAR(예 🙂 로 전달되는 경우 기본 8 비트 코드 페이지로 간주됩니다 SqlDbType.VarChar. 비표준 ASCII 문자 (즉, 값 128 이상)가 있고로 전달되는 경우 VARCHAR“?”가 표시 될 수 있습니다. BMP 문자 및 “??” SQL Server와 같은 보조 문자의 경우 UTF-16 문자열을 .NET에서 현재 데이터베이스 코드 페이지의 8 비트 문자열로 변환 한 후 다시 UTF-16 / UCS-2로 변환합니다. 그러나 오류가 발생해서는 안됩니다.

반면에 XML 선언을 지정하는 경우 일치하는 8 비트 또는 16 비트 데이터 형식을 사용하여 SQL Server에 전달 해야합니다 . 당신이 선언은 인코딩이 없다는 그래서 만약 하나 UCS-2, UTF-16, 당신은 있어야 로 전달 SqlDbType.NVarChar또는 SqlDbType.Xml. 또는, 당신은 인코딩 (즉, 8 비트 옵션 중 하나입니다한다는 선언이있는 경우 UTF-8, Windows-1252, iso-8859-1, 등), 당신은 해야한다 등의 전달을 SqlDbType.VarChar. 선언 된 인코딩을 적절한 8 비트 또는 16 비트 SQL Server 데이터 형식과 일치시키지 않으면 “인코딩을 전환 할 수 없습니다”오류가 발생합니다.

예를 들어, StringWriter기반 직렬화 코드를 사용하여 XML의 결과 문자열을 인쇄하고 SSMS에서 사용했습니다. 아래에서 볼 수 있듯이 XML 선언이 포함되어 있습니다 ( 좋아 StringWriter하는 옵션이 없기 때문에 ). 올바른 SQL Server 데이터 유형으로 문자열을 전달하는 한 문제가되지 않습니다.OmitXmlDeclarationXmlWriter

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

보시다시피, BMP 코드 포인트 U + 1234이고 ?보조 문자 코드 포인트 U + 1F638 인 경우 표준 ASCII 이상의 문자도 처리합니다 . 그러나 다음은 다음과 같습니다.

-- No upper-case "N" prefix on the string literal, hence VARCHAR:
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에 비표준 ASCII 문자가 포함 된 경우 데이터 손실을 방지하므로 XML 선언 을 유지 하고 제거하는 것보다 선호 됩니다. 예를 들면 :

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

보시다시피 이번에는 오류가 없지만 이제 데이터 손실이 있습니다 ?.


답변

한 가지 문제 StringWriter는 기본적으로 광고하는 인코딩을 설정할 수 없다는 것입니다. 따라서 인코딩을 UTF-16으로 광고하는 XML 문서로 끝날 수 있습니다. 즉, 다음과 같은 경우 UTF-16으로 인코딩해야합니다. 파일에 씁니다. 그래도 도움이되는 소규모 수업이 있습니다.

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

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

또는 UTF-8 만 필요한 경우 (종종 필요) :

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

XML을 데이터베이스에 저장할 수없는 이유에 관해서는 우리가 진단 / 수정할 수 있도록하려면 시도했을 때 발생한 일에 대한 자세한 정보를 제공해야합니다.


답변

XML 문서를 .NET 문자열로 직렬화 할 때 인코딩을 UTF-16으로 설정해야합니다. 문자열은 내부적으로 UTF-16으로 저장되므로 이것이 의미있는 유일한 인코딩입니다. 다른 인코딩으로 데이터를 저장하려면 대신 바이트 배열을 사용합니다.

SQL Server는 유사한 원리로 작동합니다. 열에 전달 된 모든 문자열 xml은 UTF-16으로 인코딩되어야합니다. SQL Server는 XML 선언이 UTF-16을 지정하지 않는 모든 문자열을 거부합니다. XML 선언이 없으면 XML 표준에 따라 기본값이 UTF-8로 설정되어 있으므로 SQL Server에서도이를 거부합니다.

이를 염두에두고 변환을 수행하는 몇 가지 유틸리티 방법이 있습니다.

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


답변

우선, 오래된 사례를 찾아야합니다. XmlTextWriter.NET 2.0에서 더 이상 사용되지 않는 을 사용 하는 것을 찾았습니다 . XmlWriter.Create대신 사용해야합니다.

다음은 객체를 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();
                }
            }
        }
    }
}


답변

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


답변

다른 곳에서 다루었을 수도 있지만 XML 소스의 인코딩 줄을 ‘utf-16’으로 변경하면 XML을 SQL Server ‘xml’데이터 형식에 삽입 할 수 있습니다.

using (DataSetTableAdapters.SQSTableAdapter tbl_SQS = new DataSetTableAdapters.SQSTableAdapter())
{
    try
    {
        bodyXML = @"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test></test>";
        bodyXMLutf16 = bodyXML.Replace("UTF-8", "UTF-16");
        tbl_SQS.Insert(messageID, receiptHandle, md5OfBody, bodyXMLutf16, sourceType);
    }
    catch (System.Data.SqlClient.SqlException ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

결과는 모든 XML 텍스트가 ‘xml’데이터 유형 필드에 삽입되지만 ‘header’행은 제거됩니다. 결과 기록에서 보는 것은

<test></test>

“Answered”항목에 설명 된 직렬화 방법을 사용하는 것은 대상 필드에 원래 헤더를 포함하는 방법이지만 결과적으로 나머지 XML 텍스트는 XML <string></string>태그로 묶입니다 .

코드의 테이블 어댑터는 Visual Studio 2013 “새 데이터 원본 추가 : 마법사를 사용하여 자동으로 빌드 된 클래스입니다. Insert 메서드에 대한 5 개의 매개 변수는 SQL Server 테이블의 필드에 매핑됩니다.


답변