簡體   English   中英

如何在數據表 c# 中按需加載 excel 行

[英]How to load on demand excel rows in a data table c#

我有一個要求,我必須從一張 Microsoft excel 中填寫數據表。

工作表可能有很多數據,因此要求是,當對應該保存 Microsoft excel 工作表中的數據的數據表進行 foreach 循環迭代時,應按需填充表。

這意味着如果工作表中有 1000000 條記錄,則數據表應根據循環中 foreach 當前項的當前 position 分批獲取 100 條數據。

任何指針或建議將不勝感激。

我建議您使用 OpenXML 從文件中解析和讀取 excel 數據。 這也將允許您從工作簿中讀出特定的部分/區域。

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

這將比使用官方的 microsoft office excel interop 更高效、更容易開發。

**我不在裝有 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();

}

試一試——它使用 NuGet package DocumentFormat.OpenXml代碼來自Using OpenXmlReader 但是,我對其進行了修改以將數據添加到 DataTable。 由於您要多次從同一個 Excel 文件中讀取數據,因此使用 SpreadSheetDocument 實例打開一次 Excel 文件並在完成后處理它會更快。 由於 SpreedSheetDocument 的實例需要在您的應用程序退出之前被處理掉,因此使用了IDisposable

在它顯示“ToDo”的地方,您需要用您自己的代碼替換創建 DataTable 列的代碼,以便為您的項目創建正確的列。

我使用包含大約 15,000 行的 Excel 文件測試了下面的代碼。 一次讀取 100 行時,第一次讀取大約需要 500 毫秒 - 800 毫秒,而后續讀取大約需要 100 毫秒 - 400 毫秒。

創建一個 class(名稱: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 中。 有關更多信息,請參閱使用打開的 xml sdk 從 xlsx 讀取日期

這是一個代碼片段:

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

我將此代碼與 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;
    }
}

另一個簡單的替代方法是:查看 NUGET package ExcelDataReader以及有關https: //github/DataReader/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();
    }
}

我要給你一個不同的答案。 如果將一百萬行加載到數據表中性能不佳,請使用驅動程序加載數據:如何有效打開巨大的 excel 文件

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'";

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM