Objective:
I want to import an Excel file, and read the rows of certain columns. For this, I use ExcelDataReader
. I've implemented a low-level class called ExcelData
which uses the ExcelDataReader
and does things like figuring out if it is an ".xls" of ".xslx" file (or maybe something completely unrelated!) etc. On top of that class I made a ReadInData
class, which will get the specific columns I want from the Excel sheet.
Major concerns:
- The list of catches in my main program
- Throwing of exceptions
- The overall construction of the code and code quality
- Should I encapsulate my
ExcelData
class withinReadInData
, or keep it like it is? - Passing of the
isFirstRowAsColumnNames
parameter
Because this is code for a company, I changed the names of a couple of classes, so I know they aren't the best of names.
The entry point of my code:
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();
}
}
In this code I create a new ReadInData
class, to which I pass the path, the name of the file and the name of the Excel sheet that I want to read.
Concern here: is it okay to pass those things within that file?
The ReadInData
class:
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();
}
}
Basically, I figured that I needed a class on top of the ExcelData
class, because it seemed somewhat higher level.
The ExcelData
class:
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;
}
}
And finally, the Recipient
class (which does nothing special):
namespace ConsoleApplicationForTestingPurposes
{
public class Recipient
{
public string Municipality { get; set; }
public string Sexe { get; set; }
public string LivingArea { get; set; }
}
}
The exception classes inherit from Exception
, and just pass a message to Exception.
".xlsm"
and".xlsb"
, as well as the template types... \$\endgroup\$