这是由于.NET Framework和.NET Core之间的更改导致的。 这是已知问题,我能够根据Microsoft的一些建议拼凑出一个有限制的解决方法。 在GitHub上,他们指出在
Write
而不是
ReadWrite
模式下打开
Package
将允许使用SAX方法流式传输大型电子表格。 由于这种方法,
顺序很重要。 在
Write
模式下写出的第一件事必须是大工作表,因为任何其他打开的
OpenXmlWriter
实例都需要
ReadWrite
,否则它们会抛出异常(因此有限制)。
以下是我遵循的步骤:
- 创建一个
FileStream
(我使用了 File.Create
)。
- 创建一个
Package
,传入 FileStream
并使用 FileMode.Create
和 FileAccess.Write
。
- 通过
SpreadsheetDocument.Create
创建一个 SpreadsheetDocument
。
- 通过
OpenXmlWriter
写入您的大型 WorksheetPart
。
- 关闭并释放写入器、包、文件流等对象。
- 创建一个
FileStream
(这次是打开的,使用 File.Open
,并使用 FileMode.Open
、FileAccess.ReadWrite
和 FileShare.None
)。
- 创建一个
Package
,传入 FileStream
并使用 FileMode.Open
和 FileAccess.ReadWrite
。
- 通过
SpreadsheetDocument.Open
创建一个 SpreadsheetDocument
。
- 为
WorkbookPart
创建一个 OpenXmlWriter
,添加 Workbook
和 Sheets
的元素,然后将其与原始创建时添加的 Sheet
相关联,关闭并释放这些对象,完成。
现在,这是一些示例代码,应该可以让您接近目标。我正在将IDataReader写入工作表中。这里有一些字符串扩展没有包含在内,但您可以根据需要删除或更改。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Reflection;
public class ExcelDoc
{
public static void ToFile(string outputFileName, IDataReader dr, string workSheetName)
{
string worksheetPartId;
using (var fs = File.Create(outputFileName))
{
using (var package = Package.Open(fs, FileMode.Create, FileAccess.Write))
{
using (var excel = SpreadsheetDocument.Create(package, SpreadsheetDocumentType.Workbook))
{
excel.AddWorkbookPart();
List<OpenXmlAttribute> oxa;
var wsp = excel.WorkbookPart.AddNewPart<WorksheetPart>();
var oxw = OpenXmlWriter.Create(wsp);
worksheetPartId = excel.WorkbookPart.GetIdOfPart(wsp);
oxw.WriteStartElement(new Worksheet());
oxw.WriteStartElement(new SheetData());
int index = 1;
oxa = new List<OpenXmlAttribute>();
oxa.Add(new OpenXmlAttribute("r", null, index.ToString()));
oxw.WriteStartElement(new Row(), oxa);
for (int x = 0; x <= dr.FieldCount - 1; x++)
{
var cell = GetCell(typeof(string), dr.GetName(x));
oxa = new List<OpenXmlAttribute>();
oxa.Add(new OpenXmlAttribute("t", null, "str"));
oxw.WriteElement(cell);
}
oxw.WriteEndElement();
while (dr.Read())
{
index += 1;
oxa = new List<OpenXmlAttribute>();
oxa.Add(new OpenXmlAttribute("r", null, index.ToString()));
oxw.WriteStartElement(new Row(), oxa);
for (int x = 0; x <= dr.FieldCount - 1; x++)
{
var cell = GetCell(dr[x].GetType(), dr[x].ToString());
oxa = new List<OpenXmlAttribute>();
oxa.Add(new OpenXmlAttribute("t", null, "str"));
oxw.WriteElement(cell);
}
oxw.WriteEndElement();
}
oxw.WriteEndElement();
oxw.WriteEndElement();
oxw.Close();
oxw.Dispose();
}
}
}
using (var fs = File.Open(outputFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
using (var package = Package.Open(fs, FileMode.Open, FileAccess.ReadWrite))
{
using (var excel = SpreadsheetDocument.Open(package))
{
var oxw = OpenXmlWriter.Create(excel.WorkbookPart);
oxw.WriteStartElement(new Workbook());
oxw.WriteStartElement(new Sheets());
oxw.WriteElement(new Sheet()
{
Name = $"{workSheetName}",
SheetId = 1,
Id = worksheetPartId
});
oxw.WriteEndElement();
oxw.WriteEndElement();
oxw.Close();
oxw.Dispose();
}
}
}
}
private static Cell GetCell(Type type, string value)
{
var cell = new Cell();
if (type.ToString() == "System.RuntimeType")
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(value.SafeLeft(32767));
return cell;
}
if (type.ToString() == "System.Guid")
{
Guid guidResult;
Guid.TryParse(value, out guidResult);
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(guidResult.ToString());
return cell;
}
if (value == null || Convert.IsDBNull(value))
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue("");
return cell;
}
var typeCode = Type.GetTypeCode(type);
switch (typeCode)
{
case TypeCode.String:
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(value.SafeLeft(32767).ToValidXmlAsciiCharacters());
break;
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Double:
case TypeCode.Decimal:
case TypeCode.Single:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
cell.DataType = CellValues.Number;
cell.CellValue = new CellValue(value);
break;
case TypeCode.DateTime:
var dt = Convert.ToDateTime(value).Date;
cell.DataType = CellValues.String;
cell.CellValue = new CellValue($"{dt.Year}/{dt.MonthTwoCharacters()}/{dt.DayTwoCharacters()}");
break;
default:
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(value);
break;
}
return cell;
}
}
显然,这并不是理想的解决方案,但它将为您提供一个大型工作表,而不会出现内存异常。我在.NET 5上进行了测试,但在3.1上也应该可以工作。
我包括了关于OpenXml库的GitHub问题以及讨论此问题并获得解决方法的dotnet运行时问题。
System.IO.Packaging
NuGet包之前,AddWorkbookPart 为我们抛出了一个异常("Cannot retrieve parts of writeonly container"),但现在我想我们正在走向成功之路。 - StriplingWarriorvar rows = data.Select(x => (IDictionary<string, object>)x).ToList();
,该数据集返回了 325K 行和 13 列。在 Visual Studio 中进行调试后,第一阶段完成后内存增加了 1GB,即使第二阶段完成后也没有减少。我认为这种 SAX 方法和将文件写入磁盘的好处有助于减少内存占用。生成的 Excel 文件大小仅为 18MB。您有什么其他建议可以帮助吗?谢谢。 - OjMoxw.WriteStartElement(new Cell(), oxa);
和oxw.WriteEndElement();
这两行代码。我猜想,这部分代码在你上面的示例中可能遗漏了? - OjM