.NET使用Office Open XML导出大量数据到 Excel

我相信很多人在做项目的都碰到过Excel数据导出的需求,我从最开始使用最原始的HTML拼接(将需要导出的数据拼接成TABLE标签)到后来happy的使用开源的NPOI, EPPlus等开源组件导出EXCEL,但不久前,我在一个项目碰到一个需求:要将几个分别有近60多万的数据源导出到Excel中,我们先不要讨论这个需求本身是否合理,客户就是要这样。我先后用NPOI和EPPlus,都发现同一个问题:OutOfMemoryException,我电脑12G内存居然不够用?

 

的确内存溢出了,但内存还剩下好几个G的,就会溢出,我用 .NET做的网站,开发的时候Host应该是Visual Studio安装的IIS Express, 应该是VS本身的限制,不过在网上查阅资料也没发现这的确也是困扰一些人的,也没查到什么结果,好在还有Google, 跃过墙外,在Stack Overflow上查到资料: OpenXML , 这不是什么新技:  Office 2007在设计的时候, 为了更好的和其它应用程序交互,使用了XML + ZIP技术来实现excel, world, PPT等组件的本地保存, 我们所使用xlsx, dox, pptx文件本质上就一个ZIP压缩包,包内是组织好的XML文件,也就是说,我们可以通过生成, 修改, 生成合规的XML文件,再压缩成ZIP包,这就是一个可以被Office识别的文件了。

用图说话:

 

 

 

在园子里其实也有不少人介绍过 Open XML, 我想就多一个视角来介绍Open XML吧,好像也有很长时间没人写关于这个博文。

 

什么是Office Open XML?

我们来看下维基百科的定义:

Office Open XML (also informally known as OOXML or Microsoft Open XML (MOX)[2) is a zipped, XML-based file format developed by Microsoft[3] for representing spreadsheets, charts, presentations and word processing documents. The format was initially standardized by Ecma (as ECMA-376), and by the ISO and IEC (as ISO/IEC 29500) in later versions.

Starting with Microsoft Office 2007, the Office Open XML file formats have become the default[4] target file format of Microsoft Office.[5][6] Microsoft Office 2010 provides read support for ECMA-376, read/write support for ISO/IEC 29500 Transitional, and read support for ISO/IEC 29500 Strict.[7] Microsoft Office 2013 and Microsoft Office 2016 additionally support both reading and writing of ISO/IEC 29500 Strict.[8]re

refer: https://en.wikipedia.org/wiki/Office_Open_XML

从Office 2007开始,就开始使用XML文件格式作为Microsoft Office的默认保存方式,其实我们通常用的NPOI  office 2007部分和EPPlus就是使用Open XML来开发的。

 

为什么同是使用Open XML, NPOI和EPPLus会出现内存溢出的问题?

 这两个开源组件有对Office套件有着很全面的支持,它们会把数据加载到内存中一次性处理,如果碰到数据量过大,就很可能 遇到这个问题,网上EPPlus在20多万条数据的就溢出了,NPOI在11多万的时候就会溢出, 这个是和数据的列数和内容有关系,不管怎样,我们以后可能是会碰到这种大量数据的EXCEL导出,我们不需要很复杂的功能,就是想要导出一个EXCEL列表,这其实是可以做到的。

 

Open XML怎样做不会内存溢出?

NPOI和EPPlus在导出大量数据 的Excel列表时可能 会发生内存溢出的问题,原因是它们都把数据保存在内存中,因为它们支持各种复杂的功能,那么简单的列表,就是数量超大,我们把它通过文件流写入磁盘,这个问题就解决了。

 

如何使用OPEN XML?

我们需要去微软官网下载OFFICE OPEN XML的SDK,链接: https://www.microsoft.com/en-hk/download/details.aspx?id=30425,推荐使用NuGet在VISULAL STUDIO直接将引用添加到Project。

在GitHub还有一些示例代码:https://github.com/OfficeDev/Open-XML-SDK

 

代码实现

说了这么多废话,我们看如何用OPEN XML实现一个EXCEL列表的导出:

从原理上讲就是用OpenXML一个一个把标签写入本地磁盘。

我截取我写的导出类的几个方法来来解释:

复制代码
/// <summary>
/// 指定磁盘路径初始化OpenWorkDoucment
/// </summary>
/// <param name="fileName"></param>
private void OpenWorkDocument(string fileName)
{document = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook);
}
复制代码

 

复制代码
///<summary>
///用datatable作为数据源,实际情况可以根据需要调整
///</summary>
public void AddSheet(DataTable dt, string sheetName)
        {if (dt == null || dt.Rows.Count == 0){throw new ArgumentNullException(nameof(dt), "data source can not be null");}if (document == null){throw new ArgumentNullException(nameof(document), "please init document first");}//this list of attributes will be used when writing a start elementList<OpenXmlAttribute> attributes;
       //这是我们为什么不会溢出的关键点, 使用XmlWriter写入磁盘OpenXmlWriter writer; WorksheetPart workSheetPart = document.WorkbookPart.AddNewPart<WorksheetPart>();writer = OpenXmlWriter.Create(workSheetPart);
    //使用OpenXML麻烦的地方就是我们要用SDK去拼接XML内容writer.WriteStartElement(new Worksheet());writer.WriteStartElement(new SheetViews()); //sheetViewswriter.WriteStartElement(new SheetView() //sheetView{TabSelected = true,WorkbookViewId = 0U //这里的下标是从0开始的});//这里是冻结列头,别问为什么是A2,我试了A1不行Pane pane = new Pane(){State = new EnumValue<PaneStateValues>(PaneStateValues.Frozen),VerticalSplit = new DoubleValue((double)1),TopLeftCell = new StringValue("A2"),ActivePane = new EnumValue<PaneValues>(PaneValues.BottomLeft)};//对于一些文档本身的结构的描述,我们可以直接把准备属性设置正确,直接写入,因为描述实例很占用资源小,当然我们也可以把描述结点的子节点,子子节点都通过WriteStartElememt写入,不过很麻烦,容易出错writer.WriteStartElement(pane); //Panewriter.WriteEndElement(); //Panewriter.WriteStartElement(new Selection(){Pane = new EnumValue<PaneValues>(PaneValues.BottomLeft)});writer.WriteEndElement(); //Selection 关闭标签writer.WriteEndElement(); //sheetView 关闭标签writer.WriteEndElement(); //sheetViews 关闭标签writer.WriteStartElement(new SheetData());var rowIndex = 0;foreach (DataRow row in dt.Rows){//build headerif (rowIndex == 0){//create a new list of attributesattributes = new List<OpenXmlAttribute>();// add the row index attribute to the listattributes.Add(new OpenXmlAttribute("r", null, (rowIndex + 1).ToString()));//header startwriter.WriteStartElement(new Row(), attributes);foreach (DataColumn col in dt.Columns){attributes = new List<OpenXmlAttribute>();//这里注意,在Excel在处理字符串的时候,会将所有的字符串保存到sharedStrings.xml, cell内写入在sharedString.XML的索引, 属性t(type)设置为s(str)//我们在导出excel的时候把sharedString.mxl考虑进来会加大复杂程度,所以将t设置为str, 一个不存在的type, excel会直接解析cell内的字串值attributes.Add(new OpenXmlAttribute("t", null, "str"));//通过s指定style样式的下标attributes.Add(new OpenXmlAttribute("s", null, FORMAT_INDEX_HEADER.ToString()));
//能过r指定单元格位置,好像不是必需, 注意这里下标位置是从1开始的attributes.Add(new OpenXmlAttribute("r", "", string.Format("{0}{1}", GetColumnName(col.Ordinal + 1), rowIndex + 1)));writer.WriteStartElement(new Cell(), attributes);writer.WriteElement(new CellValue(col.ColumnName));writer.WriteEndElement();}//header endwriter.WriteEndElement();rowIndex++;}//数据写入,我们通过xmlWriter不会触发异常//create a new list of attributesattributes = new List<OpenXmlAttribute>();// add the row index attribute to the listattributes.Add(new OpenXmlAttribute("r", null, (rowIndex + 1).ToString()));//header startwriter.WriteStartElement(new Row(), attributes);foreach (DataColumn col in dt.Columns){attributes = new List<OpenXmlAttribute>();switch (col.DataType.ToString()){case "System.Int32":attributes.Add(new OpenXmlAttribute("s", null, FORMAT_INDEX_INT.ToString()));attributes.Add(new OpenXmlAttribute("t", null, "n")); //numberbreak;case "System.Double":case "System.Decimal":case "System.Float":attributes.Add(new OpenXmlAttribute("s", null, FORMAT_INDEX_DEC.ToString())); //header styleattributes.Add(new OpenXmlAttribute("t", null, "n")); //numberbreak;default:attributes.Add(new OpenXmlAttribute("s", null, FORMAT_INDEX_STR.ToString())); //header styleattributes.Add(new OpenXmlAttribute("t", null, "str")); //stringbreak;}//add the cell reference attributeattributes.Add(new OpenXmlAttribute("r", null, string.Format("{0}{1}", GetColumnName(col.Ordinal + 1), rowIndex + 1)));writer.WriteStartElement(new Cell(), attributes);writer.WriteElement(new CellValue(row[col.Ordinal].ToString()));writer.WriteEndElement();}//header endwriter.WriteEndElement();rowIndex++;}// End SheetDatawriter.WriteEndElement();// End Worksheetwriter.WriteEndElement();writer.Close();if (document.WorkbookPart.Workbook == null){document.WorkbookPart.Workbook = new Workbook();document.WorkbookPart.Workbook.Append(new Sheets());}//数据写入完成后,注册一个sheet引用到workbook.xml, 也就是在excel最下面的sheet namevar sheet = new Sheet(){Name = !String.IsNullOrWhiteSpace(sheetName) ? sheetName : ("Sheet " + DateTime.Now.ToString("ms")),SheetId = UInt32Value.FromUInt32((uint)m_sheetIndex++),Id = document.WorkbookPart.GetIdOfPart(workSheetPart)};document.WorkbookPart.Workbook.Sheets.Append(sheet); }
复制代码

 

复制代码
//生成Style样式, 注意下标从0开始, 依次加1, 如果有跳过1直接设置3这样情况, 可能无法正常解析到样式
private Stylesheet GenerateStylesheet(){Stylesheet styleSheet = null;Fonts fonts = new Fonts(new Font( // Index 0 - defaultnew FontSize() { Val = 11 }),new Font( // Index 1 - headernew FontSize() { Val = 11 },new Bold(),new Color() { Rgb = "FFFFFF" }));Fills fills = new Fills(new Fill(new PatternFill() { PatternType = PatternValues.None }), // Index 0 - defaultnew Fill(new PatternFill() { PatternType = PatternValues.Gray125 }), // Index 1 - defaultnew Fill(new PatternFill(new ForegroundColor { Rgb = new HexBinaryValue() { Value = "0070c0" } }) { PatternType = PatternValues.Solid }));Borders borders = new Borders(new Border(), // index 0 defaultnew Border( // index 1 black bordernew LeftBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin },new RightBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin },new TopBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin },new BottomBorder(new Color() { Auto = true }) { Style = BorderStyleValues.Thin },new DiagonalBorder()));NumberingFormats numbers = new NumberingFormats(new NumberingFormat() { NumberFormatId = 0, FormatCode = new StringValue("#,##0.00") },new NumberingFormat() { NumberFormatId = 1, FormatCode = new StringValue("0") });CellFormats cellFormats = new CellFormats(// defaultnew CellFormat() { FormatId = FORMAT_INDEX_DEFUALT },// body  stringnew CellFormat { FormatId = FORMAT_INDEX_STR, FontId = 0, FillId = 0, BorderId = 1, ApplyBorder = true },// body decimalnew CellFormat { FormatId = FORMAT_INDEX_DEC, FontId = 0, FillId = 0, BorderId = 1, NumberFormatId = 0, ApplyBorder = true },//headernew CellFormat { FormatId = FORMAT_INDEX_HEADER, FontId = 1, FillId = 2, BorderId = 1, ApplyFill = true }, // header// body  intnew CellFormat { FormatId = FORMAT_INDEX_INT, FontId = 0, FillId = 0, BorderId = 1, NumberFormatId = 1, ApplyBorder = true });styleSheet = new Stylesheet(numbers, fonts, fills, borders, cellFormats);return styleSheet;  }  
复制代码

 

复制代码
private void WriteWorkbookStyle(){if (document != null){WorkbookStylesPart stylePart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();var styleSheet = GenerateStylesheet();styleSheet.Save(stylePart);}}
复制代码

 

 

 设置样式,冻结首行,这些都可以简单完成,如果需要添加图表什么的,还是建议用NPOI, EPPlus等开源方案,有图表的excel不会太大。

 

对于Open XML的介绍就到这里了,有什么错误的地方,请指正。

转载于:https://www.cnblogs.com/efreer/p/8286645.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS自定义消息提示

1.效果 2.源码 <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><style type"text/css">#confirm{position: absolute;z-index: 1;display: inline-block;border: 1px solid black;backgr…

file 选择的文件胖多有多大_如何删除 macOS 压缩包中的隐藏文件?

如何删除 macOS 压缩包中的隐藏文件&#xff1f;在工作中&#xff0c;压缩打包文件是不可缺少的一项工作。为了避免文件的损坏和缺失&#xff0c;我们通常在macOS系统中&#xff0c;把多个文件或文件夹压缩后进行传输&#xff0c;我们一般使用访达&#xff08;Finder&#xff0…

CSS画各种二维图形

1.效果 2.源码 <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><style type"text/css">#triangle_top{display: inline-block;width:0;height:0;border-left:50px solid transparent;bor…

数据库的开启与关闭

今天&#xff0c;为了一些原因&#xff0c;要重启数据库&#xff0c;但因为当时安装的时候&#xff0c;同学随便装了&#xff0c;导致很多文件都找不到&#xff0c;想使用绝对路径重启数据的计划卡死在了路上。以下&#xff0c;我写下我的数据库开启、关闭的方法&#xff0c;方…

leetcode怎么用时间刷_刷完700多题后的首次总结:LeetCode应该怎么刷?

推荐观看&#xff1a;java技术进阶&#xff1a;膜拜&#xff01;字节大神总结的666页大师级算法宝典&#xff0c;分分钟团灭LeetCode​zhuanlan.zhihu.com2020最新马士兵老师联手左程云老师丨数据结构与算法丨面试BATJ必备​www.bilibili.com我是大二的时候开始接触LeetCode的&…

IntelliJ IDEA:使用Google Guava生成equals,hashCode和toString

问题 在Java领域&#xff0c;我们经常需要编写equals &#xff0c; hashCode和toString方法。 老实说&#xff0c;这通常只是一个样板义务。 得益于智能IDE&#xff0c;我们通常不再自己这样做。 我们只是让和IDE一起努力。 不过有一个问题。 生成的代码通常非常丑陋。 让我们…

Appscan_web安全测试工具 (含修改启动浏览器的方法)

安全测试应该是测试中非常重要的一部分&#xff0c;但他常常最容易被忽视掉。 尽管国内经常出现各种安全事件&#xff0c;但没有真正的引起人们的注意。不管是开发还是测试都不太关注产品的安全。当然&#xff0c;这也不能怪我们苦B的“民工兄弟”。因为公司的所给我们的时间与…

传说之下地图素材_【开阔眼界】地图上的史记——名著轻松读

购买链接&#xff1a;https://j.youzan.com/Zns-38我读了无数次《史记》&#xff0c;每次都是读了开头没几页就放弃&#xff0c;文言文看不懂&#xff0c;翻译的白话文内容又太枯燥&#xff0c;现在居然有图画版的史记&#xff0c;还附有说明&#xff0c;真是太好了&#xff0c…

go int 转切片_「快学 Go 语言」第 4 课——低调的数组

数组就是一篇连续的内存&#xff0c;几乎所有的计算机语言都有数组&#xff0c;只不过 Go 语言里面的数组其实并不常用&#xff0c;这是因为数组是定长的静态的&#xff0c;一旦定义好长度就无法更改&#xff0c;而且不同长度的数组属于不同的类型&#xff0c;之间不能相互转换…

从XaaS到Java EE – 2012年哪一种该死的云最适合我?

您是否曾经想过要让Java EE在某个地方启动和运行需要什么&#xff1f; 是的 多年。 从托管我自己的主机开始&#xff0c;转到一些托管产品 &#xff0c;最后偶然发现了PaaS运动。 老实说&#xff0c;我并没有太认真。 我只是想把我的东西放到某个地方&#xff0c;而不在乎解决…

正方体最快最简单画_素描新手入门第一幅画可不只是“正方体”

很多素描教程都把正方体作为入门第一幅画学习内容。这种现象也成了约定俗成的规矩但是&#xff0c;学过画画的人大概都知道有很多人画了多年石膏几何形、静物、人头像甚至半身像全身像。到最后落得只会画这些学过的东西。这就说明学习出了问题。绘画练习一定要弄清楚每个物体练…

VS2015配置内核WDK7600环境,32位下.

VS2015配置内核WDK7600环境,32位下. 学习内核驱动的编写,就要会配置环境.不然总是用记事本编写.比较不方便. 环境配置如下. 1.首先下载WDK7600, 课堂资料代码中已经上传.链接&#xff1a;https://pan.baidu.com/s/1o9PjpUU 密码&#xff1a;k5sp 2.VS2015下载. 这个网络上有很多…

我的改进版2048(1)

&#xff08;假设有谁想要这个软件的话&#xff0c;在评论中留一个邮箱吧。&#xff09; 前几天好几次看到有朋友晒出玩2048刷高分的截图。我就想我能不能也做一个2048呢&#xff1f;细致想了想2048游戏的规律&#xff0c;发现事实上逻辑上非常easy&#xff0c;也不用研究什么算…

2020年市场最缺什么_2020年聚合氯化铝市场评述

2020年聚合氯化铝市场评述一、行情概述&#xff1a;今年聚合氯化铝价格整体呈下滑趋势&#xff0c;接近年底价格才有小幅反弹。但不同时期价格有小幅起伏&#xff0c;主要受疫情影响&#xff0c;在下游需求不佳的影响下价格出现下滑。1月受疫情影响&#xff0c;前期停产企业短期…

通过反射来将一个类的内容转换到另外一个类里

主函数&#xff1a; import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.serializer.ValueFilter;import com.google.common.base.Preconditions; import java.lang.reflect.Field;import java.lang.reflect.Modifier; public class leijun {public static void ma…

开发辅助 | 阿里图标库iconfont入门使用

目前大多数的互联网公司&#xff0c;前端开发和UI设计师配合中&#xff0c;针对设计师给图的效果图&#xff0c;前端开发工程师不再像往常一样对于细小图标进行切图&#xff0c;取而代之的是引用阿里图标库&#xff08;http://iconfont.cn/&#xff09;&#xff1b;简单的临时开…

计量经济学建模_一分钟看完计量经济学

建模是计量的灵魂&#xff0c;所以就从建模开始。一、建模步骤建模步骤&#xff1a;A&#xff0c;理论模型的设计: a&#xff0c;选择变量b&#xff0c;确定变量关系c&#xff0c;拟定参数范围B&#xff0c;样本数据的收集: a&#xff0c;数据的类型b&#xff0c;数据的质量C&a…

制图折断线_【机械制图】机械设计中的尺寸标注,看懂复杂机械图纸!

专注于机械行业、专业、职业信息分享服务于制造业百万工程师推荐阅读【机械制图】画图这么久&#xff0c;线型都没搞懂&#xff1f;【机械制图】机械图纸知识解析&#xff0c;这个总结溜溜的【机械制图】机械图纸尺寸标注规则&#xff0c;通过动图复习一下&#xff01;知名机械…

CCS3的过渡、变换、动画以及响应式布局、弹性布局

CSS3 过渡 、变换、动画 在没有CSS3之前&#xff0c;如果页面上需要一些动画效果&#xff0c;要么你自己编写 JavaScript&#xff0c;要么使用 JavaScript 框架(如 jQuery)来提高效率。 但是CSS3出来之后&#xff0c;有能力做一些如平滑过渡(比如在鼠标悬停时)和在屏幕上移动元…

BZOJ2659: [Beijing wc2012]算不出的算式

2659: [Beijing wc2012]算不出的算式 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1489 Solved: 891[Submit][Status][Discuss]Description 算不出的算式 背景&#xff1a; 曾经有一个老掉牙的游戏放在我面前&#xff0c;我没有珍惜。直到这个游戏停产才追悔莫及。人世间…