.Net Core Excel導(dǎo)入導(dǎo)出神器Npoi.Mapper
前言
????我們在日常開發(fā)中對Excel的操作可能會比較頻繁,好多功能都會涉及到Excel的操作。在.Net Core中大家可能使用Npoi比較多,這款軟件功能也十分強(qiáng)大,而且接近原始編程。但是直接使用Npoi大部分時候我們可能都會自己封裝一下,畢竟根據(jù)二八原則,我們百分之八十的場景可能都是進(jìn)行簡單的導(dǎo)入導(dǎo)出操作,這里就引出我們的主角Npoi.Mapper了。
簡介
????關(guān)于Npoi.Mapper看名字我們就知道,它并不是一款創(chuàng)新型的軟件,而是針對Npoi的二次封裝增強(qiáng)了關(guān)于Mapper相關(guān)的操作。秉承著使用非常簡單的原則,不過這樣能夠滿足我們?nèi)粘i_發(fā)工作中很大一部分應(yīng)用場景。它的GitHub地址為https://github.com/donnytian/Npoi.Mapper,目前Star并不多才240多,但是確實(shí)是非常好用,這里強(qiáng)烈推薦一波。接下來我們就大概演示一下的它的使用。
常規(guī)操作
Npoi.Mapper的主題內(nèi)容包括兩大塊,一個是針對導(dǎo)入,一個是針對導(dǎo)出。接下來我們先來簡單演示一下最基礎(chǔ)的導(dǎo)入導(dǎo)出。首先我們新建一個Student類作為數(shù)據(jù)承載的載體,簡單定義大致如下
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"?/>導(dǎo)出操作
接下來我們構(gòu)建一個Student集合,然后初始化一部分簡單的數(shù)據(jù),將這些數(shù)據(jù)導(dǎo)出到Excel,接下來做一個簡單的演示
static void Main(string[] args){Liststudents = new List {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();//第一個參數(shù)為導(dǎo)出Excel名稱//第二個參數(shù)為Excel數(shù)據(jù)來源//第三個參數(shù)為導(dǎo)出的Sheet名稱//overwrite參數(shù)如果是要覆蓋已存在的Excel或者新建Excel則為true,如果在原有Excel上追加數(shù)據(jù)則為false//xlsx參數(shù)是用于區(qū)分導(dǎo)出的數(shù)據(jù)格式為xlsx還是xlsmapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true);Console.WriteLine("執(zhí)行完成");}
其中overwrite參數(shù)如果是要覆蓋已存在的Excel或者新建Excel則為true,如果在原有Excel上追加數(shù)據(jù)則為false,說白了就是控制是新建Excel文件還是在原有基礎(chǔ)上直接追加。xlsx參數(shù)是用于區(qū)分導(dǎo)出的Excel格式為xlsx還是xls。通過上述簡單代碼便可以實(shí)現(xiàn)Excel的導(dǎo)出功能,真的是非常簡單,如果你只是進(jìn)行簡單的導(dǎo)出操作,通過Npoi.Mapper操作真的是不二的選擇。這樣導(dǎo)出的Excel效果如下所示
但是這樣導(dǎo)出的Excel頭信息為屬性的名稱,而且我們Student類中包含了一個時間字段BirthDay為DateTime類型,這個表示格式好像也不太符合我們常規(guī)的閱讀習(xí)慣,那該怎么辦呢?Npoi.Mapper為我們提供了兩種處理方式,一種是通過Fluent的方式指定映射關(guān)系如下所示
var mapper = new Mapper();//第一個參數(shù)表示導(dǎo)出的列名,第二個表示對應(yīng)的屬性字段mapper.Map("姓名", s => s.Name) .Map("學(xué)號", s => s.Id) .Map("性別", s => s.Sex) .Map("生日", s => s.BirthDay) //格式化操作,第一個參數(shù)表示格式,第二表示對應(yīng)字段//Format不僅僅只支持時間操作,還可以是數(shù)字或金額等.Format("yyyy-MM-dd", s => s.BirthDay); mapper.Save("Students.xlsx", students, "sheet1", overwrite: true, xlsx:true);
經(jīng)過上面相關(guān)操作之后導(dǎo)出后的效果如下所示
還有一種形式是通過ColumnAttribute的形式在導(dǎo)出的實(shí)體類的屬性上進(jìn)行聲明導(dǎo)出列相關(guān)設(shè)置,具體操作如下
public class Student{[]public int Id { get; set; }[]public string Name { get; set; }[]public string Sex { get; set; }[]public DateTime BirthDay { get; set; }}
通過這種方式操作和通過Fluent的效果是完全一樣的,至于使用哪一種完全看個人喜好,不過我個人更喜歡在屬性上直接聲明的方式,這樣看起來顯得一目了然。
有時候我們可能需要將不同的數(shù)據(jù)源導(dǎo)入到同一個Excel的不同Sheet中,Npoi.Mapper也提供了這方面的支持,具體操作方式如下所示
static void Main(string[] args){//構(gòu)建Student集合Liststudents = new List {new Student{ Id = 1,Name="夫子",Sex="男",BirthDay=new DateTime(1999,10,11) },new Student{ Id = 2,Name="余簾",Sex="女",BirthDay=new DateTime(1999,12,12) }};//構(gòu)建Person集合Listpersons = new List {new Person{ Id = 1,Name="陳某", Tel= 18833445566},new Person{ Id = 2,Name="柯浩然", Tel = 15588997766}};var mapper = new Mapper();//放入Mapper中//第一個參數(shù)是數(shù)據(jù)集合,第二個參數(shù)是Sheet名稱,第三個參數(shù)表示是追加數(shù)據(jù)還是覆蓋數(shù)據(jù)mapper.Put(students, "student",true); mapper.Put(persons, "person",true); mapper.Save("Human.xlsx");}
不過很多時候我們是通過Web程序直接將數(shù)據(jù)轉(zhuǎn)換為文件流返回的,并不會生成Excel文件,Npoi.Mapper很貼心的為我們提供了將數(shù)據(jù)讀取到Stream的操作,操作方式如下
[]public ActionResult DownLoadFile(){Liststudents = new List {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提供了幾個重載方法,其中有一個就是將數(shù)據(jù)保存到Stream中,但是這里也踩到了一個坑,不過這個是Npoi的坑并不是Npoi.Mapper的坑,那就是Workbook.Write(stream)的時候會將stream關(guān)閉,如果繼續(xù)操作這個Stream會報流已關(guān)閉的錯誤,而Npoi.Mapper的Save到Stream的方法恰恰是對這個方法的封裝,這也是為何上面我沒直接在File中直接返回Stream,而是將其轉(zhuǎn)換為byte數(shù)組再返回的原因。
導(dǎo)入操作
上面我們演示了使用Npoi.Mapper將數(shù)據(jù)導(dǎo)出的場景,接下來我們來演示通過Npoi.Mapper的讀取Excel的相關(guān)操作,操作也是非常的簡單,話不多說直接上代碼,比如我讀取上面導(dǎo)出的Excel
//Excel文件的路徑var mapper = new Mapper("Students.xlsx");//讀取的sheet信息var studentRows = mapper.Take("sheet1"); foreach (var row in studentRows){//映射的數(shù)據(jù)保留在value中Student student = row.Value;Console.WriteLine($"姓名:[{student.Name}],學(xué)號:[{student.Id}],性別:[{student.Sex}],生日:[{student.BirthDay:yyyy-MM-dd}]");}
通過Take方法直接讀取出來的是RowInfo集合,RowInfo是用來包裝讀取數(shù)據(jù)的包裝類。通過它可以獲取讀取的行號,或讀取過程中可能會出現(xiàn)異常情況,比如某一列讀取失敗,它會將列信息和報錯信息記錄下來,如果你不需要這些信息或者覺得遍歷的時候比較麻煩想直接拿到需要的集合,可以通過如下方式轉(zhuǎn)換一下
var studentRows = mapper.Take("sheet1"); //通過lambda獲取到Student集合var students = studentRows.Select(i => i.Value);
有的時候你可能不想定義一個POCO去接收返回的結(jié)果,而是想直接拿到讀取信息,轉(zhuǎn)換成你需要的數(shù)據(jù)格式。比如你想讀取Excel中的數(shù)據(jù),將結(jié)果轉(zhuǎn)換為實(shí)體類直接入庫,但是你不想定義一個專門的映射類去接收讀取結(jié)果,這時候你需要一個動態(tài)類型去接收,而Npoi.Mapper恰恰提供了這樣的功能,可以將Excel中的數(shù)據(jù)直接讀取到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.姓名}],學(xué)號:[{student.學(xué)號}],性別:[{student.性別}],生日:[{student.生日:yyyy-MM-dd}]");}
其中你要操作的字段名稱和Excel的列名是一致的,比如我的Excel列名叫姓名,那么我讀取的時候?qū)?yīng)的屬性名稱也叫姓名。
同樣的情況也存在于導(dǎo)入操作,比如許多情況下我們是通過Web接口直接上傳的文件,這種場景下,我們通常能拿到上傳的流信息,Npoi.Mapper也支持讀取Excel文件流的形式獲取Excel數(shù)據(jù),如下所示
[]public IEnumerableUploadFile(IFormFile formFile) {//通過上傳文件流初始化Mappervar mapper = new Mapper(formFile.OpenReadStream());//讀取sheet1的數(shù)據(jù)return mapper.Take("sheet1").Select(i=>i.Value); }
其他功能
除了上面介紹的主要功能之外Npoi.Mapper還提供了一些其他的功能,簡單介紹一下幾個比較實(shí)用的點(diǎn)
忽略操作
有時候我們的導(dǎo)出或?qū)霐?shù)據(jù)可能想忽略某些列不導(dǎo)出,Npoi.Mapper為了我們提供了類似EF的Ignore操作
[]public string IgnoredProperty { get; set; }
這樣的話無論是導(dǎo)入還是導(dǎo)出都會忽略這個屬性,即導(dǎo)出不會顯示這個列,導(dǎo)入不會映射這一列的數(shù)據(jù)
合并單元格
如果我們導(dǎo)入的數(shù)據(jù)有一列數(shù)據(jù)的值是大家都擁有的,在Excel上可以通過合并單元格的操作來顯示這一列,對于合并單元格的列,對于程序來講就是等價于所有列都是同一個值,Npoi.Mapper為我們做了這種處理
[]public string ClassName { get; set; }
自定義Map規(guī)則
雖然默認(rèn)情況下Npoi.Mapper能幫我們滿足大部分的類型映射關(guān)系,但是有時候我們需要根據(jù)我們自己的規(guī)則處理處理數(shù)據(jù)映射關(guān)系,這時候我們需要用到Map功能,他有許多重載的方法,我們就查看一個比較常用的方法做參數(shù)講解
/// 對應(yīng)Excel列的名稱/// 對應(yīng)實(shí)體的屬性名稱/// 該函數(shù)用于處理從Excel讀取時針對單元格數(shù)據(jù)的處理/// 該函數(shù)用于處理將數(shù)據(jù)導(dǎo)出到Excel是針對源數(shù)據(jù)的處理public static Mapper Map(this Mapper mapper, string columnName, string propertyName, Funcobject , bool> tryTake = null,Funcobject , bool> tryPut = null){}
其中tryTake用于處理從Excel導(dǎo)出時針對單元格數(shù)據(jù)的處理,IColumnInfo代表數(shù)據(jù)的來源,object代表對應(yīng)將Row導(dǎo)入到某個實(shí)體中。tryPut恰恰相反,用于處理將數(shù)據(jù)導(dǎo)出到Excel是針對源數(shù)據(jù)的處理。其中IColumnInfo代表要導(dǎo)出到的列信息,object代表數(shù)據(jù)的源。簡單演示一下,比如我想將上述示例中,讀取到Excel里的性別數(shù)據(jù)映射到實(shí)體中的時候做一下中英文的處理,就可以使用以下操作
var mapper = new Mapper("Students.xlsx");mapper.Map("性別", "Sex", (c, t) => { Student student = t as Student;student.Sex = c.CurrentValue == "男" ? "MAN" : "WOMAN";return true;}, null);
因?yàn)槲沂且x取Excel,所以使用tryTake函數(shù),t代表target表示要映射到的實(shí)體,c代表讀取到的單元格信息,我將讀取到target里的數(shù)據(jù)做一下處理,如果在單元格中讀取的是"男"那么對應(yīng)到Student轉(zhuǎn)換為"MAN",反之則為"WOMAN"。總之你想處理一下,自定義映射邏輯都可以使用這個功能。
總結(jié)
以上是我們對Npoi.Mapper的大致講解,我個人還是非常推薦的。它的使用足夠簡單而且功能非常完善,因?yàn)樗瓤梢蕴幚鞥xcel導(dǎo)入操作,也可以處理Excel導(dǎo)出操作。它很強(qiáng)大,因?yàn)樗梢詽M足我們?nèi)粘i_發(fā)中,大部分關(guān)于導(dǎo)入導(dǎo)出Excel的場景。但是它還不夠強(qiáng)大,因?yàn)樗€存在一定的缺陷,而且許多細(xì)節(jié)可能還沒考慮到。不過慶幸的是,它的源碼非常的簡單一共不到20個類,而且邏輯非常清晰。如果有的情況它真的不能滿足,我們完全可以下載它的源碼自己擴(kuò)展操作。最后再次貼上它的GitHub地址https://github.com/donnytian/Npoi.Mapper如果大家有類似的場景可以嘗試使用一下。
?歡迎掃碼關(guān)注我的公眾號?
