Mål:
Jeg vil importere en Excel-fil og læse rækkerne i bestemte kolonner. Til dette bruger jeg ExcelDataReader
. Jeg har implementeret en klasse på lavt niveau kaldet ExcelData
som bruger ExcelDataReader
og gør ting som at finde ud af om det er et “.xls “af” .xslx “-fil (eller måske noget helt uafhængigt!) osv. Oven på den klasse lavede jeg en ReadInData
-klasse, som får de specifikke kolonner, jeg vil have fra Excel ark.
Største bekymringer:
- Fangslisten i mit hovedprogram
- Kaste af undtagelser
- Den samlede konstruktion af koden og kodekvaliteten
- Skal jeg indkapsle min
ExcelData
klasse inden forReadInData
, eller beholde den som den er ? - Videregivelse af
isFirstRowAsColumnNames
parameter
Fordi dette er kode for en virksomhed, ændrede jeg navnene på et par af klasser, så jeg ved, at de ikke er de bedste navne.
Indgangsstedet for min kode:
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(); } }
I dette kode Jeg opretter en ny ReadInData
klasse, som jeg passerer s stien, navnet på filen og navnet på Excel-arket, som jeg vil læse.
Bekymring her: er det okay at videregive disse ting inden for den fil?
ReadInData
-klassen:
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(); } }
Grundlæggende regnede jeg med, at jeg havde brug for en klasse oven på ExcelData
klasse, fordi det syntes noget højere niveau.
ExcelData
klasse:
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; } }
Og til sidst Recipient
klasse (som ikke gør noget særligt):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Undtagelsesklasser arver fra Exception
, og send bare en meddelelse til Undtagelse.
Kommentarer
Svar
Forenkl din fangstkæde
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); }
Jeg ser ingen grund til at have ReadInData
til at være en ikke-statisk klasse. Du udnytter ikke det faktum, at du husker stien og regnearknavnet, og du holder ikke en slags åben forbindelse til projektmappen. Og lad altid din kode se mere kompleks ud ved at fjerne variabler, der kun er bruges en gang (valgfrit). Der er ingen grund til at ToList()
dette, fordi du alligevel returnerer en 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() }); } }
Du kan gøre den samme ting til din ExcelData
klasse, dvs. at lave disse funktioner i stedet for metoder på en klasse. Det behøver du ikke, men igen udnytter du ikke meget OO, så der er behov for, at du gør det til OO.
Hvis du ikke ønsker / har brug for nogen, der bruger din ExcelData
klasse, end at indkapsle den. Jeg føler dog, at du på et eller andet tidspunkt vil være i stand til at genbruge denne excel-læser, i hvilket tilfælde jeg vil flytte disse metoder til en statisk ExcelHelperClass
. Og din første klasse ReadInData
Jeg ville bare lave en metode i dit originale program.
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; }
Som nævnt i kommentarerne, regner du ikke med alle Excel-filtyper.
Hvis det ikke var “t for bool isFirstRowAsColumnNames
Jeg vil foreslå, at du rent faktisk går fremad med hele OOP-tingen, og lad arbejdsbogen indlæses, når du konstruerer den, og drage fordel af OO-design ved faktisk at have interne data udover bare stien. Jeg synes, det er fint, at du giver dem mulighed for at specificere isFirstRowAsColumnNames
, det kunne være meget praktisk.
Kommentarer
- Først og fremmest tak for din tid med at sætte sammen hvad der kunne forbedres med min kode. Jeg har dog et par spørgsmål; den første er, at jeg ‘ ikke er sikker på, hvad du mente med ” Og din første klasse ReadInData Jeg ville bare lave en metode i dit originale program. Den anden ting er, at jeg ‘ også ikke rigtig er sikker på, hvad du mente med første sætning i dit sidste afsnit.Siger du, at hvis jeg ikke ville ‘ ikke have isFirstRowAsColumnNames-parameteren, kunne jeg beholde min kode mere eller mindre som den var? Hvis ja, hvorfor ville det afhænge af det?
".xlsm"
og".xlsb"
samt skabelontyperne …