Daten aus Excel-Tabellen mit ExcelDataReader lesen

96301
user3488442

Zielsetzung:

Ich möchte eine Excel-Datei importieren und die Zeilen bestimmter Spalten lesen. Dafür benutze ich ExcelDataReader. Ich habe eine Low-Level-Klasse namens ExcelDatathe implementiert, die das verwendet ExcelDataReaderund Dinge tut, um herauszufinden, ob es sich um ".xls" -Dateien (.xslx) handelt (oder vielleicht etwas völlig Unverändertes!) Usw. Zusätzlich zu dieser Klasse habe ich gemacht eine ReadInDataKlasse, die aus der Excel-Tabelle die gewünschten Spalten erhält.

Hauptanliegen:

  • Die Liste der Fänge in meinem Hauptprogramm
  • Ausnahmen werfen
  • Die Gesamtkonstruktion des Codes und der Codequalität
  • Soll ich meine ExcelDataKlasse in sich kapseln ReadInDataoder so lassen, wie sie ist?
  • Übergabe des isFirstRowAsColumnNamesParameters

Da dies ein Code für ein Unternehmen ist, habe ich die Namen einiger Klassen geändert, daher weiß ich, dass sie nicht die besten Namen sind.

Der Einstiegspunkt meines Codes:

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 diesem Code erstelle ich eine neue ReadInDataKlasse, an die ich den Pfad, den Namen der Datei und den Namen der Excel-Tabelle weitergebe, die ich lesen möchte.

Sorge hier: Ist es in Ordnung, diese Dinge in dieser Datei zu übergeben?

Die ReadInDataKlasse:

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

Grundsätzlich dachte ich, dass ich eine Klasse zusätzlich zu der ExcelDataKlasse brauchte, weil sie etwas höheres Niveau schien.

Die ExcelDataKlasse:

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

Und zum Schluss die RecipientKlasse (die nichts Besonderes macht):

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

Die Ausnahmeklassen erben von ExceptionException und übergeben diese einfach an Exception.

Antworten
14
Dieser Code funktioniert nicht mit allen Excel-Dateien ... Beachten Sie, dass es auch andere EXCEL-Datei-Endungen gibt. `" .xlsm "` und `" .xlsb "` sowie die Vorlagentypen ... Vogel612 vor 6 Jahren 3
@ Vogel612 Danke, ich werde es berücksichtigen! user3488442 vor 6 Jahren 0
Das war hilfreich. Wir hatten Code, der gerade den OpenXml-Code verwendete, der eine Ausnahme auslöste, als jemand zum ersten Mal .xls versuchte (Jahre nach der ersten Implementierung). Der erste Versuch, das Problem zu beheben, funktionierte nicht, da der Binärleser nach dem ersten ungültig wurde, der Stream jedoch ruiniert wurde. Durch Überprüfen der Erweiterung wurde sie zuerst behoben. DCShannon vor 4 Jahren 0

1 Antwort auf die Frage

8
BenVlodgi

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

I see no reason to have ReadInData be a non-static class. You are not taking advantage of the fact that you are remembering the path and worksheet name, and you aren't keeping some sort of open connection to the workbook. And always feel free to make your code look more complex by removing variables that are only being used once (optional). There is no reason to ToList() this, because you are returning an IEnumerable<T> anyway.

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

You can do this same thing for your ExcelData class, that is.. making these functions instead of methods on a class. You don't have to, but again, you aren't taking much advantage of the OO, so there is need for you to make it OO.


If you don't want/need someone using your ExcelData class, than encapsulate it. However I feel that you will at some point want to be able to re-use this excel reader, in which case I would move these methods into a Static ExcelHelperClass. And your first class ReadInData I would just make a method in your original program.

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

As mentioned in the comments, you are not accounting for all Excel filetypes.


If it wasn't for the bool isFirstRowAsColumnNames I would suggest you actually go forward with the whole OOP thing, and have the workbook load when you construct it, and take advantage of OO design by actually having internal data besides just the path. I do think its is fine that you give them the option to specify isFirstRowAsColumnNames, that could be very handy.

Zunächst einmal vielen Dank für Ihre Zeit bei der Zusammenstellung, was an meinem Code verbessert werden könnte. Ich habe jedoch ein paar Fragen. Der erste ist, dass ich nicht sicher bin, was Sie mit "Und Ihre erste Klasse ReadInData" meinen. Ich würde einfach eine Methode in Ihrem ursprünglichen Programm erstellen. Zweitens bin ich auch nicht wirklich sicher, was Sie mit dem ersten Satz Ihres letzten Absatzes meinten. Wollen Sie damit sagen, wenn ich den Parameter isFirstRowAsColumnNames nicht hätte, könnte ich meinen Code mehr oder weniger so beibehalten, wie er war? Wenn ja, warum sollte es davon abhängen? user3488442 vor 6 Jahren 0