最近在项目中有一个导入Excel的功能,下文中将介绍如何在C#项目中导入Excel文件,以及实现此功能的宏观思路。
因为项目中的导入功能是用户根据自己的需要去导入Excel文件,那么对于用户上传的格式是有要求的。所以需要让用户先下载我们提供的模板,然后用户可以去编辑模板,再进行上传。
下载功能是的数据类型是使用的DataTable,也就是我们在DataTable中为用户提供列的名称放在一个DataTable中,让用户去下载。
具体代码实现
通过我们在代码中设置列的标题(或者让用户可配置),已list集合的方式传入此方法中
///
/// 定义导入数据的模板
/// 通过for循环给空白的DataTable添加列名称
///
/// 模板的列的集合
///
public DataTable ImportTemplate(List ColumnNames)
{
DataTable template = new DataTable();
DataRow dr = template.NewRow() ;
template.Rows.Add(dr);
for(int i = 0; i < ColumnNames.Count; i++)
{
template.Columns.Add(ColumnNames[i]);
}
return template;
}
导出文件的具体代码
public void Export(DataTable dtSource,string FileName)
{
try {
SaveFileDialog fileDialog = new SaveFileDialog();
fileDialog.Filter = "Excel|*.xls|TXT|*.txt|PDF|*.pdf";
fileDialog.FileName = FileName+"-"+DateTime.Now.ToString("D");
if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
{
return;
}
string FileSavePath = fileDialog.FileName;
switch (fileDialog.FilterIndex)
{
case 1:
ExportXls(FileSavePath, dtSource); //导出xls
break;
case 2:
ExportTxt(FileSavePath, dtSource); //导出txt
break;
case 3:
ExportPDF(FileSavePath, dtSource); //导出pdf
break;
//case 4:
// ExportDocx(FileSavePath, dtSource); //导出docx
// break;
}
MessageBox.Show("文件 "+ FileSavePath + " 导出成功");
}
catch(Exception e){
MessageBox.Show("导出文件失败,请稍后重新尝试"+ e);
}
}
有了以上两个方法之后,我们可以在某事件中去触发此这些方法,然后下载文件到用户的本地。
上文中已经把导入的下载模板的工作完成,接下来是需要用把编辑好的Excel文件上传到我们程序并同步到数据库中。
上传Excel这里使用的NPOI,在“管理NuGet程序包”中,搜索NPOI找到,下载并安装。
这里可能出现问题是
解决办法:先安装“SixLabors.Fonts”,需要注意的是:如果直接搜索的话可能找不到,需要把“包括预发行版”勾选上
安装好之后,再去下载NPOI。
在项目中添加类ExcelHelper
internal class ExcelHelper
{
///
/// 从Excel读取数据,只支持单表
///
/// 文件路径
public static DataTable ReadFromExcel(string FilePath)
{
IWorkbook wk = null;
string extension = System.IO.Path.GetExtension(FilePath); //获取扩展名
try
{
using (FileStream fs = File.OpenRead(FilePath))
{
if (extension.Equals(".xls")) //2003
{
wk = new HSSFWorkbook(fs);
}
else //2007以上
{
wk = new XSSFWorkbook(fs);
}
}
//读取当前表数据
ISheet sheet = wk.GetSheetAt(0);
//构建DataTable
IRow row = sheet.GetRow(0);
DataTable result = BuildDataTable(row);
if (result != null)
{
if (sheet.LastRowNum >= 1)
{
for (int i = 1; i < sheet.LastRowNum + 1; i++)
{
IRow temp_row = sheet.GetRow(i);
if (temp_row == null) { continue; }//2019-01-14 修复 行为空时会出错
List
在winform的中某触发事件中代码,调用刚刚的ExcelHelper对象,(这里还可以返回一个DataTable对象)
private void ExcelFile()
{
OpenFileDialog openFile = new OpenFileDialog();
if (openFile.ShowDialog() == DialogResult.OK)
{
string filePath = openFile.FileName;
//获取本地的Excel文件
DataTable excelDt = ExcelHelper.ReadFromExcel(filePath);
}
}
需要注意的是,在导入的时候如果有空白行的话,会把空白行也插入到数据库中,所以在读取本地的Excel数据之后,需要把空白行也去除掉。
List removelist = new List();
for (int i = 0; i < excelDt.Rows.Count; i++)
{
bool IsNull = true;
for (int j = 0; j < excelDt.Columns.Count; j++)
{
if (!string.IsNullOrEmpty(excelDt.Rows[i][j].ToString().Trim()))
{
IsNull = false;
}
}
if (IsNull)
{
removelist.Add(excelDt.Rows[i]);
}
}
for (int i = 0; i < removelist.Count; i++)
{
excelDt.Rows.Remove(removelist[i]);
}
到这里以上的操作步骤是获取本地的Excel数据了,那么接下来就可以去写入数据库中,需要注意的是:读取的数据,需要和数据库中的字段一一对应,也就是位置、名称都要对应上才行。
开发中遇到的问题:
1、用户需要提供的数据和数据库少于数据库中的字段,比如:用户需要提供姓名、手机号这两个字段,但是数据库中还需要有地址这个字段,那怎么办呢?我们可以在代码中给把这些信息补全。再写入数据库中。
示例代码
ProductTable.Columns.Add("creator");
ProductTable.Columns["creator"].SetOrdinal(26);//在第26列插入字段
ProductTable.Columns.Add("create_time");
ProductTable.Columns["create_time"].SetOrdinal(27);
2、在写数据库时,遇到数据类型不匹配。因为从本地获取的Excel数据基本上是string类型,那么数据库中有写字段是bool类型或者其他,如果插库的话就会报错,我这里解决这个问题的思路大概是在代码中把类型给转换一下,这样插入数据库时就不会出错了。
解决思路:比较笨的办法是把获取本地的数据(存放到了DataTable对象中)克隆出来一份,在克隆出来的那份中进行数据转换,因为在原来的数据不能进行转换,而且这里克隆不会把原来的数据进行克隆,所以在完成数据类型转换之后,还要把旧表中的数据同步到克隆出来的表中。
示例代码:
表克隆:
DataTable NewTable = new DataTable();
NewTable = ProductTable.Clone();//把原来的表进行克隆
修改类型:
//把新表中的表结构进行修改数据类型,和数据库中表字段进行保持一致
foreach (DataColumn col in NewTable.Columns)
{
#region ifelse
if (col.ColumnName == "conveyor_sel")
{
col.DataType = typeof(int);
}
else if (col.ColumnName == "p_length")
{
col.DataType = typeof(int);
}
else if (col.ColumnName == "p_width")
{
col.DataType = typeof(int);
}
}
旧表中的数据同步到新表中
foreach (DataRow item in ProductTable.Rows)
{
DataRow NewDtRow = NewTable.NewRow();
//获取对应行的产品代码的值
data = item.ItemArray[0];
NewDtRow["recipe"] = item["recipe"].ToString();
NewDtRow["p_name"] = item["p_name"].ToString();
NewDtRow["cust_name"] = item["cust_name"].ToString();
NewDtRow["cust_abbr"] = item["cust_abbr"].ToString();
NewDtRow["cust_field"] = item["cust_field"].ToString();
}
3、读取到本地Excel表中的的列标题是中文,但是数据库的形式是英文。这里也是用代码转换
示例代码:
ProductTable.Columns["产品代码"].ColumnName = "recipe";
ProductTable.Columns["产品名称"].ColumnName = "p_name";
这里用的EF框架。
public long AddDataTable(DataTable dt, String TableName)
{
PmsTestEntities db = new PmsTestEntities();
SqlBulkCopy copy = new SqlBulkCopy(db.Database.Connection.ConnectionString, SqlBulkCopyOptions.UseInternalTransaction);//把本地文件复制
copy.BatchSize = 100;//每次传输行数
copy.NotifyAfter = dt.Rows.Count;//传输多少行后提示
copy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied);
copy.DestinationTableName = TableName;//表名
copy.WriteToServer(dt);
return DataIncoming;
}
//显示传入了多少的数据
private void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
{
long count = e.RowsCopied;
DataIncoming = count;
}