EPPlus - Чтение таблицы Excel

Используя EPPlus, я хочу прочитать таблицу Excel, а затем сохранить все содержимое из каждого столбца в соответствующий List. Я хочу, чтобы он распознавал заголовок таблицы и классифицировал содержимое на его основе.

Например, если моя таблица excel выглядит следующим образом:

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

Я хочу, чтобы данные хранились в List<ExcelData>, где

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 удалось это сделать, но он не может распознать таблицу.


person Liren Yeo    schedule 15.04.2016    source источник
comment
Так много обещаний, но так много разочарований. Совершенно логично определить ваши данные в таблице Excel, а затем импортировать их в таблицу SQL через EPPlus. Очевидно нет.   -  person Nick.McDermaid    schedule 17.02.2020


Ответы (6)


Родного нет, а что если использовать то, что я выложил в этом посте:

Как анализировать строки excel обратно в типы с помощью EPPlus

Если вы хотите указать его только на стол, его нужно будет изменить. Что-то вроде этого должно сделать это:

public static IEnumerable<T> ConvertTableToObjects<T>(this ExcelTable table) where T : new()
{
    //DateTime Conversion
    var convertDateTime = new Func<double, DateTime>(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<ExcelRangeBase>();

    //Have to use for loops insteadof worksheet.Cells to protect against empties
    for (var r = start.Row; r <= end.Row; r++)
        for (var c = start.Column; c <= end.Column; c++)
            cells.Add(table.WorkSheet.Cells[r, c]);

    var groups = cells
        .GroupBy(cell => cell.Start.Row)
        .ToList();

    //Assume the second row represents column data types (big assumption!)
    var types = groups
        .Skip(1)
        .First()
        .Select(rcell => 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<ExcelData>();
        foreach (var data in ThatList)
        {
            Console.WriteLine(data.Id + data.Name + data.Gender);
        }

        package.Save();
    }
}

Дал это в консоли:

1JohnMale
2MariaFemale
3DanielUnknown

Просто будьте осторожны, если поле Id является числом или строкой в ​​Excel, поскольку класс ожидает строку.

person Ernie S    schedule 15.04.2016
comment
Это очень хрупкое решение, так как любые пустые ячейки полностью сломают этот код. - person jwize; 08.05.2018
comment
В тестовом методе возникает ошибка, поскольку ошибка 1 «OfficeOpenXml.Table.ExcelTable» не содержит определения для «ConvertTableToObjects», и не может быть найден метод расширения «ConvertTableToObjects», принимающий первый аргумент типа «OfficeOpenXml.Table.ExcelTable» (вы отсутствует директива использования или ссылка на сборку?) - person Suresh Kamrushi; 20.08.2018
comment
@suresh ошибка просто говорит о том, что вообще не может найти метод расширения. Убедитесь, что он объявлен где-то, где тест может добраться до него. - person Ernie S; 20.08.2018
comment
@jwize Ты прав. Я обновил код, чтобы лучше справиться с этим. Любой, кто использует это, должен все же тщательно протестировать его. Это не на 100% идеально (как и большинство вещей на SO). - person Ernie S; 31.07.2019
comment
Просто имейте в виду тот факт, что если в пути к файлу или имени есть опечатка, рабочих листов нет :-) - person Honza P.; 24.10.2019
comment
@dinosaur Что за ошибка? Возможно, напишите как вопрос, если вам нужно добавить много деталей. - person Ernie S; 01.06.2020

Не уверен, почему, но ни одно из вышеперечисленных решений не работает для меня. Итак, делимся тем, что сработало:

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 <= rowCount; row++)
        {
            for (int col = 1; col <= colCount; col++)
            {
                Console.WriteLine(" Row:" + row + " column:" + col + " Value:" + worksheet.Cells[row, col].Value?.ToString().Trim());
            }
        }
    }
}
person Suresh Kamrushi    schedule 22.08.2018
comment
Но как это отвечает на вопрос ОП? Он спрашивал о том, чтобы взять таблицу в Excel и преобразовать ее в объекты .NET - автоматически с помощью Generics. Это проходит по строкам/столбцам, но требует ручного сопоставления значений со свойствами. - person Ernie S; 25.04.2019
comment
Строка: Рабочий лист ExcelWorksheet = package.Workbook.Worksheets[1]; дает мне исключение: System.IndexOutOfRangeException: положение рабочего листа вне диапазона. Как это возможно? Кто-нибудь может помочь? - person noobprogrammer; 11.10.2019
comment
@RohanRao вы пытались использовать индекс 0 в package.Workbook.Worksheets[1] - person Noob; 22.12.2019
comment
@ Нуб, да, я сделал. Но теперь это работает нормально. Спасибо за попытку помочь мне. - person noobprogrammer; 16.01.2020
comment
Отличный пример, очень простой, понравился ваш код, покажите, добавил его в свою статью, в которой также показано, как читать excel с помощью oledb/epplus qawithexperts.com/article/c-sharp/ - person Vikas Lalwani; 04.03.2020

Это моя рабочая версия. Обратите внимание, что код распознавателя не показан, но является частью моей реализации, которая позволяет разрешать столбцы, даже если они называются немного по-разному на каждом листе.

public static IEnumerable<T> ToArray<T>(this ExcelWorksheet worksheet, List<PropertyNameResolver> 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<T>();

  // Iterate the rows starting at row 2 (ie start.Row + 1)
  for (int row = start.Row + 1; row <= end.Row; row++)
  {
    var instance = new T();
    for (int col = start.Column; col <= end.Column; col++)
    {
      object value = worksheet.Cells[row, col].Text;

      // Get the column name zero based (ie col -1)
      var column = (string)header.Skip(col - 1).First().Value;

      // Gets the corresponding property to set
      var property = properties.Property(resolvers, column);

      try
      {
        var propertyName = property.PropertyType.IsGenericType
          ? property.PropertyType.GetGenericArguments().First().FullName
          : property.PropertyType.FullName;


        // Implement setter code as needed. 
        switch (propertyName)
        {
          case "System.String":
            property.SetValue(instance, Convert.ToString(value));
            break;
          case "System.Int32":
            property.SetValue(instance, Convert.ToInt32(value));
            break;
          case "System.DateTime":
            if (DateTime.TryParse((string) value, out var date))
            {
              property.SetValue(instance, date);
            }
            property.SetValue(instance, FromExcelSerialDate(Convert.ToInt32(value)));
            break;
          case "System.Boolean":
            property.SetValue(instance, (int)value == 1);
            break;
        }
      }
      catch (Exception e)
      {
        // instance property is empty because there was a problem.
      }

    } 
    list.Add(instance);
  }
  return list;
}

// Utility function taken from the above post's inline function.
public static DateTime FromExcelSerialDate(int 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);
}
person jwize    schedule 08.05.2018
comment
Я знаю, что это немного поздно. Хотя это, кажется, работает для меня, похоже, возникает проблема с преобразованием DateTime. Если значение может быть проанализировано как DateTime, вы устанавливаете значение свойства, а затем пытаетесь установить его снова с помощью функции FromExcelSerialDate, которая затем выдает ошибку. Хотя для свойства по-прежнему установлено правильное значение, есть ли причина, по которой там нет else ?? - person Ian Boggs; 24.04.2019

У меня есть ошибка в первом ответе, поэтому я изменил некоторую строку кода.

Пожалуйста, попробуйте мой новый код, он работает для меня.

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

public static class ImportExcelReader
{
    public static List<T> ImportExcelToList<T>(this ExcelWorksheet worksheet) where T : new()
    {
        //DateTime Conversion
        Func<double, DateTime> convertDateTime = new Func<double, DateTime>(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<IGrouping<int, ExcelRangeBase>> 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<Type> types = groups.Skip(1).FirstOrDefault().Select(rcell => rcell.Value.GetType()).ToList();

        //Get the properties of T
        List<PropertyInfo> 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<List<object>> rowvalues = groups
            .Skip(1) //Exclude header
            .Select(cg => cg.Select(c => c.Value).ToList()).ToList();

        //Create the collection container
        List<T> collection = new List<T>();
        foreach (List<object> 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<FundraiserStudentListModel> GetStudentsFromExcel(HttpPostedFileBase file)
    {
        List<FundraiserStudentListModel> list = new List<FundraiserStudentListModel>();
        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<FundraiserStudentListModel>();
                        }
                    }
                }
            }
            catch (Exception err)
            {
                //save error log
            }
        }
        return list;
    }

Сбор средствStudentListModel здесь:

 public class FundraiserStudentListModel
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
}
person Sadik Ali    schedule 12.02.2019

Код ниже будет считывать данные Excel в таблицу данных, которая преобразуется в список строк данных.

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<DataRow> listOfRows = new List<DataRow>();
        listOfRows = dt.AsEnumerable().ToList();

    }
}
using OfficeOpenXml;
using System.Data;
using System.Linq;

 public static class ExcelPackageExtensions
    {
        public static DataTable ToDataTable(this ExcelPackage package)
        {
            ExcelWorksheet workSheet = package.Workbook.Worksheets.First();
            DataTable table = new DataTable();
            foreach (var firstRowCell in workSheet.Cells[1, 1, 1, workSheet.Dimension.End.Column])
            {
                table.Columns.Add(firstRowCell.Text);
            }

            for (var rowNumber = 2; rowNumber <= workSheet.Dimension.End.Row; rowNumber++)
            {
                var row = workSheet.Cells[rowNumber, 1, rowNumber, workSheet.Dimension.End.Column];
                var newRow = table.NewRow();
                foreach (var cell in row)
                {
                    newRow[cell.Start.Column - 1] = cell.Text;
                }
                table.Rows.Add(newRow);
            }
            return table;
        }

    }
person Fenil Patel    schedule 13.06.2016
comment
получаю ошибку 'ExcelPackage' does not contain a definition for 'ToDataTable' and no extension method 'ToDataTable' accepting a first argument of type 'ExcelPackage' could be found (are you missing a using directive or an assembly reference?) - person Nitin S; 08.12.2017
comment
@FenilPatel, какую библиотеку электронных таблиц вы используете, Interrop или EPPlus? - person noobprogrammer; 16.01.2020
comment
@noobprogrammer EPPLus - person Fenil Patel; 10.06.2020

Рабочее решение с подтверждением электронной почты и номера мобильного телефона

 public class ExcelProcessing
        {
            public List<ExcelUserData> ReadExcel()
            {
                string path = Config.folderPath + @"\MemberUploadFormat.xlsx";
    
                using (var excelPack = new ExcelPackage())
                {
                    //Load excel stream
                    using (var stream = File.OpenRead(path))
                    {
                        excelPack.Load(stream);
                    }
    
                    //Lets Deal with first worksheet.(You may iterate here if dealing with multiple sheets)
                    var ws = excelPack.Workbook.Worksheets[0];
    
                    List<ExcelUserData> userList = new List<ExcelUserData>();
    
                    int colCount = ws.Dimension.End.Column;  //get Column Count
                    int rowCount = ws.Dimension.End.Row;
                       
                    for (int row = 2; row <= rowCount; row++) // start from to 2 omit header
                    {
                       
                        bool IsValid = true;
                        ExcelUserData _user = new ExcelUserData();
    
                        for (int col = 1; col <= colCount; col++)
                        {
                            if (col == 1)
                            {
                                _user.FirstName = ws.Cells[row, col].Value?.ToString().Trim();
                                if (string.IsNullOrEmpty(_user.FirstName))
                                {
                                    _user.ErrorMessage += "Enter FirstName <br/>";
                                    IsValid = false;
                                }
                            }
                            else if (col == 2)
                            {
                                _user.Email = ws.Cells[row, col].Value?.ToString().Trim();
    
                                if (string.IsNullOrEmpty(_user.Email))
                                {
                                    _user.ErrorMessage += "Enter Email <br/>";
                                    IsValid = false;
                                }
                                else if (!IsValidEmail(_user.Email))
                                {
                                    _user.ErrorMessage += "Invalid Email Address <br/>";
                                    IsValid = false;
                                }
                            }
                            else if (col ==3)
                            {
                                _user.MobileNo = ws.Cells[row, col].Value?.ToString().Trim();
    
                                if (string.IsNullOrEmpty(_user.MobileNo))
                                {
                                    _user.ErrorMessage += "Enter Mobile No <br/>";
                                    IsValid = false;
                                }
                                else if (_user.MobileNo.Length != 10)
                                {
                                    _user.ErrorMessage += "Invalid Mobile No <br/>";
                                    IsValid = false;
                                }
    
                            }
                            else if (col == 4)
                            {
                                _user.IsAdmin = ws.Cells[row, col].Value?.ToString().Trim();
    
                                if (string.IsNullOrEmpty(_user.IsAdmin))
                                {
                                    _user.IsAdmin = "0";
                                }
                            }
    
                            _user.IsValid = IsValid;
                        }
                        userList.Add(_user);
                    }
                    return userList;
                }
            }
            public static bool IsValidEmail(string email)
            {
                Regex regex = new Regex(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
                 RegexOptions.CultureInvariant | RegexOptions.Singleline);
    
                return regex.IsMatch(email);
            }
        }
person Swapnil    schedule 15.11.2020