将varbinary转换为XML C#

3
我是一位有用的助手,可以为您翻译文本。
我有一个使用C#的WinForms应用程序,将文件保存到SQL Server数据库(版本2014),保存到varbinary(MAX)字段中。
保存功能如下:
 byte[] Bytefile;
        using (SqlConnection conn = new SqlConnection(DataHelper.GetConnection()))
        {
            conn.Open();
            DataTable dt = new DataTable();
            SqlCommand comm = new SqlCommand("Delete  T_Articale_Files where Artricle_id=" + ID, conn);

            comm.ExecuteNonQuery();
            foreach (string file in Directory.GetFiles(varFilePath))
            {

                using (var stream = new FileStream(Path.Combine(varFilePath, file), FileMode.Open, FileAccess.Read))
                {
                    using (var reader = new BinaryReader(stream))
                    {
                        Bytefile = reader.ReadBytes((int)stream.Length);
                    }
                }


                using (var sqlWrite = new SqlCommand("INSERT INTO T_Articale_Files (Artricle_id,FileName,FileData) Values(@ID,@FileName,@File)", conn))
                {
                    sqlWrite.Parameters.Add("@ID", SqlDbType.Int, 10).Value = ID;
                    sqlWrite.Parameters.Add("@FileName", SqlDbType.NVarChar, 50).Value = Path.GetFileName(file);
                    sqlWrite.Parameters.Add("@File", SqlDbType.VarBinary, file.Length).Value = Bytefile;
                    sqlWrite.ExecuteNonQuery();
                }
            }
        }

获取函数

 using (SqlConnection conn = new SqlConnection(DataHelper.GetConnection()))
        //   using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
        {
            conn.Open();
            DataTable dt = new DataTable();
            SqlCommand comm = new SqlCommand("SELECT id,FileName FROM T_Articale_Files WHERE Artricle_id = @varID", conn);
            comm.Parameters.AddWithValue("@varID", varID);
            dt.Load(comm.ExecuteReader());
            foreach (DataRow item in dt.Rows)
            {
                using (var sqlQuery = new SqlCommand(@"SELECT FileData FROM T_Articale_Files WHERE id = @ID", conn))
                {
                    sqlQuery.Parameters.AddWithValue("@ID", item["id"]);

                    using (var sqlQueryResult = sqlQuery.ExecuteReader())
                        while (sqlQueryResult.Read())
                        {

                            var blob = new Byte[(sqlQueryResult.GetBytes(0, 0, null, 0, int.MaxValue))];
                            sqlQueryResult.GetBytes(0, 0, blob, 0, blob.Length);
                            using (var fs = new FileStream(Path.Combine(varPathToNewLocation, item["FileName"].ToString()), FileMode.Create, FileAccess.Write))
                                fs.Write(blob, 0, blob.Length);
                        }
                }
            }

        }

目前运作良好。现在我被要求将数据库转换为 XML,以便于无法连接到服务器的个人电脑使用。

转换为 XML 的功能。

 var xmlFileData = "";
        DataSet ds = new DataSet();
        var tables = new[] { "V_Articale", "T_Articale", "T_City", "T_Classification", "T_Country", "T_Locations", "T_milishia", "T_Search", "T_statistics", "T_TerrorGroups", "T_Tribes", "T_Users", "T_Articale_Files" };
        foreach (var table in tables)
        {

            var query = "SELECT * FROM " + table;
            SqlConnection conn = GetConnection();
            SqlCommand cmd = new SqlCommand(query, conn);
            conn.Open();
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            DataTable dt = new DataTable(table);
            da.Fill(dt);
            conn.Close();
            conn.Dispose();
            ds.Tables.Add(dt);
            if(table== "T_Articale_Files")
            {
                foreach (DataRow item in dt.Rows)
                {
                    Byte[] file = GetBytes(item["FileData"].ToString());
                }
            }

        }
        xmlFileData = ds.GetXml();

除了二进制字段,IT技术相关内容运行良好。当二进制字段被转换为文本时存在问题。
输出的XML文件。
<T_Articale_Files>
<id>6</id>
<Artricle_id>1013</Artricle_id>
<FileName>falcon banner.jpg</FileName>
<FileData>/9j/4AAQSkZJRgABAgEASABIAAD/4QleRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUA</FileData>

当尝试将其转换回图像时,它会给我一个1kb的文件,里面是字符串而不是实际的图像。

是否有特殊的转换方法适用于二进制字段? 请建议。

谢谢。

编辑问题已解决,感谢 @grek40。 解决方案:转换为XML。

  var xmlstream = new StringWriter();
        ds.WriteXml(xmlstream, XmlWriteMode.WriteSchema);
        string xmlWithSchema = xmlstream.ToString();

其中ds是数据集

转换回文件

  private  void databaseFileRead(int varID, string varPathToNewLocation)
    {
         DataSet ds = new DataSet();
        ds.ReadXml(XMLpath);
        DataTable dt = new DataTable();
        dt = ds.Tables["T_Articale_Files"];
        DataView dv = new DataView(dt);
        dv.RowFilter = "Artricle_id=" + varID;

        if (dv.Count > 0)
        {
            foreach (DataRowView item in dv)
            {
                byte[] stringArray = (byte[])(item["FileData"]);
                File.WriteAllBytes(Path.Combine(Filepath, item["FileName"].ToString()), stringArray  ); // save image to disk


            }

        }

在处理二进制数据时,永远不要使用ToString()方法,因为它会破坏数据。最好使用Encoding.UTF8,并使用GetBytes()或GetString()方法;一个字符串/字符是两个字节,有一个受保护的属性来指示字符是一个字节还是两个字节。当你使用ToString()方法时,它可能会错误地确定字符是一个字符还是两个字符。使用UTF8将强制每个字符为一个字节。 - jdweng
如果你在讨论以下代码:if(table== "T_Articale_Files") { foreach (DataRow item in dt.Rows) { Byte[] file = GetBytes(item["FileData"].ToString()); } }我已经注释掉了这段代码,并使用了不包含它的方法。 - Maher Khalil
如果你不知道 GetBytes 是什么,它是一个静态方法,用于将字符串转换为字节数组。代码如下:static byte[] GetBytes(string str) { byte[] bytes = new byte[str.Length * sizeof(char)]; System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length); return bytes; } - Maher Khalil
@Maher Khalil 不是在你的“转换为XML”函数中吗?当你将varbinary(MAX)转换为Byte[]时。 - Viking
我已经注释掉了这部分代码,因为我不知道如何在转换为XML时实现它。如果table == "T_Articale_Files",则会遍历dt.Rows中的每一行,并将FileData列中的数据转换为字节数组。 - Maher Khalil
显示剩余7条评论
2个回答

2
为了对二进制数据进行可逆的字符串编码,您可以使用Base64编码。
public byte[] StrToByteArray(string str)
{
    return Convert.FromBase64String(str);
}

public string ByteArrToString(byte[] byteArr)
{
    return Convert.ToBase64String(byteArr);
}

将字节转换为存储在XML中的字符串,并在使用时从字符串中恢复字节。

最初,数据被正确地编写为XML。问题很可能出现在检索函数中。由于XML不包含模式信息,除非另有指示,否则它将把<FileData>视为文本。

为了允许正确的重新读取,您需要在读取时具有预定义的模式,或者必须使用表格编写模式:

dataSet.WriteXml(filenameOrStream, XmlWriteMode.WriteSchema)
// later read the xml and it will respect the schema information
dataSet.ReadXml(filenameOrStream);

不同方面的小样本:

var sourceDataSet = new DataSet();
var sourceTable = new DataTable("TableWithBinary");

sourceDataSet.Tables.Add(sourceTable);

sourceTable.Columns.Add("Id");
sourceTable.Columns.Add("File", typeof(byte[]));

sourceTable.Rows.Add(1, new byte[] { 1, 0, 2 });
sourceTable.Rows.Add(2, new byte[] { 1, 3, 2 });

// write option 1
string schema = sourceDataSet.GetXmlSchema();
string getxml = sourceDataSet.GetXml();

// write option 2
var writexmlstream = new StringWriter();
sourceDataSet.WriteXml(writexmlstream, XmlWriteMode.WriteSchema);
string writexmlWithSchema = writexmlstream.ToString();

// read wrong (missing schema)
var targetCorrupted = new DataSet();
targetCorrupted.ReadXml(new StringReader(getxml));

// read correct with schema in every xml file
var targetFromXmlWithSchema = new DataSet();
targetFromXmlWithSchema.ReadXml(new StringReader(writexmlWithSchema));

// read correct with separate schema definition and data
var targetFromXml = new DataSet();
targetFromXml.ReadXmlSchema(new StringReader(schema));
targetFromXml.ReadXml(new StringReader(getxml));

@MaherKhalil 你展示了你的输出XML,其中 <FileData> 部分已经损坏... 在生成XML时,只需使用 ByteArrToString<FileData> 的二进制数据转换为字符串即可。 - grek40
<fileData>是使用“将函数转换为XML(从表中选择*)”生成的,现在的问题是对于二进制表需要进行什么特殊处理。 - Maher Khalil
@MaherKhalil 实际上,如果你什么都不做,真的有问题吗?请检查FileData列的DataType。如果它是byte[],那么ds.GetXml()应该会自动具有适当的数据表示形式。也许你的问题更多地与读取数据有关,而不是写入数据... - grek40
我一开始太专注于编写部分了,现在应该会更好。请注意,您也可以为每个表结构单独编写XML模式(不要在每个数据文件中都包含它),然后加载数据。我会再编辑一次。 - grek40
1
@MaherKhalil 我认为你对stackoverflow的概念有点误解。我的回答展示了多种读/写xml作为字符串、TextReader/TextWriter以及还有使用函数的重载,这些函数可以接受Stream或者文件名参数。我并不关心你选择哪种方式与你的文件进行交互,我也不会为你编写你具体的代码 - 这是你和其他人的工作,对吧? - grek40
显示剩余2条评论

0

XML中二进制的标准格式是base64。我认为上述内容没有问题...

你必须记住,XML中禁止使用多个字符。你不能仅仅将二进制数据插入到两个XML标签之间。如果在你的二进制数据中恰好有一个<(或其他禁止字符)的数字代码,它就会出错。

你展示的字符串看起来很像一个base64编码。

试试这个:

DECLARE @string VARCHAR(100)='Hello World with forbidden characters (< & >)';
DECLARE @binary VARBINARY(MAX) = CAST(@string AS VARBINARY(MAX));
DECLARE @xml XML=(SELECT @string AS string, @binary AS bin FOR XML PATH('test'));

SELECT @xml;

--结果(包括编码实体和二进制隐式转换为base64)

<test>
  <string>Hello World with forbidden characters (&lt; &amp; &gt;)</string>
  <bin>SGVsbG8gV29ybGQgd2l0aCBmb3JiaWRkZW4gY2hhcmFjdGVycyAoPCAmID4p</bin>
</test>

--现在我们从XML中读取(实体已重新编码,二进制表示为HEX字符串,可以重新转换为以前的VARCHAR(MAX)

SELECT @xml.value('(/test/string)[1]','nvarchar(max)') AS TheStringAsIs
      ,@xml.value('(/test/bin)[1]','varbinary(max)') AS TheStringAsBinary_HEX
      ,CAST(@xml.value('(/test/bin)[1]','varbinary(max)') AS VARCHAR(100)) AS ReConverted

结果

The string *as-is*:  Hello World with forbidden characters (< & >)  
binary data HEX:     0x48656C6C6F20576F726C64207769746820666F7262696464656E206368617261637465727320283C2026203E29   
ReConverted:         Hello World with forbidden characters (< & >) 

这是 SQL 函数而不是 C#,另外它是一个文件,当它被转换时我不知道会出现什么字符。 - Maher Khalil
@MaherKhalil 这并不重要...原理是一样的。二进制可以存储任何东西。但必须编码以适应xml。 - Shnugo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接