这里是创建字节数组的部分方法,该数组可以写入 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。
我找到了答案 - 这是我修改后的代码:
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}\"");
}