Asp.Net生成大规模KML文件时遇OutOfMemoryException问题求助
解决60万条地理数据生成KML内存不足问题及Azure Blob分块实现
核心问题分析
你的代码当前存在三个关键内存瓶颈:
- 一次性加载60万条数据到
exportedData集合,内存占用陡增 - 所有Placemark(包括全量汇总的
documentAll)都在内存中构建完成后才写入流,kmlFile.Save(stream)时触发内存溢出 - 为每个Placemark重复创建Style对象,造成大量内存浪费
解决方案步骤
1. 数据库分页加载数据,避免一次性全量读取
不要把所有数据一次性加载到内存,通过数据库分页(比如EF Core的Skip/Take、SQL的OFFSET/FETCH)分批获取,每次处理1000-10000条,处理完释放内存再取下一批。
2. 复用Style对象,削减内存开销
将Style定义在Document级别,每个Placemark通过StyleUrl引用,而非重复创建:
// 在Document初始化时创建一次Style var styleId = $"style_{provider.ProviderId}_{technology.TechCode}"; var style = new Style { Id = styleId }; style.Polygon = new PolygonStyle { ColorMode = ColorMode.Normal, Color = new Color32(byte_Color_A, byte_Color_B, byte_Color_G, byte_Color_R) }; style.Balloon = new BalloonStyle { BackgroundColor = new Color32(byte_Color_A, byte_Color_B, byte_Color_G, byte_Color_R) }; document.AddStyle(style); // Placemark通过URL引用Style placemark.StyleUrl = new Uri($"#{styleId}", UriKind.Relative);
3. 直接写入Azure Blob,跳过MemoryStream
使用Azure Blob存储的AppendBlobClient分块写入KML内容,无需在内存生成完整文件。关键实现如下:
依赖安装
先安装Azure Blob存储NuGet包:
Install-Package Azure.Storage.Blobs
分块写入KML到Azure Blob示例
using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using SharpKml.Dom; using SharpKml.Engine; public async Task ExportToKmlToAzureBlob(string connectionString, string containerName, string exportBy, string geometry) { var blobServiceClient = new BlobServiceClient(connectionString); var containerClient = blobServiceClient.GetBlobContainerClient(containerName); await containerClient.CreateIfNotExistsAsync(); // 初始化全量KML文档头部并写入Blob var kmlHeader = $@"<?xml version=""1.0"" encoding=""UTF-8""?> <kml xmlns=""http://www.opengis.net/kml/2.2""> <Document> <name>All_{exportBy}.kml</name>"; var allBlobClient = containerClient.GetBlockBlobClient($"All_{exportBy}.kml"); await allBlobClient.UploadAsync(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(kmlHeader)), overwrite: true); var appendBlobClient = allBlobClient.AsAppendBlobClient(); // 分页获取数据(示例用EF Core,根据你的数据库访问方式调整) int pageSize = 10000; int pageIndex = 0; bool hasMoreData = true; while (hasMoreData) { var exportedData = await _dbContext.YourGeoData .Skip(pageIndex * pageSize) .Take(pageSize) .ToListAsync(); if (exportedData.Count == 0) { hasMoreData = false; break; } // 处理当前页数据,生成Placemark的XML片段 var placemarkXmls = new List<string>(); foreach (var kmlData in exportedData) { var placemark = CreatePlacemark(kmlData, geometry); var serializer = new KmlSerializer(); using var writer = new StringWriter(); serializer.Serialize(writer, placemark, true); placemarkXmls.Add(writer.ToString()); } // 将当前页的Placemark片段追加到Blob var content = string.Join("\n", placemarkXmls); using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content)); await appendBlobClient.AppendBlockAsync(stream); pageIndex++; } // 写入KML文档尾部 await appendBlobClient.AppendBlockAsync(new MemoryStream(System.Text.Encoding.UTF8.GetBytes("</Document></kml>"))); } private Placemark CreatePlacemark(dynamic kmlData, string geometry) { var placemark = new Placemark(); // 复用你原代码中创建Geometry、ExtendedData的逻辑 // 注意:此处Style通过StyleUrl引用全局定义,不再重复创建 return placemark; }
4. 拆分大文件为多个小KML
如果需要保留按Provider和Technology拆分的文件,同样对每个子文件采用分页+分块写入Azure Blob的方式,避免在内存中构建完整Document。
5. 禁用全量汇总文件(可选)
如果documentAll不是业务必需,直接删除这部分逻辑,可大幅降低内存占用。
额外优化建议
- 替换
dynamic为强类型实体,减少反射开销和内存占用 - 优化坐标解析逻辑,避免重复字符串分割、替换操作
- 考虑使用KMZ格式压缩KML文件,减少存储和传输体积(SharpKml可通过
KmzFile类实现)
内容的提问来源于stack exchange,提问作者Hamlet Poghosyan
相关产品推荐
相关产品推荐

