使用Azure Function更新Blob中的Excel文件数据
使用C# Azure Function(BlobTrigger)更新Azure存储中的Excel文件列数据
前置依赖
先在Function项目中安装两个NuGet包:
DocumentFormat.OpenXml(处理Excel文件)Azure.Storage.Blobs(操作Azure Blob存储)
核心实现思路
Blob触发后,将Excel文件下载到本地临时文件(规避流读写限制),通过OpenXML SDK修改指定列数据,再将修改后的文件上传回Blob存储,最后清理临时文件。
完整代码示例
using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using System.IO; using System.Linq; namespace ExcelUpdateFunction { public class UpdateExcelColumnFunction { private readonly ILogger<UpdateExcelColumnFunction> _logger; public UpdateExcelColumnFunction(ILogger<UpdateExcelColumnFunction> logger) { _logger = logger; } [Function("UpdateExcelColumn")] public async Task Run( [BlobTrigger("your-container-name/{name}", Connection = "AzureWebJobsStorage")] Stream blobStream, string name, BlobClient blobClient) { _logger.LogInformation($"Processing Excel file: {name}"); // 1. 创建临时文件存储Excel内容 string tempFilePath = Path.Combine(Path.GetTempPath(), $"temp_{name}"); using (var tempFileStream = new FileStream(tempFilePath, FileMode.Create)) { await blobStream.CopyToAsync(tempFileStream); } try { // 输入参数示例:可根据需求动态替换(比如从Blob元数据/请求参数读取) string targetColumnName = "Status"; string newValue = "Processed"; // 2. 打开Excel文件并修改指定列 using (SpreadsheetDocument document = SpreadsheetDocument.Open(tempFilePath, true)) { WorkbookPart workbookPart = document.WorkbookPart; WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First(); // 获取表头行,定位目标列索引 Row headerRow = sheetData.Elements<Row>().FirstOrDefault(); if (headerRow == null) { _logger.LogError("Excel文件无表头行"); return; } int targetColumnIndex = -1; foreach (Cell cell in headerRow.Elements<Cell>()) { string cellValue = GetCellValue(document, cell); if (cellValue.Equals(targetColumnName, System.StringComparison.OrdinalIgnoreCase)) { targetColumnIndex = CellReferenceToColumnIndex(cell.CellReference); break; } } if (targetColumnIndex == -1) { _logger.LogError($"未找到目标列:{targetColumnName}"); return; } // 遍历数据行更新目标列 foreach (Row row in sheetData.Elements<Row>().Skip(1)) // 跳过表头行 { // 获取目标列单元格,不存在则创建 Cell targetCell = row.Elements<Cell>() .FirstOrDefault(c => CellReferenceToColumnIndex(c.CellReference) == targetColumnIndex); if (targetCell == null) { targetCell = new Cell { CellReference = ColumnIndexToCellReference(targetColumnIndex, row.RowIndex.Value) }; row.Append(targetCell); } // 设置单元格值 targetCell.CellValue = new CellValue(newValue); targetCell.DataType = new EnumValue<CellValues>(CellValues.String); } // 保存修改 worksheetPart.Worksheet.Save(); } // 3. 将修改后的文件上传回Blob(覆盖原文件) using (var updatedFileStream = new FileStream(tempFilePath, FileMode.Open)) { await blobClient.UploadAsync(updatedFileStream, new BlobUploadOptions { Overwrite = true }); } _logger.LogInformation($"Excel文件 {name} 已成功更新"); } catch (System.Exception ex) { _logger.LogError(ex, $"更新Excel文件 {name} 时出错"); } finally { // 4. 清理临时文件 if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } } // 辅助方法:获取单元格文本值 private string GetCellValue(SpreadsheetDocument document, Cell cell) { SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart; if (cell.CellValue == null) { return string.Empty; } string value = cell.CellValue.InnerXml; if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { return stringTablePart.SharedStringTable.ChildElements[int.Parse(value)].InnerText; } else { return value; } } // 辅助方法:单元格引用转列索引(如A1→1) private int CellReferenceToColumnIndex(string cellReference) { int columnIndex = 0; foreach (char c in cellReference.Where(char.IsLetter)) { columnIndex = columnIndex * 26 + (c - 'A' + 1); } return columnIndex; } // 辅助方法:列索引转单元格引用前缀(如1→A) private string ColumnIndexToCellReference(int columnIndex, uint rowIndex) { string columnReference = string.Empty; while (columnIndex > 0) { int remainder = (columnIndex - 1) % 26; columnReference = System.Convert.ToChar('A' + remainder) + columnReference; columnIndex = (columnIndex - remainder) / 26; } return $"{columnReference}{rowIndex}"; } } }
关键说明
- 参数动态化:若需要动态传递目标列名、更新值,可通过以下方式实现:
- 读取触发Blob的元数据(
await blobClient.GetPropertiesAsync()获取Metadata集合) - 结合HTTP触发,将参数作为请求体传入(同时绑定Blob输入)
- 从环境变量读取配置参数
- 读取触发Blob的元数据(
- 临时文件处理:使用本地临时文件避免OpenXML SDK对可读写流的限制,处理完成后务必清理
- 单元格兼容:区分共享字符串和普通字符串单元格,确保值的读取与设置逻辑正确
- 文件覆盖策略:上传时设置
Overwrite = true会覆盖原Blob,若需保留原文件,可修改Blob名称后上传
内容的提问来源于stack exchange,提问作者SKO
相关产品推荐
相关产品推荐

