zoukankan      html  css  js  c++  java
  • C# Excel操作

    这几天做一个小工具,需求是:1.服务器有一个Excel模板 2.用户通过配置文件来下载服务器上的Excel模板文件,并把配置文件内对模板指定位置插入数据

    根据需求我就上网对Excel操作的方式/方法,大概分为以下几种:

    1.采用OleDB读取EXCEL文件:没研究(网络上说的)

    优点:将Excel直接当做数据源处理,通过SQL直接读取内容,读取速度较快。

    缺点:读取数据方式不够灵活,无法直接读取某一个单元格,只有将整个Sheet页读取出来后(结果为Datatable)再在Datatable中根据行列数来获取指定的值。

    当Excel数据量很大时。会非常占用内存,当内存不够时会抛出内存溢出的异常。

    2.引用微软提供的com组件:Microsoft.Office.Interop.Excel.dll   读取EXCEL文件 

     代码:

            //读取EXCEL的方法
            private void SetExcel(string strFileName)
            {
                object missing = System.Reflection.Missing.Value;
                Application excel = new Application();
                string l_strSaveFileName = string.Empty;
                string l_strOperationSheet = string.Empty;
                string l_strOperationDB = string.Empty;
                Hashtable ht = new Hashtable();
                string l_strSavePath = string.Empty;
    
                excel.Visible = false;
                excel.UserControl = true;
                // 以只读的形式打开EXCEL文件
                Workbook wb = excel.Application.Workbooks.Open(strFileName, missing, true, missing, missing, missing,
                 missing, missing, missing, true, missing, missing, missing, missing, missing);
                //根据指定的sheet名称取得指定工作薄
                Worksheet ws = (Worksheet)wb.Worksheets.get_Item(1);
    
                try
                {
                    //取得总记录行数   (包括标题列)
                    int rowsint = ws.UsedRange.Cells.Rows.Count; //得到行数
                    //int colsint = ws.UsedRange.Cells.Columns.Count; //得到列数
    
                    for (int i = 1; i < rowsint; i++)  //对工作表每一行  
                    {
                        string cellValue = ws.UsedRange.Cells[i, 1].Text.ToString();
                        if (cellValue != "END")
                        {
                            if (cellValue == "ファイル名")
                            {
                                l_strSaveFileName = ws.UsedRange.Cells[i, 2].Text.ToString();
                            }
                            else if (cellValue == "シート名")
                            {
                                l_strOperationSheet = ws.UsedRange.Cells[i, 2].Text.ToString();
                            }
                            else if (cellValue == "対応付け")
                            {
                                ht[ws.UsedRange.Cells[i, 3].Text.ToString()] = ws.UsedRange.Cells[i, 2].Text.ToString();
                            }
                        }
                    }
    
                    //根据指定的sheet名称取得指定工作薄
                    ws = (Worksheet)wb.Worksheets.get_Item(l_strOperationSheet);
    
                    //设置指定区域内容 --C2,...
                    foreach (string item in ht.Keys)
                    {
                        ws.Cells.get_Range(item).Value = ht[item];
                    }
                    /*以下代码:主要是针对response下载文件而多做的,因为response无法将Workbook对象响应到客户端,所以我就用了以下方式:
                     *1.先把处理完成的Excel文件存放在服务器上的作为临时文件。
                     *2.把此临时文件转换为文件流
                     *3.Response.BinaryWrite方法把文件流响应到客户端。
                     *4.全部完成后,删除临时文件。
                     */
                    //服务器临时文件
                    l_strSavePath = Server.MapPath("~/UpFile/" + l_strSaveFileName);
                    wb.Saved = true;
                    wb.SaveCopyAs(l_strSavePath);
    
                    using (FileStream fsRead = new FileStream(l_strSavePath, FileMode.Open))
                    {
                        int fsLen = (int)fsRead.Length;
                        byte[] heByte = new byte[fsLen];
                        int r = fsRead.Read(heByte, 0, heByte.Length);
    
                        Response.Clear();
                        Response.AddHeader("content-disposition", "attachment; filename=" + l_strSaveFileName);
                        Response.ContentType = "application/excel";
                        Response.ContentEncoding = Encoding.Default;
                        Response.BinaryWrite(heByte);
                        Response.End();
                    }
                }
                finally
                {
                    if (l_strSavePath != string.Empty && File.Exists(l_strSavePath))
                    {
                        File.Delete(l_strSavePath);
                    }
                    wb.Close(false, missing, missing);
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(wb);
                    wb = null;
                    excel.Workbooks.Close();
                    excel.Quit();
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(excel);
                    excel = null;
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            }
    

    优点:能够非常灵活的读取Excel中的数据,用户可以灵活的调用各种函数进行处理。

    缺点:基于单元格的处理,读取速度较慢,对于数据量较大的文件最好不要使用此种方式读取。

    需要添加相应的DLL引用,必须存在此引用才可使用,如果是Web站点部署在IIS上时,还需要服务器机子已安装了Excel,有时候还需要为配置IIS权限。
    注:此方法可以处理已制定好的模板,如:

    3.NPOI方式读取Excel

     NPOI 是 POI 项目的 .NET 版本。POI是一个开源的Java读写Excel、WORD等微软OLE2组件文档的项目。使用 NPOI 你就可以在没有安装 Office 或者相应环境的机器上对 WORD/EXCEL 文档进行读写。

    优点:读取Excel速度较快,读取方式操作灵活性

    缺点:需要下载相应的插件并添加到系统引用当中。并且会丢失模板的式样(如:在单元格内有值,单元格背景变色) --不适用于复杂模板

    下载地址:http://npoi.codeplex.com/releases

            public void NPOIGetExcelDB(string p_strFileName)
            {
                StringBuilder l_strResult = new StringBuilder();
                IWorkbook workbook = null;  //新建IWorkbook对象  
                string l_strSaveFileName = string.Empty;
                string l_strOperationSheet = string.Empty;
                string l_strOperationDB = string.Empty;
                Hashtable ht = new Hashtable();
                //string fileName = "E:\Excel2003.xls";
                FileStream fileStream = new FileStream(p_strFileName, FileMode.Open, FileAccess.Read);
                
                try
                {
                    if (p_strFileName.IndexOf(".xlsx") > 0) // 2007版本  
                    {
                        workbook = new XSSFWorkbook(fileStream);  //xlsx数据读入workbook  
                    }
                    else if (p_strFileName.IndexOf(".xls") > 0) // 2003版本  
                    {
                        workbook = new HSSFWorkbook(fileStream);  //xls数据读入workbook  
                    }
                    ISheet sheet = workbook.GetSheetAt(0);  //获取第一个工作表  
                    IRow row;// = sheet.GetRow(0);            //新建当前工作表行数据  
                    for (int i = 0; i < sheet.LastRowNum; i++)  //对工作表每一行  
                    {
                        row = sheet.GetRow(i);   //row读入第i行数据  
                        if (row != null)
                        {
                            string cellValue = row.GetCell(0).ToString(); //获取i行j列数据
                            if (cellValue != "END")
                            {
                                if (cellValue == "ファイル名")
                                {
                                    l_strSaveFileName = row.GetCell(1).ToString();
                                }
                                else if (cellValue == "シート名")
                                {
                                    l_strOperationSheet = row.GetCell(1).ToString();
                                }
                                else if (cellValue == "対応付け")
                                {
                                    ht[row.GetCell(2).ToString()] = row.GetCell(1).ToString();
                                }
                            }
                        }
                    }
                    sheet = workbook.GetSheet(l_strOperationSheet);
                    int rowindex = 0;
                    int colindex = 0;
                    //设置指定区域内容
                    foreach (string item in ht.Keys)
                    {
                //1.NPOI无法处理Excel中列名(C2),所以做了转换
                //并且Excel行列索引是从1开始的,而NPOI是从零开始的所以要减1. rowindex = GetNumberic(item) - 1; colindex = StringToNumber(GetStrings(item)) - 1; if (sheet.LastRowNum >= rowindex) { row = sheet.GetRow(rowindex); } else { row = sheet.CreateRow(rowindex); } if (row.LastCellNum >= colindex) { row.GetCell(colindex).SetCellValue(ht[item].ToString()); } else { row.CreateCell(colindex).SetCellValue(ht[item].ToString()); } } string l_strSavePath = Server.MapPath("~/UpFile/" + l_strSaveFileName); //转为字节数组 MemoryStream stream = new MemoryStream(); workbook.Write(stream); var buf = stream.ToArray(); //保存为Excel文件 using (FileStream fs = new FileStream(l_strSavePath, FileMode.Create, FileAccess.Write)) { fs.Write(buf, 0, buf.Length); fs.Flush(); } } finally { fileStream.Close(); workbook.Close(); } }

    转换方法:

            // Convert A, B, .... AAA, AAB to number 1,2, ...         
            int StringToNumber(string s)
            {
                int r = 0;
                for (int i = 0; i < s.Length; i++)
                {
                    r = r * 26 + s[i] - 'A' + 1;
                }
                return r;
            }
         //获取数字
            int GetNumberic(string str)
            {
    
                Match ms = Regex.Match(str, @"d+");
    
                return int.Parse(ms.Value);
            }
         //获取字母
            string GetStrings(string str)
            {
                Match ms = Regex.Match(str, @"[A-Z]+");
    
                return ms.Value;
            }
    

    4.ClosedXML操作Excel(借鉴网络文章,如果对Excel模板处理,这个是非常好的)

    获取页(Sheet)首先简单介绍两个类XLWorkbook和IXLWorksheet,分别对应着OpenXML里面的Workbook和Worksheet。XLWorkbook对应着你要访问的Excel文件,一个XLWorkbook会有一个IXLWorksheet集——IXLWorksheets,IXLWorksheet和Excel文件里面的Sheet是对应的。我们知道,一个Excel中可能会包含着很多个Sheet,XLWorkbook类中可以通过两种方式获取想要访问的页:

    • public IXLWorksheet Worksheet(int position);//根据索引
    • public IXLWorksheet Worksheet(string name);//根据sheet名称

    第一种方式通过页的位置,也就是顺序来获取Sheet,一般第一页对应的参数为1;后一种通过页名来获取,具体使用哪一种当然要视情况而定。例如,需要遍历Excel文件中的所有内容时,第一种明显更好;如果是定位到某一页,第二种可能更恰当。单元格集合是在Sheet中的,所以要想访问数据,获取页是第一步。
    读取数据范围当你用对象浏览器查看上文中IXLWorksheet类时,似乎觉得RowCount()和ColumnCount()这两个方法是来获取最大行数和列数的,但经过测试发现,这两个值往往都很大,包含了你没有存放数据的范围。后来查了一下类成员,发现了FirstRowUsed()这个函数,函数说明为“Gets the first row of the worksheet that contains a cell with a value”,紧接着就果然又发现了LastRowUsed()这个函数。简单测试了下,二者确实表示了包围所有数据的最大矩形的位置,所以包含数据的表范围是:

    • rowUsedCount = LastRowUsed().RowNumber() -FirstRowUsed().RowNumber()
    • ColumnUsedCount = LastColumnUsed().ColumnNumber() -FirstColumnUsed().ColumnNumber()


    读取单元格数据读取单元格数据有两种方式:

    • IXLCell Cell(string cellAddressInRange);//通过单元格符号来读取,比如“A1”、“B1”之类的
    • IXLCell Cell(int row, int column);//通过行列的位置来读取如第三行第一列为Cell(3, 1)
    • IXLCell Cell(int row, string column);//就是二者的综合,因为在Excel里面,行是用数字表示的,而列则是用ABCD来表示的

    第三种就是二者的综合,因为在Excel里面,行是用数字表示的,而列则是用ABCD来表示的。第二种更合适一些,更符合“表”的概念。

    要使用ClosedXML处理Excel就要先引用ClosedXML.dll和DocumentFormat.OpenXML.dll程序集,这些程序集可是使用Visual Studio的NuGet一键安装(没用过,可以网上搜索)
    针对上面所说的需求代码如下:

            //读取EXCEL的方法
            private void SetExcel(Stream strFileDB)
            {
                string l_strSaveFileName = string.Empty;
                string l_strOperationSheet = string.Empty;
                string l_strOperationDB = string.Empty;
                Hashtable ht = new Hashtable();
                string l_strSavePath = string.Empty;
                //实例一个Workbook 注意:XLWorkbook的参数如果我们传入的参数是文件流,发布以后不会包无访问权限,如果是文件路径的话必须把文件上传到服务器上才使用XLWorkbook
                var xlBook = new XLWorkbook(strFileDB);
                //实例一个worksheet  
                var ws = xlBook.Worksheet(1);
    
                try
                {
                    //取得总记录行数   (包括标题列)
                    int rowsint = ws.LastRowUsed().RowNumber() - ws.FirstRowUsed().RowNumber();
    
                    for (int i = 1; i <= rowsint; i++)  //对工作表每一行  
                    {
                        string cellValue = ws.Cell(i, 1).Value.ToString();
                        if (cellValue != "END")
                        {
                            if (cellValue == "ファイル名")
                            {
                                l_strSaveFileName = ws.Cell(i, 2).Value.ToString();
                            }
                            else if (cellValue == "シート名")
                            {
                                l_strOperationSheet = ws.Cell(i, 2).Value.ToString();
                            }
                            else if (cellValue == "対応付け")
                            {
                                ht[ws.Cell(i, 3).Value.ToString()] = ws.Cell(i, 2).Value.ToString();
                            }
                        }
                    }
    
                    //根据指定的sheet名称取得指定工作薄
                    ws = xlBook.Worksheet(l_strOperationSheet);
    
                    //设置指定区域内容
                    foreach (string item in ht.Keys)
                    {
                        ws.Cell(item).Value = ht[item];
                    }
                    //服务器临时文件
                    l_strSavePath = Server.MapPath("~/UpFile/" + l_strSaveFileName);
                    //xlBook.Saved = true;
                    xlBook.SaveAs(l_strSavePath);
    
                    using (FileStream fsRead = new FileStream(l_strSavePath, FileMode.Open))
                    {
                        int fsLen = (int)fsRead.Length;
                        byte[] heByte = new byte[fsLen];
                        int r = fsRead.Read(heByte, 0, heByte.Length);
    
                        Response.Clear();
                        Response.AddHeader("content-disposition", "attachment; filename=" + l_strSaveFileName);
                        Response.ContentType = "application/excel";
                        Response.ContentEncoding = Encoding.Default;
                        Response.BinaryWrite(heByte);
                        Response.End();
                    }
                }
                finally
                {
                    if (l_strSavePath != string.Empty && File.Exists(l_strSavePath))
                    {
                        File.Delete(l_strSavePath);
                    }
                }
            }
    

    个人感觉最后一种方法处理Excel模板(有特殊样式的)是比较好的,但在国内的网上搜索的时候很不容易搜到ClosedXML操作Excel的方法。

     


     

  • 相关阅读:
    Android测试工具 UIAutomator入门与介绍
    C#异步编程
    懒得找,存个笔记:easyui combogrid 下拉+关键字搜索
    mssql replace
    序列化类型为XX的对象时检测到循环引用
    shell脚本运行python命令
    python技巧
    边缘检测测评标准
    mybatis 手动生成可执行sql
    Linux如何扩容物理文件系统分区
  • 原文地址:https://www.cnblogs.com/WarBlog/p/6432351.html
Copyright © 2011-2022 走看看