咱们在平常开发中对Excel的操做可能会比较频繁,好多功能都会涉及到Excel的操做。在.Net Core中你们可能使用Npoi比较多,这款软件功能也十分强大,并且接近原始编程。可是直接使用Npoi大部分时候咱们可能都会本身封装一下,毕竟根据二八原则,咱们百分之八十的场景可能都是进行简单的导入导出操做,这里就引出咱们的主角Npoi.Mapper了。git
关于Npoi.Mapper看名字咱们就知道,它并非一款创新型的软件,而是针对Npoi的二次封装加强了关于Mapper相关的操做。秉承着使用很是简单的原则,不过这样可以知足咱们平常开发工做中很大一部分应用场景。它的GitHub地址为https://github.com/donnytian/Npoi.Mapper,目前Star并很少才240多,可是确实是很是好用,这里强烈推荐一波。接下来咱们就大概演示一下的它的使用。github
Npoi.Mapper的主题内容包括两大块,一个是针对导入,一个是针对导出。接下来咱们先来简单演示一下最基础的导入导出。首先咱们新建一个Student类做为数据承载的载体,简单定义大体以下编程
public class Student { public int Id { get; set; } public string Name { get; set; } public string Sex { get; set; } public DateTime BirthDay { get; set; } }
而后引入Npoi.Mapper的nuget包数组
<PackageReference Include="Npoi.Mapper" Version="3.5.1" />
接下来咱们构建一个Student集合,而后初始化一部分简单的数据,将这些数据导出到Excel,接下来作一个简单的演示app
static void Main(string[] args) { List<Student> students = new List<Student> { new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) }, new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) }, new Student{ Id = 3,Name="李慢慢",Sex="男",BirthDay=new DateTime(1999,11,11) }, new Student{ Id = 4,Name="叶红鱼",Sex="女",BirthDay=new DateTime(1999,10,10) } }; //声明mapper操做对象 var mapper = new Mapper(); //第一个参数为导出Excel名称 //第二个参数为Excel数据来源 //第三个参数为导出的Sheet名称 //overwrite参数若是是要覆盖已存在的Excel或者新建Excel则为true,若是在原有Excel上追加数据则为false //xlsx参数是用于区分导出的数据格式为xlsx仍是xls mapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true); Console.WriteLine("执行完成"); }
其中overwrite参数若是是要覆盖已存在的Excel或者新建Excel则为true,若是在原有Excel上追加数据则为false,说白了就是控制是新建Excel文件仍是在原有基础上直接追加。xlsx参数是用于区分导出的Excel格式为xlsx仍是xls。经过上述简单代码即可以实现Excel的导出功能,真的是很是简单,若是你只是进行简单的导出操做,经过Npoi.Mapper操做真的是不二的选择。这样导出的Excel效果以下所示
可是这样导出的Excel头信息为属性的名称,并且咱们Student类中包含了一个时间字段BirthDay为DateTime类型,这个表示格式好像也不太符合咱们常规的阅读习惯,那该怎么办呢?Npoi.Mapper为咱们提供了两种处理方式,一种是经过Fluent的方式指定映射关系以下所示函数
var mapper = new Mapper(); //第一个参数表示导出的列名,第二个表示对应的属性字段 mapper.Map<Student>("姓名", s => s.Name) .Map<Student>("学号", s => s.Id) .Map<Student>("性别", s => s.Sex) .Map<Student>("生日", s => s.BirthDay) //格式化操做,第一个参数表示格式,第二表示对应字段 //Format不只仅只支持时间操做,还能够是数字或金额等 .Format<Student>("yyyy-MM-dd", s => s.BirthDay); mapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true);
通过上面相关操做以后导出后的效果以下所示还有一种形式是经过ColumnAttribute的形式在导出的实体类的属性上进行声明导出列相关设置,具体操做以下this
public class Student { [Column("学号")] public int Id { get; set; } [Column("姓名")] public string Name { get; set; } [Column("性别")] public string Sex { get; set; } [Column("生日",CustomFormat = "yyyy-MM-dd")] public DateTime BirthDay { get; set; } }
经过这种方式操做和经过Fluent的效果是彻底同样的,至于使用哪种彻底看我的喜爱,不过我我的更喜欢在属性上直接声明的方式,这样看起来显得一目了然。
有时候咱们可能须要将不一样的数据源导入到同一个Excel的不一样Sheet中,Npoi.Mapper也提供了这方面的支持,具体操做方式以下所示spa
static void Main(string[] args) { //构建Student集合 List<Student> students = new List<Student> { new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) }, new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) } }; //构建Person集合 List<Person> persons = new List<Person> { new Person{ Id = 1,Name="陈某", Tel= 18833445566}, new Person{ Id = 2,Name="柯浩然", Tel = 15588997766} }; var mapper = new Mapper(); //放入Mapper中 //第一个参数是数据集合,第二个参数是Sheet名称,第三个参数表示是追加数据仍是覆盖数据 mapper.Put<Student>(students, "student",true); mapper.Put<Person>(persons, "person",true); mapper.Save("Human.xlsx"); }
不过不少时候咱们是经过Web程序直接将数据转换为文件流返回的,并不会生成Excel文件,Npoi.Mapper很贴心的为咱们提供了将数据读取到Stream的操做,操做方式以下code
[HttpGet] public ActionResult DownLoadFile() { List<Student> students = new List<Student> { new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) }, new Student{ Id = 2,Name="余帘",Sex="女",BirthDay=new DateTime(1999,12,12) }, new Student{ Id = 3,Name="李慢慢",Sex="男",BirthDay=new DateTime(1999,11,11) }, new Student{ Id = 4,Name="叶红鱼",Sex="女",BirthDay=new DateTime(1999,10,10) } }; var mapper = new Mapper(); MemoryStream stream = new MemoryStream(); //将students集合生成的Excel直接放置到Stream中 mapper.Save(stream, students, "sheet1", overwrite: true, xlsx: true); return File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","Student.xlsx"); }
Save提供了几个重载方法,其中有一个就是将数据保存到Stream中,可是这里也踩到了一个坑,不过这个是Npoi的坑并非Npoi.Mapper的坑,那就是Workbook.Write(stream)的时候会将stream关闭,若是继续操做这个Stream会报流已关闭的错误,而Npoi.Mapper的Save到Stream的方法偏偏是对这个方法的封装,这也是为什么上面我没直接在File中直接返回Stream,而是将其转换为byte数组再返回的缘由。orm
上面咱们演示了使用Npoi.Mapper将数据导出的场景,接下来咱们来演示经过Npoi.Mapper的读取Excel的相关操做,操做也是很是的简单,话很少说直接上代码,好比我读取上面导出的Excel
//Excel文件的路径 var mapper = new Mapper("Students.xlsx"); //读取的sheet信息 var studentRows = mapper.Take<Student>("sheet1"); foreach (var row in studentRows) { //映射的数据保留在value中 Student student = row.Value; Console.WriteLine($"姓名:[{student.Name}],学号:[{student.Id}],性别:[{student.Sex}],生日:[{student.BirthDay:yyyy-MM-dd}]"); }
经过Take方法直接读取出来的是RowInfo集合,RowInfo是用来包装读取数据的包装类。经过它能够获取读取的行号,或读取过程当中可能会出现异常状况,好比某一列读取失败,它会将列信息和报错信息记录下来,若是你不须要这些信息或者以为遍历的时候比较麻烦想直接拿到须要的集合,能够经过以下方式转换一下
var studentRows = mapper.Take<Student>("sheet1"); //经过lambda获取到Student集合 var students = studentRows.Select(i => i.Value);
有的时候你可能不想定义一个POCO去接收返回的结果,而是想直接拿到读取信息,转换成你须要的数据格式。好比你想读取Excel中的数据,将结果转换为实体类直接入库,可是你不想定义一个专门的映射类去接收读取结果,这时候你须要一个动态类型去接收,而Npoi.Mapper偏偏提供了这样的功能,能够将Excel中的数据直接读取到dynamic中去,具体操做和上面相似
var mapper = new Mapper("Students.xlsx"); var studentRows = mapper.Take<dynamic>("sheet1"); foreach (var row in studentRows) { var student = row.Value; Console.WriteLine($"姓名:[{student.姓名}],学号:[{student.学号}],性别:[{student.性别}],生日:[{student.生日:yyyy-MM-dd}]"); }
其中你要操做的字段名称和Excel的列名是一致的,好比个人Excel列名叫姓名,那么我读取的时候对应的属性名称也叫姓名。
一样的状况也存在于导入操做,好比许多状况下咱们是经过Web接口直接上传的文件,这种场景下,咱们一般能拿到上传的流信息,Npoi.Mapper也支持读取Excel文件流的形式获取Excel数据,以下所示
[HttpPost] public IEnumerable<Student> UploadFile(IFormFile formFile) { //经过上传文件流初始化Mapper var mapper = new Mapper(formFile.OpenReadStream()); //读取sheet1的数据 return mapper.Take<Student>("sheet1").Select(i=>i.Value); }
除了上面介绍的主要功能以外Npoi.Mapper还提供了一些其余的功能,简单介绍一下几个比较实用的点
有时候咱们的导出或导入数据可能想忽略某些列不导出,Npoi.Mapper为了咱们提供了相似EF的Ignore操做
[Ignore] public string IgnoredProperty { get; set; }
这样的话不管是导入仍是导出都会忽略这个属性,即导出不会显示这个列,导入不会映射这一列的数据
若是咱们导入的数据有一列数据的值是你们都拥有的,在Excel上能够经过合并单元格的操做来显示这一列,对于合并单元格的列,对于程序来说就是等价于全部列都是同一个值,Npoi.Mapper为咱们作了这种处理
[UseLastNonBlankValue] public string ClassName { get; set; }
虽然默认状况下Npoi.Mapper能帮咱们知足大部分的类型映射关系,可是有时候咱们须要根据咱们本身的规则处理处理数据映射关系,这时候咱们须要用到Map功能,他有许多重载的方法,咱们就查看一个比较经常使用的方法作参数讲解
/// <param name="columnName">对应Excel列的名称</param> /// <param name="propertyName">对应实体的属性名称</param> /// <param name="tryTake">该函数用于处理从Excel读取时针对单元格数据的处理</param> /// <param name="tryPut">该函数用于处理将数据导出到Excel是针对源数据的处理</param> public static Mapper Map<T>(this Mapper mapper, string columnName, string propertyName, Func<IColumnInfo, object, bool> tryTake = null, Func<IColumnInfo, object, bool> tryPut = null) { }
其中tryTake用于处理从Excel导出时针对单元格数据的处理,IColumnInfo表明数据的来源,object表明对应将Row导入到某个实体中。tryPut偏偏相反,用于处理将数据导出到Excel是针对源数据的处理。其中IColumnInfo表明要导出到的列信息,object表明数据的源。简单演示一下,好比我想将上述示例中,读取到Excel里的性别数据映射到实体中的时候作一下中英文的处理,就可使用如下操做
var mapper = new Mapper("Students.xlsx"); mapper.Map<Student>("性别", "Sex", (c, t) => { Student student = t as Student; student.Sex = c.CurrentValue == "男" ? "MAN" : "WOMAN"; return true; }, null);
由于我是要读取Excel,因此使用tryTake函数,t表明target表示要映射到的实体,c表明读取到的单元格信息,我将读取到target里的数据作一下处理,若是在单元格中读取的是"男"那么对应到Student转换为"MAN",反之则为"WOMAN"。总之你想处理一下,自定义映射逻辑均可以使用这个功能。
以上是咱们对Npoi.Mapper的大体讲解,我我的仍是很是推荐的。它的使用足够简单并且功能很是完善,由于它既能够处理Excel导入操做,也能够处理Excel导出操做。它很强大,由于它能够知足咱们平常开发中,大部分关于导入导出Excel的场景。可是它还不够强大,由于它还存在必定的缺陷,并且许多细节可能还没考虑到。不过庆幸的是,它的源码很是的简单一共不到20个类,并且逻辑很是清晰。若是有的状况它真的不能知足,咱们彻底能够下载它的源码本身扩展操做。最后再次贴上它的GitHub地址https://github.com/donnytian/Npoi.Mapper若是你们有相似的场景能够尝试使用一下。