2012-11-15 180 views
22

我用下面的代碼到Excel轉換到DataTable使用EPPlus:Excel中的數據表使用EPPlus - Excel的編輯鎖定

public DataTable ExcelToDataTable(string path) 
{ 
    var pck = new OfficeOpenXml.ExcelPackage(); 
    pck.Load(File.OpenRead(path)); 
    var ws = pck.Workbook.Worksheets.First(); 
    DataTable tbl = new DataTable(); 
    bool hasHeader = true; 
    foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column]) 
    { 
     tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column)); 
    } 
    var startRow = hasHeader ? 2 : 1; 
    for (var rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++) 
    { 
     var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column]; 
     var row = tbl.NewRow(); 
     foreach (var cell in wsRow) 
     { 
      row[cell.Start.Column - 1] = cell.Text; 
     } 
     tbl.Rows.Add(row); 
    } 
    pck.Dispose(); 
    return tbl; 
} 

它創建了Excel,但是,當我嘗試打開它,它給了我一個消息,說明它被鎖定以供其他用戶編輯,並且我只能以只讀模式打開它。

我想用:

pck.Dispose(); 

就解決了問題,但我仍然得到同樣的錯誤。

此外,當我嘗試刪除該文件時,我收到消息:該操作無法完成,因爲該文件在WebDev.WebServer40.EXE中打開。

任何想法如何解決這個問題? 在此先感謝。 :)

回答

72

我明白了,那就是i've posted recently here(現在更正了)。由於ExcelPackageFileStream(來自File.OpenRead)在使用後未被處置,因此可以改進。

public static DataTable GetDataTableFromExcel(string path, bool hasHeader = true) 
{ 
    using (var pck = new OfficeOpenXml.ExcelPackage()) 
    { 
     using (var stream = File.OpenRead(path)) 
     { 
      pck.Load(stream); 
     } 
     var ws = pck.Workbook.Worksheets.First(); 
     DataTable tbl = new DataTable(); 
     foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column]) 
     { 
      tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column)); 
     } 
     var startRow = hasHeader ? 2 : 1; 
     for (int rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++) 
     { 
      var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column]; 
      DataRow row = tbl.Rows.Add(); 
      foreach (var cell in wsRow) 
      { 
       row[cell.Start.Column - 1] = cell.Text; 
      } 
     } 
     return tbl; 
    } 
} 
+0

是的,我看到了代碼Codeplex EPPlus網站以及您在StackOverflow上的文章。 試過這個,工作,謝謝你:) pck.Dispose()只是配置ExcelPackage然後而不是FileStream,正確嗎? – Fahad

+2

@法赫德:對。使用'using'也更好,因爲它也會在出錯時放置'IDisposable'。我也會在Codeplex上編輯這篇文章。 –

+0

我怎樣才能得到第一列「foreach(var單元格在wsRow){」這個循環 Becoz我有一個日期字段,我想在插入到數據表格時更改格式,所以我需要檢查數據到「foreach(var cell in wsRow){「循環,所以我怎麼能確定第一個colunm值是來? –

0
public static List<T> getClassFromExcel<T>(string path, int fromRow, int fromColumn, int toColumn = 0) where T : class 
     { 
      using (var pck = new OfficeOpenXml.ExcelPackage()) 
      { 
       List<T> retList = new List<T>(); 

       using (var stream = File.OpenRead(path)) 
       { 
        pck.Load(stream); 
       } 
       var ws = pck.Workbook.Worksheets.First(); 
       toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn; 

       for (var rowNum = fromRow; rowNum <= ws.Dimension.End.Row; rowNum++) 
       { 
        T objT = Activator.CreateInstance<T>(); 
        Type myType = typeof(T); 
        PropertyInfo[] myProp = myType.GetProperties(); 

        var wsRow = ws.Cells[rowNum, fromColumn, rowNum, toColumn]; 

        for (int i = 0; i < myProp.Count(); i++) 
        { 
         myProp[i].SetValue(objT, wsRow[rowNum, fromColumn + i].Text); 
        } 
        retList.Add(objT); 
       } 
       return retList; 
      } 
     } 
+7

您能否爲您的答案至少添加一點解釋? –

5

添Schmelter的回答的一個延伸版本。

public static DataTable ToDataTable(this ExcelWorksheet ws, bool hasHeaderRow = true) 
{ 
    var tbl = new DataTable(); 
    foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column]) 
     tbl.Columns.Add(hasHeaderRow ? 
      firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column)); 
    var startRow = hasHeaderRow ? 2 : 1; 
    for (var rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++) 
    { 
     var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column]; 
     var row = tbl.NewRow(); 
     foreach (var cell in wsRow) row[cell.Start.Column - 1] = cell.Text; 
     tbl.Rows.Add(row); 
    } 
    return tbl; 
} 
3

這是對上述通用的改進。用途是如果你有一個具有以下屬性的類,名稱,姓氏,電話,傳真,並且你有一個excel表格,第一行的名字相同,它會把excel行加載到類對象,並彈出它變成一個列表

public static List<T> GetClassFromExcel<T>(string path, int fromRow, int fromColumn, int toRow = 0, int toColumn = 0) 
{ 
if (toColumn != 0 && toColumn < fromColumn) throw new   Exception("toColumn can not be less than fromColumn"); 
if (toRow != 0 && toRow < fromRow) throw new Exception("toRow can not be less than fromRow"); 
List<T> retList = new List<T>(); 
using (var pck = new ExcelPackage()) 
{ 
      using (var stream = File.OpenRead(path)) 
      { 
       pck.Load(stream); 
      } 
      //Retrieve first Worksheet 
      var ws = pck.Workbook.Worksheets.First(); 
      //If the to column is empty or 0, then make the tocolumn to the count of the properties 
      //Of the class object inserted 
      toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn; 

      //Read the first Row for the column names and place into a list so that 
      //it can be used as reference to properties 
      Dictionary<string, int> columnNames = new Dictionary<string, int>(); 
      // wsRow = ws.Row(0); 
      var colPosition = 0; 
      foreach (var cell in ws.Cells[1, 1, 1, toColumn == 0 ? ws.Dimension.Columns : toColumn]) 
      { 
       columnNames.Add(cell.Value.ToString(), colPosition); 
       colPosition++; 
      } 
      //create a instance of T 
      T objT = Activator.CreateInstance<T>(); 
      //Retrieve the type of T 
      Type myType = typeof(T); 
      //Get all the properties associated with T 
      PropertyInfo[] myProp = myType.GetProperties(); 


      //Loop through the rows of the excel sheet 
      for (var rowNum = fromRow; rowNum <= (toRow == 0? ws.Dimension.End.Row : toRow); rowNum++) 
      { 
       var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Cells.Count()]; 

       foreach (var propertyInfo in myProp) 
       { 
        if (columnNames.ContainsKey(propertyInfo.Name)) 
        { 
         int position = 0; 
         columnNames.TryGetValue(propertyInfo.Name, out position); 
         //int position = columnNames.IndexOf(propertyInfo.Name); 
         //To prevent an exception cast the value to the type of the property. 
         propertyInfo.SetValue(objT, Convert.ChangeType(wsRow[rowNum, position + 1].Value, propertyInfo.PropertyType)); 
        } 
       } 

       retList.Add(objT); 
      } 

     } 
     return retList; 
    } 

現在你可以使用列表作爲數據綁定源,如果你需要... 一個從我到你給... :)丹尼爾C. Vrey

將它更新爲toColumn工作並添加到行,並遵循Andreas的建議。豎起大拇指安德烈亞斯

+0

調用此代碼時,應該傳遞T,因爲它需要1個參數:XLReader.GetClassFromExcel <>(); –

+0

謝謝,非常有幫助!有些言論:(1)toColumn實際上並沒有被使用,可以被忽略,(2)ws.Cells.Count()在我身上崩潰,更好的是使用ws.Dimension.Columns,(3)Contains和IndexOf不是很高效,更好地使用字典並使用TryGetValue進行查詢。或者如果你想堅持使用列表,IndexOf在找不到項目時返回-1,所以你不需要額外的Contains()檢查。 (4)可以在for循環之前移動調用myType.GetProperties()的代碼,以便只執行一次。 – Andreas

2

我已經創建了一個方法,使用EPPlus將Excel文件轉換爲DataTable,並試圖維護類型安全。此外,處理重複的列名稱,並使用布爾值來告訴方法表單中是否有包含標題的行。我已經創建了一個複雜的導入過程,在上傳之後有幾個步驟,在提交到數據庫之前需要用戶輸入。

private DataTable ExcelToDataTable(byte[] excelDocumentAsBytes, bool hasHeaderRow) 
{ 
    DataTable dt = new DataTable(); 
    string errorMessages = ""; 

    //create a new Excel package in a memorystream 
    using (MemoryStream stream = new MemoryStream(excelDocumentAsBytes)) 
    using (ExcelPackage excelPackage = new ExcelPackage(stream)) 
    { 
     ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets[1]; 

     //check if the worksheet is completely empty 
     if (worksheet.Dimension == null) 
     { 
      return dt; 
     } 

     //add the columns to the datatable 
     for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++) 
     { 
      string columnName = "Column " + j; 
      var excelCell = worksheet.Cells[1, j].Value; 

      if (excelCell != null) 
      { 
       var excelCellDataType = excelCell; 

       //if there is a headerrow, set the next cell for the datatype and set the column name 
       if (hasHeaderRow == true) 
       { 
        excelCellDataType = worksheet.Cells[2, j].Value; 

        columnName = excelCell.ToString(); 

        //check if the column name already exists in the datatable, if so make a unique name 
        if (dt.Columns.Contains(columnName) == true) 
        { 
         columnName = columnName + "_" + j; 
        } 
       } 

       //try to determine the datatype for the column (by looking at the next column if there is a header row) 
       if (excelCellDataType is DateTime) 
       { 
        dt.Columns.Add(columnName, typeof(DateTime)); 
       } 
       else if (excelCellDataType is Boolean) 
       { 
        dt.Columns.Add(columnName, typeof(Boolean)); 
       } 
       else if (excelCellDataType is Double) 
       { 
        //determine if the value is a decimal or int by looking for a decimal separator 
        //not the cleanest of solutions but it works since excel always gives a double 
        if (excelCellDataType.ToString().Contains(".") || excelCellDataType.ToString().Contains(",")) 
        { 
         dt.Columns.Add(columnName, typeof(Decimal)); 
        } 
        else 
        { 
         dt.Columns.Add(columnName, typeof(Int64)); 
        } 
       } 
       else 
       { 
        dt.Columns.Add(columnName, typeof(String)); 
       } 
      } 
      else 
      { 
       dt.Columns.Add(columnName, typeof(String)); 
      } 
     } 

     //start adding data the datatable here by looping all rows and columns 
     for (int i = worksheet.Dimension.Start.Row + Convert.ToInt32(hasHeaderRow); i <= worksheet.Dimension.End.Row; i++) 
     { 
      //create a new datatable row 
      DataRow row = dt.NewRow(); 

      //loop all columns 
      for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++) 
      { 
       var excelCell = worksheet.Cells[i, j].Value; 

       //add cell value to the datatable 
       if (excelCell != null) 
       { 
        try 
        { 
         row[j - 1] = excelCell; 
        } 
        catch 
        { 
         errorMessages += "Row " + (i - 1) + ", Column " + j + ". Invalid " + dt.Columns[j - 1].DataType.ToString().Replace("System.", "") + " value: " + excelCell.ToString() + "<br>"; 
        } 
       } 
      } 

      //add the new row to the datatable 
      dt.Rows.Add(row); 
     } 
    } 

    //show error messages if needed 
    Label1.Text = errorMessages; 

    return dt; 
} 

Webforms按鈕點擊進行演示。

protected void Button1_Click(object sender, EventArgs e) 
{ 
    if (FileUpload1.HasFile) 
    { 
     DataTable dt = ExcelToDataTable(FileUpload1.FileBytes, CheckBox1.Checked); 

     GridView1.DataSource = dt; 
     GridView1.DataBind(); 
    } 
} 
0
public static List<T> GetClassFromExcel<T>(string path, int fromRow, int fromColumn, int toRow = 0, int toColumn = 0) where T: class, new() 
{ 
     if (toColumn != 0 && toColumn < fromColumn) throw new Exception("toColumn can not be less than fromColumn"); 
     if (toRow != 0 && toRow < fromRow) throw new Exception("toRow can not be less than fromRow"); 
     List<T> retList = new List<T>(); 
     using (var pck = new ExcelPackage()) 
     { 
      using (var stream = File.OpenRead(path)) 
      { 
       pck.Load(stream); 
      } 
      //Retrieve first Worksheet 
      var ws = pck.Workbook.Worksheets.First(); 

      toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn; //If the to column is empty or 0, then make the tocolumn to the count of the properties Of the class object inserted 

      //Read the first Row for the column names and place into a list so that 
      //it can be used as reference to properties 
      Dictionary<string, int> columnNames = new Dictionary<string, int>(); 
      // wsRow = ws.Row(0); 
      var colPosition = 0; 
      foreach (var cell in ws.Cells[1, 1, 1, toColumn == 0 ? ws.Dimension.Columns : toColumn]) 
      { 
       columnNames.Add(cell.Value.ToString(), colPosition); 
       colPosition++; 
      } 

      //Retrieve the type of T 
      Type myType = typeof(T); 

      //Get all the properties associated with T 
      PropertyInfo[] myProp = myType.GetProperties(); 

      //Loop through the rows of the excel sheet 
      for (var rowNum = fromRow + 1; rowNum <= (toRow == 0 ? ws.Dimension.End.Row : toRow); rowNum++) // fromRow + 1 to read from next row after columnheader 
      { 

       //create a instance of T 
       //T objT = Activator.CreateInstance<T>(); 
       T objT = new T(); 

       // var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Cells.Count()]; //ws.Cells.Count() causing out of range error hence using ws.Dimension.Columns to get last column index 
       var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Dimension.Columns]; 
       foreach (var propertyInfo in myProp) 
       { 
        var attribute = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().SingleOrDefault(); 
        string displayName = attribute != null && !string.IsNullOrEmpty(attribute.DisplayName) ? attribute.DisplayName : propertyInfo.Name; // If DisplayName annotation not used then get property name itself      
        if (columnNames.ContainsKey(displayName)) 
        { 
         int position = 0;       
         columnNames.TryGetValue(displayName, out position); 
         ////int position = columnNames.IndexOf(propertyInfo.Name); 
         ////To prevent an exception cast the value to the type of the property. 
         propertyInfo.SetValue(objT, Convert.ChangeType(wsRow[rowNum, position + 1].Value, propertyInfo.PropertyType)); 
        } 
       }     
       retList.Add(objT); 
      } 

     } 
     return retList; 
    } 
//IMPLEMENTATION DONE BY PLACING Code IT IN SEPARATE Helpers.CS file and 
//Consuming it in this manner 
List<CustomerExcelModel> records = 
Helpers.GetClassFromExcel<CustomerExcelModel>(filelocation, 1, 1); 

感謝很多用戶誰提交的代碼和Andreas的建議 這裏有以下變化做了,我是新來的仿製藥所以請原諒,並指正任何錯誤,請在下面找到它修改後的代碼可能會幫助某人

  • 添加顯示註釋實體模型以與Excel列映射 名稱,以便也可以處理帶空格的列名。
  • 有問題「T objT」,因爲它是外部的for循環,並因此引起反覆插入列表 相同的值固定它由
    內部環路實例即,使用「新T()」
  • 固定列超出範圍錯誤通過使用「ws.Dimension.Columns」來獲取列計數,而不是ws.Cells.Count(),因爲它導致出 範圍列錯誤
  • 用於循環行數據添加到+1,因爲RowNum = 1當時讀取標題名稱也是這麼做了「rowNum = fromRow + 1」的小改動