将 table 而不是范围定义为数据透视表 'cacheSource'

Defining a table rather than a range as a PivotTable 'cacheSource'

我正在构建一个工具来自动创建一个 Excel 工作簿,其中包含一个 table 和一个关联的数据透视表。 table 结构位于一个 sheet 上,稍后将使用其他工具从数据库中提取其数据。数据透视表位于第二个 sheet 上,使用前一个 sheet 中的 table 作为来源。

我正在使用 EPPlus 来帮助构建工具,但 运行 在指定 cacheSource 时遇到问题。我正在使用以下内容创建范围和数据透视表:

 var dataRange = dataWorksheet.Cells[dataWorksheet.Dimension.Address.ToString()];

 var pivotTable = pivotWorksheet.PivotTables.Add(pivotWorksheet.Cells["B3"], dataRange, name);

这会将 cacheSource 设置为:

<x:cacheSource type="worksheet" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:worksheetSource ref="A1:X2" sheet="dataWorksheet" />

或Excel内,数据源设置为:

dataWorksheet!$A:$X

如果 table 大小从不改变,这就可以正常工作,但由于行数是动态的,我发现刷新数据时,仅从指定的初始范围读取数据。

我想做的是以编程方式将 cacheSource 设置为:

<x:cacheSource type="worksheet" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <x:worksheetSource name="dataWorksheet" />
</x:cacheSource>

或在Excel中,将数据源设置为:

dataWorksheet

我相信可以通过直接访问 XML 来做到这一点(欢迎任何关于此的指示)但是有没有办法使用 EPPlus 来做到这一点?

可以做到,但这不是世界上最美好的事情。您可以提取缓存 def xml 并从创建的 EPPlus pivot table 对象对其进行编辑,但是当您调用 package.save()(或 GetAsByteArray())时,这将对保存逻辑造成严重破坏因为它在保存时解析 xml 以生成最终文件。正如您所说,这是 EPPlus 无法处理 table 作为源的结果。

因此,您的替代方法是使用 EPPlus 正常保存文件,然后使用 .net ZipArchive 操作 xlsx 的内容,它是重命名的 zip 文件。诀窍是你不能在 zip 中乱序操作文件,否则 Excel 打开文件时会报错。由于您无法插入条目(只能添加到末尾),因此您必须重新创建 zip。这是 ZipArchive 上的扩展方法,可让您更新缓存源:

public static bool SetCacheSourceToTable(this ZipArchive xlsxZip, FileInfo destinationFileInfo, string tablename, int cacheSourceNumber = 1)
{
    var cacheFound = false;
    var cacheName = String.Format("pivotCacheDefinition{0}.xml", cacheSourceNumber);

    using (var copiedzip = new ZipArchive(destinationFileInfo.Open(FileMode.Create, FileAccess.ReadWrite), ZipArchiveMode.Update))
    {
        //Go though each file in the zip one by one and copy over to the new file - entries need to be in order
        xlsxZip.Entries.ToList().ForEach(entry =>
        {
            var newentry = copiedzip.CreateEntry(entry.FullName);
            var newstream = newentry.Open();
            var orgstream = entry.Open();

            //Copy all other files except the cache def we are after
            if (entry.Name != cacheName)
            {
                orgstream.CopyTo(newstream);
            }
            else
            {
                cacheFound = true;

                //Load the xml document to manipulate
                var xdoc = new XmlDocument();
                xdoc.Load(orgstream);

                //Get reference to the worksheet xml for proper namespace
                var nsm = new XmlNamespaceManager(xdoc.NameTable);
                nsm.AddNamespace("default", xdoc.DocumentElement.NamespaceURI);

                //get the source
                var worksheetSource = xdoc.SelectSingleNode("/default:pivotCacheDefinition/default:cacheSource/default:worksheetSource", nsm);

                //Clear the attributes
                var att = worksheetSource.Attributes["ref"];
                worksheetSource.Attributes.Remove(att);

                att = worksheetSource.Attributes["sheet"];
                worksheetSource.Attributes.Remove(att);

                //Create the new attribute for table
                att = xdoc.CreateAttribute("name");
                att.Value = tablename;
                worksheetSource.Attributes.Append(att);

                xdoc.Save(newstream);
            }

            orgstream.Close();
            newstream.Flush();
            newstream.Close();
        });
    }

    return cacheFound;

}

下面是使用方法:

//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[]
{
    new DataColumn("Col1", typeof (int)), new DataColumn("Col2", typeof (int)), new DataColumn("Col3", typeof (object))
});

for (var i = 0; i < 10; i++)
{
    var row = datatable.NewRow();
    row[0] = i; row[1] = i*10; row[2] = Path.GetRandomFileName();
    datatable.Rows.Add(row);
}

const string tablename = "PivotTableSource";
using (var pck = new ExcelPackage())
{
    var workbook = pck.Workbook;

    var source = workbook.Worksheets.Add("source");
    source.Cells.LoadFromDataTable(datatable, true);
    var datacells = source.Cells["A1:C11"];

    source.Tables.Add(datacells, tablename);

    var pivotsheet = workbook.Worksheets.Add("pivot");
    pivotsheet.PivotTables.Add(pivotsheet.Cells["A1"], datacells, "PivotTable1");

    using (var orginalzip = new ZipArchive(new MemoryStream(pck.GetAsByteArray()), ZipArchiveMode.Read))
    {
        var fi = new FileInfo(@"c:\temp\Pivot_From_Table.xlsx");
        if (fi.Exists)
            fi.Delete(); 

        var result = orginalzip.SetCacheSourceToTable(fi, tablename, 1);
        Console.Write("Cache source was updated: ");
        Console.Write(result);
    }
}