Objetivo:
Quiero importar un archivo de Excel y leer las filas de determinadas columnas. Para esto, utilizo ExcelDataReader
. He implementado una clase de bajo nivel llamada ExcelData
que usa ExcelDataReader
y hace cosas como averiguar si es un «.xls «del archivo» .xslx «(¡o tal vez algo completamente no relacionado!) etc. Además de esa clase, hice una clase ReadInData
, que obtendrá las columnas específicas que quiero de Excel hoja.
Principales preocupaciones:
- La lista de capturas en mi programa principal
- Lanzamiento de excepciones
- La construcción general del código y la calidad del código
- ¿Debo encapsular mi clase
ExcelData
dentro deReadInData
, o mantenerlo como está ? - Pasando el
isFirstRowAsColumnNames
parámetro
Debido a que este es el código para una empresa, cambié los nombres de un par de clases, así que sé que no son los mejores nombres.
El punto de entrada de mi código:
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(); } }
En este código creo una nueva clase ReadInData
, a la que paso s la ruta, el nombre del archivo y el nombre de la hoja de Excel que quiero leer.
Preocupación aquí: ¿está bien pasar esas cosas dentro de ese archivo?
La ReadInData
clase:
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(); } }
Básicamente, pensé que necesitaba una clase además del ExcelData
clase, porque parecía un nivel algo más alto.
La clase 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; } }
Y finalmente, la clase Recipient
(que no hace nada especial):
namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } }
Las clases de excepción heredan de Exception
, y simplemente pasan un mensaje a Exception.
Comentarios
Responder
Simplifique su cadena de captura
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); }
No veo ninguna razón para que ReadInData
sea una clase no estática. No está aprovechando el hecho de que está recordando la ruta y el nombre de la hoja de trabajo, y no está manteniendo algún tipo de conexión abierta con el libro de trabajo. Y siempre siéntase libre de hacer que su código parezca más complejo eliminando variables que solo son se está utilizando una vez (opcional). No hay razón para ToList()
esto, porque está devolviendo un IEnumerable<T>
de todos modos.
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() }); } }
Puede hacer lo mismo para su clase ExcelData
, es decir … hacer estas funciones en lugar de métodos en una clase. No tienes que hacerlo, pero de nuevo, no estás aprovechando mucho el OO, por lo que es necesario que lo conviertas en OO.
Si no quiere / necesita que alguien use su clase ExcelData
, que la encapsule. Sin embargo, creo que en algún momento querrá poder reutilizar este lector de Excel, en cuyo caso movería estos métodos a un ExcelHelperClass
estático. Y su primera clase ReadInData
simplemente haría un método en su programa original.
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; }
Como mencionado en los comentarios, no está contabilizando todos los tipos de archivo de Excel.
Si no fuera por el bool isFirstRowAsColumnNames
, le sugiero que siga adelante con todo el asunto de OOP, y hacer que se cargue el libro de trabajo cuando lo construya, y aproveche el diseño de OO al tener datos internos además de la ruta. Creo que está bien que les dé la opción de especificar isFirstRowAsColumnNames
, eso podría ser muy útil.
Comentarios
- Primero que nada, muchas gracias por su tiempo en poner juntos qué se podría mejorar sobre mi código. Sin embargo, tengo un par de preguntas; la primera es que ‘ no estoy seguro de lo que quisiste decir con » Y su primera clase ReadInData, simplemente crearía un método en su programa original. La segunda cosa es que yo ‘ tampoco estoy muy seguro de lo que quiso decir con la primera oración de su último párrafo.¿Está diciendo que si no ‘ no tuviera el parámetro isFirstRowAsColumnNames, podría mantener mi código más o menos como estaba? Si es así, ¿por qué dependería de eso?
".xlsm"
y".xlsb"
, así como los tipos de plantilla …