Mål:
Jag vill importera en Excel-fil och läsa raderna i vissa kolumner. För detta använder jag ExcelDataReader
. Jag har implementerat en lågnivåklass som heter ExcelData
som använder ExcelDataReader
och gör saker som att ta reda på om det är ett ”.xls ”of” .xslx ”-fil (eller kanske något helt orelaterat!) etc. På toppen av den klassen skapade jag en ReadInData
-klass, som får de specifika kolumner jag vill ha från Excel blad.
Största problem:
- Fångstlistan i mitt huvudprogram
- Kasta undantag
- Den övergripande konstruktionen av koden och kodkvaliteten
- Ska jag kapsla in min
ExcelData
-klass inomReadInData
, eller behålla den som den är ? - Vidarebefordran av
isFirstRowAsColumnNames
-parametern
Eftersom detta är kod för ett företag ändrade jag namnen på ett par av klasser, så jag vet att de inte är de bästa namnen.
Ingångspunkten för min kod:
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 detta kod Jag skapar en ny ReadInData
-klass, som jag passerar s sökvägen, namnet på filen och namnet på Excel-arket som jag vill läsa.
Oro här: är det okej att skicka dessa saker i 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 grund och botten tänkte jag att jag behövde en klass ovanpå ExcelData
-klass, eftersom det verkade något högre.
ExcelData
-klassen:
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; } }
Och slutligen Recipient
-klassen (som inte gör något speciellt):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Undantagsklasserna ärver från Exception
och skickar bara ett meddelande till Undantag.
Kommentarer
- Den här koden fungerar inte med alla Excel-filer … Tänk på att det också finns några andra EXCEL-filändelser.
".xlsm"
och".xlsb"
, samt malltyper … - @ Vogel612 Tack, jag tar det med i beräkningen!
- Det var till hjälp. Vi hade kod som just använde OpenXml-en, vilket gav ett undantag första gången någon försökte använda .xls (år efter första implementeringen). Då fungerade det första försöket att fixa det ’, eftersom det bara försökte den binära läsaren efter att den första läsaren var ogiltig, men det förstörde strömmen. Kontrollerar först att tillägget fixade det.
Svar
Förenkla din fångkedja
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); }
Jag ser ingen anledning att ReadInData
vara en icke-statisk klass. Du utnyttjar inte det faktum att du kommer ihåg sökvägen och kalkylbladets namn, och du behåller inte någon form av öppen anslutning till arbetsboken. Och känn dig alltid fri att göra din kod mer komplex genom att ta bort variabler som bara är används en gång (valfritt). Det finns ingen anledning att ToList()
detta, eftersom du ändå returnerar 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öra samma sak för din ExcelData
-klass, det vill säga .. göra dessa funktioner istället för metoder på en klass. Du behöver inte, men igen, du tar inte mycket nytta av OO, så det finns behov av att du gör det till OO.
Om du inte vill / behöver någon som använder klassen ExcelData
än att kapsla in den. Men jag känner att du någon gång vill kunna återanvända denna excel-läsare, i vilket fall jag skulle flytta dessa metoder till en statisk ExcelHelperClass
. Och din första klass ReadInData
Jag skulle bara göra en metod i ditt ursprungliga 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 som nämns i kommentarerna redovisar du inte alla Excel-filtyper.
Om det inte var ”t för bool isFirstRowAsColumnNames
föreslår jag att du faktiskt går vidare med hela OOP-saken, och ha arbetsboken laddad när du konstruerar den, och dra nytta av OO-design genom att faktiskt ha interna data förutom bara vägen. Jag tycker att det är bra att du ger dem möjlighet att specificera isFirstRowAsColumnNames
, det kan vara väldigt praktiskt.
Kommentarer
- Först och främst tack så mycket för din tid att sätta tillsammans vad som kan förbättras med min kod. Jag har dock ett par frågor; den första var att jag ’ inte vet vad du menade med ” Och din första klass ReadInData Jag skulle bara göra en metod i ditt ursprungliga program. Det andra är att jag ’ inte heller är riktigt säker på vad du menade med den första meningen i ditt sista stycke.Säger du att om jag inte ’ inte skulle ha parametern isFirstRowAsColumnNames, kunde jag behålla min kod mer eller mindre som den var? Om så är fallet, varför skulle det bero på det?