【问题标题】:how to write Excel from datatable without using row by row access如何在不使用逐行访问的情况下从数据表编写 Excel
【发布时间】:2021-09-01 13:52:48
【问题描述】:

我正在尝试通过逐行循环数据表来使用数据表数据填充 Excel。

在此过程中,20-3 万条记录需要更多时间(15 分钟)

有没有其他方法可以写入 Excel 而无需逐行循环并更快?

我没有使用 sqlserver,只是用 20K 记录填充 xml 到数据集并填充 excel。

我在 google 中找到了以下代码,但这在 .net 中不起作用。(说没有定义 ExcelFile)

    Dim ef2 As New ExcelFile

    ' Imports all the tables from DataSet to new file.
    For Each table In ds.Tables
        ' Add new worksheet to the file.
        Dim ws As ExcelWorksheet = ef2.Worksheets.Add(table.TableName)

        ' Change the value of the first cell in the DataTable.
        table.Rows(0)(0) = "This is new file!"

        ' Insert the data from DataTable to the worksheet starting at cell "A1".
        ws.InsertDataTable(table, "A1", True)
    Next

    ' Save the file to XLS format.
    ef2.SaveXls("DataSet.xls")

【问题讨论】:

  • 您可以按列查看,但要写入 20,000 条记录。它总是会很慢。
  • 你在后台使用sql server吗?您是否考虑过为此使用sql server?您可以从服务器上的查询创建 Excel 文件。我不知道,您甚至可以将其作为 blob 返回。
  • 什么版本的 Excel? Excel will read XML 升级版本会变得更容易。

标签: .net asp.net excel


【解决方案1】:

看看这篇文章,它解释了如何使用 OpenXML SDK 将 DataTable 导出到 Excel。

http://lateral8.com/articles/2010/3/5/openxml-sdk-20-export-a-datatable-to-excel.aspx

以下是文章的主要内容:

创建 ExcelExport 类

现在在您的项目中创建一个名为 ExcelExport.cs 的新类文件。在文件开头添加以下引用:

使用系统; 使用 System.Data; 使用 System.Linq; 使用 DocumentFormat.OpenXml.Packaging; 使用 DocumentFormat.OpenXml.Spreadsheet;

接下来,将以下方法添加到文件中:

public void ExportDataTable(DataTable table, string exportFile)
{
    //create the empty spreadsheet template and save the file 
    //using the class generated by the Productivity tool  ExcelDocument excelDocument = new ExcelDocument();
    excelDocument.CreatePackage(exportFile);

    //populate the data into the spreadsheet  using (SpreadsheetDocument spreadsheet =
    SpreadsheetDocument.Open(exportFile, true))
    {
        WorkbookPart workbook = spreadsheet.WorkbookPart;
        //create a reference to Sheet1  WorksheetPart worksheet = workbook.WorksheetParts.Last();
        SheetData data = worksheet.Worksheet.GetFirstChild<SheetData>();

        //add column names to the first row  Row header = new Row();
        header.RowIndex = (UInt32)1;

        foreach (DataColumn column in table.Columns)
        {
            Cell headerCell = createTextCell(table.Columns.IndexOf(column) + 1, 1, column.ColumnName);
            header.AppendChild(headerCell); 
        }

        data.AppendChild(header);

        //loop through each data row  DataRow contentRow;
        for (int i = 0;i < table.Rows.Count; i++)
        {
            contentRow = table.Rows[i];
            data.AppendChild(createContentRow(contentRow, i + 2));
        }
    }            
}

上述方法首先使用之前创建的 ExcelDocument 类创建一个新文件,该文件保存到 exportFile 参数中指定的位置。创建文件后,会发生两个主要循环。第一个循环遍历 DataTable 对象的列,并使用 createTextCell 方法为每个列名创建一个 Cell 对象:

private Cell createTextCell(int columnIndex, int rowIndex, object cellValue)
{
    Cell cell = new Cell();

    cell.DataType = CellValues.InlineString;
    cell.CellReference = getColumnName(columnIndex) + rowIndex;

    InlineString inlineString = new InlineString();
    Text t = new Text();

    t.Text = cellValue.ToString();
    inlineString.AppendChild(t);
    cell.AppendChild(inlineString);

    return cell;

}

接下来,使用 createContentRow 方法将每一行附加到工作表:

private Row createContentRow(DataRow dataRow, int rowIndex)
{
    Row row = new Row  {RowIndex = (UInt32)rowIndex }; 

    for (int i = 0; i < dataRow.Table.Columns.Count; i++)
    {
        Cell dataCell = createTextCell(i + 1, rowIndex, dataRow[i]);
        row.AppendChild(dataCell);
    }
    return row;
}

createTextCell 方法使用另一种支持方法来完成将行和列映射到正确的单元格引用的工作,方法 getColumnName:

private string getColumnName(int columnIndex)
{
    int dividend = columnIndex;
    string columnName = String.Empty;
    int modifier;

    while (dividend > 0)
    {
        modifier = (dividend - 1) % 26;
        columnName = 
            Convert.ToChar(65 + modifier).ToString() + columnName;
        dividend = (int)((dividend - modifier) / 26);
    }

    return columnName;
}

此方法提供了一种将列索引号映射到 Excel 列名称 (A-Z) 的快速简便方法。 OpenXML SDK 中的 Cell 对象需要指定有效的 Excel 单元格引用(例如 A1、C2),因此此方法与行索引引用相结合来创建单元格引用。需要注意的是,这里的索引不是从零开始的。

最后,要实现上面的类,使用下面的代码:

//create DataTable from sample data DataSet sampleDataSet = new DataSet();
sampleDataSet.ReadXml(context.Server.MapPath("~/sampleData.xml"));
DataTable productsTable = sampleDataSet.Tables[0];
string exportFile = context.Server.MapPath("~/excelExport.xslx");

ExcelExport export = new ExcelExport();
export.ExportDataTable(productsTable, exportFile);

【讨论】:

  • 这里我猜这也需要 20-30 分钟,因为我们在 DATAROW 中循环 20K 次
  • @Ramesh:它必须以某种方式将数据放在那里。为了提高性能,您可以将其拆分为 3-5k 批次,分布在多个线程中。
【解决方案2】:

我在 MSDN 上找到了一种解决方案,只需 5 行代码,您可以通过它在几秒钟内将数据从数据表导出到 Excel。

       //using ClosedXML.Excel;
        XLWorkbook wb = new XLWorkbook();
        DataTable dt = GetTable();
        wb.Worksheets.Add(dt, "WorksheetName");
        wb.SaveAs(@"D:\HelloWorld.xlsx");

ClosedXML

【讨论】:

    【解决方案3】:

    要在一个操作中获取或设置范围,您必须计算出大小,然后使用一个大型二维对象数组获取/设置它。

    //get values
    object[,] objectArray = shtName.get_Range("A1:Z100").Value2;
    iNumber = Convert.ToInt32(objectArray[1,1]);
    
    //set values
    object[,] objectArray = new object[3,1] {{"A"}{"B"}{"C"}};
    rngName.Value2 = objectArray;
    

    要“一次性”将 DataTable 设置为 Excel 电子表格,您需要将其转换为对象数组:

    //set DataTable 
    var targetRange = "Sheet1!A1:" + GetExcelColumnName(dt.Columns.Count) + rows.Count.ToString();
    
    rng = Application.Range(targetRange);
    rng.Value2 = ConvertTo2DArray(dataTable1);
    
    public static object[,] ConvertTo2DArray(DataTable dt)
    {
        var rows = dt.Rows;
        int rowCount = rows.Count;
        int colCount = dt.Columns.Count;
        var result = new object[rowCount, colCount];
    
        for (int i = 0; i < rowCount; i++)
        {
            var row = rows[i];
            for (int j = 0; j < colCount; j++)
            {
                result[i, j] = row[j];
            }
        }    
        return result;
    }
    
    private string GetExcelColumnName(int columnNumber)
    {
        string columnName = "";    
        while (columnNumber > 0)
        {
            int modulo = (columnNumber - 1) % 26;
            columnName = Convert.ToChar('A' + modulo) + columnName;
            columnNumber = (columnNumber - modulo) / 26;
        }     
        return columnName;
    }
    

    请注意,了解 Excel 存储的数据类型(文本或数字)很重要,因为当您将类型从对象数组转换回时,它不会自动为您执行此操作。

    ps 如果您想在Exporting a DataGridView to Excel with all the cells format 时保留样式。

    参考:https://stackoverflow.com/a/2294087/495455https://stackoverflow.com/a/2294087/495455

    【讨论】:

      【解决方案4】:

      为什么不从创建多个线程开始。每个线程将同时读取 1000 条记录(一个接一个)并将它们写入各自的临时文件或 Stream Writer。最后,合并结果。

      希望对您有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-04
        • 2010-11-25
        • 2010-10-29
        • 1970-01-01
        • 1970-01-01
        • 2018-12-04
        • 2018-04-20
        相关资源
        最近更新 更多