【问题标题】:How to load on demand excel rows in a data table c#如何在数据表c#中按需加载excel行
【发布时间】:2021-08-22 05:24:12
【问题描述】:

我有一个要求,我必须从一张 Microsoft excel 表格中填写 dataTable。

工作表可能有大量数据,因此要求是当对应该保存 Microsoft excel 工作表中数据的数据表进行迭代时,应按需填充表。

意味着如果工作表中有 1000000 条记录,则数据表应根据循环中 foreach 当前项的当前位置,分批获取 100 条数据。

任何指针或建议将不胜感激。

【问题讨论】:

  • 查看 NPOI 库以读取 excel 文件并尝试执行您需要的操作。
  • 我已经使用 NPOI 库和 ClosedXML 来读取 excel 文件并批量加载行,而不是一次性加载。为此,我编写了自定义枚举器,可枚举,其中我将批处理大小定义为可配置,在 moveNext 中,我正在检查批处理大小,如果需要加载下一批行。但是加载是通过从当前位置遍历每一行来发生的。这行得通,但不是很好的性能和指针我能做的有多么不同
  • 把你的示例代码扔进去,会有人帮忙的
  • 您的期望是什么?您认为什么是“表现不佳”?

标签: c# .net datatable oledb ienumerable


【解决方案1】:

我建议您使用 OpenXML 从文件中解析和读取您的 excel 数据。 这也将允许您从工作簿中读出特定部分/区域。

您将在此链接中找到更多信息和示例: Microsoft Docs - Parse and read a large spreadsheet document (Open XML SDK)

这将比使用官方的 microsoft office excel interop 更高效、更容易开发。

【讨论】:

  • 嗨。感谢您的输入,我阅读了提供的链接,但该示例显示了如何逐个单元格地读取。我试图找到如何阅读特定部分/区域或一些特定行无法找到它们。任何指向此类示例或文档的指针
【解决方案2】:

**我不在装有 Visual stuido 的 PC 附近,因此此代码未经测试,在我稍后测试之前可能存在语法错误。

它仍然可以让您大致了解需要做什么。

private void ExcelDataPages(int firstRecord, int numberOfRecords)
{
    
    Excel.Application dataApp = new Excel.Application(); 
    Excel.Workbook dataWorkbook = new Excel.Workbook();
    int x = 0;
    
    dataWorkbook.DisplayAlerts = false;
    dataWorkbook.Visible = false;
    dataWorkbook.AutomationSecurity = Microsoft.Office.Core.MsoAutomationSecurity.msoAutomationSecurityLow;
    dataWorkbook = dataApp.Open(@"C:\Test\YourWorkbook.xlsx");
    
    try
    {
        Excel.Worksheet dataSheet = dataWorkbook.Sheet("Name of Sheet");
        
        while (x < numberOfRecords)
        {
            Range currentRange = dataSheet.Rows[firstRecord + x]; //For all columns in row 
    

            foreach (Range r in currentRange.Cells) //currentRange represents all the columns in the row
            {
                // do what you need to with the Data here.
            }
             x++;
        }
    }
    catch (Exception ex)
    {
        //Enter in Error handling
    }

    dataWorkbook.Close(false); //Depending on how quick you will access the next batch of data, you may not want to close the Workbook, reducing load time each time.  This may also mean you need to move the open of the workbook to a higher level in your class, or if this is the main process of the app, make it static, stopping the garbage collector from destroying the connection.
    dataApp.Quit();

}

【讨论】:

    【解决方案3】:

    试试下面的 -- 它使用 NuGet 包DocumentFormat.OpenXml 代码来自Using OpenXmlReader。但是,我对其进行了修改以将数据添加到 DataTable。由于您要多次从同一个 Excel 文件中读取数据,因此使用 SpreadSheetDocument 实例打开 Excel 文件一次并在完成后将其处理掉会更快。由于 SpreedSheetDocument 的实例需要在您的应用程序退出之前被处理掉,因此使用了IDisposable

    其中显示“ToDo”,您需要将创建 DataTable 列的代码替换为您自己的代码,以便为您的项目创建正确的列。

    我使用包含大约 15,000 行的 Excel 文件测试了下面的代码。一次读取 100 行时,第一次读取大约需要 500 毫秒 - 800 毫秒,而后续读取大约需要 100 毫秒 - 400 毫秒。

    创建一个类(名称:HelperOpenXml)

    HelperOpenXml.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using DocumentFormat.OpenXml;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Spreadsheet;
    using System.Data;
    using System.Diagnostics;
    
    namespace ExcelReadSpecifiedRowsUsingOpenXml
    {
        public class HelperOpenXml : IDisposable
        {
            public string Filename { get; private set; } = string.Empty;
            public int RowCount { get; private set; } = 0;
    
            private SpreadsheetDocument spreadsheetDocument = null;
    
            private DataTable dt = null;
            
    
            public HelperOpenXml(string filename)
            {
                this.Filename = filename;
            }
    
            public void Dispose()
            {
                if (spreadsheetDocument != null)
                {
                    try
                    {
                        spreadsheetDocument.Dispose();
                        dt.Clear();
                    }
                    catch(Exception ex)
                    {
                        throw ex;
                    }
                }
            }
    
            public DataTable GetRowsSax(int startRow, int endRow, bool firstRowIsHeader = false)
            {
                int startIndex = startRow;
                int endIndex = endRow;
    
                if (firstRowIsHeader)
                {
                    //if first row is header, increment by 1
                    startIndex = startRow + 1;
                    endIndex = endRow + 1;
                }
    
                if (spreadsheetDocument == null)
                {
                    //create new instance
                    spreadsheetDocument = SpreadsheetDocument.Open(Filename, false);
    
                    //create new instance
                    dt = new DataTable();
    
                    //ToDo: replace 'dt.Columns.Add(...)' below with your code to create the DataTable columns
                    //add columns to DataTable
                    dt.Columns.Add("A");
                    dt.Columns.Add("B");
                    dt.Columns.Add("C");
                    dt.Columns.Add("D");
                    dt.Columns.Add("E");
                    dt.Columns.Add("F");
                    dt.Columns.Add("G");
                    dt.Columns.Add("H");
                    dt.Columns.Add("I");
                    dt.Columns.Add("J");
                    dt.Columns.Add("K");
    
                }
                else
                {
                    //remove existing data from DataTable
                    dt.Rows.Clear(); 
    
                }
    
                WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
    
                int numWorkSheetParts = 0;
    
                foreach (WorksheetPart worksheetPart in workbookPart.WorksheetParts)
                {
                    using (OpenXmlReader reader = OpenXmlReader.Create(worksheetPart))
                    {
                        int rowIndex = 0;
    
                        //use the reader to read the XML
                        while (reader.Read())
                        {
                            if (reader.ElementType == typeof(Row))
                            {
                                reader.ReadFirstChild();
    
                                List<string> cValues = new List<string>();
                                int colIndex = 0;
                                do
                                {
                                    //only get data from desired rows
                                    if ((rowIndex > 0 && rowIndex >= startIndex && rowIndex <= endIndex) ||
                                    (rowIndex == 0 && !firstRowIsHeader && rowIndex >= startIndex && rowIndex <= endIndex))
                                    {
    
                                        if (reader.ElementType == typeof(Cell))
                                        {
                                            Cell c = (Cell)reader.LoadCurrentElement();
    
                                            string cellRef = c.CellReference; //ex: A1, B1, ..., A2, B2
    
                                            string cellValue = string.Empty;
    
                                            //string/text data is stored in SharedString
                                            if (c.DataType != null && c.DataType == CellValues.SharedString)
                                            {
                                                SharedStringItem ssi = workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(int.Parse(c.CellValue.InnerText));
    
                                                cellValue = ssi.Text.Text;
                                            }
                                            else
                                            {
                                                cellValue = c.CellValue.InnerText;
                                            }
    
                                            //Debug.WriteLine("{0}: {1} ", c.CellReference, cellValue);
    
                                            //add value to List which is used to add a row to the DataTable
                                            cValues.Add(cellValue);
                                        }
                                    }
    
                                    colIndex += 1; //increment
    
                                } while (reader.ReadNextSibling());
    
                                if (cValues.Count > 0)
                                {
                                    //if List contains data, use it to add row to DataTable
                                    dt.Rows.Add(cValues.ToArray()); 
                                }
    
                                rowIndex += 1; //increment
    
                                if (rowIndex > endIndex)
                                {
                                    break; //exit loop
                                }
                            }
                        }
                    }
    
                    numWorkSheetParts += 1; //increment
                }
    
                DisplayDataTableData(dt); //display data in DataTable
    
                return dt;
            }
    
            
            private void DisplayDataTableData(DataTable dt)
            {
                foreach (DataColumn dc in dt.Columns)
                {
                    Debug.WriteLine("colName: " + dc.ColumnName);
                }
    
                foreach (DataRow r in dt.Rows)
                {
                    Debug.WriteLine(r[0].ToString() + " " + r[1].ToString());
                }
            }
    
        }
    }
    

    用法

    private string excelFilename = @"C:\Temp\Test.xlsx";
    private HelperOpenXml helperOpenXml = null;
    
                ...
    
    private void GetData(int startIndex, int endIndex, bool firstRowIsHeader)
    {
        helperOpenXml.GetRowsSax(startIndex, endIndex, firstRowIsHeader);
    }
    

    注意:确保在您的应用程序退出之前调用Dispose()(例如:helperOpenXml.Dispose();)。

    更新

    OpenXML 将日期存储为自 1900 年 1 月 1 日以来的天数。对于 1900 年 1 月 1 日之前的日期,它们存储在 SharedString 中。欲了解更多信息,请参阅Reading a date from xlsx using open xml sdk

    这是一个代码sn-p:

    Cell c = (Cell)reader.LoadCurrentElement();
                 ...
    string cellValue = string.Empty
                 ...
    cellValue = c.CellValue.InnerText;
    
    double dateCellValue = 0;
    Double.TryParse(cellValue, out dateCellValue);
    
    DateTime dt = DateTime.FromOADate(dateCellValue);
    
    cellValue = dt.ToString("yyyy/MM/dd");
    

    【讨论】:

    • 这种方法我面临的问题是具有日期值的单元格和具有大量数字并以 1.71E + 15 格式存储的单元格。任何人都可以帮忙
    • @user3048027:您尚未提供任何示例数据。我在上面帖子的末尾添加了一个代码 sn-p,以显示当单元格包含 Date 值时如何从 int 值转换为 Date 值。不确定“1.71E+15”遇到什么问题。如果需要,可以使用Decimal.TryParse 将字符串值1.71E+15 转换为十进制。然后使用Decimal.ToString(...) 将其转换为所需的字符串格式。
    【解决方案4】:

    我将此代码与 EPPlus DLL 一起使用,不要忘记添加参考。但应检查是否符合您的要求。

    public DataTable ReadExcelDatatable(bool hasHeader = true)
    {
        using (var pck = new OfficeOpenXml.ExcelPackage())
        {
            using (var stream = File.OpenRead(this._fullPath))
            {
                pck.Load(stream);
            }
    
            var ws = pck.Workbook.Worksheets.First();
    
            DataTable tbl = new DataTable();
    
            int i = 1;
            foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
            {
                //table head
                tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
    
                tbl.Columns.Add(_tableHead[i]);
                i++;
            }
    
            var startRow = hasHeader ? 2 : 1;
            
            for (int rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
            {
                var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
                DataRow row = tbl.Rows.Add();
                foreach (var cell in wsRow)
                {
                    row[cell.Start.Column - 1] = cell.Text;
                }
            }
    
            return tbl;
        }
    }
    

    【讨论】:

      【解决方案5】:

      另一个简单的替代方法是:查看 NUGET 包 ExcelDataReader,以及关于 https://github.com/ExcelDataReader/ExcelDataReader

      用法示例:

      [Fact] 
      void Test_ExcelDataReader() 
      {
          
          System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
          var scriptPath = Path.GetDirectoryName(Util.CurrentQueryPath); // LinqPad script path
          var filePath = $@"{scriptPath}\TestExcel.xlsx";
          using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read))
          {
              // Auto-detect format, supports:
              //  - Binary Excel files (2.0-2003 format; *.xls)
              //  - OpenXml Excel files (2007 format; *.xlsx, *.xlsb)
              using (var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(stream))
              {
                  var result = reader.AsDataSet();
                  // The result of each spreadsheet is in result.Tables
                  var t0 = result.Tables[0];
                  Assert.True(t0.Rows[0][0].Dump("R0C0").ToString()=="Hello", "Expected 'Hello'");
                  Assert.True(t0.Rows[0][1].Dump("R0C1").ToString()=="World!", "Expected 'World!'");          
              } // using
          } // using
      } // fact
      

      在开始阅读之前,您需要设置和编码提供者如下:

       System.Text.Encoding.RegisterProvider(
            System.Text.CodePagesEncodingProvider.Instance);
      

      单元格的寻址方式如下:

       var t0 = result.Tables[0]; // table 0 is the first worksheet
       var cell = t0.Rows[0][0];  // on table t0, read cell row 0 column 0
      

      您可以轻松地循环遍历for 循环中的行和列,如下所示:

      for (int r = 0; r < t0.Rows.Count; r++)
      {
          var row = t0.Rows[r];
          var columns = row.ItemArray;
          for (int c = 0; c < columns.Length; c++)
          {
              var cell = columns[c];
              cell.Dump();
          }
      }
      

      【讨论】:

        【解决方案6】:

        我会给你一个不同的答案。如果将一百万行加载到 DataTable 中性能不佳,请使用 Driver 加载数据:How to open a huge excel file efficiently

        DataSet excelDataSet = new DataSet();
        
        string filePath = @"c:\temp\BigBook.xlsx";
        
        // For .XLSXs we use =Microsoft.ACE.OLEDB.12.0;, for .XLS we'd use Microsoft.Jet.OLEDB.4.0; with  "';Extended Properties=\"Excel 8.0;HDR=YES;\"";
        string connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" + filePath + "';Extended Properties=\"Excel 12.0;HDR=YES;\"";
        
        using (OleDbConnection conn = new OleDbConnection(connectionString))
        {
            conn.Open();
            OleDbDataAdapter objDA = new System.Data.OleDb.OleDbDataAdapter
            ("select * from [Sheet1$]", conn);
            objDA.Fill(excelDataSet);
            //dataGridView1.DataSource = excelDataSet.Tables[0];
        }
        

        接下来使用 DataView 过滤 DataSet 的 DataTable。使用 DataView 的 RowFilter 属性,您可以根据列值指定行的子集。

        DataView prodView = new DataView(excelDataSet.Tables[0],  
        "UnitsInStock <= ReorderLevel",  
        "SupplierID, ProductName",  
        DataViewRowState.CurrentRows); 
        

        参考:https://www.c-sharpcorner.com/article/dataview-in-C-Sharp/

        或者您可以直接使用 DataTables 的 DefaultView RowFilter:

        excelDataSet.Tables[0].DefaultView.RowFilter = "Amount >= 5000 and Amount <= 5999 and Name = 'StackOverflow'";
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-02-03
          • 1970-01-01
          • 2018-07-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多