2012-11-15 32 views
22

Tôi đang sử dụng đoạn mã sau để chuyển đổi Excel để một DataTable sử dụng EPPlus:Excel để DataTable sử dụng EPPlus - excel bị khóa để chỉnh sửa

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; 
} 

Nó tạo ra Excel, tuy nhiên, khi tôi cố gắng để mở nó , nó cho tôi thông báo rằng nó bị khóa để chỉnh sửa bởi một người dùng khác và tôi chỉ có thể mở nó ở chế độ Chỉ đọc.

Tôi nghĩ sử dụng:

pck.Dispose(); 

sẽ giải quyết vấn đề này, tuy nhiên tôi vẫn nhận được cùng một lỗi.

Ngoài ra, khi tôi cố xóa tệp, tôi nhận được thông báo: Hành động không thể hoàn thành vì tệp được mở trong WebDev.WebServer40.EXE.

Bất kỳ ý tưởng nào giải quyết vấn đề này? Cảm ơn trước. :)

Trả lời

72

Tôi hiểu, đó là những gì i've posted recently here (hiện đã được sửa). Nó có thể được cải thiện kể từ khi ExcelPackageFileStream (từ File.OpenRead) không được xử lý sau khi sử dụng.

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

có, tôi đã thấy mã trên Trang web Codeplex EPPlus cũng như trên bài đăng của bạn trên StackOverflow. Đã cố gắng này, hoạt động, cảm ơn một tấn :) pck.Dispose() chỉ cần xử lý ExcelPackage sau đó và không phải là FileStream, đúng không? – Fahad

+2

@Fahad: Đúng. Nó cũng tốt hơn để sử dụng 'using' vì nó cũng loại bỏ lỗi' IDisposable'. Tôi cũng sẽ chỉnh sửa bài đăng trên Codeplex. –

+0

Làm thế nào tôi có thể nhận được cột đầu tiên trong "foreach (var cell in wsRow) {" vòng lặp này Becoz tôi có một trường ngày và tôi muốn thay đổi định dạng trong khi chèn vào datatable vì vậy tôi cần phải kiểm tra dữ liệu vào "foreach (var cell in wsRow) {"vòng lặp như vậy làm thế nào tôi có thể xác định rằng giá trị colunm đầu tiên là đến? –

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

Bạn có thể vui lòng thêm ít nhất một chút lời giải thích cho câu trả lời của bạn không? –

5

Một phiên bản mở rộng của câu trả lời Tim Schmelter của.

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

Đây là một cải tiến đối với phiên bản chung ở trên. Sử dụng là nếu bạn có một lớp với các thuộc tính sau, "Tên", "Họ", "Điện thoại", "Fax" và bạn có một bảng excel với hàng đầu tiên có cùng tên, nó sẽ tải các hàng excel vào một đối tượng lớp và bật nó thành một danh sách

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; 
    } 

bây giờ bạn có thể sử dụng danh sách như một nguồn databinding nếu bạn cần ... một cho từ tôi đến với bạn ... :) Daniel C. Vrey

Cập nhật nó cho toColumn để làm việc và thêm vàoRow và theo sau các đề xuất của Andreas. Thumbs up cho Andreas

+0

Khi gọi mã này - tôi nên chuyển T như thế nào vì nó yêu cầu 1 đối số: XLReader.GetClassFromExcel <>(); –

+0

Cảm ơn, rất hữu ích! Một số nhận xét: (1) toColumn là không thực sự được sử dụng và có thể được bỏ qua, (2) ws.Cells.Count() tai nạn trên tôi, tốt hơn là sử dụng ws.Dimension.Columns, (3) Chứa và IndexOf không phải là rất hiệu quả, sử dụng tốt hơn một từ điển và truy vấn với TryGetValue. Hoặc nếu bạn muốn gắn bó với List, IndexOf trả về -1 nếu không tìm thấy item, vì vậy bạn không cần kiểm tra Contains(). (4) bạn có thể di chuyển mã gọi myType.GetProperties() trước vòng lặp for để bạn chỉ thực hiện nó một lần. – Andreas

2

Tôi đã tạo phương thức chuyển đổi tệp Excel sang DataTable bằng EPPlus và cố gắng duy trì Loại an toàn. Ngoài ra các tên cột trùng lặp được xử lý và với một boolean bạn có thể cho biết phương thức kết nối với trang có các tiêu đề. Tôi đã tạo nó cho một quá trình nhập phức tạp có vài bước sau khi tải lên yêu cầu đầu vào của người dùng trước khi cam kết với cơ sở dữ liệu.

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; 
} 

Nút bấm biểu mẫu web cho mục đích demo.

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); 

Cảm ơn rất nhiều cho người dùng Submitted mã và Andreas gợi ý Dưới đây là những thay đổi sau thực hiện, tôi mới để Generics để tha thứ và chính xác cho tôi cho bất kỳ sai lầm xin vui lòng tìm mã sửa đổi dưới nó có thể giúp ai đó

  • Thêm Hiển thị mô hình thực thể chú thích để ánh xạ với cột Excel để có thể xử lý tên cột bằng dấu cách.
  • có vấn đề "T objT" như nó đã được bên ngoài của vòng lặp for và do đó gây ra cùng một giá trị liên tục đưa vào Danh sách cố định nó bằng cách
    instantiating bên trong vòng lặp tức là sử dụng "mới T()"
  • Cột Cố định ra khỏi phạm vi lỗi bằng cách sử dụng "ws.Dimension.Columns" để nhận số cột, thay vì ws.Cells.Count() vì nó gây ra lỗi lỗi phạm vi
  • để lặp qua dữ liệu hàng được thêm +1 vào nó, như RowNum = 1 đã đọc tên tiêu đề cũng đã thực hiện thay đổi nhỏ "rowNum = fromRow + 1"
Các vấn đề liên quan