将 IEnumerable<object> 写入 Excel,文件在 Excel web 中不可编辑
Write IEnumerable<object> to Excel, File isn't editable in Excel web
我写这个 class 是为了将任何类型的集合写入 excel 文件:
public static class ExcelWriter
{
public static void WriteToExcelFile(IEnumerable<object> collection, string filePath)
{
if (collection?.Any() != true || String.IsNullOrWhiteSpace(filePath))
return;
if (File.Exists(filePath))
File.Delete(filePath);
using (var document = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
Sheet sheet = new Sheet()
{
Id = workbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "Sheet1"
};
sheets.Append(sheet);
workbookPart.Workbook.Save();
SheetData sheetData = worksheetPart.Worksheet.AppendChild(new SheetData());
sheetData.AppendChild(generateHeaderRow(collection.First().GetType()));
foreach (var item in collection)
{
sheetData.AppendChild(generateValuesRow(item));
}
worksheetPart.Worksheet.Save();
}
}
private static Row generateHeaderRow(Type dataType)
{
var propertyNames = dataType.GetProperties().Select(p => p.Name).ToList();
var headerCells = new Cell[propertyNames.Count];
for (int i = 0; i < propertyNames.Count; i++)
{
headerCells[i] = createCell(propertyNames[i], CellValues.String);
}
return new Row(headerCells);
}
private static Row generateValuesRow(object rowData)
{
var cells = new List<Cell>();
foreach (var property in rowData.GetType().GetProperties())
{
var propertyValue = property.GetValue(rowData);
cells.Add(createCell(propertyValue.ToString(), getCellValueByType(propertyValue)));
}
return new Row(cells);
}
private static CellValues getCellValueByType(object propertyValue)
{
string propertyValueString = propertyValue.ToString();
if (Double.TryParse(propertyValueString, out _))
{
return CellValues.Number;
}
if (DateTime.TryParse(propertyValueString, out _))
{
return CellValues.Date;
}
if (Boolean.TryParse(propertyValueString, out _))
{
return CellValues.Boolean;
}
return CellValues.String;
}
private static Cell createCell(string value, CellValues dataType)
{
return new Cell()
{
CellValue = new CellValue(value),
DataType = new EnumValue<CellValues>(dataType)
};
}
}
这实际上生成了 excel 文件,但是当我在 Excel web 中打开该文件时,它以查看模式打开并提示您需要修复它,以便编辑(Excel 虽然无法修复):
我更改了我的代码以使用特定类型,并且没有出现此问题。该代码如下所示:
public class TypeDependentWriter
{
public static void WriteToExcelFile(IEnumerable<TestDataType> collection, string filePath)
{
if (collection?.Any() != true || String.IsNullOrWhiteSpace(filePath))
return;
if (File.Exists(filePath))
File.Delete(filePath);
using (var document = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
Sheet sheet = new Sheet()
{
Id = workbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "Sheet1"
};
sheets.Append(sheet);
workbookPart.Workbook.Save();
SheetData sheetData = worksheetPart.Worksheet.AppendChild(new SheetData());
// Header row.
Row row = new Row();
row.Append(
createCell("Id", CellValues.String),
createCell("Name", CellValues.String),
createCell("Birth Date", CellValues.String));
sheetData.AppendChild(row);
// Data rows.
foreach (var item in collection)
{
row = new Row();
row.Append(
createCell(item.Id.ToString(), CellValues.Number),
createCell(item.Name, CellValues.String),
createCell(item.DateOfBirth.ToString("yyyy/MM/dd"), CellValues.String));
sheetData.AppendChild(row);
}
worksheetPart.Worksheet.Save();
}
}
private static Cell createCell(string value, CellValues dataType)
{
return new Cell()
{
CellValue = new CellValue(value),
DataType = new EnumValue<CellValues>(dataType)
};
}
}
如何让第一个代码生效?
我的第二个代码没有产生问题的原因是这部分:
row.Append(
createCell(item.Id.ToString(), CellValues.Number),
createCell(item.Name, CellValues.String),
createCell(item.DateOfBirth.ToString("yyyy/MM/dd"), CellValues.String /* I'm using string for DateTime here. */));
我发现我应该:
- 使用
CellValues.Number
作为日期。
- 并在调用
ToString()
. 之前对 DateTime 变量调用 .ToOADate()
扩展方法
- 我应该定义一个单元格样式并将该样式应用于存储日期的单元格。
所以我现在以这种方式创建日期单元格:
var dateCell = new Cell()
{
CellValue = new CellValue(((DateTime)propertyValue).ToOADate()
.ToString(CultureInfo.InvariantCulture)),
DataType = new EnumValue<CellValues>(CellValues.Number),
StyleIndex = 1 /* Style index for date cells */
}
日期单元格的样式可以这样定义(我从Dave Williams post中获取):
var CellFormats = new CellFormats();
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 14,
FormatId = 0,
ApplyNumberFormat = true
});
CellFormats.Count = (uint)CellFormats.ChildElements.Count;
var StyleSheet = new Stylesheet();
StyleSheet.Append(CellFormats);
但是,如果您没有任何其他已定义的 Fill、Font...这将不起作用,因为我们正在使用 FillId = 0
等我们尚未定义的最小样式您也可以在 Dave Williams post:
上找到获得这项工作的定义
private static Stylesheet GetStylesheet()
{
var StyleSheet = new Stylesheet();
// Create "fonts" node.
var Fonts = new Fonts();
Fonts.Append(new Font()
{
FontName = new FontName() { Val = "Calibri" },
FontSize = new FontSize() { Val = 11 },
FontFamilyNumbering = new FontFamilyNumbering() { Val = 2 },
});
Fonts.Count = (uint)Fonts.ChildElements.Count;
// Create "fills" node.
var Fills = new Fills();
Fills.Append(new Fill()
{
PatternFill = new PatternFill() { PatternType = PatternValues.None }
});
Fills.Append(new Fill()
{
PatternFill = new PatternFill() { PatternType = PatternValues.Gray125 }
});
Fills.Count = (uint)Fills.ChildElements.Count;
// Create "borders" node.
var Borders = new Borders();
Borders.Append(new Border()
{
LeftBorder = new LeftBorder(),
RightBorder = new RightBorder(),
TopBorder = new TopBorder(),
BottomBorder = new BottomBorder(),
DiagonalBorder = new DiagonalBorder()
});
Borders.Count = (uint)Borders.ChildElements.Count;
// Create "cellStyleXfs" node.
var CellStyleFormats = new CellStyleFormats();
CellStyleFormats.Append(new CellFormat()
{
NumberFormatId = 0,
FontId = 0,
FillId = 0,
BorderId = 0
});
CellStyleFormats.Count = (uint)CellStyleFormats.ChildElements.Count;
// Create "cellXfs" node.
var CellFormats = new CellFormats();
// A default style that works for everything but DateTime
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 0,
FormatId = 0,
ApplyNumberFormat = true
});
// A style that works for DateTime (just the date)
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 14, // or 22 to include the time
FormatId = 0,
ApplyNumberFormat = true
});
CellFormats.Count = (uint)CellFormats.ChildElements.Count;
// Create "cellStyles" node.
var CellStyles = new CellStyles();
CellStyles.Append(new CellStyle()
{
Name = "Normal",
FormatId = 0,
BuiltinId = 0
});
CellStyles.Count = (uint)CellStyles.ChildElements.Count;
// Append all nodes in order.
StyleSheet.Append(Fonts);
StyleSheet.Append(Fills);
StyleSheet.Append(Borders);
StyleSheet.Append(CellStyleFormats);
StyleSheet.Append(CellFormats);
StyleSheet.Append(CellStyles);
return StyleSheet;
}
我写这个 class 是为了将任何类型的集合写入 excel 文件:
public static class ExcelWriter
{
public static void WriteToExcelFile(IEnumerable<object> collection, string filePath)
{
if (collection?.Any() != true || String.IsNullOrWhiteSpace(filePath))
return;
if (File.Exists(filePath))
File.Delete(filePath);
using (var document = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
Sheet sheet = new Sheet()
{
Id = workbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "Sheet1"
};
sheets.Append(sheet);
workbookPart.Workbook.Save();
SheetData sheetData = worksheetPart.Worksheet.AppendChild(new SheetData());
sheetData.AppendChild(generateHeaderRow(collection.First().GetType()));
foreach (var item in collection)
{
sheetData.AppendChild(generateValuesRow(item));
}
worksheetPart.Worksheet.Save();
}
}
private static Row generateHeaderRow(Type dataType)
{
var propertyNames = dataType.GetProperties().Select(p => p.Name).ToList();
var headerCells = new Cell[propertyNames.Count];
for (int i = 0; i < propertyNames.Count; i++)
{
headerCells[i] = createCell(propertyNames[i], CellValues.String);
}
return new Row(headerCells);
}
private static Row generateValuesRow(object rowData)
{
var cells = new List<Cell>();
foreach (var property in rowData.GetType().GetProperties())
{
var propertyValue = property.GetValue(rowData);
cells.Add(createCell(propertyValue.ToString(), getCellValueByType(propertyValue)));
}
return new Row(cells);
}
private static CellValues getCellValueByType(object propertyValue)
{
string propertyValueString = propertyValue.ToString();
if (Double.TryParse(propertyValueString, out _))
{
return CellValues.Number;
}
if (DateTime.TryParse(propertyValueString, out _))
{
return CellValues.Date;
}
if (Boolean.TryParse(propertyValueString, out _))
{
return CellValues.Boolean;
}
return CellValues.String;
}
private static Cell createCell(string value, CellValues dataType)
{
return new Cell()
{
CellValue = new CellValue(value),
DataType = new EnumValue<CellValues>(dataType)
};
}
}
这实际上生成了 excel 文件,但是当我在 Excel web 中打开该文件时,它以查看模式打开并提示您需要修复它,以便编辑(Excel 虽然无法修复):
我更改了我的代码以使用特定类型,并且没有出现此问题。该代码如下所示:
public class TypeDependentWriter
{
public static void WriteToExcelFile(IEnumerable<TestDataType> collection, string filePath)
{
if (collection?.Any() != true || String.IsNullOrWhiteSpace(filePath))
return;
if (File.Exists(filePath))
File.Delete(filePath);
using (var document = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
Sheet sheet = new Sheet()
{
Id = workbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "Sheet1"
};
sheets.Append(sheet);
workbookPart.Workbook.Save();
SheetData sheetData = worksheetPart.Worksheet.AppendChild(new SheetData());
// Header row.
Row row = new Row();
row.Append(
createCell("Id", CellValues.String),
createCell("Name", CellValues.String),
createCell("Birth Date", CellValues.String));
sheetData.AppendChild(row);
// Data rows.
foreach (var item in collection)
{
row = new Row();
row.Append(
createCell(item.Id.ToString(), CellValues.Number),
createCell(item.Name, CellValues.String),
createCell(item.DateOfBirth.ToString("yyyy/MM/dd"), CellValues.String));
sheetData.AppendChild(row);
}
worksheetPart.Worksheet.Save();
}
}
private static Cell createCell(string value, CellValues dataType)
{
return new Cell()
{
CellValue = new CellValue(value),
DataType = new EnumValue<CellValues>(dataType)
};
}
}
如何让第一个代码生效?
我的第二个代码没有产生问题的原因是这部分:
row.Append(
createCell(item.Id.ToString(), CellValues.Number),
createCell(item.Name, CellValues.String),
createCell(item.DateOfBirth.ToString("yyyy/MM/dd"), CellValues.String /* I'm using string for DateTime here. */));
我发现我应该:
- 使用
CellValues.Number
作为日期。 - 并在调用
ToString()
. 之前对 DateTime 变量调用 - 我应该定义一个单元格样式并将该样式应用于存储日期的单元格。
.ToOADate()
扩展方法
所以我现在以这种方式创建日期单元格:
var dateCell = new Cell()
{
CellValue = new CellValue(((DateTime)propertyValue).ToOADate()
.ToString(CultureInfo.InvariantCulture)),
DataType = new EnumValue<CellValues>(CellValues.Number),
StyleIndex = 1 /* Style index for date cells */
}
日期单元格的样式可以这样定义(我从Dave Williams post中获取):
var CellFormats = new CellFormats();
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 14,
FormatId = 0,
ApplyNumberFormat = true
});
CellFormats.Count = (uint)CellFormats.ChildElements.Count;
var StyleSheet = new Stylesheet();
StyleSheet.Append(CellFormats);
但是,如果您没有任何其他已定义的 Fill、Font...这将不起作用,因为我们正在使用 FillId = 0
等我们尚未定义的最小样式您也可以在 Dave Williams post:
private static Stylesheet GetStylesheet()
{
var StyleSheet = new Stylesheet();
// Create "fonts" node.
var Fonts = new Fonts();
Fonts.Append(new Font()
{
FontName = new FontName() { Val = "Calibri" },
FontSize = new FontSize() { Val = 11 },
FontFamilyNumbering = new FontFamilyNumbering() { Val = 2 },
});
Fonts.Count = (uint)Fonts.ChildElements.Count;
// Create "fills" node.
var Fills = new Fills();
Fills.Append(new Fill()
{
PatternFill = new PatternFill() { PatternType = PatternValues.None }
});
Fills.Append(new Fill()
{
PatternFill = new PatternFill() { PatternType = PatternValues.Gray125 }
});
Fills.Count = (uint)Fills.ChildElements.Count;
// Create "borders" node.
var Borders = new Borders();
Borders.Append(new Border()
{
LeftBorder = new LeftBorder(),
RightBorder = new RightBorder(),
TopBorder = new TopBorder(),
BottomBorder = new BottomBorder(),
DiagonalBorder = new DiagonalBorder()
});
Borders.Count = (uint)Borders.ChildElements.Count;
// Create "cellStyleXfs" node.
var CellStyleFormats = new CellStyleFormats();
CellStyleFormats.Append(new CellFormat()
{
NumberFormatId = 0,
FontId = 0,
FillId = 0,
BorderId = 0
});
CellStyleFormats.Count = (uint)CellStyleFormats.ChildElements.Count;
// Create "cellXfs" node.
var CellFormats = new CellFormats();
// A default style that works for everything but DateTime
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 0,
FormatId = 0,
ApplyNumberFormat = true
});
// A style that works for DateTime (just the date)
CellFormats.Append(new CellFormat()
{
BorderId = 0,
FillId = 0,
FontId = 0,
NumberFormatId = 14, // or 22 to include the time
FormatId = 0,
ApplyNumberFormat = true
});
CellFormats.Count = (uint)CellFormats.ChildElements.Count;
// Create "cellStyles" node.
var CellStyles = new CellStyles();
CellStyles.Append(new CellStyle()
{
Name = "Normal",
FormatId = 0,
BuiltinId = 0
});
CellStyles.Count = (uint)CellStyles.ChildElements.Count;
// Append all nodes in order.
StyleSheet.Append(Fonts);
StyleSheet.Append(Fills);
StyleSheet.Append(Borders);
StyleSheet.Append(CellStyleFormats);
StyleSheet.Append(CellFormats);
StyleSheet.Append(CellStyles);
return StyleSheet;
}