OpenXml“仅在生产时无法访问封闭流”

时间:2015-02-03 16:27:42

标签: c# asp.net openxml

生产环境:Server 2008 R2,作为网络服务运行的应用程序池。 我在特定的生产线上遇到了这个问题,我无法理解为什么会失败。

使用堆栈跟踪记录:

2015-02-03 11:19:29,389,DEBUG,44,Before Test1
2015-02-03 11:19:29,389,DEBUG,44,Before Test2
2015-02-03 11:19:29,451,DEBUG,44,Before Getting Row
2015-02-03 11:19:29,451,DEBUG,44,After Getting Row
2015-02-03 11:19:29,826,DEBUG,44,Before Test1
2015-02-03 11:19:29,841,DEBUG,44,Before Test2
2015-02-03 11:19:29,841,DEBUG,44,Before Getting Row
2015-02-03 11:19:29,841,DEBUG,44,After Getting Row
2015-02-03 11:19:30,044,DEBUG,44,Before Test1
2015-02-03 11:19:30,060,DEBUG,44,Before Test2
2015-02-03 11:19:30,075,DEBUG,44,Before Getting Row
2015-02-03 11:19:30,075,DEBUG,44,After Getting Row
2015-02-03 11:19:30,138,DEBUG,44,Before Test1
2015-02-03 11:19:30,138,DEBUG,44,Before Test2
2015-02-03 11:19:30,356,DEBUG,44,Before Getting Row
2015-02-03 11:19:30,356,DEBUG,44,After Getting Row
2015-02-03 11:19:31,058,DEBUG,44,Before Test1
2015-02-03 11:19:31,074,DEBUG,44,Before Test2
2015-02-03 11:19:31,245,DEBUG,44,Before Getting Row
2015-02-03 11:19:31,245,DEBUG,44,After Getting Row
2015-02-03 11:19:31,729,DEBUG,44,Before Test1
2015-02-03 11:19:31,729,DEBUG,44,Before Test2
2015-02-03 11:19:31,745,DEBUG,44,Before Getting Row
2015-02-03 11:19:31,745,DEBUG,44,After Getting Row
2015-02-03 11:19:31,776,DEBUG,44,Before Test1
2015-02-03 11:19:31,791,DEBUG,44,Before Test2
2015-02-03 11:19:31,807,DEBUG,44,Before Getting Row
2015-02-03 11:19:31,807,DEBUG,44,After Getting Row
2015-02-03 11:19:31,869,DEBUG,44,Before Test1
2015-02-03 11:19:31,869,DEBUG,44,Before Test2
2015-02-03 11:19:31,885,DEBUG,44,Before Getting Row
2015-02-03 11:19:31,885,DEBUG,44,After Getting Row
2015-02-03 11:19:31,947,DEBUG,44,Before Test1
2015-02-03 11:19:31,947,DEBUG,44,Before Test2
2015-02-03 11:19:32,103,ERROR,44,Error exporting using template 
System.ObjectDisposedException: Can not access a closed Stream.
   at System.IO.Compression.DeflateStream.EnsureNotDisposed()
   at MS.Internal.IO.Packaging.CompressStream.Flush()
   at MS.Internal.IO.Zip.ZipIOLocalFileBlock.FlushExposedStreams()
   at MS.Internal.IO.Zip.ZipIOLocalFileBlock.UpdateReferences(Boolean closingFlag)
   at MS.Internal.IO.Zip.ZipIOBlockManager.SaveContainer(Boolean closingFlag)
   at MS.Internal.IO.Zip.ZipIOBlockManager.SaveStream(ZipIOLocalFileBlock blockRequestingFlush, Boolean closingFlag)
   at MS.Internal.IO.Zip.ZipIOModeEnforcingStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.Xml.XmlUtf8RawTextWriter.Close()
   at System.Xml.XmlWellFormedWriter.Close()
   at DocumentFormat.OpenXml.OpenXmlPartRootElement.SaveToPart(OpenXmlPart openXmlPart)
   at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.SavePartContents()
   at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.Dispose(Boolean disposing)
   at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.Dispose()
   at Metrico.DatumBase.BizLogic.ExporterBL.CreateSpreadsheetExportUsingTemplate(DataSet dataSet, String templatePath, String exportPath)

代码:

try
{
    using (SpreadsheetDocument document = SpreadsheetDocument.Open(exportPath, true))
    {
        WorkbookPart workbookPart = document.WorkbookPart;

        // Iterate through the DataTables, and populate the existing worksheets with the same names
        for (int i = 1; i < dataSet.Tables.Count; i++)
        {
            // Grab the DataTable/Worksheet name from the first DataTable (like a Table of Contents)
            var worksheetName = dataSet.Tables[0].Rows[i - 1].Field<string>("FriendlyName");

            // Get the worksheet that has the same name
            Sheet theSheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault(s => s.Name == worksheetName);

            // Only attempt to process the sheet if it was found in the template
            if (theSheet != null)
            {
                // Get the worksheet to read from
                var worksheetPart = (WorksheetPart) workbookPart.GetPartById(theSheet.Id);
                string originalSheetID = workbookPart.GetIdOfPart(worksheetPart);

                // Make a copy of the worksheet to write to
                var replacementPart = workbookPart.AddNewPart<WorksheetPart>();
                string replacementPartID = workbookPart.GetIdOfPart(replacementPart);

                // Create the reader for the original and the writer for the replacement
                using (var reader = OpenXmlReader.Create(worksheetPart))
                {
                    using (var writer = OpenXmlWriter.Create(replacementPart))
                    {
                        // Get the cell formats for the first non-header row
                        WorkbookStylesPart stylesPart = workbookPart.GetPartsOfType<WorkbookStylesPart>().First();
                        Logger.Log(LogLevel.Debug, "Before Test1");
                        var test1 = worksheetPart;
                        Logger.Log(LogLevel.Debug, "Before Test2");
                        var test2 = worksheetPart.Worksheet;
                        Logger.Log(LogLevel.Debug, "Before Getting Row");
                        Row firstContentRow = worksheetPart.Worksheet.Descendants<Row>().FirstOrDefault(r => r.RowIndex == 2);
                        Logger.Log(LogLevel.Debug, "After Getting Row");
                        Dictionary<string, uint> columnFormats = GetCellFormats(firstContentRow);
                        // Create a DateTime cell format style
                        var dateFormat = new CellFormat
                        {
                            NumberFormatId = UInt32Value.FromUInt32(22),
                            ApplyNumberFormat = BooleanValue.FromBoolean(true)
                        };
                        stylesPart.Stylesheet.CellFormats.Append(dateFormat);
                        UInt32Value dateStyleIndex = stylesPart.Stylesheet.CellFormats.Count;
                        stylesPart.Stylesheet.CellFormats.Count++;

                        // Read from the template worksheet and write to the new worksheet
                        while (reader.Read())
                        {
                            // We only care about altering the contents of the SheetData element
                            if (reader.ElementType == typeof (SheetData))
                            {
                                if (reader.IsEndElement)
                                {
                                    continue;
                                }

                                // Write the start of the SheetData element
                                writer.WriteStartElement(new SheetData());

                                // Add column names to the first Excel row
                                var headerRow = new Row();
                                foreach (DataColumn column in dataSet.Tables[i].Columns)
                                {
                                    var headerCell = CreateTextCell(
                                        dataSet.Tables[i].Columns.IndexOf(column) + 1, 1, column.ColumnName, null);
                                    headerRow.Append(headerCell);
                                }
                                // Write the head row element
                                writer.WriteElement(headerRow);

                                // Loop through each DataRow and populate the corresponding Excel row
                                for (int j = 0; j < dataSet.Tables[i].Rows.Count; j++)
                                {
                                    var contentRow = dataSet.Tables[i].Rows[j];

                                    Row row = CreateContentRow(contentRow, j + 2, columnFormats, dateStyleIndex);

                                    // Write the row element
                                    writer.WriteElement(row);

                                }

                                // Write the end of the SheetData element
                                writer.WriteEndElement();
                            }
                            else
                            {
                                // Automatically copy over all other elements
                                if (reader.IsStartElement)
                                {
                                    writer.WriteStartElement(reader);
                                }
                                else if (reader.IsEndElement)
                                {
                                    writer.WriteEndElement();
                                }
                            }
                        }
                    }
                }

                // Point the workbook to the new sheet and delete the old one
                Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().First(s => s.Id.Value.Equals(originalSheetID));
                sheet.Id.Value = replacementPartID;
                workbookPart.DeletePart(worksheetPart);
            }
        }

        //This section will remove data from sheets that have not been populated, leaving the header row intact
        List<string> emptyTasks = new List<string>
        {
            "task",
        };

        var currentTasks =
            dataSet.Tables[0].AsEnumerable()
                .Select(x => x.Field<string>("FriendlyName").ToString(CultureInfo.InvariantCulture))
                .ToList();

        foreach (string task in currentTasks.Where(emptyTasks.Contains))
        {
            emptyTasks.Remove(task);
        }

        foreach (string task in emptyTasks)
        {
            Sheet theSheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault(s => s.Name == task);
            if (theSheet != null)
            {
                WorksheetPart worksheetPart = (WorksheetPart) workbookPart.GetPartById(theSheet.Id);

                SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

                sheetData.Elements<Row>().Where(r => r.RowIndex > 1).ToList().ForEach(x => x.Remove());
                theSheet.Elements<Row>().ToList().ForEach(x => x.Remove());

                worksheetPart.Worksheet.Save();
            }
        }

        workbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
        workbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;
    }
}
catch (Exception ex)
{
    Logger.Log(LogLevel.Error, "Error exporting using template ", ex);
    throw;
}

正如您所看到的那样,它成功运行了几次这个循环,然后它似乎只是在访问工作表时中断了? WTF?这似乎也不会发生在每个服务器上。任何想法都被赞赏,因为我没有想法。

另外值得注意的是:即使抛出异常,似乎某些东西仍然锁定了这个文件。如果我尝试复制我得到的文件:该操作无法完成,因为该文件在w3wp.exe中打开。“所以文件不会被处理掉?

将应用程序池更改为“LocalSystem”可防止抛出异常,但会导致系统锁定。它在完全相同的位置失败,但不会抛出异常并且在进程中没有得到任何进一步(该文件与其他失败文件的大小完全相同,并且日志是相同的)。

将try / catch移动到using语句中会暴露一个新的异常:

2015-02-04 09:45:23,577,ERROR,6,Error exporting using template 
System.IO.IsolatedStorage.IsolatedStorageException: Unable to create mutex. (Exception from HRESULT: 0x80131464)
   at System.IO.IsolatedStorage.IsolatedStorageFile.Open(String infoFile, String syncName)
   at System.IO.IsolatedStorage.IsolatedStorageFile.Lock(Boolean& locked)
   at System.IO.IsolatedStorage.IsolatedStorageFileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, IsolatedStorageFile isf)
   at System.IO.IsolatedStorage.IsolatedStorageFileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, IsolatedStorageFile isf)
   at MS.Internal.IO.Packaging.PackagingUtilities.SafeIsolatedStorageFileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, ReliableIsolatedStorageFileFolder folder)
   at MS.Internal.IO.Packaging.PackagingUtilities.CreateUserScopedIsolatedStorageFileStreamWithRandomName(Int32 retryCount, String& fileName)
   at MS.Internal.IO.Packaging.SparseMemoryStream.EnsureIsolatedStoreStream()
   at MS.Internal.IO.Packaging.SparseMemoryStream.SwitchModeIfNecessary()
   at MS.Internal.IO.Packaging.DeflateEmulationTransform.Decompress(Stream source, Stream sink)
   at MS.Internal.IO.Packaging.CompressEmulationStream..ctor(Stream baseStream, Stream tempStream, Int64 position, IDeflateTransform transformer)
   at MS.Internal.IO.Packaging.CompressStream.ChangeMode(Mode newMode)
   at MS.Internal.IO.Packaging.CompressStream.Seek(Int64 offset, SeekOrigin origin)
   at MS.Internal.IO.Zip.ZipIOModeEnforcingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Xml.XmlTextReaderImpl.InitStreamInput(Uri baseUri, String baseUriStr, Stream stream, Byte[] bytes, Int32 byteCount, Encoding encoding)
   at System.Xml.XmlTextReaderImpl.FinishInitStream()
   at System.Xml.XmlReaderSettings.CreateReader(Stream input, Uri baseUri, String baseUriString, XmlParserContext inputContext)
   at DocumentFormat.OpenXml.OpenXmlPartRootElement.LoadFromPart(OpenXmlPart openXmlPart, Stream partStream)
   at DocumentFormat.OpenXml.Packaging.OpenXmlPart.LoadDomTree[T]()
   at DocumentFormat.OpenXml.Packaging.WorksheetPart.get_Worksheet()
   at Metrico.DatumBase.BizLogic.ExporterBL.CreateSpreadsheetExportUsingTemplate(DataSet dataSet, String templatePath, String exportPath)

1 个答案:

答案 0 :(得分:2)

今天我读到了OpenXML SDK(2.5及以下版本)中的一个令人讨厌的错误,它可能是这些异常的来源。

该错误仅在非常特定的情况下发生。 System.IO.Packaging使用IsolatedStorage文件&gt; 10MB但如果两个线程或可执行文件尝试访问它将失败。

  

System.IO.Packaging中存在一个错误,它在某些情况下会导致在Web前端使用Open XML SDK实现的Open XML功能时抛出虚假的ObjectDisposedException和NullReferenceException。此错误的要点是,如果System.IO.Packaging的内部内存使用率超过10MB的阈值,则System.IO.Packaging使用System.IO.IsolatedStorage,如果两个可执行文件或线程同时尝试访问它,则会发生故障。当使用具有相同强名称的多个exes(就像我们将在多个Web前端应用程序中使用)或从高性能多线程Open XML应用程序中使用IsolatedStorage时,会发生这种情况。

Source

目前您需要从GitHub repo构建SDK,因为还没有正式版本。