【问题标题】:From Excel to DataTable in C# with Open XML使用 Open XML 从 Excel 到 C# 中的 DataTable
【发布时间】:2011-03-20 06:18:46
【问题描述】:

我使用的是 Visual Studio 2008,我需要使用 Open XML SDK 2.0 从 Excel 工作表创建一个DataTable。我需要用工作表第一行的 DataTable 列创建它,并用其余的值完成它。

有人有示例代码或链接可以帮助我做到这一点吗?

【问题讨论】:

  • 您需要使用 OpenXML SDK 2.0 的任何特殊原因?
  • DOK:运行程序的服务器无法安装MS Office(安全策略)。
  • 我一直在尝试的旧方法也不适用于 Office 365。但它可以在我的计算机上使用旧版本的 Office。这种方式适用于 365。

标签: c# openxml openxml-sdk import-from-excel


【解决方案1】:

我认为这应该符合您的要求。如果您有共享字符串,则另一个功能只是为了处理,我假设您在列标题中执行此操作。不确定这是否完美,但我希望它有所帮助。

static void Main(string[] args)
{
    DataTable dt = new DataTable();

    using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(@"..\..\example.xlsx", false))
    {

        WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
        IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
        string relationshipId = sheets.First().Id.Value;
        WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId);
        Worksheet workSheet = worksheetPart.Worksheet;
        SheetData sheetData = workSheet.GetFirstChild<SheetData>();
        IEnumerable<Row> rows = sheetData.Descendants<Row>();

        foreach (Cell cell in rows.ElementAt(0))
        {
            dt.Columns.Add(GetCellValue(spreadSheetDocument, cell));
        }

        foreach (Row row in rows) //this will also include your header row...
        {
            DataRow tempRow = dt.NewRow();

            for (int i = 0; i < row.Descendants<Cell>().Count(); i++)
            {
                tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i-1));
            }

            dt.Rows.Add(tempRow);
        }

    }
    dt.Rows.RemoveAt(0); //...so i'm taking it out here.

}


public static string GetCellValue(SpreadsheetDocument document, Cell cell)
{
    SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;
    string value = cell.CellValue.InnerXml;

    if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
    {
        return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
    }
    else
    {
        return value;
    }
}

【讨论】:

  • 为什么使用 rows.ElementAt(0)?我只有 10 列,但在我的 excel 文件中至少有 30 列!
  • 似乎这会跳过空单元格,因此您最终可能会在 tempRow 中将 A1、D1 和 G1 彼此相邻。同样在某些情况下CellValue 为空,我在GetCellValue 的第二行中遇到异常。
  • @RahulNikate,您的链接会将您带到这个页面!
  • 如果单元格值为空,以下将行将引发异常。字符串值 = cell.CellValue.InnerXml;在它之前添加一行检查是否为空: Checklace if (cell.CellValue == null) { return ""; }
【解决方案2】:

您好,上面的代码工作正常,除了一个变化

替换下面的代码行

tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i-1));

tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));

如果使用 (i-1) 会抛出异常:

specified argument was out of the range of valid values. parameter name index.

【讨论】:

    【解决方案3】:

    此解决方案适用于没有空单元格的电子表格。

    要处理空单元格,您需要替换此行:

    tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i-1));
    

    类似这样的:

    Cell cell = row.Descendants<Cell>().ElementAt(i);
    int index = CellReferenceToIndex(cell);
    tempRow[index] = GetCellValue(spreadSheetDocument, cell);
    

    并添加此方法:

    private static int CellReferenceToIndex(Cell cell)
    {
        int index = -1;
        string reference = cell.CellReference.ToString().ToUpper();
        foreach (char ch in reference)
        {
            if (Char.IsLetter(ch))
            {
                int value = (int)ch - (int)'A';
                index = (index + 1) * 26 + value;
            }
            else
                return index;
        }
        return index;
    }
    

    【讨论】:

    • (index == 0) ? value : ((index + 1) * 26) + value => 将从A 开始的多字符索引返回不正确的结果,例如AAABAAC 等,因为A 值将转换为@ 987654330@ 代替 26 乘数
    【解决方案4】:

    这是我的完整解决方案,其中还考虑了空单元格。

    public static class ExcelHelper
            {
                //To get the value of the cell, even it's empty. Unable to use loop by index
                private static string GetCellValue(WorkbookPart wbPart, List<Cell> theCells, string cellColumnReference)
                {
                    Cell theCell = null;
                    string value = "";
                    foreach (Cell cell in theCells)
                    {
                        if (cell.CellReference.Value.StartsWith(cellColumnReference))
                        {
                            theCell = cell;
                            break;
                        }
                    }
                    if (theCell != null)
                    {
                        value = theCell.InnerText;
                        // If the cell represents an integer number, you are done. 
                        // For dates, this code returns the serialized value that represents the date. The code handles strings and 
                        // Booleans individually. For shared strings, the code looks up the corresponding value in the shared string table. For Booleans, the code converts the value into the words TRUE or FALSE.
                        if (theCell.DataType != null)
                        {
                            switch (theCell.DataType.Value)
                            {
                                case CellValues.SharedString:
                                    // For shared strings, look up the value in the shared strings table.
                                    var stringTable = wbPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
                                    // If the shared string table is missing, something is wrong. Return the index that is in the cell. Otherwise, look up the correct text in the table.
                                    if (stringTable != null)
                                    {
                                        value = stringTable.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
                                    }
                                    break;
                                case CellValues.Boolean:
                                    switch (value)
                                    {
                                        case "0":
                                            value = "FALSE";
                                            break;
                                        default:
                                            value = "TRUE";
                                            break;
                                    }
                                    break;
                            }
                        }
                    }
                    return value;
                }
    
                private static string GetCellValue(WorkbookPart wbPart, List<Cell> theCells, int index)
                {
                    return GetCellValue(wbPart, theCells, GetExcelColumnName(index));
                }
    
                private static string GetExcelColumnName(int columnNumber)
                {
                    int dividend = columnNumber;
                    string columnName = String.Empty;
                    int modulo;
                    while (dividend > 0)
                    {
                        modulo = (dividend - 1) % 26;
                        columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
                        dividend = (int)((dividend - modulo) / 26);
                    }
                    return columnName;
                }
    
                //Only xlsx files
                public static DataTable GetDataTableFromExcelFile(string filePath, string sheetName = "")
                {
                    DataTable dt = new DataTable();
                    try
                    {
                        using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, false))
                        {
                            WorkbookPart wbPart = document.WorkbookPart;
                            IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
                            string sheetId = sheetName != "" ? sheets.Where(q => q.Name == sheetName).First().Id.Value : sheets.First().Id.Value;
                            WorksheetPart wsPart = (WorksheetPart)wbPart.GetPartById(sheetId);
                            SheetData sheetdata = wsPart.Worksheet.Elements<SheetData>().FirstOrDefault();
                            int totalHeaderCount = sheetdata.Descendants<Row>().ElementAt(0).Descendants<Cell>().Count();
                            //Get the header                    
                            for (int i = 1; i <= totalHeaderCount; i++)
                            {
                                dt.Columns.Add(GetCellValue(wbPart, sheetdata.Descendants<Row>().ElementAt(0).Elements<Cell>().ToList(), i));
                            }
                            foreach (Row r in sheetdata.Descendants<Row>())
                            {
                                if (r.RowIndex > 1)
                                {
                                    DataRow tempRow = dt.NewRow();
    
                                    //Always get from the header count, because the index of the row changes where empty cell is not counted
                                    for (int i = 1; i <= totalHeaderCount; i++)
                                    {
                                        tempRow[i - 1] = GetCellValue(wbPart, r.Elements<Cell>().ToList(), i);
                                    }
                                    dt.Rows.Add(tempRow);
                                }
                            }                    
                        }
                    }
                    catch (Exception ex)
                    {
    
                    }
                    return dt;
                }
            }
    

    【讨论】:

    • 这与MSDN article 中的代码非常相似,其中也有很多有用的提示,例如共享字符串表等
    【解决方案5】:
     Public Shared Function ExcelToDataTable(filename As String) As DataTable
            Try
    
                Dim dt As New DataTable()
    
                Using doc As SpreadsheetDocument = SpreadsheetDocument.Open(filename, False)
    
                    Dim workbookPart As WorkbookPart = doc.WorkbookPart
                    Dim sheets As IEnumerable(Of Sheet) = doc.WorkbookPart.Workbook.GetFirstChild(Of Sheets)().Elements(Of Sheet)()
                    Dim relationshipId As String = sheets.First().Id.Value
                    Dim worksheetPart As WorksheetPart = DirectCast(doc.WorkbookPart.GetPartById(relationshipId), WorksheetPart)
                    Dim workSheet As Worksheet = worksheetPart.Worksheet
                    Dim sheetData As SheetData = workSheet.GetFirstChild(Of SheetData)()
                    Dim rows As IEnumerable(Of Row) = sheetData.Descendants(Of Row)()
    
                    For Each cell As Cell In rows.ElementAt(0)
                        dt.Columns.Add(GetCellValue(doc, cell))
                    Next
    
                    For Each row As Row In rows
                        'this will also include your header row...
                        Dim tempRow As DataRow = dt.NewRow()
    
                        For i As Integer = 0 To row.Descendants(Of Cell)().Count() - 1
                            tempRow(i) = GetCellValue(doc, row.Descendants(Of Cell)().ElementAt(i))
                        Next
    
                        dt.Rows.Add(tempRow)
                    Next
                End Using
    
                dt.Rows.RemoveAt(0)
    
                Return dt
    
            Catch ex As Exception
                Throw ex
            End Try
        End Function
    
    
        Public Shared Function GetCellValue(document As SpreadsheetDocument, cell As Cell) As String
            Try
    
                If IsNothing(cell.CellValue) Then
                    Return ""
                End If
    
                Dim value As String = cell.CellValue.InnerXml
    
                If cell.DataType IsNot Nothing AndAlso cell.DataType.Value = CellValues.SharedString Then
                    Dim stringTablePart As SharedStringTablePart = document.WorkbookPart.SharedStringTablePart
                    Return stringTablePart.SharedStringTable.ChildElements(Int32.Parse(value)).InnerText
                Else
                    Return value
                End If
    
            Catch ex As Exception
                Return ""
            End Try
        End Function
    

    【讨论】:

      【解决方案6】:

      首先将 ExcelUtility.cs 添加到您的项目中:

      ExcelUtility.cs

      using System.Data;
      using System.Linq;
      using DocumentFormat.OpenXml.Packaging;
      using DocumentFormat.OpenXml.Spreadsheet;
      
      namespace Core_Excel.Utilities
      {
          static class ExcelUtility
          {
              public static DataTable Read(string path)
              {
                  var dt = new DataTable();
      
                  using (var ssDoc = SpreadsheetDocument.Open(path, false))
                  {
                      var sheets = ssDoc.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
                      var relationshipId = sheets.First().Id.Value;
                      var worksheetPart = (WorksheetPart) ssDoc.WorkbookPart.GetPartById(relationshipId);
                      var workSheet = worksheetPart.Worksheet;
                      var sheetData = workSheet.GetFirstChild<SheetData>();
                      var rows = sheetData.Descendants<Row>().ToList();
      
                      foreach (var row in rows) //this will also include your header row...
                      {
                          var tempRow = dt.NewRow();
      
                          var colCount = row.Descendants<Cell>().Count();
                          foreach (var cell in row.Descendants<Cell>())
                          {
                              var index = GetIndex(cell.CellReference);
      
                              // Add Columns
                              for (var i = dt.Columns.Count; i <= index; i++)
                                  dt.Columns.Add();
      
                              tempRow[index] = GetCellValue(ssDoc, cell);
                          }
      
                          dt.Rows.Add(tempRow);
                      }
                  }
      
                  var m = dt.Rows[0][9];
      
                  return dt;
              }
      
              private static string GetCellValue(SpreadsheetDocument document, Cell cell)
              {
                  var stringTablePart = document.WorkbookPart.SharedStringTablePart;
                  var value = cell.CellValue.InnerXml;
      
                  if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
                      return stringTablePart.SharedStringTable.ChildElements[int.Parse(value)].InnerText;
      
                  return value;
              }
      
              public static int GetIndex(string name)
              {
                  if (string.IsNullOrWhiteSpace(name))
                      return -1;
      
                  int index = 0;
                  foreach (var ch in name)
                  {
                      if (char.IsLetter(ch))
                      {
                          int value = ch - 'A' + 1;
                          index = value + index * 26;
                      }
                      else
                          break;
                  }
      
                  return index - 1;
              }
          }
      }
      

      用法:

      var path = "D:\\Documents\\test.xlsx";
      var dt = ExcelUtility.Read(path);
      

      那就尽情享受吧!

      【讨论】:

        【解决方案7】:

        我知道自从这个帖子开始以来已经很久了。但是,上述解决方案都没有真正适合我。空单元格问题和其他问题。

        我在 GitHub 上使用“MIT”许可证找到了一个非常好的解决方案: https://github.com/ExcelDataReader/ExcelDataReader 这对我来说适用于 C# 和 VBnet 应用程序。 来自 VBNET 的示例调用(c# 的示例代码在 GitHub 上):

                Using stream As FileStream = New FileStream(DataPath & "\" & fName.Name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
        
                    Using reader As IExcelDataReader = ExcelReaderFactory.CreateReader(stream)
                        ds = reader.AsDataSet(New ExcelDataSetConfiguration() With {
                            .UseColumnDataType = False,
                            .ConfigureDataTable = Function(tableReader) New ExcelDataTableConfiguration() With {
                                .UseHeaderRow = True
                            }
                        })
                    End Using
        
                End Using
        

        结果是一个数据集,工作簿中的每个工作表都有一个表。

        我真的很喜欢自己编译用 C# 制作的 dll,而不是使用现成的 dll。这样我就可以控制向客户交付的内容。

        【讨论】:

          【解决方案8】:

          如果 rows 值为 null 或为空,则获取值错误。

          所有列都填充了数据,如果它正在工作的话。但也许所有行都不是

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-07-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-23
            相关资源
            最近更新 更多