咱們在平常開發中對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若是你們有相似的場景能夠嘗試使用一下。