为什么这段代码变慢了?

4
我目前正在将一些Access数据库转换为Xml文件。我之前已经做过这个,并且还保存了以前项目的代码。然而,这段代码不能让我随心所欲地构建xml,而这正是我这次需要做的。我正在使用XDocumentfor循环实现这一点,但在几千行数据后,速度变得非常缓慢。
阅读关于XDocument如何工作的资料告诉我,XElement.Add实际上会复制整个xml代码,并将新元素添加到它将所有内容粘贴回文件中的位置。如果这是真的,那可能就是问题所在。
以下是读取和写入来自Access到Xml的部分内容,请查看并看看是否有任何方法可以解决这个问题。转换一个具有27列和12,256行的数据库需要近30分钟,而一个仅有500行的较小数据库则需要大约5秒钟。
private void ReadWrite(string file)
{
    using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source={0}", pathAccess)))
    {
        _Connection.Open();
        //Gives me values from the AccessDB: tableName, columnName, colCount, rowCount and listOfTimeStamps.
        GetValues(pathAccess);

        XDocument doc = new XDocument(new XDeclaration("1.0", "utf-8", "true"), new XElement(tableName));
        for (int rowInt = 0; rowInt < rowCount; rowInt++)
        {
            XElement item = new XElement("Item", new XAttribute("Time", listOfTimestamps[rowInt].ToString().Replace(" ", "_")));
            doc.Root.Add(item);

            //colCount"-1" prevents the timestamp from beeing written again.
            for (int colInt = 0; colInt < colCount - 1; colInt++)
            {
                using (OleDbCommand cmnd = new OleDbCommand(string.Format("SELECT {0} FROM {1} Where TimeStamp = #{2}#", columnName[colInt] , tableName, listOfTimestamps[rowInt]), _Connection))
                {
                    XElement value = new XElement(columnName[colInt], cmnd.ExecuteScalar().ToString());
                    item.Add(value);
                }
            }
            //Updates progressbar
            backgroundWorker1.ReportProgress(rowInt);
        }
        backgroundWorker1.ReportProgress(0);
        doc.Save(file);
    }
}

这是我旧转换器的代码。无论数据库的大小如何,该代码都不会受到太大影响,12556个数据库只需要一秒钟即可转换。可能有一种方法可以将这两个合并吗?
public void ReadWrite2(string file)
{
    DataSet dataSet = new DataSet();
    using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source={0}", file)))
    {
        _Connection.Open();

        DataTable schemaTable = _Connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });

        foreach (DataRow dataTableRow in schemaTable.Rows)
        {
            string tableName = dataTableRow["Table_Name"].ToString();

            DataTable dataTable = dataSet.Tables.Add(tableName);
            using (OleDbCommand readRows = new OleDbCommand("SELECT * from " + tableName, _Connection))
            {
                OleDbDataAdapter adapter = new OleDbDataAdapter(readRows);
                adapter.Fill(dataTable);
            }
        }
    }
    dataSet.WriteXml(file.Replace(".mdb", ".xml"));
}
编辑:为澄清起见,应用程序在执行时会变慢。无论数据库有多大,前500个都需要5秒钟。

更新:好的,我经过周末回来了,现在我在代码中做了一个小调整,通过在一个循环中用值填充一个锯齿形数组,并在另一个循环中将其写入以分离读取和写入。这证明了我的理论是错误的,实际上是读取需要花费大量时间。有什么办法可以在循环内填充数组而不需要访问数据库吗?

更新2:在切换到DataReader.Read()循环并立即收集所有数据后,这就是最终结果。

public void ReadWrite3(string Save, string Load)
    {
        using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source={0}", Load)))
        {
            _Connection.Open();
            GetValues(_Connection);

            _Command = new OleDbCommand(String.Format("SELECT {0} FROM {1}", strColumns, tables), _Connection);
            XDocument doc = new XDocument(new XDeclaration("1.0", "utf-8", "true"), new XElement("plmslog", new XAttribute("machineid", root)));
            using (_DataReader = _Command.ExecuteReader())
            {
                for (int rowInt = 0; _DataReader.Read(); rowInt++ )
                {
                    for (int logInt = 0; logInt < colCount; logInt++)
                    {

                        XElement log = new XElement("log");
                        doc.Root.Add(log);

                        elementValues = updateElementValues(rowInt, logInt);

                        for (int valInt = 0; valInt < elements.Length; valInt++)
                        {
                            XElement value = new XElement(elements[valInt], elementValues[valInt]);
                            log.Add(value);
                        }
                    }
                }
            }
            doc.Save(Save);
        }
    }

你尝试过对代码进行分析以查看瓶颈在哪里吗? - ChrisBint
@ChrisBint 没有,我可以不使用任何第三方工具或软件来完成吗? - Hjalmar Z
3个回答

3

请原谅我,但我认为您让自己的生活变得比必要的更加复杂。如果您使用 OleDbDataReader 对象,您可以打开它并逐行读取Access表格数据,而无需将行数据缓存到数组中(因为您已经在 DataReader 中拥有它)。

例如,我有一些示例数据:

dbID    dbName  dbCreated
----    ------  ---------
bar     barDB   2013-04-08 14:19:27
foo     fooDB   2013-04-05 11:23:02

以下代码遍历表格...
static void Main(string[] args)
{
    OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Documents and Settings\Administrator\Desktop\Database1.accdb;");
    conn.Open();

    OleDbCommand cmd = new OleDbCommand("SELECT * FROM myTable", conn);
    OleDbDataReader rdr = cmd.ExecuteReader();

    int rowNumber = 0;
    while (rdr.Read())
    {
        rowNumber++;
        Console.WriteLine("Row " + rowNumber.ToString() + ":");
        for (int colIdx = 0; colIdx < rdr.FieldCount; colIdx++)
        {
            string colName = rdr.GetName(colIdx);
            Console.WriteLine("    rdr[\"" + colName + "\"]: " + rdr[colName].ToString());
        }
    }
    rdr.Close();
    conn.Close();

    Console.WriteLine("Done.");
}

...并产生结果...

Row 1:
    rdr["dbID"]: foo
    rdr["dbName"]: fooDB
    rdr["dbCreated"]: 2013-04-05 11:23:02
Row 2:
    rdr["dbID"]: bar
    rdr["dbName"]: barDB
    rdr["dbCreated"]: 2013-04-08 14:19:27
Done.

谢谢!这个速度快多了,我可以在不到5秒的时间内转换大型数据库!唯一的问题似乎是,如果我尝试更新进度条,表单会冻结。不过这不是什么大问题,因为执行速度不慢到需要进度条。再次感谢!我会更新问题以展示最终结果。 - Hjalmar Z

2

您正在从嵌套循环内部(所有行和列)访问数据库

 using (OleDbCommand cmnd = new OleDbCommand(string.Format("SELECT {0} FROM {1} 

你最好将数据保存在数组或集合中,然后再访问数据库。


我考虑过这个问题,但我不确定它是否能解决减速的问题,因为我认为这与写入有关而不是读取。不过尝试一下也无妨,感谢您的建议! - Hjalmar Z
更新:好的,周末之后我回来了,我对代码进行了小调整,通过在一个循环中用值填充一个嵌套数组并在另一个循环中写入它们来分离读取和写入。这证明了我的理论是错误的,实际上是读取需要花费很多时间。你是对的。你有什么想法可以在循环内部填充数组而不需要访问数据库吗?我认为你说的有道理。 - Hjalmar Z
如果这个问题已经解决,您可以标记正确的答案,谢谢。 - Danahi

1
简单的数学计算可以告诉你原因。 (数据量)
27 * 12256 = 330912
27 * 500 = 13500
330912 / 13500 = 24512
所以你的大声明要大24512倍!
(时间方面)
30*60 = 1800
1800 / 5 = 360
所以你的时间要大360倍!
你可以看到你的代码似乎并没有做什么坏事。

1
这难道不会导致代码以恒定的速度变慢吗?我之前尝试使用 OleDbDataReaderwhile(dataReader.read) 循环进行实验,这导致了一个恒定的速度,速度取决于数据库中的数据量。 - Hjalmar Z

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