Objetivo:

Desejo importar um arquivo Excel e ler as linhas de certas colunas. Para isso, uso ExcelDataReader. Eu implementei uma classe de baixo nível chamada ExcelData que usa o ExcelDataReader e faz coisas como descobrir se é um “.xls “of” .xslx “(ou talvez algo completamente não relacionado!) etc. Além dessa classe, criei uma classe ReadInData, que obterá as colunas específicas que desejo do Excel planilha.

Principais preocupações:

  • A lista de capturas em meu programa principal
  • Lançamento de exceções
  • A construção geral do código e da qualidade do código
  • Devo encapsular minha ExcelData classe em ReadInData ou mantê-la como está ?
  • Passagem do parâmetro isFirstRowAsColumnNames

Como este é o código de uma empresa, mudei os nomes de algumas das classes, então eu sei que eles “não são os melhores dos nomes.

O ponto de entrada do meu 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(); } } 

Neste código Eu crio uma nova classe ReadInData, para a qual eu passo s o caminho, o nome do arquivo e o nome da planilha do Excel que desejo ler.

Preocupação aqui: é possível passar essas coisas dentro desse arquivo?

A ReadInData classe:

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(); } } 

Basicamente, imaginei que precisava de uma classe além da ExcelData classe, porque parecia um nível um tanto superior.

A ExcelData classe:

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; } } 

E, finalmente, a Recipient classe (que não faz nada de especial):

namespace ConsoleApplicationForTestingPurposes { public class Recipient { public string Municipality { get; set; } public string Sexe { get; set; } public string LivingArea { get; set; } } } 

As classes de exceção herdam de Exception e apenas passam uma mensagem para Exception.

Comentários

  • Este código não funciona com todos os arquivos Excel … Lembre-se de que também existem outras terminações do arquivo EXCEL. ".xlsm" e ".xlsb", bem como os tipos de modelo …
  • @ Vogel612 Obrigado, irei aceitar em conta!
  • Isso foi útil. Tínhamos código que usava apenas o OpenXml, o que gerou uma exceção na primeira vez que alguém tentou usar .xls (anos após a primeira implementação). Então a primeira tentativa de consertá-lo não ‘ funcionou, porque ele tentou o leitor binário depois que o primeiro leitor era inválido, mas isso arruinou o fluxo. Verificar a extensão primeiro corrigiu.

Resposta

Simplifique seu catch-chain

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); } 

Não vejo razão para ReadInData ser uma classe não estática. Você não está aproveitando o fato de que está se lembrando do caminho e do nome da planilha e não está mantendo algum tipo de conexão aberta com a pasta de trabalho. E sempre fique à vontade para tornar seu código mais complexo removendo variáveis que são apenas sendo usado uma vez (opcional). Não há razão para ToList() isso, porque você está retornando um IEnumerable<T> de qualquer maneira.

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() }); } } 

Você pode fazer a mesma coisa para sua ExcelData classe, isto é … fazer essas funções em vez de métodos em uma aula. Você não precisa, mas, novamente, você não está aproveitando muito o OO, então é necessário que você faça isso OO.


Se você não deseja / precisa de alguém usando sua ExcelData classe, do que encapsulá-la. No entanto, sinto que em algum momento você desejará reutilizar este leitor do Excel, caso em que moveria esses métodos para um ExcelHelperClass Estático. E sua primeira aula ReadInData Eu faria apenas um método em seu 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 nos comentários, você não está contabilizando todos os tipos de arquivos do Excel.


Se não fosse pelo bool isFirstRowAsColumnNames, sugiro que você realmente vá em frente com toda a coisa OOP, e ter a pasta de trabalho carregada ao construí-la, e aproveitar as vantagens do design OO, tendo dados internos além de apenas o caminho. Acho que não há problema em você dar a eles a opção de especificar isFirstRowAsColumnNames, isso pode ser muito útil.

Comentários

  • Em primeiro lugar, muito obrigado pelo seu tempo em colocar juntos o que poderia ser melhorado em meu código. No entanto, tenho algumas perguntas; a primeira é que ‘ não tenho certeza do que você quis dizer com ” E sua primeira classe ReadInData, eu faria apenas um método em seu programa original. A segunda coisa é que eu ‘ também não tenho certeza do que você quis dizer com a primeira frase do último parágrafo.Você está dizendo que se eu não ‘ tivesse o parâmetro isFirstRowAsColumnNames, poderia manter meu código mais ou menos do jeito que estava? Se sim, por que dependeria disso?

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *