C# Excel操作记录 及 代码优化考虑

最近,因为有需求,找了很多C#操作Excel的资料来学习,完了准备在这里做个记录,以备后日不时之需。

我看到的,操作Excel的方式有四种:

  1. com组件
  2. OleDb
  3. npoi
  4. epplus

com组件

首先,com 组件的方式指的是利用 office 的 Excel 组件对Excel文件进行操作,这种方式需要电脑上安装有Excel,项目中也要添加相关引用,项目运行时也要启动Excel进程。

据说,这种方式的功能很强大,说是Excel能实现的功能都能够通过这种方法在代码里进行设置,反正我是没有验证过,因为它实在是太慢了。其他方法1s就可以搞定的,用它稳定在8.7s...

OleDb

使用Microsoft Jet 提供程序用于连接到 Excel 工作簿,将Excel文件作为数据源来读写,这种方法读写都相当快,就是不是很灵活。

它需要使用指定的连接字符串初始化 System.Data.OleDb.OleDbConnection 类的新实例,.xls文件 和 .xlsx文件的连接字符串并不一样。

case ".xls":
          strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + 
";Extended Properties='Excel 8.0;HDR=Yes;IMEX=2;'";
          break;
case ".xlsx":
          strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pathName + 
";Extended Properties='Excel 12.0;HDR=Yes;IMEX=2;'";
          break;

上面的HDR用于判断是否将第一行作为数据读取(个人感觉应该只在读操作有用),IMEX用于判断是读取还是写入,这个需要自己尝试,我只能说,我在写入操作的时候将其设为0,读取的时候设为1,你也可以设为2,错了再调整...

它能返回所有的sheet的名称,以及单个sheet的所有列名,这有时是相当有用的。

        #region 获取Excel文件的表名 返回list集合
        /// 路径名
        /// 
        public static List GetExcelTableName(OleDbConnection conn, string pathName)
        {
            List tableName = new List();
            if (File.Exists(pathName))
            {
                //conn.Open();
                System.Data.DataTable dt = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
                string strSheetTableName = null;
                foreach (System.Data.DataRow row in dt.Rows)
                {
                    strSheetTableName = row["TABLE_NAME"].ToString();
                    //过滤无效SheetName   
                    if (strSheetTableName.Contains("$") && strSheetTableName.Replace("'", "").EndsWith("$"))
                    {
                        strSheetTableName = strSheetTableName.Replace("'", "");   //可能会有 '1X$' 出现
                        strSheetTableName = strSheetTableName.Substring(0, strSheetTableName.Length - 1);
                        tableName.Add(strSheetTableName);
                    }
                }
            }
            return tableName;
        }

        #endregion
        #region 获取EXCEL工作表的列名 返回list集合
        /// Excel路径名
        /// 
        public static List getExcelFileInfo(string pathName)
        {
            string strConn;
            List list_ColumnName = new List();
            FileInfo file = new FileInfo(pathName);
            if (!file.Exists) { throw new Exception("文件不存在"); }
            string extension = file.Extension;
            switch (extension)
            {
                case ".xls":
                    strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=2;'";
                    break;
                case ".xlsx":
                    strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pathName + ";Extended Properties='Excel 12.0;HDR=Yes;IMEX=2;'";
                    break;
                default:
                    strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1;'";
                    break;
            }
            System.Data.OleDb.OleDbConnection conn = new System.Data.OleDb.OleDbConnection(strConn);
            conn.Open();
            System.Data.DataTable table = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, new object[] { null, null, null, null });

            string TableName = null;
            string ColumnName = null;
            foreach (System.Data.DataRow drow in table.Rows)
            {
                TableName = drow["Table_Name"].ToString();
                if (TableName.Contains("$") && TableName.Replace("'", "").EndsWith("$"))
                {
                    System.Data.DataTable tableColumns = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Columns, new object[] { null, null, TableName, null });
                    foreach (System.Data.DataRow drowColumns in tableColumns.Rows)
                    {
                        ColumnName = drowColumns["Column_Name"].ToString();
                        list_ColumnName.Add(ColumnName);
                    }
                }
            }
            return list_ColumnName;
        }

        #endregion

读写的部分准备用copy来进行说明,虽然不会用到。

        #region 读取Excel,将第一行作为标题,其他行作为数据,再将其导出到Excel,对空白行进行处理
        public static bool copy(string copyPath, string pastePath, string copy_sheetName)
        {
            DataSet ds = new DataSet();
            OleDbConnection copy_objConn = null;//Excel的连接
            OleDbConnection paste_objConn = null;//Excel的连接
            try
            {  
                string strExtension = System.IO.Path.GetExtension(copyPath);//获取文件扩展名
                string strFileName = System.IO.Path.GetFileName(copyPath);//获取文件名
                switch (strExtension)
                {
                    case ".xls":
                        copy_objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + copyPath
                            + ";" + "Extended Properties=\"Excel 8.0;HDR=YES;IMEX=1;\"");
                        break;
                    case ".xlsx":
                        copy_objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + copyPath
                            + ";" + "Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1;\"");
                        break;
                    default:
                        copy_objConn = null;
                        break;
                }
                if (copy_objConn == null)
                {
                    return false;
                }
                copy_objConn.Open();
                string strSql = "select * from [" + copy_sheetName + "] where [系统物料号] is not null";
                OleDbCommand objCmd = new OleDbCommand(strSql, copy_objConn);//获取Excel指定Sheet表中的信息
                OleDbDataAdapter myData = new OleDbDataAdapter(strSql, copy_objConn);
                myData.Fill(ds, copy_sheetName);//填充数据

                DataTable dt = ds.Tables[0];
                strExtension = System.IO.Path.GetExtension(pastePath);//获取文件扩展名
                strFileName = System.IO.Path.GetFileName(pastePath);//获取文件名
                switch (strExtension)
                {
                    case ".xls":
                        paste_objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pastePath
                            + ";" + "Extended Properties=\"Excel 8.0;HDR=YES;IMEX=0;\"");
                        break;
                    case ".xlsx":
                        paste_objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pastePath
                            + ";" + "Extended Properties=\"Excel 12.0;HDR=YES;IMEX=0;\"");
                        break;
                    default:
                        paste_objConn = null;
                        break;
                }
                if (paste_objConn == null)
                {
                    return false;
                }
                paste_objConn.Open();
                StringBuilder strSQL = new StringBuilder();
                System.Data.OleDb.OleDbCommand cmd;
                string tableName = dt.TableName.Substring(0, dt.TableName.Length - 1);
                try
                {
                    List list = GetExcelTableName(paste_objConn, pastePath);
                    if (list.Contains(tableName))
                    {
                        //覆盖文件时可能会出现Table 'Sheet1' already exists.所以这里先删除了一下
                        cmd = new System.Data.OleDb.OleDbCommand(string.Format("drop table {0}", tableName), paste_objConn);
                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("drop table failed!" + e.Message);
                }
                //创建表格字段
                strSQL.Append("CREATE TABLE ").Append("[" + tableName + "]");
                strSQL.Append("(");
                for (int i = 0,int dt_column_count = dt.Columns.Count; i < dt_column_count ; i++)
                {
                    strSQL.Append("[" + dt.Columns[i].ColumnName + "] text,");
                }
                strSQL = strSQL.Remove(strSQL.Length - 1, 1);
                strSQL.Append(")");

                cmd = new System.Data.OleDb.OleDbCommand(strSQL.ToString(), paste_objConn);
                cmd.ExecuteNonQuery();
                //添加数据
                StringBuilder strvalue = null;
                for (int i = 0,int dt_row_count = dt.Rows.Count; i < dt_row_count; i++)
                {
                    strSQL.Clear();
                    strvalue = new StringBuilder();
                    for (int j = 0; j < dt.Columns.Count; j++)
                    {
                        strvalue.Append("'" + dt.Rows[i][j].ToString() + "'");
                        if (j != dt.Columns.Count - 1)
                        {
                            strvalue.Append(",");
                        }
                    }
                    cmd.CommandText = strSQL.Append(" insert into [" + dt.TableName.Substring(0, dt.TableName.Length - 1) + "] values (").Append(strvalue).Append(")").ToString();
                    cmd.ExecuteNonQuery();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("copy失败!" + e.Message);
                return false;
            }
            finally
            {
                copy_objConn.Close();
                paste_objConn.Close();
            }
            return true;
        }
        #endregion

有一点需要注意的是,win32 平台是可以正常用的,any CPU 平台是不可以的,需要安装微软的程序等操作。

因为这个原因,我也没有找有没有操作格式的方法,有需要的自己找找看。

NPOI

这是花费时间最久的插件,用完只能说真的不好用。

想要用这个插件的需要在NuGet程序包里搜寻安装到项目中。

读写都不是很慢,但是数据量太大的无法处理,大概30000 * 20 的规模已经不能胜任了。

它对于.xls文件 和 .xlsx文件的处理也是不一样的:

ISheet sheet = null;
IWorkbook workbook = null;
switch (strExtension)
{
      case ".xls":
           workbook = new HSSFWorkbook(fs);
           sheet = workbook.GetSheet(sheetName);
           break;
      case ".xlsx":
           workbook = new XSSFWorkbook(fs);
           sheet = workbook.GetSheet(sheetName);
           break;
      default:
           break;
}

npoi读取Excel需要对内容进行类型判断

private static object GetValueType(ICell cell, IWorkbook workbook)
{
    if (cell == null)
        return null;
    switch (cell.CellType)
    {
        case CellType.Blank: //BLANK:
            return null;
        case CellType.Boolean: //BOOLEAN:
            return cell.BooleanCellValue;
        case CellType.Numeric: //NUMERIC:
            return cell.NumericCellValue;
        case CellType.String: //STRING:
            return cell.StringCellValue;
        case CellType.Error: //ERROR:
            return cell.ErrorCellValue;
        case CellType.Formula: //FORMULA:
               
        default:
            return "=" + cell.CellFormula;
    }
}

现在来说遇到的第一个问题,比如说,我希望读取A1的内容怎么办,用下面的方法可以吗?

IRow row1 = sheet.GetRow(0);
ICell cell1 = row1.GetCell(0);
object value = GetValueType(cell1, workbook);

一般情况,是可以的,但是如果是公式,出来的结果并不是希望的结果,对上面的方法进行修改:

private static object GetValueType(ICell cell, IWorkbook workbook)
{
    if (cell == null)
        return null;
    switch (cell.CellType)
    {
        case CellType.Blank: //BLANK:
            return null;
        case CellType.Boolean: //BOOLEAN:
            return cell.BooleanCellValue;
        case CellType.Numeric: //NUMERIC:
            return cell.NumericCellValue;
        case CellType.String: //STRING:
            return cell.StringCellValue;
        case CellType.Error: //ERROR:
            return cell.ErrorCellValue;
        case CellType.Formula: //FORMULA:
            IFormulaEvaluator evaluator = null;
            if (workbook is HSSFWorkbook)
                evaluator = new HSSFFormulaEvaluator(workbook);
            else
                evaluator = new XSSFFormulaEvaluator(workbook);
            cell = evaluator.EvaluateInCell(cell);
            return cell.NumericCellValue;
                    
        default:
            return "=" + cell.CellFormula;
    }
}

修改过后,会发现,一部分公式成功获得结果,一部分公式提示捕获异常,在进行修改:

private static object GetValueType(ICell cell, IWorkbook workbook)
{
    if (cell == null)
        return null;
    switch (cell.CellType)
    {
        case CellType.Blank: //BLANK:
            return null;
        case CellType.Boolean: //BOOLEAN:
            return cell.BooleanCellValue;
        case CellType.Numeric: //NUMERIC:
            return cell.NumericCellValue;
        case CellType.String: //STRING:
            return cell.StringCellValue;
        case CellType.Error: //ERROR:
            return cell.ErrorCellValue;
        case CellType.Formula: //FORMULA:
            IFormulaEvaluator evaluator = null;
            if (workbook is HSSFWorkbook)
                evaluator = new HSSFFormulaEvaluator(workbook);
            else
                evaluator = new XSSFFormulaEvaluator(workbook);
            cell = evaluator.EvaluateInCell(cell);
            try
            {
                return cell.NumericCellValue;
            }
            catch
            {
                return cell.StringCellValue;
            }
                    
        default:
            return "=" + cell.CellFormula;
    }
}

这样应该能在大多数情况下,获取我们希望的值了。

                                                                                                                                                                                             

第二个问题,同一个sheet中数据区域中间隔着很多空行,不知道你们有没有遇到过这种情况,npoi读取Excel文件的行数时,即使该行没有数据,但是只要最初的行高被动过,也或被读到,即,把3000行的行高改了,但是其实只有100行数据,它计算的行数就会是3000,如果你知道只需要读取100行,但是如果从200行开始又有100行数据怎么办?如果说这个也知道,那当我没说。事实上,虽然npoi统计出来有那么多行,但是当你获取空行时就会报异常,提示你引用对象没有获取实例,大概是这么个意思,这说明sheet里是没有存这些空行的信息的,除了行数。如果你对Excel结构不太清楚,那么就需要在用异常做流程处理,虽然这样做实际上效率很低。

        /// 
        /// Excel导入成Datable
        /// 
        /// 导入路径(包含文件名与扩展名)
        /// 
        private static DataTable ExcelToDataTable(string strExcelPath, string sheetName)
        {
            DataTable dataTable = new DataTable();
            FileStream fs = null;
            try
            {
                //获取文件扩展名
                string strExtension = System.IO.Path.GetExtension(strExcelPath);
                
                using (fs = new FileStream(strExcelPath, FileMode.Open, FileAccess.Read))
                {
                    ISheet sheet = null;
                    IWorkbook workbook = null;
                    switch (strExtension)
                    {
                        case ".xls":
                            workbook = new HSSFWorkbook(fs);
                            sheet = workbook.GetSheet(sheetName);
                            break;
                        case ".xlsx":
                            workbook = new XSSFWorkbook(fs);
                            sheet = workbook.GetSheet(sheetName);
                            break;
                        default:
                            break;
                    }
                    if (sheet == null)
                        Console.WriteLine("文件后缀名错误!");
                    //表头
                    IRow header = sheet.GetRow(sheet.FirstRowNum);
                    List columns = new List();
                    object obj = null;
                    for (int i = 0, int dt_column_count = header.LastCellNum; i < dt_column_count; i++)
                    {
                        obj = GetValueType(header.GetCell(i), workbook); string a = header.GetCell(0).StringCellValue;
                        if (obj == null || obj.ToString() == string.Empty)
                        {
                            dataTable.Columns.Add(new DataColumn("Columns" + i.ToString()));
                            //continue;
                        }
                        else
                            dataTable.Columns.Add(new DataColumn(obj.ToString()));
                        columns.Add(i);
                    }
                    //数据
                    object value = null;
                    DataRow dr = null;
                    for (int i = sheet.FirstRowNum + 1, int dt_row_count = sheet.LastRowNum; i <= dt_row_count; i++)
                    {
                        
                        try
                        {
                            value = GetValueType(sheet.GetRow(i).GetCell(0), workbook);
                        }
                        catch
                        {
                            continue;
                        }
                        if (value != null)
                        {
                            dr = dataTable.NewRow();
                            bool hasValue = false;
                            foreach (int j in columns)
                            {
                                dr[j] = GetValueType(sheet.GetRow(i).GetCell(j), workbook);
                                if (dr[j] != null && dr[j].ToString() != string.Empty)
                                    hasValue = true;
                            }
                            if (hasValue)
                                dataTable.Rows.Add(dr);
                        }
                    }
                    return dataTable;
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return dataTable;
            }
            finally
            {
                if(fs != null)
                    fs.Close();
            }
        }

读取的情况应该没什么大问题的,下面贴上Excel转datatable的方法,也可以转到集合里,按需求做些修改就行了。

                                                                                                                                                                                             

第三个问题,关于这个写入的问题,实际上我到最终也没什么好方法,如果有人知道,欢迎帮我解惑。

先贴上一段写入的方法:

        /// 
        /// Datable导出成Excel
        /// 
        /// 数据源
        /// 导出路径(包括文件名与扩展名)
        /// 生成的sheet的name
        /// 
        private static bool TableToExcel(DataTable dt, string strExcelPath, string sheetName)
        {
            //获取文件扩展名
            string strExtension = System.IO.Path.GetExtension(strExcelPath);
            //string strFileName = System.IO.Path.GetFileName(strExcelPath);
            IWorkbook workbook = null;
            switch (strExtension)
            {
                case ".xls":
                    workbook = new HSSFWorkbook();
                    break;
                case ".xlsx":
                    workbook = new XSSFWorkbook();
                    break;
                default:
                    break;
            }
            if (workbook == null)
                return false;
            ISheet sheet = workbook.CreateSheet(sheetName);

            //表头  
            ICell cell = null;
            IRow row = sheet.CreateRow(0);
            for (int i = 0,int column_count = dt.Columns.Count; i < column_count; i++)
            {
                cell = row.CreateCell(i);
                cell.SetCellValue(dt.Columns[i].ColumnName);
            }

            //数据  
            IRow row1 = null;
            ICell cell = null;
            for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
            {
                row1 = sheet.CreateRow(i + 1);
                for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
                {
                    cell = row1.CreateCell(j);
                    cell.SetCellValue(dt.Rows[i][j].ToString());
                }
            }

            //转为字节数组  
            MemoryStream stream = new MemoryStream();
            workbook.Write(stream);
            var buf = stream.ToArray();

            //保存为Excel文件
            FileMode mode = FileMode.OpenOrCreate;
            FileAccess access = FileAccess.ReadWrite;
            FileShare share = FileShare.ReadWrite;
            
            using (FileStream fs = new FileStream(strExcelPath, mode, access, share))
            {
                fs.Write(buf, 0, buf.Length);
                fs.Flush();
                fs.Close();
            }

            return true;
        }

事实上,我一开始在数据库里读了一些数据然后写入Excel,成功了,我就没管了。

知道我在一系列读取后在进行写入时出现了问题,整个Excel文件都无法打开了。我经过一系列调试,发现读取之后都是没问题的,写入的最后出现问题,即FileStream出现后的那几句代码,我怀疑过很多地方,比如之前的fs未close,实际上不需要手动close;再比如new FileStream时的参数,这让我又发现了一个问题,我进行写入操作的时候会将原先的删除再新建,并且更改参数并不起效果,我猜测应该将原有文件作为参数传进来,找了一下确实是这个问题,在已存在的Excel文件添加sheet的完整代码:

        /// 
        /// Datable导出成Excel
        /// 
        /// 数据源
        /// 导出路径(包括文件名与扩展名)
        /// 生成的sheet的name
        /// 是新建or打开:true 为 create;false 为 open
        /// 
        private static bool TableToExcel(DataTable dt, string strExcelPath, string sheetName, bool flag)
        {
            //获取文件扩展名
            string strExtension = System.IO.Path.GetExtension(strExcelPath);
            //string strFileName = System.IO.Path.GetFileName(strExcelPath);
            IWorkbook workbook = null;
            switch (strExtension)
            {
                case ".xls":
                    if(flag)
                        workbook = new HSSFWorkbook();
                    else
                        workbook = new HSSFWorkbook(File.OpenRead(strExcelPath));
                    break;
                case ".xlsx":
                    if(flag)
                        workbook = new XSSFWorkbook();
                    else
                        workbook = new XSSFWorkbook(File.OpenRead(strExcelPath));
                    break;
                default:
                    break;
            }
            if (workbook == null)
                return false;
            ISheet sheet = workbook.CreateSheet(sheetName);

            //表头  
            IRow row = sheet.CreateRow(0);
            ICell cell = null;
            for (int i = 0, int column_count = dt.Columns.Count; i < column_count; i++)
            {
                cell = row.CreateCell(i);
                cell.SetCellValue(dt.Columns[i].ColumnName);
            }

            //数据  
            IRow row1 = null;
            ICell cell = null;
            for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
            {
                row1 = sheet.CreateRow(i + 1);
                for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
                {
                    cell = row1.CreateCell(j);
                    cell.SetCellValue(dt.Rows[i][j].ToString());
                }
            }

            //转为字节数组  
            MemoryStream stream = new MemoryStream();
            workbook.Write(stream);
            var buf = stream.ToArray();

            //保存为Excel文件
            FileMode mode = FileMode.OpenOrCreate;
            FileAccess access = FileAccess.ReadWrite;
            FileShare share = FileShare.ReadWrite;
            
            using (FileStream fs = new FileStream(strExcelPath, mode, access, share))
            {
                fs.Write(buf, 0, buf.Length);
                fs.Flush();
                fs.Close();
            }

            return true;
        }

虽然上面代码可以添加sheet,并且原有的sheet依然存在,但是这有个前提条件:原先存在的sheet需要时通过npoi添加的,也就是说通过office新建的sheet,上面的方法是不行的,会导致文件损坏,无法打开。

为了解决这个问题,我看了很多资料,据说是因为npoi写入会在末尾添加一个标志,那么可以猜测一下,是不是给自己新建的sheet加一个这样的标志就能解决问题?我是没有进行这个测试,最后是新建了一个Excel文件。有哪位大神知道这种方法或者其他方法解决这个问题的,欢迎指导。

最后说一下,这个方式是可以设置单元格格式的,下面贴上一部分代码,对第一行设置格式:

            //设置列宽
            sheet.SetColumnWidth(0,25 * 256);
            sheet.SetColumnWidth(1, 45 * 256);
            sheet.SetColumnWidth(2, 10 * 256);
            sheet.SetColumnWidth(3, 6 * 256);
            sheet.SetColumnWidth(4, 20 * 256);
            sheet.SetColumnWidth(5, 20 * 256);
            sheet.SetColumnWidth(6, 20 * 256);
            sheet.SetColumnWidth(7, 20 * 256);

            //字体
            IFont font = workbook.CreateFont();
            font.IsBold = true; //字体为粗体
            font.FontName = "等线";

            //创建单元格格式
            IDataFormat dataFormat = workbook.CreateDataFormat();
            //ICellStyle style1 = workbook.CreateCellStyle();
            //style1.DataFormat = dataFormat.GetFormat("0"); //精度到整数
            //ICellStyle style2 = workbook.CreateCellStyle();
            //style2.DataFormat = dataFormat.GetFormat("0.00%"); //百分数
            //ICellStyle style3 = workbook.CreateCellStyle();
            //style3.SetFont(font);
            //ICellStyle style4 = workbook.CreateCellStyle(); 
            //style4.FillBackgroundColor = IndexedColors.White.Index;
            //style4.FillForegroundColor = IndexedColors.Black.Index; 
            //style4.FillPattern = FillPattern.SolidForeground;

            //表头  style : 字体粗体,背景色为蓝,居中对齐
            ICellStyle style_header = workbook.CreateCellStyle();
            style_header.SetFont(font);
            style_header.FillBackgroundColor = IndexedColors.White.Index;
            style_header.Alignment = HorizontalAlignment.Center;
            //style_header.FillForegroundColor = IndexedColors.LightBlue.Index;
            style_header.FillForegroundColor = (short)44;
            style_header.FillPattern = FillPattern.SolidForeground;

            IRow row = sheet.CreateRow(0);
            ICell cell = null;
            for (int i = 0, int count = firstRowName.Count; i < count; i++)
            {
                cell = row.CreateCell(i);
                cell.SetCellValue(firstRowName[i]);
                cell.CellStyle = style_header;
            }

epplus

这种方法挺不错的,但是由于某些原因并没有深入了解,不过数据量比npoi强,而且没那么多问题。

放一个比较全的测试方法吧,里面进行了很多功能测试,我自己测试没通过的都有标志,没标注的应该都是可以成功的。

        public static Boolean Load_Main()
        {
            FileInfo newFile = new FileInfo(System.AppDomain.CurrentDomain.BaseDirectory + "test1.xlsx");
            
            using (ExcelPackage package = new ExcelPackage(newFile)) //创建Excel
            {
                #region 获取sheet
                if (package.Workbook.Worksheets["test1"] != null)
                {
                    package.Workbook.Worksheets.Delete("test1");//删除工作表
                }
                //创建一个新的sheet
                ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("test1");
                //获取excel中的一个sheet用于后续操作(通过模板写数据的可用此方法)
                ExcelWorksheet worksheet2 = package.Workbook.Worksheets["Sheet1"];
                if (package.Workbook.Worksheets["hidesheet"] != null)
                {
                    package.Workbook.Worksheets.Delete("hidesheet");//删除工作表
                }
                ExcelWorksheet hideSheet = package.Workbook.Worksheets.Add("hidesheet");
                #endregion
                #region 添加数据
                /*  Epplus中给单元格赋值非常简单,两种方法:(ps:Epplus的所有行列数都是以1开始的)
                 *  1. worksheet.Cells[1, 1].Value = "名称";//直接指定行列数进行赋值
                 *  2. worksheet.Cells["A1"].Value = "名称";//直接指定单元格进行赋值
                 */
                worksheet.Cells[1, 1].Value = "名称";
                worksheet.Cells[1, 2].Value = "单价";
                worksheet.Cells[1, 3].Value = "销量";

                worksheet.Cells[2, 1].Value = "洗衣机";
                worksheet.Cells[2, 2].Value = 1000;
                worksheet.Cells[2, 3].Value = 1000;

                worksheet.Cells[3, 1].Value = "空调";
                worksheet.Cells[3, 2].Value = 2300;
                worksheet.Cells[3, 3].Value = 1760;

                worksheet.Cells[4, 1].Value = "冰箱";
                worksheet.Cells[4, 2].Value = 3400;
                worksheet.Cells[4, 3].Value = 1530;

                worksheet.Cells[5, 1].Value = "电视机";
                worksheet.Cells[5, 2].Value = 7800;
                worksheet.Cells[5, 3].Value = 2700;
                #endregion
                #region 公式应用
                //乘法公式,第二列乘以第三列的值赋值给第四列
                worksheet.Cells["E2"].Formula = "B2*C2";//单个单元格
                worksheet.Cells["D2:D5"].Formula = "B2*C2";//多个单元格
                //自动求和 第二列中,将2,3,4,5行求和值赋给第六行
                //worksheet2.Cells[6, 2, 6, 4].Formula = string.Format("SUBTOTAL(9,{0})", new ExcelAddress(2, 2, 5, 2).Address);
                worksheet.Cells["B7:D7"].Formula = string.Format("SUBTOTAL(9,{0})", new ExcelAddress(2, 2, 5, 2).Address);
                #endregion
                #region 设置单元格格式
                worksheet.Cells[5, 3].Style.Numberformat.Format = "#,##0.00";//这是保留两位小数
                #endregion
                #region 设置单元格对齐方式
                using (ExcelRange range = worksheet.Cells[1, 1, 5, 3])
                {
                    range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;//水平居中
                    range.Style.VerticalAlignment = ExcelVerticalAlignment.Center;//垂直居中
                }
                //worksheet.Cells[1, 4, 1, 5].Merge = true;//合并单元格
                //worksheet.Cells.Style.WrapText = true;//自动换行
                #endregion
                #region 设置单元格字体样式
                using (ExcelRange range = worksheet.Cells[1, 1, 1, 3])
                {
                    range.Style.Font.Bold = true;
                    range.Style.Font.Color.SetColor(Color.White);
                    range.Style.Font.Name = "微软雅黑";
                    range.Style.Font.Size = 12;
                    range.Style.Fill.PatternType = ExcelFillStyle.Solid;
                    range.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(128, 128, 128));
                }
                #endregion
                #region 设置单元格背景样式
                worksheet.Cells[1, 1].Style.Fill.PatternType = ExcelFillStyle.Solid;
                worksheet.Cells[1, 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(128, 128, 128));//设置单元格背景色
                #endregion
                #region 设置单元格边框,两种方法
                //worksheet.Cells[1, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));//设置单元格所有边框
                //worksheet.Cells[1, 1].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;//单独设置单元格底部边框样式和颜色(上下左右均可分开设置)
                //worksheet.Cells[1, 1].Style.Border.Bottom.Color.SetColor(Color.FromArgb(191, 191, 191));

                worksheet.Cells[1, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[1, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[1, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));

                worksheet.Cells[2, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[2, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[2, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));

                worksheet.Cells[3, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[3, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[3, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));

                worksheet.Cells[4, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[4, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[4, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));

                worksheet.Cells[5, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[5, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                worksheet.Cells[5, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
                #endregion
                #region 设置单元格的行高和列宽
                worksheet.Cells.Style.ShrinkToFit = true;//单元格自动适应大小
                worksheet.Row(1).Height = 15;//设置行高
                worksheet.Row(1).CustomHeight = true;//自动调整行高
                worksheet.Column(1).Width = 15;//设置列宽
                #endregion
                #region 设置sheet背景
                worksheet.View.ShowGridLines = false;//去掉sheet的网格线
                worksheet.Cells.Style.Fill.PatternType = ExcelFillStyle.Solid;
                worksheet.Cells.Style.Fill.BackgroundColor.SetColor(Color.LightGray);//设置背景色
                //worksheet.BackgroundImage.Image = Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\8.jpg");//设置背景图片 没看出效果
                #endregion
                #region 插入图片
                ExcelPicture picture = worksheet.Drawings.AddPicture("logo1", Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\9.jpg"));//插入图片
                picture.SetPosition(100, 100);//设置图片的位置
                picture.SetSize(100, 100);//设置图片的大小
                #endregion
                #region 插入形状 看不出来
                //ExcelShape shape = worksheet.Drawings.AddShape("shape", eShapeStyle.Rect);//插入形状
                //shape.Font.Color = Color.Red;//设置形状的字体颜色
                //shape.Font.Size = 15;//字体大小
                //shape.Font.Bold = true;//字体粗细
                //shape.Fill.Style = eFillStyle.NoFill;//设置形状的填充样式
                //shape.Border.Fill.Style = eFillStyle.NoFill;//边框样式
                //shape.SetPosition(200, 300);//形状的位置
                //shape.SetSize(80, 30);//形状的大小
                //shape.Text = "test";//形状的内容
                #endregion
                #region 给图片加超链接  有问题
                //picture = worksheet.Drawings.AddPicture("logo2", Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\5.jpg"), new ExcelHyperLink("http:\\www.baidu.com", UriKind.Relative));
                //picture.SetPosition(700, 700);//设置图片的位置
                //picture.SetSize(100, 100);//设置图片的大小
                #endregion
                #region 给单元格加超链接
                //worksheet.Cells[1, 1].Hyperlink = new ExcelHyperLink("http:\\www.baidu.com", UriKind.Relative);
                #endregion
                #region 隐藏sheet 有问题
                //worksheet.Hidden = eWorkSheetHidden.Hidden;//隐藏sheet
                //worksheet.Column(1).Hidden = true;//隐藏某一列
                //worksheet.Row(1).Hidden = true;//隐藏某一行
                #endregion
                #region 创建柱状图
                ExcelChart chart = worksheet.Drawings.AddChart("chart", eChartType.ColumnClustered);//创建图表

                ExcelChartSerie serie = chart.Series.Add(worksheet.Cells[2, 3, 5, 3], worksheet.Cells[2, 1, 5, 1]);//选择数据,设置图表的x轴和y轴
                serie.HeaderAddress = worksheet.Cells[1, 3];//设置图表的图例

                chart.SetPosition(150, 10);//设置位置
                chart.SetSize(500, 300);//设置大小
                chart.Title.Text = "销量走势";//设置图表的标题
                chart.Title.Font.Color = Color.FromArgb(89, 89, 89);//设置标题的颜色
                chart.Title.Font.Size = 15;//标题的大小
                chart.Title.Font.Bold = true;//标题的粗体
                chart.Style = eChartStyle.Style15;//设置图表的样式
                chart.Legend.Border.LineStyle = eLineStyle.Solid;
                chart.Legend.Border.Fill.Color = Color.FromArgb(217, 217, 217);//设置图例的样式
                #endregion
                #region Excel加密和锁定 没测过
                //worksheet.Protection.IsProtected = true;//设置是否进行锁定
                //worksheet.Protection.SetPassword("yk");//设置密码
                //worksheet.Protection.AllowAutoFilter = false;//下面是一些锁定时权限的设置
                //worksheet.Protection.AllowDeleteColumns = false;
                //worksheet.Protection.AllowDeleteRows = false;
                //worksheet.Protection.AllowEditScenarios = false;
                //worksheet.Protection.AllowEditObject = false;
                //worksheet.Protection.AllowFormatCells = false;
                //worksheet.Protection.AllowFormatColumns = false;
                //worksheet.Protection.AllowFormatRows = false;
                //worksheet.Protection.AllowInsertColumns = false;
                //worksheet.Protection.AllowInsertHyperlinks = false;
                //worksheet.Protection.AllowInsertRows = false;
                //worksheet.Protection.AllowPivotTables = false;
                //worksheet.Protection.AllowSelectLockedCells = false;
                //worksheet.Protection.AllowSelectUnlockedCells = false;
                //worksheet.Protection.AllowSort = false;
                #endregion
                #region 下拉框 
                var citys = InitCity();
                for (int idx = 0, int count = citys.Count; idx < count; idx++)
                {
                    hideSheet.Cells[idx + 1, 1].Value = citys[idx].Name;
                    //hideSheet.Cells[idx + 1, 2].Value = citys[idx].ParentId;
                }
                var val = worksheet.DataValidations.AddListValidation("H1");//设置下拉框显示的数据区域
                val.Formula.ExcelFormula = "=hideSheet!$A:$A";//数据区域的名称  //数据需要时通过代码添加的
                val.Prompt = "选择省份";//下拉提示
                val.ShowInputMessage = true;//显示提示内容
                #endregion
                package.Save();//保存Excel
            }

            return true;
        }


        private static  List InitCity()
        {
            var list = new List
            {
                new KeyValue {Id = 1, ParentId = 1, Name = "上海市1"},
                new KeyValue {Id = 2, ParentId = 2, Name = "徐州市"},
                new KeyValue {Id = 3, ParentId = 2, Name = "苏州市"},
                new KeyValue {Id = 4, ParentId = 2, Name = "昆山市"},
                new KeyValue {Id = 5, ParentId = 2, Name = "南京市"},
                new KeyValue {Id = 6, ParentId = 3, Name = "北京市1"}
            };
            return list;
        }

另外,直接将datatable转化成Excel的代码很简单,也可以将其他的集合输出到Excel,像list、dictionary等,需要的自己研究下,我就不贴了。

        #region 导出到Excel ExportByEPPlus(DataTable sourceTable, string strFileName)
        /// 
        /// 使用EPPlus导出Excel(xlsx)
        /// 
        /// 数据源
        /// xlsx文件名(不含后缀名)
        public static string ExportByEPPlus(DataTable sourceTable, string strFileName)
        {
            string FileName = AppDomain.CurrentDomain.BaseDirectory + strFileName + ".xlsx";
            if (System.IO.File.Exists(FileName))                                //存在则删除
                System.IO.File.Delete(FileName);
            ExcelPackage ep = new ExcelPackage();
            ExcelWorkbook wb = ep.Workbook;
            ExcelWorksheet ws = wb.Worksheets.Add("result");
            ws.Cells["A1"].LoadFromDataTable(sourceTable, false);
            ep.SaveAs(new FileInfo(FileName));
            return FileName;
        }
        #endregion

 

                                                                                                                                                                                                           

最后记录一下,写代码时考虑到的优化tip:

  • 尽可能使用局部变量

主要是为了内存考虑,局部变量在方法调用结束后就over了。另外,就是局部变量创建的速度更快。

  • 减少重复计算

主要是针对循环里面

for (int idx = 0, int count = citys.Count; idx < count; idx++)
{
}
  • 循环内不要不断创建对象引用,可以更改引用
            IRow row = null;
            ICell cell = null;
            for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
            {
                row = sheet.CreateRow(i + 1);
                for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
                {
                    cell = row.CreateCell(j);
                    cell.SetCellValue(dt.Rows[i][j].ToString());
                }
            }
  • 使用带缓冲的输入输出流进行IO操作
  • 非必要真的别再catch里进行流程控制

 

你可能感兴趣的:(C#操作Excel文件)