EPPlus - Excelテーブルを読む

EPPlusを使って、エクセルの表を読み、各カラムの内容をすべて対応するListに格納したいです。テーブルの見出しを認識し、それに基づいてコンテンツを分類したい。

例えば、私のエクセルテーブルが以下のようなものだとします:

Id    Name     Gender
 1    John     Male
 2    Maria    Female
 3    Daniel   Unknown

List`にデータを格納したいのですが、ここで

public class ExcelData
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
}

というように、見出し名で内容を呼び出せるようにしたい。例えば、こうすると

foreach (var data in ThatList)
{
     Console.WriteLine(data.Id + data.Name + data.Gender);
}

このように出力されます:

1JohnMale
2MariaFemale
3DanielUnknown

本当にこれだけしか出ませんでした:

var package = new ExcelPackage(new FileInfo(@"C:\ExcelFile.xlsx"));
ExcelWorksheet sheet = package.Workbook.Worksheets[1];

var table = sheet.Tables.First();

table.Columns.Something //I guess I can use this to do what I want

助けてください :( この件に関して、サンプルコードから学ぼうと思い、長い時間をかけて検索したのですが、無駄でした。また、ExcelToLinQはなんとかなっているようですが、テーブルを認識することができないようです。

理由はわかりませんが、上記のソリューションはどれも機能しません。 だから何がうまくいったかを共有します。


public void readXLS(string FilePath)
{
    FileInfo existingFile = new FileInfo(FilePath);
    using (ExcelPackage package = new ExcelPackage(existingFile))
    {
        //get the first worksheet in the workbook
        ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
        int colCount = worksheet.Dimension.End.Column;  //get Column Count
        int rowCount = worksheet.Dimension.End.Row;     //get row count
        for (int row = 1; row 
解説 (3)
ソリューション

ネイティブはいないけど、この記事に載せたものを使ったらどうだろう:

https://stackoverflow.com/questions/33436525/how-to-parse-excel-rows-back-to-types-using-epplus

もし、テーブルだけを対象にしたい場合は、修正する必要があります。 このようなものでよいでしょう:

public static IEnumerable ConvertTableToObjects(this ExcelTable table) where T : new()
{
    //DateTime Conversion
    var convertDateTime = new Func(excelDate =>
    {
        if (excelDate < 1)
            throw new ArgumentException("Excel dates cannot be smaller than 0.");

        var dateOfReference = new DateTime(1900, 1, 1);

        if (excelDate > 60d)
            excelDate = excelDate - 2;
        else
            excelDate = excelDate - 1;
        return dateOfReference.AddDays(excelDate);
    });

    //Get the properties of T
    var tprops = (new T())
        .GetType()
        .GetProperties()
        .ToList();

    //Get the cells based on the table address
    var start = table.Address.Start;
    var end = table.Address.End;
    var cells = new List();

    //Have to use for loops insteadof worksheet.Cells to protect against empties
    for (var r = start.Row; r  rcell.Value.GetType())
        .ToList();

    //Assume first row has the column names
    var colnames = groups
        .First()
        .Select((hcell, idx) => new { Name = hcell.Value.ToString(), index = idx })
        .Where(o => tprops.Select(p => p.Name).Contains(o.Name))
        .ToList();

    //Everything after the header is data
    var rowvalues = groups
        .Skip(1) //Exclude header
        .Select(cg => cg.Select(c => c.Value).ToList());

    //Create the collection container
    var collection = rowvalues
        .Select(row =>
        {
            var tnew = new T();
            colnames.ForEach(colname =>
            {
                //This is the real wrinkle to using reflection - Excel stores all numbers as double including int
                var val = row[colname.index];
                var type = types[colname.index];
                var prop = tprops.First(p => p.Name == colname.Name);

                //If it is numeric it is a double since that is how excel stores all numbers
                if (type == typeof(double))
                {
                    if (!string.IsNullOrWhiteSpace(val?.ToString()))
                    {
                        //Unbox it
                        var unboxedVal = (double)val;

                        //FAR FROM A COMPLETE LIST!!!
                        if (prop.PropertyType == typeof(Int32))
                            prop.SetValue(tnew, (int)unboxedVal);
                        else if (prop.PropertyType == typeof(double))
                            prop.SetValue(tnew, unboxedVal);
                        else if (prop.PropertyType == typeof(DateTime))
                            prop.SetValue(tnew, convertDateTime(unboxedVal));
                        else
                            throw new NotImplementedException(String.Format("Type '{0}' not implemented yet!", prop.PropertyType.Name));
                    }
                }
                else
                {
                    //Its a string
                    prop.SetValue(tnew, val);
                }
            });

            return tnew;
        });

    //Send it back
    return collection;
}

テスト方法はこちらです:

[TestMethod]
public void Table_To_Object_Test()
{
    //Create a test file
    var fi = new FileInfo(@"c:\temp\Table_To_Object.xlsx");

    using (var package = new ExcelPackage(fi))
    {
        var workbook = package.Workbook;
        var worksheet = workbook.Worksheets.First();
        var ThatList = worksheet.Tables.First().ConvertTableToObjects();
        foreach (var data in ThatList)
        {
            Console.WriteLine(data.Id + data.Name + data.Gender);
        }

        package.Save();
    }
}

コンソールにこう表示されました:

1JohnMale
2MariaFemale
3DanielUnknown

Idフィールドが数値や文字列の場合は、クラスが文字列を想定しているため、注意が必要です。

解説 (5)

これは、私の作業バージョンです。リゾルバのコードは表示されていませんが、私の実装をアレンジしたもので、各ワークシートで列の名前が微妙に異なっていても解決できるようにしています。


public static IEnumerable ToArray(this ExcelWorksheet worksheet, List resolvers) where T : new()
{

  // List of all the column names
  var header = worksheet.Cells.GroupBy(cell => cell.Start.Row).First();

  // Get the properties from the type your are populating
  var properties = typeof(T).GetProperties().ToList();

  var start = worksheet.Dimension.Start;
  var end = worksheet.Dimension.End;

  // Resulting list
  var list = new List();

  // Iterate the rows starting at row 2 (ie start.Row + 1)
  for (int row = start.Row + 1; row 
解説 (1)

以下のコードは、エクセルデータをデータテーブルに読み込んで、データローリストに変換します。

if (FileUpload1.HasFile)
{
    if (Path.GetExtension(FileUpload1.FileName) == ".xlsx")
    {
        Stream fs = FileUpload1.FileContent;
        ExcelPackage package = new ExcelPackage(fs);
        DataTable dt = new DataTable();
        dt= package.ToDataTable();
        List listOfRows = new List();
        listOfRows = dt.AsEnumerable().ToList();

    }
}
解説 (1)

最初の回答にエラーが表示されたので、コード行を変更しました。

新しいコードを試してみてください。

using OfficeOpenXml;
using OfficeOpenXml.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public static class ImportExcelReader
{
    public static List ImportExcelToList(this ExcelWorksheet worksheet) where T : new()
    {
        //DateTime Conversion
        Func convertDateTime = new Func(excelDate =>
        {
            if (excelDate < 1)
            {
                throw new ArgumentException("Excel dates cannot be smaller than 0.");
            }

            DateTime dateOfReference = new DateTime(1900, 1, 1);

            if (excelDate > 60d)
            {
                excelDate = excelDate - 2;
            }
            else
            {
                excelDate = excelDate - 1;
            }

            return dateOfReference.AddDays(excelDate);
        });

        ExcelTable table = null;

        if (worksheet.Tables.Any())
        {
            table = worksheet.Tables.FirstOrDefault();
        }
        else
        {
            table = worksheet.Tables.Add(worksheet.Dimension, "tbl" + ShortGuid.NewGuid().ToString());

            ExcelAddressBase newaddy = new ExcelAddressBase(table.Address.Start.Row, table.Address.Start.Column, table.Address.End.Row + 1, table.Address.End.Column);

            //Edit the raw XML by searching for all references to the old address
            table.TableXml.InnerXml = table.TableXml.InnerXml.Replace(table.Address.ToString(), newaddy.ToString());
        }

        //Get the cells based on the table address
        List groups = table.WorkSheet.Cells[table.Address.Start.Row, table.Address.Start.Column, table.Address.End.Row, table.Address.End.Column]
            .GroupBy(cell => cell.Start.Row)
            .ToList();

        //Assume the second row represents column data types (big assumption!)
        List types = groups.Skip(1).FirstOrDefault().Select(rcell => rcell.Value.GetType()).ToList();

        //Get the properties of T
        List modelProperties = new T().GetType().GetProperties().ToList();

        //Assume first row has the column names
        var colnames = groups.FirstOrDefault()
            .Select((hcell, idx) => new
            {
                Name = hcell.Value.ToString(),
                index = idx
            })
            .Where(o => modelProperties.Select(p => p.Name).Contains(o.Name))
            .ToList();

        //Everything after the header is data
        List rowvalues = groups
            .Skip(1) //Exclude header
            .Select(cg => cg.Select(c => c.Value).ToList()).ToList();

        //Create the collection container
        List collection = new List();
        foreach (List row in rowvalues)
        {
            T tnew = new T();
            foreach (var colname in colnames)
            {
                //This is the real wrinkle to using reflection - Excel stores all numbers as double including int
                object val = row[colname.index];
                Type type = types[colname.index];
                PropertyInfo prop = modelProperties.FirstOrDefault(p => p.Name == colname.Name);

                //If it is numeric it is a double since that is how excel stores all numbers
                if (type == typeof(double))
                {
                    //Unbox it
                    double unboxedVal = (double)val;

                    //FAR FROM A COMPLETE LIST!!!
                    if (prop.PropertyType == typeof(int))
                    {
                        prop.SetValue(tnew, (int)unboxedVal);
                    }
                    else if (prop.PropertyType == typeof(double))
                    {
                        prop.SetValue(tnew, unboxedVal);
                    }
                    else if (prop.PropertyType == typeof(DateTime))
                    {
                        prop.SetValue(tnew, convertDateTime(unboxedVal));
                    }
                    else if (prop.PropertyType == typeof(string))
                    {
                        prop.SetValue(tnew, val.ToString());
                    }
                    else
                    {
                        throw new NotImplementedException(string.Format("Type '{0}' not implemented yet!", prop.PropertyType.Name));
                    }
                }
                else
                {
                    //Its a string
                    prop.SetValue(tnew, val);
                }
            }
            collection.Add(tnew);
        }

        return collection;
    }
}

この関数を呼び出す方法? 以下のコードを表示してください。

private List GetStudentsFromExcel(HttpPostedFileBase file)
    {
        List list = new List();
        if (file != null)
        {
            try
            {
                using (ExcelPackage package = new ExcelPackage(file.InputStream))
                {
                    ExcelWorkbook workbook = package.Workbook;
                    if (workbook != null)
                    {
                        ExcelWorksheet worksheet = workbook.Worksheets.FirstOrDefault();
                        if (worksheet != null)
                        {
                            list = worksheet.ImportExcelToList();
                        }
                    }
                }
            }
            catch (Exception err)
            {
                //save error log
            }
        }
        return list;
    }

FundraiserStudentListModel here:

 public class FundraiserStudentListModel
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
}
解説 (0)