读取Excel文件(.xls/.xlsx)的最佳方式

85

我知道有多种方法可以读取Excel文件:

  • Iterop
  • Oledb
  • Open Xml SDK

兼容性不是问题,因为程序将在受控环境中执行。

我的要求:
将文件读入 DataTable / 自定义实体(我不知道如何为对象制作动态属性/字段[Excel文件中的列名会变化])

使用 DataTable/Custom Entities 对其数据执行一些操作。

使用操作结果更新 DataTable

将更新后的数据写回到 excel 文件中。

哪种方法更简单。

如果可能的话,请就自定义实体向我提供建议(动态添加属性/字段到一个对象中)。


@AmiramKorach 关于写回Excel的问题,您怎么看? - Ankesh
我使用商业第三方工具来实现这个。这个问题已经在这里被问到了:https://dev59.com/_XI_5IYBdhLWcg3wF_B3 - Amiram Korach
我认为最有效的方法之一是使用GemBox.Spreadsheet库,该库具有直接将DataTable导出到表格和将表格导出到DataTable的方法。 - Hazel Patton
只是一点小建议,Excel文件实际上就是zip文件。提取Excel文件会留下几个文件夹。文件的字符串存储在“[文件名文件夹]/xl/sharedStrings.xml”中,而工作簿存储在“[文件名文件夹]/xl/workbook.xml”中。理论上,您可以通过编程方式解压缩Excel文件并从提取的文件中提取信息。 - Zach Pedigo
NPOI.dll和EPPlus.dll都可以用于这个任务。你应该可以找到旧的v4.x版本的EPPlus.dll来入门。 是的,.xlsx文件是压缩的XML块(数据、格式等)。由于NPOI和EPPlus都不涉及Excel COM .dlls(因此不需要在服务器上安装Office许可证),它们都是安全的,不会因为某些进程没有正确或完全关闭而导致服务器出现僵尸COM进程最终导致服务器崩溃的风险。 - undefined
8个回答

79

看看Linq-to-Excel吧,它很不错。

var book = new LinqToExcel.ExcelQueryFactory(@"File.xlsx");

var query =
    from row in book.Worksheet("Stock Entry")
    let item = new
    {
        Code = row["Code"].Cast<string>(),
        Supplier = row["Supplier"].Cast<string>(),
        Ref = row["Ref"].Cast<string>(),
    }
    where item.Supplier == "Walmart"
    select item;

它还允许强类型的行访问。


6
请注意,Linq-to-Excel使用了一系列第三方库。 - florians
6
@fschricker提到了两个名词:“log4net”和“Remotion”。 - Enigmativity
13
Access数据库引擎,尽管它是由微软提供的另一个依赖项。 - Alan B
6
尽管看起来不错,但由于“访问数据库引擎”的原因,这并不是可行的生产资产。 - Prisoner ZERO
14
不要成为“巨魔”...但是...如果有人正在考虑使用它,我需要说:唯一可以“可能”使用它的地方是从您自己的桌面。没有人会让您在客户端桌面或Web服务器上安装“Access数据库引擎”...许多地方(出于良好的原因)甚至不允许您在本地安装此类东西。再次说明,我喜欢语法和想法...但这不是广泛可行的解决方案。不过,非常酷。 - Prisoner ZERO
显示剩余9条评论

36

我意识到这个问题已经被问了将近7年,但它仍然是某些关键词在谷歌搜索结果中排名靠前的关于使用C#导入Excel数据的问题,因此我想提供一种基于最新技术发展的替代方法。

导入Excel数据已经成为了我日常工作中如此常见的任务,以至于我已经简化了这个过程并在我的博客上记录了这个方法:使用c#读取excel文件的最佳方法

我使用NPOI,因为它可以在没有安装Microsoft Office的情况下读写Excel文件,并且它不使用COM+或任何互操作。这意味着它可以在云端工作!

但真正的魔力来自于与NPOI Mapper Donny Tian配对,因为它允许我将Excel列映射到我的C#类属性上,而无需编写任何代码。这真是太棒了。

这里是基本思路:

我创建一个匹配/映射我感兴趣的Excel列的.net类:

        class CustomExcelFormat
        {
            [Column("District")]
            public int District { get; set; }

            [Column("DM")]
            public string FullName { get; set; }

            [Column("Email Address")]
            public string EmailAddress { get; set; }

            [Column("Username")]
            public string Username { get; set; }

            public string FirstName
            {
                get
                {
                    return Username.Split('.')[0];
                }
            }

            public string LastName
            {
                get
                {
                    return Username.Split('.')[1];
                }
            }
        }

注意,如果我想的话,它允许我根据列名称进行映射!

然后当我处理Excel文件时,我只需要像这样做:

        public void Execute(string localPath, int sheetIndex)
        {
            IWorkbook workbook;
            using (FileStream file = new FileStream(localPath, FileMode.Open, FileAccess.Read))
            {
                workbook = WorkbookFactory.Create(file);
            }

            var importer = new Mapper(workbook);
            var items = importer.Take<CustomExcelFormat>(sheetIndex);
            foreach(var item in items)
            {
                var row = item.Value;
                if (string.IsNullOrEmpty(row.EmailAddress))
                    continue;

                UpdateUser(row);
            }

            DataContext.SaveChanges();
        }

诚然,我的代码并没有修改Excel文件本身。相反,我使用Entity Framework将数据保存到数据库中(这就是你在我的示例中看到"UpdateUser"和"SaveChanges"的原因)。但是,关于如何使用NPOI来保存/修改文件已经有了一个很好的SO讨论:save/modify a file using NPOI


2
这个非常好用!目前为止最简单的解决方案。两者都可以作为NuGet包使用。 - Stefan
1
嗨,丹,我无法在服务器上处理Excel文件而不使用Excel软件或OLEDB驱动程序。但是,通过使用它,我能够实现。也有很好的性能。 非常感谢。 - Ramakrishnankt
1
我怎样才能在不事先知道列名的情况下将其应用到通用的Excel文件中呢?例如,我更想将工作表导出为DataTable。 - JM217
David Pio - 使用新的 dynamic 功能。我想这就是你要找的。mapper.Take<dynamic>(0).ToList(); - Piotr Kula
1
太棒了,运行得非常顺利,节省了大量时间。谢谢! - Flatpick13
你能否分享一下你所拥有的实际Excel文件的预览?此外,这是否适用于.NET Core? - shaikhspear

30

使用 OLE 查询非常简单(例如,sheetName 是 Sheet1):

DataTable LoadWorksheetInDataTable(string fileName, string sheetName)
{           
    DataTable sheetData = new DataTable();
    using (OleDbConnection conn = this.returnConnection(fileName))
    {
       conn.Open();
       // retrieve the data using data adapter
       OleDbDataAdapter sheetAdapter = new OleDbDataAdapter("select * from [" + sheetName + "$]", conn);
       sheetAdapter.Fill(sheetData);
       conn.Close();
    }                        
    return sheetData;
}

private OleDbConnection returnConnection(string fileName)
{
    return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileName + "; Jet OLEDB:Engine Type=5;Extended Properties=\"Excel 8.0;\"");
}

对于较新的Excel版本:

return new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + fileName + ";Extended Properties=Excel 12.0;");

你也可以使用Excel Data Reader,这是一个在CodePlex上的开源项目。它非常适用于从Excel表格中导出数据。

链接指定的示例代码:

FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read);

//1. Reading from a binary Excel file ('97-2003 format; *.xls)
IExcelDataReader excelReader = ExcelReaderFactory.CreateBinaryReader(stream);
//...
//2. Reading from a OpenXml Excel file (2007 format; *.xlsx)
IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
//...
//3. DataSet - The result of each spreadsheet will be created in the result.Tables
DataSet result = excelReader.AsDataSet();
//...
//4. DataSet - Create column names from first row
excelReader.IsFirstRowAsColumnNames = true;
DataSet result = excelReader.AsDataSet();

//5. Data Reader methods
while (excelReader.Read())
{
//excelReader.GetInt32(0);
}

//6. Free resources (IExcelDataReader is IDisposable)
excelReader.Close();

参考文献:如何使用 Microsoft.Office.Interop.Excel 将 Excel 数据导入 DataSet?


4
this.returnConnection(fileName) 的代码在哪里? - Maslow
由于OleDb不可用(因为它不跨平台),因此在.NET Core中不再起作用。 - codeMonkey
1
ExcelDataReader 对我很有用,但现在它已经托管在 https://github.com/ExcelDataReader/ExcelDataReader 上。 - Wernsey
1
每个选项都需要我安装Access数据库引擎吗? - Jamshaid K.
System.Data.OleDb现在可用于dotnet core https://www.nuget.org/packages/System.Data.OleDb - nedstark179
@codeMonkey仍然作为官方Microsoft软件包可用(支持net core 2.1及以上版本)。 - harili

6
尝试使用这个免费的方法进行操作:https://freenetexcel.codeplex.com
 Workbook workbook = new Workbook();

 workbook.LoadFromFile(@"..\..\parts.xls",ExcelVersion.Version97to2003);
 //Initialize worksheet
 Worksheet sheet = workbook.Worksheets[0];

 DataTable dataTable = sheet.ExportDataTable();

有一些限制,比如可读取的工作表数量和行数。您需要购买许可证以消除这些限制... 此外,CodePlex 链接即将失效。这是官方产品页面:https://www.e-iceblue.com/Introduce/free-xls-component.html - Dude Pascalou

5
如果您只需要限制为(Open Office XML格式)*.xlsx文件,那么最受欢迎的库可能是EPPlus
额外的好处是,没有其他依赖项。只需使用nuget进行安装:
Install-Package EPPlus

0

从Excel读取,修改并写回

 /// <summary>
/// /Reads an excel file and converts it into dataset with each sheet as each table of the dataset
/// </summary>
/// <param name="filename"></param>
/// <param name="headers">If set to true the first row will be considered as headers</param>
/// <returns></returns>
public DataSet Import(string filename, bool headers = true)
{
    var _xl = new Excel.Application();
    var wb = _xl.Workbooks.Open(filename);
    var sheets = wb.Sheets;
    DataSet dataSet = null;
    if (sheets != null && sheets.Count != 0)
    {
        dataSet = new DataSet();
        foreach (var item in sheets)
        {
            var sheet = (Excel.Worksheet)item;
            DataTable dt = null;
            if (sheet != null)
            {
                dt = new DataTable();
                var ColumnCount = ((Excel.Range)sheet.UsedRange.Rows[1, Type.Missing]).Columns.Count;
                var rowCount = ((Excel.Range)sheet.UsedRange.Columns[1, Type.Missing]).Rows.Count;

                for (int j = 0; j < ColumnCount; j++)
                {
                    var cell = (Excel.Range)sheet.Cells[1, j + 1];
                    var column = new DataColumn(headers ? cell.Value : string.Empty);
                    dt.Columns.Add(column);
                }

                for (int i = 0; i < rowCount; i++)
                {
                    var r = dt.NewRow();
                    for (int j = 0; j < ColumnCount; j++)
                    {
                        var cell = (Excel.Range)sheet.Cells[i + 1 + (headers ? 1 : 0), j + 1];
                        r[j] = cell.Value;
                    }
                    dt.Rows.Add(r);
                }

            }
            dataSet.Tables.Add(dt);
        }
    }
    _xl.Quit();
    return dataSet;
}



 public string Export(DataTable dt, bool headers = false)
    {
        var wb = _xl.Workbooks.Add();
        var sheet = (Excel.Worksheet)wb.ActiveSheet;
        //process columns
        for (int i = 0; i < dt.Columns.Count; i++)
        {
            var col = dt.Columns[i];
            //added columns to the top of sheet
            var currentCell = (Excel.Range)sheet.Cells[1, i + 1];
            currentCell.Value = col.ToString();
            currentCell.Font.Bold = true;
            //process rows
            for (int j = 0; j < dt.Rows.Count; j++)
            {
                var row = dt.Rows[j];
                //added rows to sheet
                var cell = (Excel.Range)sheet.Cells[j + 1 + 1, i + 1];
                cell.Value = row[i];
            }
            currentCell.EntireColumn.AutoFit();
        }
        var fileName="{somepath/somefile.xlsx}";
        wb.SaveCopyAs(fileName);
        _xl.Quit();
        return fileName;
    }

_xl.Workbooks 是什么? - javdromero

0

尝试使用Aspose.cells库(不免费,但试用版足以阅读),它非常好

Install-package Aspose.cells

这里有示例代码:

using Aspose.Cells;
using System;

namespace ExcelReader
{
    class Program
    {
        static void Main(string[] args)
        {
            // Replace path for your file
            readXLS(@"C:\MyExcelFile.xls"); // or "*.xlsx"
            Console.ReadKey();
        }

        public static void readXLS(string PathToMyExcel)
        {
            //Open your template file.
            Workbook wb = new Workbook(PathToMyExcel);

            //Get the first worksheet.
            Worksheet worksheet = wb.Worksheets[0];

            //Get cells
            Cells cells = worksheet.Cells;

            // Get row and column count
            int rowCount = cells.MaxDataRow;
            int columnCount = cells.MaxDataColumn;

            // Current cell value
            string strCell = "";

            Console.WriteLine(String.Format("rowCount={0}, columnCount={1}", rowCount, columnCount));

            for (int row = 0; row <= rowCount; row++) // Numeration starts from 0 to MaxDataRow
            {
                for (int column = 0; column <= columnCount; column++)  // Numeration starts from 0 to MaxDataColumn
                {
                    strCell = "";
                    strCell = Convert.ToString(cells[row, column].Value);
                    if (String.IsNullOrEmpty(strCell))
                    {
                        continue;
                    }
                    else
                    {
                        // Do your staff here
                        Console.WriteLine(strCell);
                    }
                }
            }
        }
    }
}

2
它不是免费的,并且在完成一些解析后(我猜大约1K),有许可证限制。 - Mehmet Kurtipek

0

我使用了Office的NuGet包:DocumentFormat.OpenXml,并从该组件的文档站点中拼凑了代码。

通过下面的辅助代码,与项目中其他CSV文件格式解析的复杂度类似...

public static async Task ImportXLSX(Stream stream, string sheetName) {
{
    // This was necessary for my Blazor project, which used a BrowserFileStream object
    MemoryStream ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    using (var document = SpreadsheetDocument.Open(ms, false))
    {
        // Retrieve a reference to the workbook part.
        WorkbookPart wbPart = document.WorkbookPart;

        // Find the sheet with the supplied name, and then use that 
        // Sheet object to retrieve a reference to the first worksheet.
        Sheet theSheet = wbPart?.Workbook.Descendants<Sheet>().Where(s => s?.Name == sheetName).FirstOrDefault();

        // Throw an exception if there is no sheet.
        if (theSheet == null)
        {
            throw new ArgumentException("sheetName");
        }

        WorksheetPart wsPart = (WorksheetPart)(wbPart.GetPartById(theSheet.Id));
        // For shared strings, look up the value in the
        // shared strings table.
        var stringTable = 
            wbPart.GetPartsOfType<SharedStringTablePart>()
            .FirstOrDefault();

        // I needed to grab 4 cells from each row
        // Starting at row 11, until the cell in column A is blank
        int row = 11;

        while (true) {
                var accountNameCell = GetCell(wsPart, "A" + row.ToString());
                var accountName = GetValue(accountNameCell, stringTable);
                if (string.IsNullOrEmpty(accountName)) {
                    break;
                }
                var investmentNameCell = GetCell(wsPart, "B" + row.ToString());
                var investmentName = GetValue(investmentNameCell, stringTable);
                var symbolCell = GetCell(wsPart, "D" + row.ToString());
                var symbol = GetValue(symbolCell, stringTable);
                var marketValue = GetCell(wsPart, "J" + row.ToString()).InnerText;
                
                // DO STUFF with data

                row++;
        }
    }
}

private static string? GetValue(Cell cell, SharedStringTablePart stringTable) {
    try {
        return stringTable.SharedStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText;
    } catch (Exception) {
        return null;
    }
}
private static Cell GetCell(WorksheetPart wsPart, string cellReference) {
    return wsPart.Worksheet.Descendants<Cell>().Where(c => c.CellReference.Value == cellReference)?.FirstOrDefault();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接