高效将XML转换为Excel,选择一些字段

时间:2016-06-28 19:21:04

标签: c# xml excel

我的问题

我需要阅读1000000个XML文件,并且每个文件都会提取一些信息,然后使用这些信息创建一个电子表格。目前我有一个有效的代码,但是,它需要大约一个小时......我需要快速生成

XML文件示例

http://pastebin.com/M5uvVaYt(太大了)

我当前的代码

        string[] arquivosArr = Directory.GetFiles(@"D:\ALL_FILES", "*.xml", SearchOption.AllDirectories);
        List<string> arquivos = new List<string>(arquivosArr);
        XNamespace ns = "http://www.portalfiscal.inf.br/nfe";


        //EXCEL OBJ
        var excel = new Application();
        excel.DisplayAlerts = false;

        var workbooks = excel.Workbooks;
        var workbook = workbooks.Add(Type.Missing);
        var worksheets = workbook.Sheets;
        var worksheet = (Worksheet)worksheets[1];

        worksheet.Columns[58].NumberFormat = "@";

        var watch = System.Diagnostics.Stopwatch.StartNew();

        int i = 0;
        Parallel.ForEach(arquivos, arquivo =>            
        {
             try
            {
                var doc = XDocument.Load(arquivo);

                if (doc.Root.Name.LocalName == "nfeProc")
                {
                    var chave = doc.Descendants(ns + "chNFe").First().Value;
                    var itens = doc.Descendants(ns + "det");
                    //var info3 = .......
                    //var info4 = .......
                    //var info5 = .......
                    //var info6 = .......
                    //var info7 = .......
                    //var info8 = .......
                    //etc......



                    int starts = i;
                    Interlocked.Add(ref i, itens.Count());
                    foreach (var item in itens)
                    {
                        var data = new object[1, 58];
                        //data[0, 0] = .....
                        //data[0, 1] = .....
                        //data[0, 2] = .....
                        //data[0, 3] = .....
                        //data[0, 4] = .....
                        //data[0, 5] = .....
                        //data[0, 6] = .....
                        data[0, 27] = item.Attribute("nItem").Value;
                        data[0, 57] = chave;
                        var startCell = (Range)worksheet.Cells[(starts + 1), 1];
                        var endCell = (Range)worksheet.Cells[(starts + 1), 58];
                        var writeRange = worksheet.Range[startCell, endCell];
                        writeRange.Value2 = data;
                        starts++;
                    }


                        double perc = ((i + 1.00) / arquivos.Count) * 100;
                        Console.WriteLine("Add: " + (i + 1) + " (" + Math.Round(perc, 2) + "%)");                    


                }

            }
            catch (XmlException ex)
            {
                Console.WriteLine(ex.Message);
            }



            });

        watch.Stop();
        var elapsedMs = watch.ElapsedMilliseconds;
        Console.WriteLine(elapsedMs / 1000.0);



        workbook.SaveAs(@"D:\MY_INFO.xls");
        workbook.Close();
        excel.Quit();

我是C#的新手,所以我为我的代码道歉

2 个答案:

答案 0 :(得分:0)

一小时内有一百万个文件?你怎么期望比那更好?您当前正在处理277个文件!

你需要运行多个进程和/或机器写入单独的文件,然后在最后编译它们以实现任何重大改进。

答案 1 :(得分:0)

此问题可能更适合codereview,因为您的代码目前有效。话虽如此,我可以提出以下建议:

  1. 对于每个1000000文件,不要在Console.Writeline()Parallel.ForEach()!它是slowblocking

    相反,请考虑每十秒钟左右输出一次心跳消息,并从不会干扰XML处理线程的单独线程执行此操作。例如,请参阅here中的NonBlockingConsole

  2. 不是将每个XML文件加载到XDocument,而是可以使用XmlReader仅在给定时间内将XElement加载到内存中所需的最小值来流式传输每个文件,沿{ {3}}。这应该通过跳过不需要的object[,] table子树的构造并通过降低GC压力间接地提高性能。

    以下方法流式传输您的某个XML文件,并在const int ColumnLength = 58; const int ChaveIndex = 57; const int ItemIndex = 27; static bool TryExtractTable(string arquivo, out object[,] table) { XNamespace ns = "http://www.portalfiscal.inf.br/nfe"; var rootName = ns + "nfeProc"; var chaveName = ns + "chNFe"; var itemsName = ns + "det"; try { using (var reader = XmlReader.Create(arquivo)) { // Move to the root element, verify it's correct. if (!reader.ReadToElement() || reader.XName() != rootName) { table = null; return false; } string chaveValue = null; List<object> itemValues = new List<object>(); bool alreadyReadNext = false; while (alreadyReadNext || reader.Read()) { alreadyReadNext = false; if (reader.NodeType != XmlNodeType.Element) continue; var name = reader.XName(); if (chaveValue == null && name == chaveName) { chaveValue = ((XElement)XNode.ReadFrom(reader)).Value; // XNode.ReadFrom advances the reader to the next node after the end of the current element. // Thus a subsequent call to reader.Read() would skip this node, and so should not be made. alreadyReadNext = true; } else if (name == itemsName) { // Access the "nItem" attribute directly. var itemValue = reader["nItem"]; itemValues.Add(itemValue); } } if (itemValues.Count > 0) { var nRows = itemValues.Count; table = new object[nRows, ColumnLength]; for (int iRow = 0; iRow < nRows; iRow++) { table[iRow, ChaveIndex] = chaveValue; table[iRow, ItemIndex] = itemValues[iRow]; } return true; } } } catch (Exception ex) { Console.WriteLine(ex.Message); } table = null; return false; } 中返回您选择的值:

    public static class XmlReaderExtensions
    {
        public static XName XName(this XmlReader reader)
        {
            return System.Xml.Linq.XName.Get(reader.LocalName, reader.NamespaceURI);
        }
    
        public static bool ReadToElement(this XmlReader reader)
        {
            while (reader.NodeType != XmlNodeType.Element)
                if (!reader.Read())
                    return false;
            return true;
        }
    }
    

    使用扩展方法:

    object [,]
  3. Excel COM Interop也很慢,因为每个方法调用实际上都是对另一个进程的RPC调用。因此,不是单独将每一行写入Excel,而应创建一个包含给定XML文件中所有行的二维数组(如上所述),并在单个块中将该二维数组写入Excel。为此,请参阅How to: Stream XML Fragments from an XmlReaderWrite Array to Excel RangeExcel Interop - Efficiency and performance

    在写入Excel之前,您还可以考虑将各个表分块为更大的块,以进一步减少Interop调用的数量。如果您有1000000个文件,那么至少 1000000个RPC调用。

  4. 此外,根据Microsoft.Office.Interop.Excel really slowthis answer,Excel COM Interop显然不是真正的多线程。相反,它是单元线程,并且调用被编组到从其他线程创建COM对象的线程(如果需要)。

    因此,请考虑更改线程策略以使用多个生产者/单个使用者队列,沿this onethis question行。

    在生产者线程中,从每个XML文件中提取必要的数据表object [,]。在单个使用者线程中,打开Excel文件,使用每个public JsonDocument put(final String key, final JsonObject googleJsonObject) { try { JsonDocument document = JsonDocument.create(key, com.couchbase.client.java.document.json.JsonObject.fromJson(googleJsonObject.toString())); return bucket.upsert(document); } catch (Throwable th) { return null; } } public String get(final String key) { JsonDocument d = bucket.get(JsonDocument.create(key)); if (d != null) { return d.content().toString(); } else { return null; } } 表并将其作为单个2d范围写入Excel文件,然后最终关闭该文件。这应该避免任何编组惩罚。

  5. 现在您要从单个线程写入Excel,请考虑完全放弃Excel COM Interop并使用this oneImport and Export Excel - What is the best library?中的选项直接写入文件。它甚至可以是一个简单的CSV文件!

  6. 其中,考虑到您当前的代码结构,#1 - #3看起来很简单。 #4和#5会更具挑战性。