尝试使用 OpenXML SDK 的 C# 方法将 DataSet 中的所有 DataTable 导出为新 Excel 工作簿中的工作表时遇到困难

问题描述 投票:0回答:1

这里是创建字节数组的部分方法,该数组可以写入 xslx 文件,该文件采用 DataSet 并将该 DataSet 中的所有表导出为 Excel 工作簿中的工作表......除了我所能做的一切到目前为止,我已经创建了工作表本身,但我一生都无法弄清楚如何用实际数据填充这些工作表:

    private byte[] DataSetToXlsx (DataSet ds)
    {
        byte[] bytes;

        using (var stream = new MemoryStream())
        {
            using (SpreadsheetDocument spreadsheet = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
            {
                WorkbookPart workbookPart = spreadsheet.AddWorkbookPart();
                workbookPart.Workbook = new Workbook();

                WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
                worksheetPart.Worksheet = new Worksheet(new SheetData());  // This line needs to be here and I don't fully understand why

                Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

                for (int iTable = 0; iTable < ds.Tables.Count; iTable++)
                {
                    DataTable dt = ds.Tables[iTable];

                    var sheet = new Sheet
                    {
                        Id = workbookPart.GetIdOfPart(worksheetPart),
                        SheetId = (uint)iTable + 1,
                        Name = string.IsNullOrWhiteSpace(dt.TableName) ? $"Sheet {iTable + 1}" : dt.TableName
                    };

                    // Now I want to iterate through the DataRows on the table to transfer the values into the sheet, but I just can't figure out how to do it.

                    sheets.Append(sheet);
                }

                workbookPart.Workbook.Save();
            }
            stream.Seek(0, SeekOrigin.Begin);
            bytes = stream.ToArray();
        }

        return bytes;
    }

我意识到我部分地没有完全理解

Worksheet
Sheet
之间的区别。 正如我在代码中的注释所暗示的那样,我不完全理解 WNY 我需要创建一个新的
Worksheet
,但为数据集中的每个数据表创建一个
Sheet

但是我的代码确实可以创建一个新的工作簿,并在该工作簿中为每个数据表放入一个工作表。

但更重要的是,我不明白如何开始在

Sheet
实例中创建行,以便反映实际数据本身。 我只是对如何做到这一点感到困惑。 我所做的每一次尝试都可以正常编译,但会创建 Excel 无法读取的损坏文件。

我很想知道这个技巧!

请不要将我指向第三方库或 Excel interrop。

c# excel dataset openxml-sdk
1个回答
0
投票

我找到了答案 - 这是我修改后的代码:

    private byte[] DataSetToXlsx (DataSet ds)
    {
        if (ds == null || ds.Tables.Count < 1)
        {
            return null;
        }

        byte[] bytes;

        using (var stream = new MemoryStream())
        {
            using (SpreadsheetDocument spreadsheet = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
            {
                // Setup requirements for minimal spreadsheet document - workbook and sheets.
                WorkbookPart workbookPart = spreadsheet.AddWorkbookPart();
                workbookPart.Workbook = new Workbook();
                Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

                for (int iTable = 0; iTable < ds.Tables.Count; iTable++)
                {
                    // Add a new worksheet part for the table
                    WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
                    worksheetPart.Worksheet = new Worksheet(new SheetData());

                    DataTable dt = ds.Tables[iTable];

                    // Add sheet to the workbook with ID from worksheet
                    sheets.Append(new Sheet
                    {
                        Id = workbookPart.GetIdOfPart(worksheetPart),
                        SheetId = (uint)iTable + 1,
                        Name = string.IsNullOrWhiteSpace(dt.TableName) ? $"Sheet {iTable + 1}" : dt.TableName
                    });

                    if (dt.Columns.Count < 1)
                    {
                        continue;  // No data in this table.
                    }

                    // Get the sheetData for the grid on which we will place rows.
                    SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

                    CellValues[] columnTypes = new CellValues[dt.Columns.Count];

                    // Retrieve header data from the table columns and place in first row of the sheet
                    var row = new Row { RowIndex = 1 };
                    for (int iColumn = 0; iColumn < dt.Columns.Count; iColumn++)
                    {
                        DataColumn dc = dt.Columns[iColumn];

                        columnTypes[iColumn] = FindCellValuesForColumn(dc);

                        row.Append(new Cell { DataType = CellValues.String, CellValue = new CellValue(dc.ColumnName) });
                    }
                    sheetData?.Append(row);

                    // Retrieve remaining data from table rows and populate the sheet
                    for (int iRow = 0; iRow < dt.Rows.Count; iRow++)
                    {
                        DataRow dr = dt.Rows[iRow];
                        row = new Row { RowIndex = (uint)iRow + 2 };
                        for(int iColumn = 0; iColumn < dt.Columns.Count; iColumn++)
                        {
                            row.Append(new Cell { DataType = columnTypes[iColumn], CellValue = CellValueFromObject(dr[iColumn]) });
                        }
                        sheetData?.Append(row);
                    }
                }

                workbookPart.Workbook.Save();
            }
            stream.Seek(0, SeekOrigin.Begin);
            bytes = stream.ToArray();
        }

        return bytes;
    }

这确实取决于几个辅助方法和一个静态查找表,我也将其包含在此处:

    private static readonly IReadOnlyDictionary<Type, Tuple<CellValues, Func<object, CellValue>>> TypeLookup = new ReadOnlyDictionary<Type, Tuple<CellValues, Func<object, CellValue>>>(new Dictionary<Type, Tuple<CellValues, Func<object, CellValue>>>
    {
        { typeof(char), new Tuple<CellValues, Func<object, CellValue>>(CellValues.String, o => new CellValue(o.ToString())) },
        { typeof(string), new Tuple<CellValues, Func<object, CellValue>>(CellValues.String, o => new CellValue((string)o)) },
        { typeof(DateTime), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Date, o => new CellValue((DateTime)o)) },
        { typeof(bool), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Boolean, o => new CellValue((bool)o)) },
        { typeof(byte), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(Convert.ToInt32(o))) },
        { typeof(short), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(Convert.ToInt32(o))) },
        { typeof(int), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue((int)o)) },
        { typeof(long), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(o.ToString())) },
        { typeof(ushort), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(o.ToString())) },
        { typeof(uint), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(o.ToString())) },
        { typeof(ulong), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(o.ToString())) },
        { typeof(float), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue(Convert.ToDouble(o))) },
        { typeof(double), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue((double)o)) },
        { typeof(decimal), new Tuple<CellValues, Func<object, CellValue>>(CellValues.Number, o => new CellValue((decimal)o)) }
    });

    private static CellValue CellValueFromObject (object obj)
    {
        return obj == null
            ? new CellValue()
            : TypeLookup.TryGetValue(obj.GetType(), out Tuple<CellValues, Func<object, CellValue>> tuple)
                ? tuple.Item2(obj)
                : new CellValue(Convert.ToString(obj));
    }

    private static CellValues FindCellValuesForColumn(DataColumn dc)
    {
        dc = dc ?? throw new ArgumentNullException(nameof(dc));

        if (TypeLookup.TryGetValue(dc.DataType, out Tuple<CellValues, Func<object, CellValue>> tuple))
        {
            return tuple.Item1;
        }

        throw new InvalidOperationException($"No CellValues mapping available for data type \"{dc.DataType.FullName}\" for column \"{dc.ColumnName}\" in table \"{dc.Table?.TableName}\" in dataset \"{dc.Table?.DataSet?.DataSetName}\"");
    }
© www.soinside.com 2019 - 2024. All rights reserved.