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 w ReadInData, 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

  • Ten kod nie działa ze wszystkimi plikami Excela … Należy pamiętać, że są też inne zakończenia plików EXCEL. ".xlsm" i ".xlsb", a także typy szablonów …
  • @ Vogel612 Dzięki, wezmę pod uwagę!
  • To było pomocne. Mieliśmy kod, który właśnie używał kodu OpenXml, który wyrzucił wyjątek, gdy ktoś po raz pierwszy próbował użyć .xls (lata po pierwszej implementacji). Wtedy pierwsza próba jego naprawienia nie powiodła się ' nie powiodła się, ponieważ właśnie wypróbował czytnik binarny po tym, jak pierwszy czytnik był nieprawidłowy, ale to zrujnowało strumień. Sprawdzenie rozszerzenia najpierw rozwiązało problem.

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ć?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *