如何使用 OpenXmlWriter 配置 xlsx 格式和公式

How to configure xlsx format and formulas using OpenXmlWriter

我需要创建大型通用 xlsx 报告(超过 50 万行,约 50 列)。 我编写了生成此报告的方法,但我无法弄清楚如何设置单元格格式(数据 format/style(边框等))以及如何使用 OpenXmlWriter 将公式放入单元格。

  private void FillExcelWorksheet<T>(String fileName, IEnumerable<T> source)
        {
            var properties = typeof(T).GetProperties();
            using (SpreadsheetDocument document = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook))
            {
                //this list of attributes will be used when writing a start element
                List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
                {
                    new OpenXmlAttribute("t", null, "str")
                };

                OpenXmlWriter writer;

                var workBookPart = document.AddWorkbookPart();
                var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();

                writer = OpenXmlWriter.Create(workSheetPart);
                writer.WriteStartElement(new Worksheet());
                writer.WriteStartElement(new SheetData());

                writer.WriteStartElement(new Row());
                foreach (PropertyInfo property in properties)
                {
                    var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();

                    if (pAttributes.Any())
                    {
                        var attribute = pAttributes.First();
                        writer.WriteStartElement(new Cell(), stringAttributes);
                        //write the cell value
                        writer.WriteElement(new CellValue(attribute.ColumnName));

                        // write the end cell element
                        writer.WriteEndElement();
                    }
                }

                // write the end row element
                writer.WriteEndElement();

                foreach (var item in source)
                {
                    //write the row start element with the row index attribute
                    writer.WriteStartElement(new Row());

                    foreach (PropertyInfo property in properties)
                    {
                        var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();

                        if (pAttributes.Any())
                        {
                            //write the cell start element with the type and reference attributes
                            writer.WriteStartElement(new Cell(), stringAttributes);
                            //write the cell value
                            writer.WriteElement(new CellValue(property.GetValue(item) != null ?
                                property.GetValue(item).ToString() : String.Empty));

                            var attribute = pAttributes.First();
                            var format = attribute.Format;
                            //todo apply data format

                            // write the end cell element
                            writer.WriteEndElement();
                        }
                    }

                    // write the end row element
                    writer.WriteEndElement();
                }
                // write the end SheetData element
                writer.WriteEndElement();
                // write the end Worksheet element
                writer.WriteEndElement();
                writer.Close();

                writer = OpenXmlWriter.Create(document.WorkbookPart);
                writer.WriteStartElement(new Workbook());
                writer.WriteStartElement(new Sheets());

                writer.WriteElement(new Sheet()
                {
                    Name = fileName,
                    SheetId = 1,
                    Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
                });

                // End Sheets
                writer.WriteEndElement();
                // End Workbook
                writer.WriteEndElement();

                writer.Close();

                document.Close();
            }
        }
result with styles 
private DownloadResult FillExcelWorksheet<T>(String name, IEnumerable<T> source, Action<OpenXmlWriter, WorkbookStylesPart, SharedStringTablePart, Int32> addNonGenericReportInfo = null)
    {
        var properties = typeof(T).GetProperties();
        var folder = ConfigurationManager.AppSettings["ReportsPath"];
        if (!Directory.Exists(folder)) {
            Directory.CreateDirectory(folder);
        }

        //remove old files
        Directory.GetFiles(folder)
             .Select(f => new FileInfo(f))
             .Where(f => f.CreationTime < DateTime.Now.AddDays(-1))
             .ToList()
             .ForEach(f => f.Delete());

        var fileName = String.Format("{0}_{1}.xlsx", name, Guid.NewGuid());

        using (var document = SpreadsheetDocument.Create(folder + fileName, SpreadsheetDocumentType.Workbook))
        {
            //this list of attributes will be used when writing a start element
            List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
            {
                new OpenXmlAttribute("t", null, "str")
            };

            OpenXmlWriter writer;

            var workBookPart = document.AddWorkbookPart();
            var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
            var sharedStringPart = workBookPart.AddNewPart<SharedStringTablePart>();
            sharedStringPart.SharedStringTable = new SharedStringTable();

            var stylesPart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
            stylesPart.Stylesheet = new Stylesheet();
            stylesPart.Stylesheet.NumberingFormats = new NumberingFormats();

            // create fills
            stylesPart.Stylesheet.Fills = new Fills();
            stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); // required, reserved by Excel
            //EXAMPLE DON'T REMOVE
            //stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } }); // required, reserved by Excel

            //// create a solid red fill
            //var solidRed = new PatternFill() { PatternType = PatternValues.Solid };
            //solidRed.ForegroundColor = new ForegroundColor { Rgb = HexBinaryValue.FromString("FFFF0000") }; // red fill
            //solidRed.BackgroundColor = new BackgroundColor { Indexed = 64 };
            //stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = solidRed });
            stylesPart.Stylesheet.Fills.Count = 3;

            // blank cell format list
            stylesPart.Stylesheet.CellStyleFormats = new CellStyleFormats();
            stylesPart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat());
            stylesPart.Stylesheet.CellStyleFormats.Count = 1;

            //Borders
            stylesPart.Stylesheet.Borders = new Borders();
            stylesPart.Stylesheet.Borders.AppendChild(new Border());
            var border = new Border();
            border.Append(new BottomBorder() { Style = BorderStyleValues.Thin });
            stylesPart.Stylesheet.Borders.AppendChild(border);
            stylesPart.Stylesheet.Borders.Count = 2;

            //Fonts
            stylesPart.Stylesheet.Fonts = new Fonts();
            stylesPart.Stylesheet.Fonts.AppendChild(new Font());
            var font = new Font();
            font.Bold = new Bold();
            stylesPart.Stylesheet.Fonts.AppendChild(font);
            stylesPart.Stylesheet.Fonts.Count = 2;

            // cell format list
            stylesPart.Stylesheet.CellFormats = new CellFormats();
            // empty one for index 0, seems to be required
            stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat());
            // cell format references style format 0, font 0, border 0, fill 2 and applies the fill
            stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { FormatId = 0, FontId = 1, BorderId = 1, FillId = 0, ApplyFill = true }).AppendChild(new Alignment { Horizontal = HorizontalAlignmentValues.Center });
            stylesPart.Stylesheet.CellFormats.Count = 2;


            writer = OpenXmlWriter.Create(workSheetPart);
            writer.WriteStartElement(new Worksheet());

            writer.WriteStartElement(new SheetData());

            writer.WriteStartElement(new Row());
            foreach (PropertyInfo property in properties)
            {
                var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();

                if (pAttributes.Any())
                {
                    var attribute = pAttributes.First();

                    var cell = new Cell();
                    cell.DataType = CellValues.SharedString;
                    cell.StyleIndex = 1;
                    writer.WriteStartElement(cell);
                    //write the cell value
                    var index = InsertSharedStringItem(attribute.ColumnName, sharedStringPart);
                    writer.WriteElement(new CellValue(index.ToString()));

                    // write the end cell element
                    writer.WriteEndElement();
                    if (!String.IsNullOrEmpty(attribute.Format) && !_styleFormats.ContainsKey(attribute.Format))
                    {
                        //164 is first free format id
                        var formatId = 164 + (uint)_styleFormats.Count;
                        var format = new NumberingFormat { NumberFormatId = formatId, FormatCode = attribute.Format };
                        stylesPart.Stylesheet.NumberingFormats.AppendChild(format);
                        stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { NumberFormatId = formatId, FontId = 0, BorderId = 0, FillId = 0, ApplyFill = true });
                        //stylesPart.Stylesheet.CellFormats.AppendChild(format);
                        stylesPart.Stylesheet.CellStyleFormats.Count++;

                        _styleFormats.Add(attribute.Format, new StyleIndexes(stylesPart.Stylesheet.CellFormats.Count, formatId));
                        stylesPart.Stylesheet.CellFormats.Count++;
                    }

                }
            }
            // write the end row element
            writer.WriteEndElement();

            foreach (var item in source)
            {
                //write the row start element with the row index attribute
                writer.WriteStartElement(new Row());

                foreach (PropertyInfo property in properties)
                {
                    var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();

                    if (pAttributes.Any())
                    {
                        var attribute = pAttributes.First();

                        var cell = new Cell();

                        if (!String.IsNullOrEmpty(attribute.Format))
                        {
                            cell.StyleIndex = (uint)_styleFormats[attribute.Format].StyleIndex;
                        }

                        var typeCode = Type.GetTypeCode(property.PropertyType);
                        //write the cell start element with the type and reference attributes
                        if (typeCode == TypeCode.String)
                        {
                            writer.WriteStartElement(cell, stringAttributes);
                        }
                        else
                        {
                            cell.DataType = CellValues.Number;
                            writer.WriteStartElement(cell);
                        }

                        var value = String.Empty;

                        if (typeCode == TypeCode.DateTime)
                        {
                            value = property.GetValue(item) != null ?
                                Convert.ToDateTime(property.GetValue(item)).ToOADate().ToString() : String.Empty;

                        }
                        else { 
                            value = property.GetValue(item) != null ?
                                property.GetValue(item).ToString() : String.Empty;
                        }

                        //write the cell value
                        writer.WriteElement(new CellValue(value));

                        // write the end cell element
                        writer.WriteEndElement();
                    }
                }

                // write the end row element
                writer.WriteEndElement();
            }

            if (addNonGenericReportInfo != null)
            {
                addNonGenericReportInfo(writer, stylesPart, sharedStringPart, source.Count());
            }

            sharedStringPart.SharedStringTable.Save();
            stylesPart.Stylesheet.Save();

            // write the end SheetData element
            writer.WriteEndElement();
            // write the end Worksheet element
            writer.WriteEndElement();
            writer.Close();

            writer = OpenXmlWriter.Create(document.WorkbookPart);
            writer.WriteStartElement(new Workbook());
            writer.WriteStartElement(new Sheets());

            writer.WriteElement(new Sheet()
            {
                Name = name,
                SheetId = 1,
                Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
            });

            // End Sheets
            writer.WriteEndElement();
            // End Workbook
            writer.WriteEndElement();

            writer.Close();

            document.Close();
        }

        return new DownloadResult
        {
            Name = name + ".xlsx",
            PathToDownload = folder + fileName,
        };
    }

    private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
    {
        // If the part does not contain a SharedStringTable, create one.
        if (shareStringPart.SharedStringTable == null)
        {
            shareStringPart.SharedStringTable = new SharedStringTable();
        }

        int i = 0;

        // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
        foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
        {
            if (item.InnerText == text)
            {
                return i;
            }

            i++;
        }

        // The text does not exist in the part. Create the SharedStringItem and return its index.
        shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
        shareStringPart.SharedStringTable.Save();

        return i;
    }

    private Dictionary<String, StyleIndexes> _styleFormats;

    private class StyleIndexes
    {
        public StyleIndexes(uint styleIndex, uint numberIndex)
        {
            StyleIndex = styleIndex;
            NumberIndex = numberIndex;
        }

        public uint StyleIndex { get; set; }
        public uint NumberIndex { get; set; }
    }