Obiectiv:
Vreau să import un fișier Excel și să citesc rândurile anumitor coloane. Pentru aceasta, folosesc ExcelDataReader
. Am „implementat o clasă de nivel scăzut numită ExcelData
care folosește ExcelDataReader
și face lucruri precum să aflu dacă este un .xls fișierul „din„ .xslx ”(sau poate ceva complet lipsit de legătură!) etc. Pe lângă clasa respectivă am făcut o clasă ReadInData
, care va primi coloanele specifice pe care le doresc din Excel foaie.
Preocupări majore:
- Lista capturilor din programul meu principal
- Aruncarea excepțiilor
- Construcția generală codului și calității codului
- Ar trebui să încapsulez clasa mea
ExcelData
înReadInData
sau să o păstrez așa cum este ? - Trecerea parametrului
isFirstRowAsColumnNames
Deoarece acesta este cod pentru o companie, am schimbat numele unui cuplu de clase, așa că știu că nu sunt cele mai bune nume.
Punctul de intrare al codului meu:
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(); } }
În acest cod creez o nouă clasă ReadInData
, la care trec este calea, numele fișierului și numele foii Excel pe care vreau să o citesc.
Îngrijorare aici: este bine să treceți acele lucruri în acel fișier?
Clasa ReadInData
:
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(); } }
Practic, am crezut că am nevoie de o clasă deasupra ExcelData
, deoarece părea un nivel ceva mai ridicat.
Clasa ExcelData
:
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; } }
Și în cele din urmă, clasa Recipient
(care nu face nimic special):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Clasele de excepție moștenesc de la Exception
și doar transmit un mesaj către Exception.
Comentarii
Răspuns
Simplificați lanțul de captură
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); }
Nu văd niciun motiv pentru ca ReadInData
să fie o clasă nestatică. Nu profitați de faptul că vă amintiți calea și numele foii de lucru și nu păstrați un fel de conexiune deschisă la registrul de lucru. Și nu ezitați să faceți codul să arate mai complex, eliminând variabilele care sunt doar utilizându-se o dată (opțional). Nu există niciun motiv pentru a ToList()
acest lucru, deoarece oricum întoarceți un 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() }); } }
Puteți face același lucru pentru clasa dvs. ExcelData
, adică realizarea acestor funcții în loc de metode pe o clasă. Nu trebuie, dar din nou, nu profitați mult de OO, deci este necesar să o faceți OO.
Dacă nu faceți acest lucru doriți / aveți nevoie de cineva care să vă utilizeze clasa ExcelData
, decât să o încapsulați. Cu toate acestea, consider că veți dori la un moment dat să puteți reutiliza acest cititor Excel, caz în care aș muta aceste metode într-un ExcelHelperClass
static. Și prima dvs. clasă ReadInData
aș face doar o metodă în programul dvs. original.
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; }
As menționat în comentarii, nu țineți cont de toate tipurile de fișiere Excel.
Dacă nu ar fi „t pentru bool isFirstRowAsColumnNames
vă sugerez să mergeți mai departe cu întregul lucru OOP și încărcați registrul de lucru atunci când îl construiți și profitați de proiectarea OO având de fapt date interne în afară doar de calea. Cred că este bine că le dați opțiunea de a specifica isFirstRowAsColumnNames
, ar putea fi foarte la îndemână.
Comentarii
- În primul rând, vă mulțumesc foarte mult pentru timpul acordat împreună ce ar putea fi îmbunătățit în legătură cu codul meu. Totuși, am câteva întrebări; prima fiind că ‘ nu știu ce vrei să spui cu ” Și prima ta clasă ReadInData aș face doar o metodă în programul tău original. Al doilea lucru este că eu ‘ nu știu cu adevărat ce ai vrut să spui cu prima teză din ultimul tău paragraf.Spuneți că dacă nu aș avea ‘ parametrul isFirstRowAsColumnNames, aș putea păstra codul meu mai mult sau mai puțin așa cum era? Dacă da, de ce ar depinde de asta?
".xlsm"
și".xlsb"
, precum și tipurile de șabloane …