Mål:
Jeg vil importere en Excel-fil, og lese radene i visse kolonner. Til dette bruker jeg ExcelDataReader
. Jeg har implementert en klasse på lavt nivå kalt ExcelData
som bruker ExcelDataReader
og gjør ting som å finne ut om det er en .xls «of» .xslx «-fil (eller kanskje noe helt uten tilknytning!) osv. På toppen av den klassen laget jeg en ReadInData
-klasse, som får de spesifikke kolonnene jeg vil ha fra Excel ark.
Store bekymringer:
- Fangslisten i hovedprogrammet mitt
- Kaster unntak
- Den samlede konstruksjonen av koden og kodekvaliteten
- Skal jeg kapsle
ExcelData
-klassen iReadInData
, eller beholde den slik den er ? - Overføring av
isFirstRowAsColumnNames
parameter
Fordi dette er kode for et selskap, endret jeg navnene på et par av klasser, så jeg vet at de ikke er de beste navnene.
Inngangspunktet til koden min:
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 oppretter en ny ReadInData
klasse, som jeg passerer s banen, navnet på filen og navnet på Excel-arket som jeg vil lese.
Bekymring her: er det greit å sende disse tingene i den filen?
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(); } }
I utgangspunktet skjønte jeg at jeg trengte en klasse på toppen av ExcelData
klasse, fordi den virket noe høyere nivå.
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 slutt, Recipient
-klassen (som ikke gjør noe spesielt):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Unntaksklassene arver fra Exception
, og sender bare en melding til Unntak.
Kommentarer
- Denne koden fungerer ikke med alle Excel-filer … Husk at det også er noen andre EXCEL-filavslutninger.
".xlsm"
og".xlsb"
, samt maltypene … - @ Vogel612 Takk, jeg tar det i betraktning!
- Dette var nyttig. Vi hadde kode som nettopp brukte OpenXml-en, noe som ga et unntak første gang noen prøvde å bruke .xls (år etter første implementering). Da fungerte det første forsøket på å fikse det ‘, fordi det bare prøvde den binære leseren etter at den første leseren var ugyldig, men det ødela strømmen. Sjekker utvidelsen først fikset det.
Svar
Forenkle fangstkjeden
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 grunn til å ha ReadInData
være en ikke-statisk klasse. Du utnytter ikke det faktum at du husker banen og navnet på regnearket, og du beholder ikke en slags åpen forbindelse til arbeidsboken. Og gjør alltid koden din mer kompleks ved å fjerne variabler som bare er brukes en gang (valgfritt). Det er ingen grunn til å ToList()
dette, fordi du uansett 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 gjøre det samme for ExcelData
-klassen, det vil si .. lage disse funksjonene i stedet for metoder på en klasse. Du trenger ikke, men igjen, du drar ikke mye nytte av OO, så det er behov for deg å gjøre det OO.
Hvis du ikke ønsker / trenger noen som bruker ExcelData
-klassen, enn å kapsle den inn. Jeg føler imidlertid at du på et eller annet tidspunkt vil kunne bruke denne excel-leseren på nytt, i så fall vil jeg flytte disse metodene til en statisk ExcelHelperClass
. Og din første klasse ReadInData
Jeg vil bare lage en metode i det opprinnelige programmet.
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 nevnt i kommentarene, regner du ikke med alle Excel-filtyper.
Hvis det ikke var «t for bool isFirstRowAsColumnNames
, vil jeg foreslå at du faktisk går videre med hele OOP-tingen, og ha arbeidsboklastingen når du konstruerer den, og dra nytte av OO-design ved å faktisk ha interne data i tillegg til bare stien. Jeg synes det er greit at du gir dem muligheten til å spesifisere isFirstRowAsColumnNames
, det kan være veldig nyttig.
Kommentarer
- Først og fremst, tusen takk for din tid med å sette sammen hva som kan forbedres med koden min. Jeg har imidlertid et par spørsmål; den første er at jeg ‘ ikke er sikker på hva du mente med » Og din første klasse ReadInData, jeg vil bare lage en metode i det opprinnelige programmet. Den andre tingen er at jeg ‘ er heller ikke helt sikker på hva du mente med første setning i ditt siste avsnitt.Sier du at hvis jeg ikke ville ha ‘ parameteren isFirstRowAsColumnNames, kunne jeg beholde koden min mer eller mindre slik den var? I så fall, hvorfor ville det avhenge av det?