Tavoite:
Haluan tuoda Excel-tiedoston ja lukea tiettyjen sarakkeiden rivit. Tähän käytän ExcelDataReader
. Olen toteuttanut matalan tason luokan nimeltä ExcelData
, joka käyttää tiedostoa ExcelDataReader
ja tekee esimerkiksi selvittääkseen onko se .xls ”of” .xslx ”-tiedosto (tai ehkä jotain, joka ei liity täysin!) jne. Tämän luokan päälle tein luokan ReadInData
, joka saa haluamasi sarakkeet Excelistä taulukko.
Suurimmat huolenaiheet:
- Pääohjelmani saalisluettelo
- Poikkeusten heittäminen
- Kokonaisrakenne koodin ja koodin laadun
- Pitäisikö minun kapseloida luokkani
ExcelData
ReadInData
, vai pitääkö se sellaisenaan ? -
isFirstRowAsColumnNames
-parametrin
välittäminen Koska tämä on yrityksen koodi, muutin parin nimet luokista, joten tiedän, että ne eivät ole parhaita nimiä.
Koodini lähtökohta:
class Program { static void Main(string[] args) { try { ReadInData readInData = new ReadInData(@"C:\SC.xlsx", "sc_2014"); IEnumerable<Recipient> recipients = readInData.GetData(); } catch (FileNotFoundException ex) { Console.WriteLine(ex.Message); } catch (ArgumentException ex) { Console.WriteLine(ex.Message); } catch (WorksheetDoesNotExistException ex) { Console.WriteLine(ex.Message); } catch (FileToBeProcessedIsNotInTheCorrectFormatException ex) { Console.WriteLine(ex.Message); } Console.ReadLine(); } }
Tässä koodi luon uuden ReadInData
luokan, johon passin s polku, tiedoston nimi ja Excel-taulukon nimi, jonka haluan lukea.
Huolenaihe: onko hyvä siirtää nämä asiat tiedostoon?
ReadInData
-luokka:
public class ReadInData { private string path; private string worksheetName; public ReadInData(string path, string worksheetName) { this.path = path; this.worksheetName = worksheetName; } public IEnumerable<Recipient> GetData(bool isFirstRowAsColumnNames = true) { var excelData = new ExcelData(path); var dataRows = excelData.GetData(worksheetName, isFirstRowAsColumnNames); return dataRows.Select(dataRow => new Recipient() { Municipality = dataRow["Municipality"].ToString(), Sexe = dataRow["Sexe"].ToString(), LivingArea = dataRow["LivingArea"].ToString() }).ToList(); } }
Pohjimmiltaan ajattelin, että tarvitsin luokan ExcelData
-luokka, koska se tuntui hieman korkeammalta tasolta.
ExcelData
-luokka:
public class ExcelData { private string path; public ExcelData(string path) { this.path = path; } private IExcelDataReader GetExcelDataReader(bool isFirstRowAsColumnNames) { using (FileStream fileStream = File.Open(path, FileMode.Open, FileAccess.Read)) { IExcelDataReader dataReader; if (path.EndsWith(".xls")) { dataReader = ExcelReaderFactory.CreateBinaryReader(fileStream); } else if (path.EndsWith(".xlsx")) { dataReader = ExcelReaderFactory.CreateOpenXmlReader(fileStream); } else { //Throw exception for things you cannot correct throw new FileToBeProcessedIsNotInTheCorrectFormatException("The file to be processed is not an Excel file"); } dataReader.IsFirstRowAsColumnNames = isFirstRowAsColumnNames; return dataReader; } } private DataSet GetExcelDataAsDataSet(bool isFirstRowAsColumnNames) { return GetExcelDataReader(isFirstRowAsColumnNames).AsDataSet(); } private DataTable GetExcelWorkSheet(string workSheetName, bool isFirstRowAsColumnNames) { DataSet dataSet = GetExcelDataAsDataSet(isFirstRowAsColumnNames); DataTable workSheet = dataSet.Tables[workSheetName]; if (workSheet == null) { throw new WorksheetDoesNotExistException(string.Format("The worksheet {0} does not exist, has an incorrect name, or does not have any data in the worksheet", workSheetName)); } return workSheet; } public IEnumerable<DataRow> GetData(string workSheetName, bool isFirstRowAsColumnNames = true) { DataTable workSheet = GetExcelWorkSheet(workSheetName, isFirstRowAsColumnNames); IEnumerable<DataRow> rows = from DataRow row in workSheet.Rows select row; return rows; } }
Ja lopuksi Recipient
-luokka (joka ei tee mitään erityistä):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Poikkeusluokat perivät kohteen Exception
ja välittävät vain viestin poikkeukselle.
Kommentit
Vastaa
Yksinkertaista sahaketjuasi
static void Main(string[] args) { try { ReadInData readInData = new ReadInData(@"C:\SC.xlsx", "sc_2014"); IEnumerable<Recipient> recipients = readInData.GetData(); } catch (Exception ex) { if(!(ex is FileNotFoundException || ex is ArgumentException || ex is FileToBeProcessedIsNotInTheCorrectFormatException)) throw; Console.WriteLine(ex.Message); } Console.Write(Press any key to continue...); Console.ReadKey(true); }
En näe syytä ReadInData
olla ei-staattinen luokka. Et hyödynnä sitä, että muistat polun ja laskentataulukon nimen, etkä pidä jonkinlaista avointa yhteyttä työkirjaan. Ja tee aina vapaasti tehdä koodistasi monimutkaisempi poistamalla vain muuttujia käytetään kerran (valinnainen). Ei ole mitään syytä ToList()
tähän, koska palautat joka tapauksessa IEnumerable<T>
.
public static class ReadInData { public static IEnumerable<Recipient> GetData(string path, string worksheetName, bool isFirstRowAsColumnNames = true) { return new ExcelData(path).GetData(worksheetName, isFirstRowAsColumnNames) .Select(dataRow => new Recipient() { Municipality = dataRow["Municipality"].ToString(), Sexe = dataRow["Sexe"].ToString(), LivingArea = dataRow["LivingArea"].ToString() }); } }
Voit tehdä tämän saman luokan ExcelData
luokassa, eli .. tehdä nämä toiminnot menetelmien sijaan luokka. Sinun ei tarvitse, mutta jälleen kerran, et hyödynnä paljon OO: ta, joten sinun on tehtävä siitä OO.
Jos et haluavat / tarvitsevat jonkun ExcelData
-luokan käyttäjällä kuin kapseloivat sen. Uskon kuitenkin, että haluat jossain vaiheessa pystyä käyttämään tätä Excel-lukijaa uudelleen, jolloin siirrän nämä menetelmät staattiseen ExcelHelperClass
. Ja ensimmäisen luokan ReadInData
tein vain menetelmän alkuperäiseen ohjelmaan.
private static IExcelDataReader GetExcelDataReader(string path, bool isFirstRowAsColumnNames) { using (FileStream fileStream = File.Open(path, FileMode.Open, FileAccess.Read)) { IExcelDataReader dataReader; if (path.EndsWith(".xls")) dataReader = ExcelReaderFactory.CreateBinaryReader(fileStream); else if (path.EndsWith(".xlsx")) dataReader = ExcelReaderFactory.CreateOpenXmlReader(fileStream); else throw new FileToBeProcessedIsNotInTheCorrectFormatException("The file to be processed is not an Excel file"); dataReader.IsFirstRowAsColumnNames = isFirstRowAsColumnNames; return dataReader; } } private static DataSet GetExcelDataAsDataSet(string path, bool isFirstRowAsColumnNames) { return GetExcelDataReader(path, isFirstRowAsColumnNames).AsDataSet(); } private static DataTable GetExcelWorkSheet(string path, string workSheetName, bool isFirstRowAsColumnNames) { DataTable workSheet = GetExcelDataAsDataSet(path, isFirstRowAsColumnNames).Tables[workSheetName]; if (workSheet == null) throw new WorksheetDoesNotExistException(string.Format("The worksheet {0} does not exist, has an incorrect name, or does not have any data in the worksheet", workSheetName)); return workSheet; } private static IEnumerable<DataRow> GetData(string path, string workSheetName, bool isFirstRowAsColumnNames = true) { return from DataRow row in GetExcelWorkSheet(path, workSheetName, isFirstRowAsColumnNames).Rows select row; }
Kuten kommenteissa mainittu, et ota huomioon kaikkia Excel-tiedostotyyppejä.
Jos bool isFirstRowAsColumnNames
ei olisi ”t”, suosittelen, että jatkat eteenpäin koko OOP-jutun kanssa, ja työkirja on ladattava, kun rakennat sen, ja hyödynnä OO-suunnittelua hyödyntämällä todella sisäisiä tietoja polun lisäksi. Minusta on hienoa, että annat heille mahdollisuuden määrittää isFirstRowAsColumnNames
, se voi olla erittäin kätevä.
Kommentit
- Ensinnäkin, kiitos paljon panoksestasi. Yhdessä siihen, mitä koodissani voitaisiin parantaa. Minulla on kuitenkin pari kysymystä; ensimmäinen on se, että en ’ ole varma, mitä tarkoitit ” Ja ensimmäisen luokan ReadInDatan tekisin vain menetelmän alkuperäisessä ohjelmassa. Toinen asia on, että en ’ ole myöskään varma siitä, mitä tarkoitit viimeisen kappaleen ensimmäisellä virkkeellä.Tarkoitatko, että jos minulla ’ ei olisi isFirstRowAsColumnNames-parametria, voisin pitää koodini enemmän tai vähemmän samalla tavalla kuin se oli? Jos on, niin miksi se riippuu siitä?
".xlsm"
ja".xlsb"
sekä mallityypit …