Excel 到 DataTable 使用 EPPlus - Excel 锁定编辑
Posted
技术标签:
【中文标题】Excel 到 DataTable 使用 EPPlus - Excel 锁定编辑【英文标题】:Excel to DataTable using EPPlus - excel locked for editing 【发布时间】:2012-11-15 11:29:06 【问题描述】:我正在使用以下代码使用 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 中打开。
任何想法如何解决这个问题? 提前致谢。 :)
【问题讨论】:
【参考方案1】:我明白了,这就是i've posted recently here(现已更正)。由于ExcelPackage
和FileStream
(来自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;
【讨论】:
是的,我在 Codeplex EPPlus 网站以及您在 *** 上的帖子中看到了代码。试过这个,工作,非常感谢:) pck.Dispose() 只是处理 ExcelPackage 而不是 FileStream,对吗? @Fahad:正确。最好使用using
,因为它还会处理IDisposable
错误。我也会编辑 Codeplex 上的帖子。
我怎样才能在“foreach(wsRow 中的 var 单元格)”这个循环中获取第一列因为我有一个日期字段,我想在插入数据表时更改格式,所以我需要将数据签入"foreach (var cell in wsRow)" 循环那么我如何确定第一个列值来了?
@TimSchmelter 我的 excel 有前 2 行作为垃圾信息,第三行包含所有列,其余行是数据。我如何跳过前 2 行?我正在使用完全相同的代码。在此先感谢您的帮助。
请告诉我是否有办法从 ws.Dimension.End.Column 中删除空单元格?还是只选择有数据的范围?【参考方案2】:
Tim 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】:我创建了一个使用 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;
点击网页表单按钮进行演示。
protected void Button1_Click(object sender, EventArgs e)
if (FileUpload1.HasFile)
DataTable dt = ExcelToDataTable(FileUpload1.FileBytes, CheckBox1.Checked);
GridView1.DataSource = dt;
GridView1.DataBind();
【讨论】:
这是适合我的方法。不幸的是,蒂姆的答案虽然较短,但没有计算出引发错误的总行数。 如果您不能完全确定工作表的内容,那么以这种方式确定类型很容易出错。第二行可能是空的,或者可能包含一个整数值而另一行包含一个双精度值,你会遇到麻烦并丢失数据 感谢 VDWWD 这个方法,我在下面的更新答案中解决了一些问题。【参考方案4】:这是对上述通用版本的改进。使用的是,如果您有一个具有以下属性的类,“姓名”、“姓氏”、“电话”、“传真”,并且您有一个第一行具有相同名称的 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;
现在您可以根据需要将该列表用作数据绑定源... 我给你的礼物...... :) Daniel C. Vrey
更新它以使 toColumn 工作并添加 toRow 并遵循 Andreas 的建议。为安德烈亚斯竖起大拇指
【讨论】:
调用此代码时 - 我应该传递什么 T 因为它需要 1 个参数:XLReader.GetClassFromExcel(); 谢谢,很有帮助!一些评论:(1)toColumn实际上并没有使用,可以忽略,(2)ws.Cells.Count()在我身上崩溃,更好的是使用ws.Dimension.Columns,(3)包含和IndexOf不是很好高效,更好地使用 DictionaryVDWWD 的上述答案非常适合保持类型安全,我在此基础上进行了一些改进。
方法直接从文件中读取。 使用所有行而不是一个值来检测列类型。如果在列中找到多个类型,则将列类型设置为字符串。 在字符串列表中返回错误消息。这是更新版本:
public static DataTable ExcelToDataTable(string path, ref List<string> errorList, bool hasHeaderRow = true )
DataTable dt = new DataTable();
errorList = new List<string>();
//create a new Excel package
using (ExcelPackage excelPackage = new ExcelPackage())
using (var stream = File.OpenRead(path))
excelPackage.Load(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;
//Build hashset with all types in the row
var columnTypes = new HashSet<Type>();
for (int i = worksheet.Dimension.Start.Row + Convert.ToInt32(hasHeaderRow); i <= worksheet.Dimension.End.Row; i++)
//Only add type if cell value not empty
if (worksheet.Cells[i, j].Value != null)
columnTypes.Add(worksheet.Cells[i, j].Value.GetType());
var excelCell = worksheet.Cells[1, j].Value;
if (excelCell != null)
Type excelCellDataType = null;
//if there is a headerrow, set the next cell for the datatype and set the column name
if (hasHeaderRow == true)
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;
//Select input type for the column
if (columnTypes.Count == 1)
excelCellDataType = columnTypes.First();
else
excelCellDataType = typeof(string);
//try to determine the datatype for the column (by looking at the next column if there is a header row)
if (excelCellDataType == typeof(DateTime))
dt.Columns.Add(columnName, typeof(DateTime));
else if (excelCellDataType == typeof(Boolean))
dt.Columns.Add(columnName, typeof(Boolean));
else if (excelCellDataType == typeof(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
errorList.Add("Row " + (i - 1) + ", Column " + j + ". Invalid " + dt.Columns[j - 1].DataType.ToString().Replace("System.", "") + " value: " + excelCell.ToString() );
//add the new row to the datatable
dt.Rows.Add(row);
return dt;
【讨论】:
【参考方案6】: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;
【讨论】:
能否请您至少为您的答案添加一点解释? 这个例子是将数据导入List Collection而不是DataTable【参考方案7】: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 循环之外,因此导致 重复插入列表的相同值通过修复它 实例化内部循环,即使用“new T()” 通过使用“ws.Dimension.Columns”获取列计数,而不是 ws.Cells.Count() 来修复列超出范围错误,因为它导致了 范围列错误 为了循环遍历行数据,向它添加了 +1,因为 RowNum=1 正在读取标题名称,因此对“rowNum = fromRow + 1”进行了微小的更改【讨论】:
【参考方案8】:我在这里分享如何阅读 Excel。您可以修改它以将每个日期存储在数据表中。
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++)
//You can update code here to add each cell value to DataTable.
Console.WriteLine(" Row:" + row + " column:" + col + " Value:" + worksheet.Cells[row, col].Value.ToString().Trim());
参考:http://sforsuresh.in/read-data-excel-sheet-insert-database-table-c/
【讨论】:
【参考方案9】:如果您想从 excel 中读取每个工作表的数据,以及如果工作表包含特定列的日期格式数据,请使用以下代码。
public static DataSet ReadExcelFileToDataSet2(string filePath, bool isFirstRowHeader=true)
DataSet result = new DataSet();
Excel.ExcelPackage xlsPackage = new Excel.ExcelPackage(new FileInfo(filePath)); //using Excel = OfficeOpenXml; <--EPPLUS
Excel.ExcelWorkbook workBook = xlsPackage.Workbook;
try
for (int count = 1; count <= workBook.Worksheets.Count; count++)
Excel.ExcelWorksheet wsworkSheet = workBook.Worksheets[count];
if (wsworkSheet.Name.ToLower() == "sheetName")
wsworkSheet.Column(4).Style.Numberformat.Format = "MM-dd-yyyy"; // set column value to read as Date Type or numberformat
DataTable tbl = new DataTable();
// wsworkSheet.Dimension - (It will return cell dimesion like A1:N7 , means returning the worksheet dimesions.)
// wsworkSheet.Dimension.End.Address - (It will return right bottom cell like N7)
// wsworkSheet.Dimension.End.Columns - (It will return count from A1 to N7 like here 14)
foreach (var firstRowCell in wsworkSheet.Cells[1, 1, 1, wsworkSheet.Dimension.End.Column]) //.Cells[Row start, Column Start, Row end, Column End]
var colName = "";
colName = firstRowCell.Text;
tbl.Columns.Add(isFirstRowHeader ? colName : string.Format("Column 0", firstRowCell.Start.Column)); //Geth the Column index (index starting with 1) from the left top.
var startRow = isFirstRowHeader ? 2 : 1;
for (int rowNum = startRow; rowNum <= wsworkSheet.Dimension.End.Row; rowNum++)
var wsRow = wsworkSheet.Cells[rowNum, 1, rowNum, wsworkSheet.Dimension.End.Column]; // wsworkSheet.Cells[Row start, Column Start, Row end, Column End]
DataRow row = tbl.Rows.Add();
foreach (var cell in wsRow)
row[cell.Start.Column - 1] = cell.Text;
tbl.TableName = wsworkSheet.Name;
result.Tables.Add(tbl);
catch (Exception ex)
Console.WriteLine(ex.Message);
return result;
【讨论】:
当您回答一个 7 年前的问题时,如果您可以添加关于您的答案的作用以及它如何添加到已经发布的其他 8 个答案的描述会更好.以上是关于Excel 到 DataTable 使用 EPPlus - Excel 锁定编辑的主要内容,如果未能解决你的问题,请参考以下文章
使用 DataTable 和 Linq 导出到 Excel 中缺少某些数据
在 c# 中使用 Open Xml SDK 将 DataTable 导出到 Excel