Cel:
Chcę zaimportować plik Excela i odczytać wiersze w niektórych kolumnach. W tym celu używam ExcelDataReader
. Zaimplementowałem klasę niskiego poziomu o nazwie ExcelData
, która używa elementu ExcelDataReader
i wykonuje takie czynności, jak sprawdzanie, czy jest to plik „.xls” Plik „z” .xslx „(a może coś zupełnie niezwiązanego!) itp. Oprócz tej klasy utworzyłem klasę ReadInData
, która będzie pobierać określone kolumny z Excela arkuszu.
Główne obawy:
- Lista połowów w moim głównym programie
- Zgłaszanie wyjątków
- Ogólna konstrukcja kodu i jakości kodu
- Czy powinienem hermetyzować moją klasę
ExcelData
wReadInData
, czy tak, jak jest ? - Przekazanie parametru
isFirstRowAsColumnNames
Ponieważ jest to kod dla firmy, zmieniłem nazwiska kilku klas, więc wiem, że nie są to najlepsze nazwy.
Punkt wejścia mojego kodu:
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(); } }
W tym kod tworzę nową klasę ReadInData
do której pasuję to ścieżka, nazwa pliku i nazwa arkusza Excela, który chcę przeczytać.
Zastanawiam się: czy można przekazać te informacje w tym pliku?
Klasa 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(); } }
Wydaje mi się, że potrzebuję klasy oprócz ExcelData
, ponieważ wydawał się nieco wyższy poziom.
Klasa 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 wreszcie klasa Recipient
(która nie robi nic specjalnego):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Klasy wyjątków dziedziczą po Exception
i po prostu przekazują wiadomość do wyjątku.
Komentarze
Odpowiedź
Uprość łańcuch przechwytywania
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); }
Nie widzę powodu, aby ReadInData
był klasą niestatyczną. Nie wykorzystujesz faktu, że pamiętasz ścieżkę i nazwę arkusza oraz nie utrzymujesz jakiegoś otwartego połączenia ze skoroszytem. I zawsze możesz sprawić, że kod będzie wyglądał bardziej złożonym, usuwając zmienne, które są tylko używane raz (opcjonalnie). Nie ma powodu, aby ToList()
to robić, ponieważ i tak zwracasz 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() }); } }
Możesz zrobić to samo dla swojej klasy ExcelData
, czyli … włączyć te funkcje zamiast metod klasy. Nie musisz, ale znowu nie wykorzystujesz zbyt wiele z OO, więc musisz zrobić to OO.
Jeśli nie chcesz / potrzebujesz kogoś używającego Twojej klasy ExcelData
, niż ją hermetyzować. Jednak wydaje mi się, że w pewnym momencie będziesz chciał móc ponownie użyć tego czytnika programu Excel. W takim przypadku przeniósłbym te metody do statycznego ExcelHelperClass
. Twoja pierwsza klasa ReadInData
Po prostu utworzyłbym metodę w Twoim oryginalnym programie.
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; }
Jak wspomniano w komentarzach, nie bierzesz pod uwagę wszystkich typów plików programu Excel.
Gdyby nie dotyczyło to bool isFirstRowAsColumnNames
, sugerowałbym, abyś faktycznie kontynuował z całą sprawą OOP i załaduj skoroszyt podczas konstruowania go i skorzystaj z projektu OO, mając w rzeczywistości dane wewnętrzne oprócz tylko ścieżki. Myślę, że to w porządku, że dajesz im opcję określenia isFirstRowAsColumnNames
, to może być bardzo przydatne.
Komentarze
- Po pierwsze, bardzo dziękuję za poświęcony czas razem, co można by poprawić w moim kodzie. Mam jednak kilka pytań. Pierwsze z nich dotyczy tego, że ' nie jestem pewien, co masz na myśli, mówiąc o ” A twoja pierwsza klasa ReadInData, po prostu utworzyłbym metodę w twoim oryginalnym programie. Drugą rzeczą jest to, że ' również nie jestem pewien, co masz na myśli w pierwszym zdaniu ostatniego akapitu.Czy twierdzisz, że gdybym nie ' nie miał parametru isFirstRowAsColumnNames, mógłbym zachować swój kod mniej więcej tak, jak był? Jeśli tak, dlaczego miałoby to zależeć?
".xlsm"
i".xlsb"
, a także typy szablonów …